Skip to content

[WRONG BRANCH] promote: dev to main for v2.29.0 - #2284

Merged
lidge-jun merged 85 commits into
mainfrom
codex/promote-main-2.29.0
Aug 21, 2026
Merged

[WRONG BRANCH] promote: dev to main for v2.29.0#2284
lidge-jun merged 85 commits into
mainfrom
codex/promote-main-2.29.0

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Summary

Promote dev to main for the v2.29.0 release.

main last moved for v2.28.0, which is already published (npm latest = 2.28.0). This promotion carries the 83-commit unreleased train on dev: the #2188 sidecar/web-search unification (L1-L9), the xAI wire-policy series, and the 260821 bug-backlog train (#2269, #2271, #2246, #2273, #2274, #2276, #2277), plus the routed web-search capability backfill (#2283) that closes the half-landed path #2262 left open.

Both branches carry unique commits, so this is a merge rather than a fast-forward: main keeps its own release commits.

Verification

  • bun run typecheck — exit 0
  • bun run test — 14090 pass / 0 fail across 887 files
  • bun run privacy:scan — passed
  • fix(router): backfill the xAI web-search capability into routed provider config #2283 landed on dev with 23/23 required checks green, 0 failures.
  • Independent adversarial audit of the full main..dev delta (two rounds): round 1 raised exactly one blocker — the unlanded capability backfill — and round 2 confirmed closure at origin/dev 7881319 with no new blocker. The audit found no workflow, release-automation, lockfile, or dependency change in the delta; the security-sensitive parts are sidecar auth eligibility and OAuth log redaction, both tightening.
  • Live end-to-end on a remote macOS host at the merged dev head: with the Grok Responses lane opted in and no hand-written capability in config.json, a multi-step codex exec tool-use turn completed over adapter "openai-responses" with status: 200.
  • Merge result verified clean: the only difference from origin/dev is the package.json version line, which scripts/release.ts owns.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Summary by CodeRabbit

  • New Features

    • Added xAI, Gemini, and Exa web-search backends with authentication, validation, and optional xAI search filters.
    • Added dashboard and CLI sidecar model listings.
    • Added Prime Agent integration and configuration export support.
    • Added Grok Responses API opt-in controls and Cursor checkpoint reuse.
  • Bug Fixes

    • Improved tool-call pairing, stream handling, source deduplication, credential redaction, and integration synchronization.
    • Improved model metadata refresh and agent roster synchronization.
  • Documentation

    • Updated multilingual configuration, integration, sidecar, provider, and account-pool guidance, including provider-policy compliance notices.

YUHAO-corn and others added 30 commits August 20, 2026 15:54
docs(devlog): record the v2.28.0 release and its CI evidence
… replay cache

- Freeform/custom tools (such as default_api:exec) are emitted to clients as
  custom_tool_call with { input: '{"cmd":...}' } while upstream observation
  records parsed function arguments { cmd: ... }.
- applyAntigravityReplay now unwraps { input: string } to match against observed
  JSON argument signatures when exact matching misses, fixing 400 errors during
  early turns and replayed freeform execution (#2125).
…ut length pre-parse

- Restore call.signature when call && matchedKey (not gated on ck), ensuring
  whitespace-padded wrapped arguments whose ck overflows 64 KiB still restore
  their signature if the parsed inner JSON is within bounds.
- Bound argsObj.input.trim() to REPLAY_MAX_CANONICAL_ARGS_BYTES before calling
  JSON.parse in the custom tool replay unwrap, preventing oversized remote payloads
  from triggering unbounded allocations.
- Add regression tests for oversized whitespace-wrapped input restoring correctly
  and oversized JSON payloads being rejected before parse (addressing review feedback from @Ingwannu on #2198).
…undaries

- Compute trimmedInput and pass that exact validated string to JSON.parse,
  preventing large whitespace-padded prefixes/suffixes from reaching JSON.parse.
- Add parse seam assertions proving that trimmed small payloads are parsed directly
  and that oversized valid payloads never reach JSON.parse (addressing feedback from @Ingwannu on #2198).
- Guard trimmedInput.length <= REPLAY_MAX_CANONICAL_ARGS_BYTES before calling
  utf8.encode(trimmedInput), preventing large ASCII/serialized input from allocating
  proportional Uint8Array buffers on the request path.
- Add regression test asserting that oversized inputs are rejected before TextEncoder.encode
  and JSON.parse (addressing review feedback from @Ingwannu and CodeRabbit on #2198).
docs: clarify ChatGPT account pool policy boundary
fix(responses): bound orphan call reordering work
…ence-and-compaction

fix(google): match unwrapped freeform tool arguments in antigravity replay cache
…ention

fix(responses): bound terminal guard delta retention
Hygiene blocked the PR for missing_regression_test; lock in the
userMessageAction path for composer-2.5 and keep resumeAction on fast.

Co-authored-by: Cursor <cursoragent@cursor.com>
composer-2.5 now routes tool continuations through the external path;
keep native turn-step replay coverage on composer-2.5-fast.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep isCursorExternalWireModel for history/replay and apply the
composer-2.5 userMessageAction exception only on trailing tool results.

Co-authored-by: Cursor <cursoragent@cursor.com>
Prime Agent (PrimeIntellect) is not a Pi lookalike — it is the pi coding
agent shipped under a different brand. Its package declares a `piConfig`
block, and the agent derives both its config directory (`.prime/agent`)
and its env prefix from that block alone; with no block the same code
falls back to `@earendil-works/pi-coding-agent` and `~/.pi`. `models.json`
is therefore the same contract Pi already reads, so this client reuses
Pi's builder and summarizer verbatim rather than restating a schema that
would drift. A test locks the two generated documents byte-for-byte so a
future Pi-only change cannot quietly diverge.

The one thing that could still differ is the path we own, and it does
not: Prime keeps our entries under the same `providers.<id>` key. The
only new behavior is path resolution, which honors
`PRIME_AGENT_CODING_AGENT_DIR` (verified live: setting it moves which
models.json `prime-agent model list` reads) and refuses relative
overrides for the same reason MCode and ZCode do.

Registered loopback-only. Prime's provider block does accept `headers`,
so a dedicated admission header has somewhere to live, but remote
credential wiring is deferred from this initial integration — the same
stance OMP took.

The English CLI reference for `--client` was two clients stale; it now
names the current set. Translated copies are left for a docs sync.
Top-level help advertised "(10 clients)" while the export registry held
eleven, so the first screen a user sees understated the supported set.

The reason the suite could not catch it is the more useful half: the
assertion in tests/cli-help.test.ts froze the same literal, so code and
test stayed self-consistent and wrong while the registry grew. The test
now derives the number from EXPORT_CLIENT_IDS.length.

help.ts keeps a literal on purpose. It currently imports only node:fs,
node:path, node:url and the pure-data src/cli/registry.ts; importing the
export registry there would pull node:os and the whole client-export
module onto the `ocx --help` path. The assertion is the cheaper place to
hold the invariant.
…ists

Adds the Prime Agent row to the destination table, updates the sentence
that counts those rows so the page stays internally consistent, names
Prime in the loopback-only list with its own reason (its provider block
does accept headers; remote credential wiring is deferred), and documents
PRIME_AGENT_CODING_AGENT_DIR and the relative-path refusal alongside the
equivalent MiniMax paragraph.

Also syncs the `--client` accepted-value list in the seven translated CLI
references. An accepted-value list is correctness rather than
translation: a stale one tells users a valid flag value will be rejected.
Those lists were two clients behind, so they now name mcode and zcode as
well as prime.
Two review findings, both about the same failure mode: stating a path as
if it were fixed when an environment override can move it.

The dashboard's Prime description hard-coded ~/.prime/agent/models.json
even though PRIME_AGENT_CODING_AGENT_DIR redirects it — and the panel
already renders the resolved path directly beneath that sentence, so the
literal was both redundant and capable of contradicting what the user
could see. All nine locales now name the default and the override.

The CLI reference's canonical-destination table also stopped at dsh.
Widening the advertised `--client` set in the previous commit is what
made that gap reachable for mcode and zcode, not just prime, so all
three rows are added with their own overrides and the relative-path
refusal, plus a paragraph noting that the three are loopback-only and
that Prime and Pi produce the same document from different destinations.
Syncing only the `--client` heading in the translated CLI references left
each of those pages contradicting itself: the heading named mcode, zcode
and prime while the flag table two lines below still stopped at dsh. That
is worse than leaving both stale, and it was mine to fix.

All seven translations now carry the full accepted-value list in both the
heading and the flag table, plus mcode, zcode and prime rows in the
canonical-destination table with each client's own environment override
and the relative-path refusal, phrased in that locale's existing wording
for the placeholder column.

Verified by pattern rather than by eye this time: every locale, English
included, now reports the same eleven-client list in both places and
eleven destination rows.
ja, ko, ru and zh-cn each restated the loopback-only set as "Pi, OMP,
Kimi and DSH", which was already wrong for mcode and zcode and became
wrong for prime with this PR. English and the other three locales carry a
generic statement instead — configs hold either a documented environment
reference or a non-secret loopback placeholder — so these four now match
it rather than growing a list that has to be edited every time a client
is added.

The Gajae sentence in the same paragraph enumerated the same set as a
comparison; it now simply says the generated Gajae integration remains
loopback-only, which is what the English text says.
luvs01 and others added 19 commits August 21, 2026 13:09
Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
…eries (#2254 rebased) (#2258)

* fix(responses): keep compaction blobs on the backend that minted them

A replayed compaction item carries an `encrypted_content` blob only its minting
backend can decode, and Codex replays it on every later turn. Two paths modified
or misrouted it, and because the item outlives the failure in the client
transcript, both wedged the session until its history was cleared — the routed
compaction turn the proxy itself drives replays the same item.

Relay: `scrubOcxCompactionItems` treated every non-`ocx1:` blob as OpenAI's and
forwarded it verbatim, with no check that the destination was the issuer. A
session that compacted on a canonical route and then switched to a routed
provider sent that blob to an upstream that could only answer "Could not decode
the compaction blob". Native blobs now travel only to destinations that mint
them — forward-auth routes, which relay the caller's own OpenAI credentials to
the ChatGPT backend or a relay in front of it, and the official OpenAI API under
key auth — and degrade elsewhere to the same opaque note the bridged parser uses.

Backfill: the response-side exemption list named `compaction` alone, so
`compaction_summary` and `context_compaction` received synthesized ids that the
client stored and replayed as "modified from the compact response". That
divergence was possible because the compact wire family was enumerated in three
places; it is now one predicate in `src/responses/compaction.ts`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(responses): stop reshaping reasoning items that carry encrypted_content

Codex replays the reasoning item it received in the next request's input, and a
backend that issued `encrypted_content` verifies what comes back. The
content-to-summary channel rewrite deletes `content` and substitutes a
synthesized `summary`, so the client stored and replayed an item the issuer had
never sent, and every later turn failed with "Could not decrypt the provided
encrypted_content. Ensure the value is the unmodified encrypted_content from a
previous response." No route change is needed to reach this: it fires on the
second turn of a fresh session.

The rewrite's replay round trip was verified against DeepSeek, which is
`statelessResponses` and issues no blob — its reasoning replay goes through the
proxy-side cache instead. Providers that do issue a blob joined the same route
later through `preserveReasoningContentModels`, a flag whose own purpose is
Chat-wire prompt-cache replay, and the verified premise did not follow them.

Only the stored item is exempt. The `reasoning_text` delta events carry no blob
and still route to the summary channel, so the expandable trace Codex renders
for the live turn is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(responses): drop a null reasoning content channel before routed passthrough

Codex serializes an absent reasoning content channel as `"content": null`, and
the sanitizer only acted on a non-empty array, so the null went to the wire
verbatim. xAI rejects the item and blames the sibling field:

  {"code":"invalid-argument",
   "error":"Could not decode the compaction blob. Ensure it is unmodified from
            the compact response."}

The blob is not the problem. Captured from a live failing request and bisected
against it: replaying the body verbatim reproduces the 400, deleting only the
`content` key returns 200, and setting it to `[]` also returns 200 — while
removing `encrypted_content` instead fails schema validation, so the blob is
both required and intact. The proxy was verified not to alter the blob: the
value grok streamed to the client and the value replayed upstream matched in
length, prefix and suffix, under identical `x-grok-conv-id`, `x-grok-session-id`
and account.

This bites the second turn of every Grok conversation — the first request that
replays a reasoning item — which is why a fresh session fails just as reliably
as a resumed one, and why the error looked like stale compaction state.

The field is optional and null carries nothing, so the key is dropped rather
than rewritten; an array content channel still follows the existing rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(responses): decide native-blob relay by destination, not by forward auth

Review found the discriminator unsound, and it was. `authMode === "forward"`
describes local credential handling, not which backend answers: the adapter
forwards caller credentials only to the canonical ChatGPT Codex surface, so a
noncanonical forward provider receives none and may point anywhere.

That produced both errors at once. A self-hosted or xAI-backed forward gateway
was classified as able to decode a foreign blob, was sent it unchanged, and
stayed wedged — the exact failure this branch exists to fix. Meanwhile a
key-auth relay genuinely fronting OpenAI was classified as unable to decode and
needlessly lost its compacted context.

Relay is now positive only for the canonical surface, the exact official OpenAI
API, or a destination whose operator opts in with the new
`decodesNativeCompactionBlobs` provider flag. Verified that the flag survives
config derivation and reaches the predicate, since the unit tests construct
provider literals and would not have caught it being dropped there.

Also corrects a stale line in the transport notes: compact-wire items are not
exempt from the `store: false` item-id strip. That exemption was deliberately
reverted to match codex-rs (`core/src/client.rs:918-925`).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(responses): stop asserting a disproven cause for the blob-preservation guard

The guard is sound, but its comments claimed it fixed Grok's `Could not decrypt
the provided encrypted_content` failure. Live bisection disproved that: Grok
emits summary-channel reasoning natively, so `reasoningItemToSummaryShape`
returns early and this rewrite never fires on that route. The real cause was
`"content": null` on the replayed reasoning item, fixed separately.

A false causal claim in a comment is worse than none — the next reader trusts it.
The rule is restated on its own terms: an item carrying opaque provider state
should not have its stored shape changed unless that backend has an explicit
replay contract, which is why DeepSeek was safe and why the Kimi/GLM/NeuralWatt
routes now on `preserveReasoningContentModels` are the ones this actually guards.

Comments and prose only; no behaviour change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(responses): scope the null-content strip to routed destinations

The first version stripped `"content": null` from every reasoning item, which
broke OpenAI. Caught in live traffic minutes after deploying it locally:

  400 invalid_request_error
  The encrypted content k7pQ...Px7D could not be verified.
  Reason: Encrypted content could not be decrypted or parsed.

An OpenAI-operated backend binds the blob to the item's exact shape, so removing
a field invalidates it. The two requirements are exactly opposed: xAI refuses the
null key, OpenAI needs it kept — so the strip has to follow the destination.

The predicate is deliberately not `authMode === "forward"`. A noncanonical
forward provider never receives the caller's credentials, so forward auth says
nothing about which backend answers; only the canonical ChatGPT surface and the
official OpenAI API are treated as OpenAI-operated, and a self-hosted relay is
routed like any other gateway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(xai): restore Grok Responses tool compatibility

* fix(responses): address namespace review findings

* fix(responses): close the remaining private-shape leaks on the routed boundary

The namespace boundary lowered complete groups but still let several Codex-private
shapes reach a strict gateway, each reproducing the pre-inference rejection the
boundary exists to prevent.

No `type: "namespace"` value survives now. A group the layer cannot express —
empty, nested, or with an unusable child name — is dropped along with the children
it cannot represent. Relaying the private shape costs the whole request rather
than one tool, so "preserve rather than lose a tool" was losing strictly more.

Replayed call items are lowered whether or not this turn declares the group they
name. The routed compaction turn strips the entire tool surface before the
boundary runs, so every compaction after a namespaced tool call shipped the
private `namespace` key this layer's own restoration had stamped on the item.
Only tool_choice resolves a bare name through the catalog: a history item records
which tool actually ran, so re-pointing it at a same-named namespace child would
rewrite that record on a coincidence rather than translate it.

Codex-private tool fields now come from one table instead of one bespoke pass
each, and it gains `defer_loading` — `activateDeferredTool` clears that only for
tools a `tool_search_output` already loaded, so the first turn of a deferred
catalog carried it to the wire — and the `web_search_preview` variant.

A bare declaration and a `functions` child of the same name are one logical tool:
`buildTools` flattens the reserved group without a namespace, the parser tolerates
the duplicate, and `promoteClientLoadedTools` produces it. That shape raised a
wire-name collision that escaped every catch up to the Bun handler, so an ordinary
catalog became an unstructured 500 with no request log — while the rotation-rebuild
path answered 400 for the identical throw. It is now deduped, and a genuine
collision is a typed error the passthrough maps to 400.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(responses): drop reasoning blobs and output-only status across a route switch

Switching models mid-conversation broke the next turn. Reproduced end to end
through the proxy: mint a reasoning item on xai/grok-4.6, replay to
openai/gpt-5.6-sol.

  replay grok -> grok : OK
  replay grok -> SOL  : Unknown parameter: 'input[1].status'
    ... status removed:
  replay grok -> SOL  : The encrypted content ZvQ+...fBJg could not be verified.
    ... status and encrypted_content removed:
  replay grok -> SOL  : OK

Two independent problems. Grok emits an output-only `status` on reasoning items
that OpenAI rejects on input, and a reasoning blob is decodable only by the
backend that minted it, so after a switch the client replays blobs the new
destination cannot read.

This extends the mechanism the repo already uses for opaque provider state
rather than adding a retry: `reasoning-replay-cache` already keeps a bounded,
thread-scoped store and already computes the provider/destination/adapter/model/
credential identity. It now also records which identity served a thread last, and
a request whose identity differs from that record drops `encrypted_content` from
replayed reasoning items before they go out. No record — fresh process, evicted,
expired, no client thread — keeps the blobs rather than discarding valid cached
reasoning on a guess; that leaves a switch spanning a proxy restart uncovered,
which the comment states rather than implies.

`status` is stripped only from items that are not forwarding a blob. An
OpenAI-operated backend binds the blob to the item's exact shape, so removing any
field from an item we still expect it to decode can invalidate it — the same
failure an unconditional `content` strip already produced once on this codebase.
Content blanking predates that invariant and is unchanged; an item carrying both
a native blob and raw content is a known unresolved conflict, noted in place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(responses): make namespace dedup order-independent and restore custom calls by wire identity

Review found two defects in the flattening layer; both are fixed here.

Deduplication depended on declaration order. A bare declaration and a `functions`
child of the same name are one logical tool, but which one owned the wire name —
and therefore which one was emitted — followed whichever container the rewrite
reached first. The plan now records the bare wire names from the complete catalog
and the bare declaration always wins, so the same catalog flattens identically
whichever container declares it.

Custom-call restoration used the wrong coordinate. A custom tool inside a
non-`functions` namespace is lowered twice on the way out (custom to function,
then renamed to `<ns>__<name>`), while on the way back namespace restore runs
first and replaces the wire name with the bare one. Custom restore then matched
that bare name and could convert an unrelated same-named function call, sending
Codex a `custom_tool_call` with the wrong payload shape.

Converted custom tools are now tracked by their final upstream wire name, and
restoration reconstructs that identity from the `{namespace, name}` an earlier
rewrite restored. A namespaced custom and a namespaced function sharing a child
name now round-trip to their own item types, on both the JSON and SSE paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(responses): compare the serving identity on rotation-safe dimensions

The serving-identity record compared `credentialIdentity`, which for OAuth is
`accountId + generation` and therefore changes on every token refresh. Six of the
eight `bindRouteReasoningReplayScope` call sites are key-rotation or OAuth-refresh
rebinds, so an ordinary refresh registered as "the backend changed" and the next
turn on that thread dropped a valid blob. Key-pool providers would have paid that
repeatedly, and silently — nothing errors, the model just loses cached reasoning.

The module already distinguishes the durable dimensions for exactly this reason
(#1926: the rotating generation deliberately does not participate). The serving
record now compares `providerDestinationDurableIdentity` and
`credentialDurableIdentity`, and refuses to record at all when those are missing
rather than falling back to the volatile pair: a missed strip costs one degraded
turn, a spurious strip is a permanent quality regression. The proxy-owned replay
cache keeps its stricter key, which is deliberate.

Also documents two behaviours that would otherwise read as bugs: a combo that
rotates targets between turns legitimately drops blobs while the SSE model-name
rewrite hides the switch from the client, and the image/web-search loops consume
the replay scope without rebinding, which is what stops an internal small-model
call from poisoning the record for the main conversation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(responses): recover when an upstream rejects foreign opaque state

The thread-scoped serving-identity record strips replayed blobs
deterministically, but it is in-process and bounded, and it deliberately
keeps blobs when it has no record — stripping on "unknown" would discard
valid reasoning after every restart.

That leaves a failure users hit routinely. From the live usage log, one
conversation:

  19:33:31  xai     grok-4.6      200        <- last grok turn
  19:38     proxy restarted (records wiped)
  19:48:11  openai  gpt-5.6-sol   400
            "The encrypted content Py6J...kwW9 could not be verified.
             Reason: Encrypted content could not be decrypted or parsed."

The proxy never served the turn that minted those blobs, so it cannot know
they are foreign. TTL expiry, LRU eviction and any transcript older than the
process open the same hole.

Register a recovery kind rather than invent a retry path: `image-413`
already reacts to an upstream rejection by rebuilding the body once and
refetching inside the recovery loop, with a single-attempt guard. This adds
`opaque-blob-rejection` on the same shape, triggered only by a decoder's own
4xx identity — OpenAI's nested `invalid_encrypted_content`, or xAI's two
concrete decoder messages — and only when the exact outbound body still
carried a blob, so an unrelated `invalid-argument` never gains a hidden
resend and a blobless body never triggers an identical resend.

The deterministic pre-flight stays primary: when a record exists the first
request is already correct and this never runs. Cost when it does run is one
extra round trip and one turn of degraded reasoning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(responses): compare serving identity for compaction blobs too

`scrubOcxCompactionItems` forwarded any non-`ocx1:` blob whenever the
destination could decode native blobs. That is sound only if native blobs
have a single minter, and they do not: xAI mints them as well, so an
xAI-minted compaction blob replayed to an OpenAI-operated destination was
forwarded verbatim and rejected.

Reproduced against the live proxy on a thread whose serving identity had
already changed and was known to have changed — the reasoning path stripped
correctly while the compaction item sailed through:

  POST /v1/responses  model=gpt-5.6-sol, thread last served by xai/grok-4.6
  input: [{"type":"compaction","encrypted_content":<opaque non-ocx blob>}, ...]
  -> 400 invalid_encrypted_content
     "The encrypted content rmey...SQ== could not be verified."

Reuse the signal the reasoning path already consumes rather than recomputing
identity in the adapter: on a known mismatch a native blob degrades through
the existing `compactionItemToText` note instead of being forwarded. With no
known mismatch, behaviour is unchanged.

This covers threads the process has served. A cold record — after a restart,
TTL expiry or eviction — still forwards, which is a separate change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(responses): strip output-only reasoning status unconditionally

The cross-backend fix kept `status` on any reasoning item that forwarded its
`encrypted_content`, to honour "an item whose blob is forwarded is not
otherwise modified". That invariant was defensive rather than observed, and
it broke the cold-record recovery path.

With no provenance record — after a restart, TTL expiry or eviction — the
blob is retained, so `status` is retained too, and OpenAI rejects the request
on the field before it ever validates the blob:

  400  Unknown parameter: 'input[1].status'.

The opaque-blob recovery correctly does not match that error, so the
conversation stayed broken.

Measured against the live backends:

- OpenAI never mints `status` on a reasoning item (keys are content,
  encrypted_content, id, summary, type), so the retain branch could only ever
  fire for an item minted elsewhere — the exact item OpenAI then rejects. It
  never protected an OpenAI-minted item.
- Grok accepts its own 1707-char blob with `status` removed: 200.
- With `status` removed, that same item replayed to gpt-5.6-sol returns 200
  and the usage log records sendCount=2,
  recoveryKinds=['opaque-blob-rejection'] — removing the field is what lets
  the request reach the blob check the recovery is armed for.

The `content` rule is untouched: blanking predates this and is required by
ChatGPT's input contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(responses): converge the two opaque-blob recovery call sites

Two units landed separately and left duplication. The recovery unit was
written on a branch without the compaction-provenance change, so it degraded
compaction items itself by rewriting `parsed._rawBody.input` in place. Once
both are merged that walk is redundant: it sets
`_stripReasoningEncryptedContent`, which is exactly the signal the adapter's
own compaction scrub consumes.

Verified rather than assumed, since the two call sites rebuild through
different adapters. Both reach `openai-responses` (the recovery predicate
restricts to it), whose `buildRequest` consumes `_rawBody` and runs
`scrubOcxCompactionItems`; the native passthrough site resolves a passthrough
retry adapter, the generic site rebuilds through the retained
`activeAdapter`. So the manual walk changes no outbound body on either path,
and dropping it removes a mutation whose side effect outlived the request.

The native Responses branch returns before the generic `recovery:` loop, so
the recovery block was also written out twice. Whoever next adds a recovery
kind to the generic loop would not know a second loop exists. Extract the
shared predicate, guard, preparation, body cancellation and rebuild into one
`attemptOpaqueBlobRecovery` helper both sites call, each keeping its own
control flow and its site-specific rebuild — the generic one still
invalidates the same-target request. Cross-reference comments on both loops
name the other.

No outbound behaviour changes. Existing recovery tests are untouched; added
coverage for routed compaction recovery through the generic loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(responses): pin that the first send already drops reasoning status

The recovery suite asserted the resend well but said nothing about the first
outbound send beyond "it carries a blob". That the first send has `status`
already stripped is load-bearing: the recovery is armed for the upstream's
blob-rejection error, and if `status` survives, OpenAI answers

  400  Unknown parameter: 'input[1].status'.

before it validates the blob. The recovery correctly does not match that
error, so it never fires.

That exact regression shipped once — `stripOutputStatus` was gated on the
item not forwarding its `encrypted_content`, which is precisely the cold
provenance case — and the entire suite stayed green while the live path was
unchanged.

Assert the first send's reasoning item by shape: blob present, no `status`.
Verified the guard bites: reintroducing the old condition turns this test
red, where before it left the suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: olddonkey <olddonkeyblog@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…#2250 rebased) (#2259)

* fix(integrations): honor OFF for Claude Desktop drift and Grok ensure

Claude Desktop disable refused owned gateway_drifted profiles (including a
missing appliedFingerprint), so leftover Claude-3p configs could not be turned
off and looked like they needed an update. Grok ensure still called
syncGrokConfig unconditionally, rewriting ~/.grok after updates even when the
durable switch stayed off. Gate ensure on desired state, clear Desktop residue
when OFF, and stop treating OFF leftovers as stale applies.

* test(integrations): cover desired-off gateway drift status

Keep leftover owned drift and assert the status route reports residue, not a stale apply.

* fix(integrations): re-read desired state before ensure mutations

A toggle during ensure's probe/start window could make the stale snapshot strip a freshly enabled Grok fence or delete a freshly applied Desktop profile. Re-read persisted desired state immediately before each file write, and show leftover desired-off Desktop gateways as stale/cleanup-pending instead of absent.

* fix(integrations): close ensure desired-state race

---------

Co-authored-by: lilinxiong <lilinxiong1997@gmail.com>
…only table (#2262)

The #2258 series unconditional strip collided with the #2238 capability contract: official OpenAI API-key traffic lost external_web_access (lidge full suite 1/14025 fail). The table row is now capabilityGated, active only when supportsOpenAiWebSearchToolFields === false; defer_loading stays unconditional. 387/0 across six suites.
…2266)

* feat: add xAI Responses opt-in switch

* docs(pr): screenshot asset for the responses opt-in switch
Every required publisher-key ACL harden failure reached CI as one fixed
string, "public publisher key ACL hardening did not complete". The
discriminator existed only on `cause`, which the test reporter does not
print, so the three causes that occur on the Windows leg -- ETIMEDOUT
(the budget), EACLIDENTITY (the effective-SID lookup) and EICACLS
(icacls refusing the path) -- were indistinguishable from a log, and
each needs a different fix.

Append the bounded errno-shaped code to the message. Only the code
crosses that boundary, re-checked for shape here rather than trusted,
so a pathname or username component cannot reach a public log through
it. A cause without such a code keeps the previous message unchanged.

Refs #2152.
Co-authored-by: Ingwannu <ingwannu@users.noreply.github.com>
…ngs (#2264 rebased) (#2273)

* fix(responses): address review findings on the native passthrough lane

Three defects found in review of the Grok Responses series, plus one stale
comment. All confirmed against the code before fixing.

**One gate used the wrong predicate.** Custom-tool lowering was gated on
`provider.authMode !== "forward"` while every neighbouring gate uses
`!isCanonicalOpenAiForwardProvider`. A noncanonical forward provider therefore
skipped `rewriteRoutedCustomToolsForUpstream` but still ran namespace
lowering, so a namespace child that was a custom tool got promoted while
keeping `type: "custom"` and the gateway rejected it.

This repeats the mistake the same series documented elsewhere: forward auth
says nothing about which backend answers, because a noncanonical forward
provider never receives the caller's credentials. Both sides move together —
the adapter's lowering gate and core's converted-name collection — since
lowering names without restoring them is worse than not lowering at all.

**The OpenAI-operated classifier missed a legitimate base-URL form.** It
compared the normalized base URL for exact equality with
`https://api.openai.com/v1`, so a provider configured as
`baseUrl: "https://api.openai.com"` with `responsesPath: "/v1/responses"`
reaches the official endpoint yet was classified as routed. That is not
cosmetic: routed classification drops `content: null` from OpenAI-minted
encrypted reasoning and degrades native compaction blobs — this series'
own regression, in reverse. Both official forms are now accepted, still by
exact normalized match so a lookalike host cannot qualify.

**Request rebuilds left the namespace alias map stale.** Every recovery
rebuild replaces `request` without refreshing the alias map the response path
uses to restore private tool names, so a rebuild that changes the lowering
decision restores against a stale map. Refreshed from the rebuilt request on
every path that replaces it — the pre-existing OAuth-401 and image-413
rebuilds included, since the bug is in the rebuild pattern rather than in one
caller.

**`_stripReasoningEncryptedContent` is no longer only a route-switch flag.**
It is also set when an upstream rejects opaque state of unknown provenance.
The comment now names both producers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(responses): record the serving route only after it serves

`updateReasoningReplayServingIdentity` compared and committed in one call, and
`bindRouteReasoningReplayScope` calls it before the request goes out — so the
candidate destination was recorded whether or not that request ever completed.

  turn 1  -> A succeeds                    record = A
  turn 2  -> B: A != B, strip A blobs      record = B   (committed too early)
             ... this request then fails (rate limit, transport, 5xx)
  turn 3  -> retry B: B == B, no strip
             but the transcript still carries A-minted blobs -> rejected

The opaque-blob recovery rescues turn 3, so this degraded rather than broke:
one wasted round trip and one turn of degraded reasoning on a path meant to be
deterministic. The record's meaning was the defect — it should mean "this
destination served this thread", and a request that never completed served
nothing.

Split the call in two. `reasoningReplayServingIdentityChanged` compares without
writing; `commitReasoningReplayServingIdentity` records, and runs only at a
successful terminal response. Bounded discipline is unchanged: same LRU/TTL and
byte accounting, same refusal to record without a durable identity dimension,
same fail-soft direction where no record still means keep the blobs.

For bridged transports a terminal means `completed` or `incomplete`. For
streamed passthrough it means a non-error upstream status before relay starts:
waiting for SSE completion would retain request state for the stream's
lifetime, and a later body failure does not undo that the destination accepted
and served the turn. That boundary is stated in the code rather than implied.

The two post-recovery re-records are gone — a successful recovery now reaches
the same terminal commit as any other success.

Regression test: A succeeds, an A->B turn strips and then fails, and the next B
request for the same thread still strips. Verified it fails against the old
code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(responses): classify the resolved Responses endpoint, not the base URL

isOpenAiOperatedResponsesDestination() matched on the base URL alone, so a
provider with baseUrl "https://api.openai.com" and a custom responsesPath such
as "/other" was classified as OpenAI-operated even though the adapter posts that
request to a non-Responses endpoint. That preserved OpenAI-only null-content and
reasoning semantics for a destination that never sees the official Responses API.

Resolve the effective endpoint with the adapter's own construction rules — a
configured responsesPath is appended verbatim, only the default branch runs the
/v1/responses suffix normalization — and require an exact normalized match on
https://api.openai.com/v1/responses. The conventional /v1 base and the bare
official origin still classify; lookalike hosts still do not.

Adds negative regressions for a custom non-Responses path on both official base
forms, plus positive coverage for the bare origin default and an explicit
/responses path.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zpLCh4eEms6un3VjapRgL

---------

Co-authored-by: olddonkey <olddonkeyblog@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… caching (#2274)

Agent-Generated-By: Codex

Co-authored-by: leon80900 <80900400+leon80900@users.noreply.github.com>
* fix(integrations): sync MCode model capabilities

* fix(mcode): drop the guessed effort ladder from the doc example and pin the unlimited count

- The minimax.md example showed thinking.effortOptions on anthropic/claude-opus-5,
  but the catalog has no authoritative reasoning-effort ladder for it (the
  low..max ladder is the Cursor adapter's map, not catalog data). Keep
  limit.context, drop thinking.
- modelsWithoutLimits in the MCode route test now asserts exactly 2, matching
  the two fixture models without context limits (a/m2 and b/no-context).

* fix(integrations): refresh owned MCode on sync

* fix(integrations): serialize MCode sync writes

---------

Co-authored-by: 弘扬 <hylouis@stu.xmu.edu.cn>
…ion (#2054 rebased) (#2277)

* fix(cursor): reuse conversation checkpoints for incremental continuation

Preserve Cursor's returned ConversationStateStructure after a successful
no-tool turn and reuse that snapshot on validated linear continuations
instead of rebuilding the full root history.

Tool-result turns reuse the last completed checkpoint plus only the
uncovered suffix. Compaction, helper/shadow isolation, account or model
mismatch, missing refs, decode failures, and invalid_argument recovery
keep the existing full-replay path.

Bind checkpoint snapshots to conversation, credential identity, and
model affinity. Keep an opaque process-local checkpointRef on Responses
continuation state, pin referenced blobs for the checkpoint lifetime,
and never treat OpenCodex usage as a cache-hit counter.

Refs #1527

* fix(cursor): pin store:false chat hops and helper-owned cache

Chat Completions / Codex Sol hops often omit previous_response_id and
thread headers, so every hop minted a new conversation and missed the
checkpoint store. Pin those hops to the first user text and reuse the
live snapshot. Isolated helpers keep their own cache and stay off the
parent thread.

Refs #1527

* fix(cursor): keep recovered checkpoints and close review holes

Do not invalidate the checkpoint just committed during forced-fresh
recovery. Invalidate the inherited ref, including compaction leftovers.
Pin checkpoint blobs atomically, collect nested subagent blob ids, and
keep suffix replay off the system prompt.

Refs #1527

* fix(cursor): fail closed on stale refs and prefix collisions

An explicit missing checkpointRef now expires instead of picking another
snapshot. Ref-less Chat hops look up only a unique covered-prefix plus
system digest. Identical first prompts no longer share a conversation.
Expired snapshots are pruned by an unref timer, not the next request.

Refs #1527

* fix(cursor): frame checkpoint digests and drop stale recovery state

Length-prefix instruction and prefix hashes so delimiter splits cannot
collide. Clear the failed transport before forced-fresh retry so the
recovered turn cannot commit the previous attempt. The idle TTL test
now pins a real blob and asserts the lease is gone after prune.

Refs #1527

* test(cursor): distinguish stale refs from ref-less fallback

---------

Co-authored-by: keepitmello <71975659+keepitmello@users.noreply.github.com>
…der config

Routed Grok turns on the Responses lane died with
`400 Argument not supported: external_web_access` before inference.

routedProviderConfig() backfills every other registry-only scalar
(supportsServiceTier, preserveResponsesReasoningContent, fastWire) but not
supportsOpenAiWebSearchToolFields. enrichProviderFromRegistry() does fill it,
and the request path never calls that function -- so a saved xai row reached
the Responses adapter with the flag undefined. The #2262 capability gate reads
undefined as "unclassified upstream, keep the fields", so Codex's OpenAI-only
web_search config went to the wire and xAI rejected the whole request.

Live probe against the OAuth Grok endpoint (2026-08-21) isolates the cause:
bare {type:"web_search"} returns 200, +external_web_access returns 400, and
+search_context_size returns 400 -- individually, before inference.

Verified end-to-end on a remote macOS host running this dev head: with the
GUI Responses opt-in on and NO hand-written capability in config.json, a
multi-step codex exec tool-use turn now completes over adapter
"openai-responses" with status 200, where the same turn 400'd before.

The existing tests could not catch this: they hand-build a provider with the
flag already set, or call enrichProviderFromRegistry() directly, so both start
downstream of the break. The new tests assert on routedProviderConfig() output
and were driven red against the unfixed router.
…-capability

fix(router): backfill the xAI web-search capability into routed provider config
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner August 21, 2026 08:13
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot changed the title promote: dev to main for v2.29.0 [WRONG BRANCH] promote: dev to main for v2.29.0 Aug 21, 2026
@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • wrong target branch (main); retarget to dev. UI screenshot required.

What to do

  • Retarget this PR to dev — all contributions go to dev.
  • Add a screenshot of the UI change to the PR description.

Its title has been prefixed with [WRONG BRANCH].
Automatic draft conversion failed (token cannot change draft status). Please convert this pull request to a draft manually. The required enforce-target check will keep failing until every issue above is resolved.

@github-actions
github-actions Bot marked this pull request as draft August 21, 2026 08:13
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0a8cd8f6-7004-4fb1-a7ed-f0fea8c27655

📥 Commits

Reviewing files that changed from the base of the PR and between 5840591 and 0498a05.

⛔ Files ignored due to path filters (4)
  • .github/pr-assets/xai-responses-optin-switch.png is excluded by !**/*.png
  • devlog/_plan/260820_sidecar_selection_unification/assets/l4-websearch-picker-filtered.png is excluded by !**/*.png
  • gui/tests/fixtures/select-dropdown-opaque-after.png is excluded by !**/*.png
  • gui/tests/fixtures/select-dropdown-opaque-before.jpg is excluded by !**/*.jpg
📒 Files selected for processing (275)
  • README.md
  • devlog/_plan/260814_bug_resolution_campaign/030_wave3_cursor.md
  • devlog/_plan/260820_bug_pr_backlog_consolidation/090_merge_log.md
  • devlog/_plan/260820_bug_pr_backlog_consolidation/110_three_bug_issues.md
  • devlog/_plan/260820_bug_pr_backlog_consolidation/120_release_2280.md
  • devlog/_plan/260820_sidecar_selection_unification/000_unit_overview.md
  • devlog/_plan/260820_sidecar_selection_unification/000_wp0_branch_worktree_cleanup.md
  • devlog/_plan/260820_sidecar_selection_unification/001_research_current_state.md
  • devlog/_plan/260820_sidecar_selection_unification/002_protocol_research.md
  • devlog/_plan/260820_sidecar_selection_unification/003_grok_tools_research.md
  • devlog/_plan/260820_sidecar_selection_unification/010_layer1_sidecar_auth.md
  • devlog/_plan/260820_sidecar_selection_unification/020_layer2_picker_candidates.md
  • devlog/_plan/260820_sidecar_selection_unification/030_layer3_websearch_slots.md
  • devlog/_plan/260820_sidecar_selection_unification/031_future_backend_research.md
  • devlog/_plan/260820_sidecar_selection_unification/040_layer4_write_gates_gui.md
  • devlog/_plan/260820_sidecar_selection_unification/050_layer5_cli_and_final.md
  • devlog/_plan/260820_sidecar_selection_unification/060_layer6_backend_union.md
  • devlog/_plan/260820_sidecar_selection_unification/070_layer7_xai_executor.md
  • devlog/_plan/260820_sidecar_selection_unification/080_layer8_gemini_executor.md
  • devlog/_plan/260820_sidecar_selection_unification/090_layer9_exa_executor.md
  • devlog/_plan/260820_sidecar_selection_unification/100_chat_default_regression.md
  • devlog/_plan/260820_sidecar_selection_unification/110_global_merge_order.md
  • devlog/_plan/260820_sidecar_selection_unification/120_sidecar_chain_merge.md
  • devlog/_plan/260820_sidecar_selection_unification/130_xai_responses_optin_switch.md
  • devlog/_plan/260820_sidecar_selection_unification/140_release_prep.md
  • devlog/_plan/260820_sidecar_selection_unification/141_release_prep_artifact.md
  • devlog/_plan/260820_sidecar_selection_unification/150_lidge_final_gate.md
  • docs-site/src/content/docs/fr/guides/integrations.md
  • docs-site/src/content/docs/fr/guides/minimax.md
  • docs-site/src/content/docs/fr/guides/sidecars.md
  • docs-site/src/content/docs/fr/reference/cli/agents.md
  • docs-site/src/content/docs/fr/reference/configuration/providers.md
  • docs-site/src/content/docs/fr/reference/configuration/server.md
  • docs-site/src/content/docs/guides/claude-code.md
  • docs-site/src/content/docs/guides/integrations.md
  • docs-site/src/content/docs/guides/minimax.md
  • docs-site/src/content/docs/guides/providers.md
  • docs-site/src/content/docs/guides/sidecars.md
  • docs-site/src/content/docs/guides/web-dashboard.md
  • docs-site/src/content/docs/ja/guides/claude-code.md
  • docs-site/src/content/docs/ja/guides/sidecars.md
  • docs-site/src/content/docs/ja/reference/cli/agents.md
  • docs-site/src/content/docs/ja/reference/configuration/providers.md
  • docs-site/src/content/docs/ja/reference/configuration/server.md
  • docs-site/src/content/docs/ko/guides/claude-code.md
  • docs-site/src/content/docs/ko/guides/sidecars.md
  • docs-site/src/content/docs/ko/reference/adapters.md
  • docs-site/src/content/docs/ko/reference/cli/agents.md
  • docs-site/src/content/docs/ko/reference/configuration/providers.md
  • docs-site/src/content/docs/ko/reference/configuration/server.md
  • docs-site/src/content/docs/reference/adapters.md
  • docs-site/src/content/docs/reference/cli/agents.md
  • docs-site/src/content/docs/reference/configuration/providers.md
  • docs-site/src/content/docs/reference/configuration/server.md
  • docs-site/src/content/docs/ru/guides/claude-code.md
  • docs-site/src/content/docs/ru/guides/sidecars.md
  • docs-site/src/content/docs/ru/reference/cli/agents.md
  • docs-site/src/content/docs/ru/reference/configuration/providers.md
  • docs-site/src/content/docs/ru/reference/configuration/server.md
  • docs-site/src/content/docs/tr/guides/integrations.md
  • docs-site/src/content/docs/tr/guides/sidecars.md
  • docs-site/src/content/docs/tr/reference/cli/agents.md
  • docs-site/src/content/docs/tr/reference/configuration/providers.md
  • docs-site/src/content/docs/tr/reference/configuration/server.md
  • docs-site/src/content/docs/zh-cn/guides/claude-code.md
  • docs-site/src/content/docs/zh-cn/guides/sidecars.md
  • docs-site/src/content/docs/zh-cn/reference/cli/agents.md
  • docs-site/src/content/docs/zh-cn/reference/configuration/providers.md
  • docs-site/src/content/docs/zh-cn/reference/configuration/server.md
  • docs-site/src/content/docs/zh-tw/guides/integrations.md
  • docs-site/src/content/docs/zh-tw/guides/sidecars.md
  • docs-site/src/content/docs/zh-tw/reference/cli/agents.md
  • docs-site/src/content/docs/zh-tw/reference/configuration/providers.md
  • docs-site/src/content/docs/zh-tw/reference/configuration/server.md
  • gui/src/app-routing.ts
  • gui/src/components/apikeys-workspace/client-config-clients.ts
  • gui/src/components/provider-workspace/ProviderAuthPanel.tsx
  • gui/src/components/provider-workspace/ProviderDetails.tsx
  • gui/src/components/provider-workspace/ProviderOverview.tsx
  • gui/src/components/provider-workspace/ProviderSettings.tsx
  • gui/src/components/provider-workspace/types.ts
  • gui/src/i18n/de.ts
  • gui/src/i18n/en.ts
  • gui/src/i18n/fr.ts
  • gui/src/i18n/ja.ts
  • gui/src/i18n/ko.ts
  • gui/src/i18n/ru.ts
  • gui/src/i18n/tr.ts
  • gui/src/i18n/zh-TW.ts
  • gui/src/i18n/zh.ts
  • gui/src/pages/Integrations.tsx
  • gui/src/pages/dashboard-overview-sections.tsx
  • gui/src/pages/dashboard-shared.ts
  • gui/src/pages/integrations/FileIntegrationPage.tsx
  • gui/src/pages/integrations/integration-api.ts
  • gui/src/pages/integrations/overview-clients.ts
  • gui/src/pages/providers-shared.ts
  • gui/src/pages/use-dashboard-data.ts
  • gui/src/pages/use-providers-crud.ts
  • gui/src/provider-workspace/catalog.ts
  • gui/src/styles.css
  • gui/src/styles/provider-workspace-settings.css
  • gui/src/ui.tsx
  • gui/tests/client-config-panel.test.tsx
  • gui/tests/fr-localization.test.ts
  • gui/tests/integrations-api.test.ts
  • gui/tests/integrations-overview-rows.test.ts
  • gui/tests/locale-parity.test.ts
  • gui/tests/provider-xai-responses-optin.test.tsx
  • gui/tests/select-dropdown-opaque.test.ts
  • gui/tests/use-providers-crud-update.test.tsx
  • gui/tests/vision-model-options.test.ts
  • src/adapters/base.ts
  • src/adapters/cursor.ts
  • src/adapters/cursor/checkpoint-store.ts
  • src/adapters/cursor/discovery.ts
  • src/adapters/cursor/live-transport.ts
  • src/adapters/cursor/native-exec.ts
  • src/adapters/cursor/protobuf-request.ts
  • src/adapters/cursor/request-builder.ts
  • src/adapters/cursor/transport.ts
  • src/adapters/cursor/types.ts
  • src/adapters/google-antigravity-replay.ts
  • src/adapters/google.ts
  • src/adapters/openai-chat.ts
  • src/adapters/openai-responses.ts
  • src/bridge.ts
  • src/claude/desktop-3p.ts
  • src/cli/agent.ts
  • src/cli/claude-agent-startup-sync.ts
  • src/cli/dispatch.ts
  • src/cli/ensure-desired-integrations.ts
  • src/cli/help.ts
  • src/cli/index.ts
  • src/cli/integrations.ts
  • src/cli/registry.ts
  • src/clients/config-export.ts
  • src/codex/affinity-debug.ts
  • src/codex/inject.ts
  • src/codex/journal.ts
  • src/config.ts
  • src/integrations/mutation-flight.ts
  • src/integrations/owned-refresh.ts
  • src/integrations/registry.ts
  • src/integrations/writer.ts
  • src/lab/public/signature.ts
  • src/lab/subject/behavior-fingerprint.ts
  • src/lib/redact.ts
  • src/oauth/log.ts
  • src/providers/derive.ts
  • src/providers/fastwire.ts
  • src/providers/openai-tiers.ts
  • src/providers/registry.ts
  • src/providers/xai-responses-opt-in.ts
  • src/responses/compaction.ts
  • src/responses/custom-tool-compat.ts
  • src/responses/namespace-tool-compat.ts
  • src/responses/parser.ts
  • src/responses/provider-continuation.ts
  • src/responses/reasoning-replay-cache.ts
  • src/responses/spill-store.ts
  • src/responses/state.ts
  • src/router.ts
  • src/routing/compatibility/behavior.ts
  • src/server/auth-cors.ts
  • src/server/management/agent-settings-routes.ts
  • src/server/management/config-routes.ts
  • src/server/management/integration-routes.ts
  • src/server/management/model-routes.ts
  • src/server/management/provider-routes.ts
  • src/server/management/vision-sidecar-options.ts
  • src/server/management/web-search-sidecar-options.ts
  • src/server/responses-custom-tool-repair.ts
  • src/server/responses-reasoning-summary-rewrite.ts
  • src/server/responses-tool-search-repair.ts
  • src/server/responses/core.ts
  • src/server/responses/responses-field-backfill.ts
  • src/server/responses/terminal-guard.ts
  • src/sidecar/auth.ts
  • src/sidecar/candidates.ts
  • src/types.ts
  • src/types/config.ts
  • src/types/provider.ts
  • src/types/request.ts
  • src/usage/log.ts
  • src/vision/index.ts
  • src/web-search/backends.ts
  • src/web-search/exa-executor.ts
  • src/web-search/gemini-executor.ts
  • src/web-search/index.ts
  • src/web-search/loop.ts
  • src/web-search/parse.ts
  • src/web-search/sources.ts
  • src/web-search/xai-executor.ts
  • structure/03_catalog-and-subagents.md
  • structure/04_transports-and-sidecars.md
  • structure/05_gui-and-management-api.md
  • structure/08_openai-provider-tiers.md
  • tests/adapter-resolve.test.ts
  • tests/bridge.test.ts
  • tests/claude-agent-startup-sync.test.ts
  • tests/claude-management-api.test.ts
  • tests/cli-headless-parity.test.ts
  • tests/cli-help.test.ts
  • tests/cli-ready.test.ts
  • tests/client-config-export-new-clients.test.ts
  • tests/client-config-export.test.ts
  • tests/codex-affinity-debug.test.ts
  • tests/codex-envkey-admission-substitution.test.ts
  • tests/codex-inject-integration.test.ts
  • tests/codex-journal.test.ts
  • tests/codex-restore-app-rewrite.test.ts
  • tests/cursor-adapter.test.ts
  • tests/cursor-blob.test.ts
  • tests/cursor-discovery.test.ts
  • tests/cursor-hardening.test.ts
  • tests/cursor-request-builder.test.ts
  • tests/deepseek-reasoning-replay.test.ts
  • tests/desktop-3p-removal.test.ts
  • tests/ensure-desired-integrations-race.test.ts
  • tests/exa-web-search.test.ts
  • tests/fastwire-policy.test.ts
  • tests/gemini-web-search.test.ts
  • tests/google-antigravity-replay.test.ts
  • tests/google-hardening.test.ts
  • tests/google-signature-history-roundtrip.test.ts
  • tests/google-tool-result-adjacency.test.ts
  • tests/grok-lifecycle.test.ts
  • tests/integrations-invariants.test.ts
  • tests/integrations-state.test.ts
  • tests/lab-public-security-regressions.test.ts
  • tests/management-client-config-route.test.ts
  • tests/management-integration-routes.test.ts
  • tests/management-provider-validation.test.ts
  • tests/minimax-clients.test.ts
  • tests/namespace-tool-compat.test.ts
  • tests/native-claude-desktop-toggle.test.ts
  • tests/oauth-log.test.ts
  • tests/openai-provider-option.test.ts
  • tests/openai-responses-passthrough.test.ts
  • tests/pi-path-contract.test.ts
  • tests/prime-client.test.ts
  • tests/provider-registry-parity.test.ts
  • tests/reasoning-replay-identity.test.ts
  • tests/redact.test.ts
  • tests/responses-compaction.test.ts
  • tests/responses-field-backfill.test.ts
  • tests/responses-opaque-blob-recovery.test.ts
  • tests/responses-reasoning-summary-rewrite.test.ts
  • tests/responses-routed-web-search-fields.test.ts
  • tests/responses-state.test.ts
  • tests/responses-stateless-dangling-call-repair.test.ts
  • tests/responses-tool-search-repair.test.ts
  • tests/server-auth.test.ts
  • tests/server-combo-failover-e2e.test.ts
  • tests/server-opencode-go-goal-streaming.test.ts
  • tests/server-xai-chat-reasoning-streaming.test.ts
  • tests/server-xai-oauth-401-replay.test.ts
  • tests/server-xai-responses-streaming.test.ts
  • tests/sidecar-auth.test.ts
  • tests/sidecar-candidates.test.ts
  • tests/sidecar-settings-vision-filter.test.ts
  • tests/sidecar-settings-web-search-gate.test.ts
  • tests/sync-client-integrations.test.ts
  • tests/terminal-continuation-owner-rotation.test.ts
  • tests/terminal-guard.test.ts
  • tests/thought-signature-credential-scope.test.ts
  • tests/usage-log.test.ts
  • tests/vision-anthropic.test.ts
  • tests/web-search-backend-union.test.ts
  • tests/web-search-candidates.test.ts
  • tests/web-search-parse.test.ts
  • tests/web-search-sources.test.ts
  • tests/xai-web-search.test.ts
  • tests/zcode-client.test.ts

📝 Walkthrough

Walkthrough

This PR bundles many concerns: extensive devlog/docs updates; a new Prime Agent client (GUI, CLI export, integration registry); unified sidecar authentication and candidate selection feeding new xAI/Gemini/Exa web-search executors and an xAI Responses opt-in switch; Cursor conversation checkpoint reuse; Responses namespace-tool, reasoning-replay, and opaque-blob-recovery compatibility work; Google adapter response validation; and smaller fixes to OAuth logging, ACL error codes, and OpenAI-chat EOF handling.

Changes

Documentation

Layer / File(s) Summary
Devlog planning and campaign records
devlog/_plan/260814_bug_resolution_campaign/*, devlog/_plan/260820_bug_pr_backlog_consolidation/*, devlog/_plan/260820_sidecar_selection_unification/*
Adds planning, research, merge-log, and release-gate devlogs. The records document the bug backlog consolidation and sidecar-selection-unification campaigns.
Localized docs-site guides and reference pages
docs-site/src/content/docs/**/*, structure/03_catalog-and-subagents.md, structure/04_transports-and-sidecars.md, structure/05_gui-and-management-api.md, structure/08_openai-provider-tiers.md
Updates guides and reference pages in all supported locales. The updates cover sidecar backend selection, Prime Agent, xAI Responses opt-in, MiniMax capability sync, Cursor checkpointing, and Codex affinity diagnostics, plus matching architecture notes.

Estimated code review effort: 2 (Simple) | ~10 minutes

GUI

Layer / File(s) Summary
Prime Agent client registration
gui/src/app-routing.ts, gui/src/components/apikeys-workspace/client-config-clients.ts, gui/src/pages/Integrations.tsx, gui/src/pages/integrations/*, gui/src/i18n/*.ts, gui/tests/*
Registers Prime Agent as a client across routing, client lists, integration tabs, Claude Desktop drift reporting, and localized strings.
xAI Responses opt-in control
gui/src/components/provider-workspace/*, gui/src/pages/use-providers-crud.ts, gui/src/pages/providers-shared.ts, gui/src/provider-workspace/catalog.ts, gui/src/ui.tsx, gui/src/styles*.css, gui/tests/provider-xai-responses-optin.test.tsx
Adds a mixed-state Switch control. The control atomically toggles openai-responses for Grok 4.5/4.6 through a shared ProviderUpdateResult contract.
Web-search sidecar model picker
gui/src/pages/dashboard-shared.ts, gui/src/pages/dashboard-overview-sections.tsx, gui/src/pages/use-dashboard-data.ts, gui/tests/vision-model-options.test.ts
Builds picker options from server-provided web-search models with catalog fallback. The change preserves persisted models across saves.
Select-dropdown opacity fix
gui/src/styles.css, gui/tests/select-dropdown-opaque.test.ts
Replaces translucent dropdown backgrounds with the opaque surface token. Reduced-transparency fallbacks also use the opaque token.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sidecar/Web-search backend unification

Layer / File(s) Summary
Shared sidecar auth and candidate selection
src/sidecar/auth.ts, src/sidecar/candidates.ts, src/vision/index.ts, tests/sidecar-auth.test.ts, tests/sidecar-candidates.test.ts, tests/web-search-candidates.test.ts
Adds resolveSidecarAuth and sidecarAuthSlots. Both web-search and vision consume the same picker/vision candidate helpers.
Web-search backend registry and executors
src/web-search/backends.ts, src/web-search/{xai,gemini,exa}-executor.ts, src/web-search/{index,loop,parse,sources}.ts, src/types/config.ts, src/lib/redact.ts
Adds WEB_SEARCH_BACKENDS, xAI/Gemini/Exa executors, safe-source sanitization, xSearch configuration, and Exa key redaction.
Management API and CLI
src/server/management/{web-search-sidecar-options,vision-sidecar-options,agent-settings-routes,config-routes}.ts, src/server/auth-cors.ts, src/cli/agent.ts, src/cli/integrations.ts, tests/claude-management-api.test.ts, tests/cli-headless-parity.test.ts, tests/sidecar-settings-*.test.ts
Adds server-side option/rejection helpers, expanded backend validation, and --list CLI support.
xAI Responses opt-in and registry defaults
src/server/management/provider-routes.ts, src/providers/{xai-responses-opt-in,registry,derive,fastwire}.ts, src/router.ts, tests/management-provider-validation.test.ts, tests/fastwire-policy.test.ts, tests/adapter-resolve.test.ts, tests/server-xai-*.test.ts
Adds the PATCH endpoint for xAI opt-in. The registry now defaults Grok 4.5/4.6 to openai-chat unless a caller opts in explicitly.

Estimated code review effort: 4 (Complex) | ~60 minutes

Responses server compatibility

Layer / File(s) Summary
Namespace tool wire-name compatibility
src/responses/{custom-tool-compat,namespace-tool-compat}.ts, src/adapters/base.ts, src/server/responses-custom-tool-repair.ts, tests/namespace-tool-compat.test.ts
Flattens and restores namespaced tool calls for noncanonical destinations. Collision detection rejects ambiguous wire names.
Compaction handling and reasoning-replay identity
src/responses/{compaction,parser,provider-continuation,reasoning-replay-cache,spill-store,state}.ts, src/server/responses/{responses-field-backfill,terminal-guard}.ts, src/server/responses-tool-search-repair.ts, src/server/responses-reasoning-summary-rewrite.ts, src/providers/openai-tiers.ts, src/config.ts, src/usage/log.ts, tests/responses-*
Adds isCompactionItemType, provider-continuation owner validation, serving-identity change detection, and bounded item-ID budgets.
openai-responses adapter sanitization
src/adapters/openai-responses.ts, tests/openai-responses-passthrough.test.ts, tests/responses-routed-web-search-fields.test.ts
Adds destination/identity-aware reasoning sanitization, tool-field stripping, and namespace-alias-aware routing.
Bridge web-search source handling
src/bridge.ts, tests/bridge.test.ts
Sanitizes and deduplicates sources. The bridge tracks and releases the retained-byte budget across every stream-termination path.
Responses core pipeline
src/server/responses/core.ts, src/codex/affinity-debug.ts, tests/codex-affinity-debug.test.ts, tests/server-combo-failover-e2e.test.ts, tests/responses-opaque-blob-recovery.test.ts
Binds provider-continuation ownership, adds opaque-blob one-shot recovery, credential-substitution reporting, and Codex affinity diagnostics.

Estimated code review effort: 5 (Critical) | ~120 minutes

Cursor adapter checkpoint continuity

Layer / File(s) Summary
Checkpoint store and blob leases
src/adapters/cursor/checkpoint-store.ts, src/adapters/cursor/{live-transport,native-exec,transport,types,discovery}.ts
Adds a bounded in-memory checkpoint store, checkpoint-scoped blob leases, and checkpoint capture in the live transport.
Checkpoint reuse in request building
src/adapters/cursor/{request-builder,protobuf-request}.ts
Adds instruction/prefix digests, lineage validation, and suffix-only replay for tool-result continuations.
Adapter wiring and tests
src/adapters/cursor.ts, tests/cursor-*.test.ts
Wires checkpoint commit and invalidation into the turn lifecycle. Tests cover checkpoint reuse, isolation, and TTL eviction.

Estimated code review effort: 5 (Critical) | ~100 minutes

Google adapter hardening

Layer / File(s) Summary
Response validation and tool-result repair
src/adapters/google.ts, src/adapters/google-antigravity-replay.ts, tests/google-*.test.ts
Validates malformed candidate/content/parts/function-call shapes with a fail-closed strategy. The change also repairs tool-call/result adjacency.

Estimated code review effort: 4 (Complex) | ~40 minutes

Client export and integration lifecycle

Layer / File(s) Summary
Prime/MCode/ZCode/Pi export
src/clients/config-export.ts, src/integrations/registry.ts, src/server/management/model-routes.ts, tests/{prime-client,minimax-clients,zcode-client,pi-path-contract}.test.ts
Adds Prime Agent export, MCode context/effort sync, ZCode OpenAI-compatible wiring, and PI_CODING_AGENT_DIR overrides.
Mutation-flight, owned-refresh, and ensure/startup reconciliation
src/integrations/{writer,mutation-flight,owned-refresh}.ts, src/cli/{dispatch,ensure-desired-integrations,claude-agent-startup-sync,index,help,registry}.ts, src/claude/desktop-3p.ts, src/codex/{inject,journal}.ts, tests/*
Adds shared mutation coordination, owned-integration refresh, and Grok/Claude Desktop desired-state reconciliation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Small provider, security, and streaming fixes

Layer / File(s) Summary
OAuth logging and ACL failure codes
src/oauth/log.ts, src/lab/public/signature.ts, tests/{oauth-log,lab-public-security-regressions}.test.ts
Redacts secrets from OAuth logs. The change also adds bounded ACL failure codes.
OpenAI-chat EOF tolerance
src/adapters/openai-chat.ts, src/lab/subject/behavior-fingerprint.ts, src/routing/compatibility/behavior.ts, tests/server-opencode-go-goal-streaming.test.ts
Adds opt-in tolerance for a clean EOF after complete tool-call arguments.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ResponsesCore as src/server/responses/core.ts
  participant CursorAdapter as src/adapters/cursor.ts
  participant CheckpointStore as checkpoint-store.ts
  participant Upstream as Cursor upstream

  Client->>ResponsesCore: POST /v1/responses (tool-result turn)
  ResponsesCore->>CursorAdapter: runTurn(parsed request)
  CursorAdapter->>CheckpointStore: getCursorCheckpointForPrefix(digests)
  alt checkpoint valid
    CheckpointStore-->>CursorAdapter: CursorCheckpointSnapshot
    CursorAdapter->>Upstream: request with checkpointBytes + suffix
  else checkpoint invalid or missing
    CheckpointStore-->>CursorAdapter: invalidation reason
    CursorAdapter->>Upstream: full-replay request
  end
  Upstream-->>CursorAdapter: conversationCheckpointUpdate frame
  CursorAdapter->>CheckpointStore: commitCursorCheckpoint(bytes)
  CursorAdapter-->>ResponsesCore: done event with checkpointRef
  ResponsesCore-->>Client: response with provider continuation state
Loading
sequenceDiagram
  participant CLI as ocx agent sidecar --list
  participant ManagementAPI as agent-settings-routes.ts
  participant SidecarAuth as src/sidecar/auth.ts
  participant Backends as web-search/backends.ts

  CLI->>ManagementAPI: GET /api/sidecar-settings
  ManagementAPI->>SidecarAuth: resolveSidecarAuth(config)
  SidecarAuth-->>ManagementAPI: SidecarAuthState (Codex/Anthropic slots)
  ManagementAPI->>Backends: webSearchSidecarCandidates(config, auth, all)
  Backends-->>ManagementAPI: eligible candidates per active backend
  ManagementAPI-->>CLI: webSearchModels options
  CLI->>ManagementAPI: PUT /api/sidecar-settings {backend, model}
  ManagementAPI->>Backends: webSearchModelIsRejected(backend, model, candidates)
  alt model rejected
    ManagementAPI-->>CLI: 400 with allowed models
  else model accepted
    ManagementAPI-->>CLI: 200 persisted settings
  end
Loading
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/promote-main-2.29.0

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0498a05223

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +646 to +650
const effectiveBackend = body.webSearch.backend === "anthropic"
? "anthropic"
: body.webSearch.backend === "openai" || body.webSearch.backend === null
? "openai"
: config.webSearchSidecar?.backend ?? "openai";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate every requested web-search backend directly

When the dashboard selects an offered xAI or Gemini model, it sends both the model and its backend, but this ternary recognizes only anthropic and openai; xai, gemini, and exa therefore fall back to the previously stored backend—normally openai—and webSearchModelIsRejected returns 400 for the otherwise valid pair. The same narrowing occurs for Claude overrides in agent-settings-routes.ts; accept every validated backend-union value as the effective backend in both routes.

Useful? React with 👍 / 👎.

updateFramed(hash, cursorInstructionDigest(parsed));
for (const message of parsed.context.messages.slice(0, coveredMessageCount)) {
updateFramed(hash, message.role);
updateFramed(hash, contentToText(message.content));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include tool-call metadata in checkpoint lineage hashes

When covered history contains tool calls, changing a call's id, name, namespace, arguments, or paired result metadata without changing its visible text leaves this digest unchanged: contentToText deliberately discards assistant toolCall parts and reduces tool results to content alone. lineageMismatch can consequently accept a stale checkpoint and omit the changed exchange from the suffix, giving Cursor provider state from a different tool execution; hash the complete normalized wire representation, including tool-call and result metadata.

AGENTS.md reference: src/AGENTS.md:L17-L20

Useful? React with 👍 / 👎.

// boundary, leaving an unscrubbable key prefix in the surviving text.
return { text: "", sources: [], error: `exa sidecar HTTP ${res.status}: ${scrub(t).slice(0, 200)}` };
}
const payload = await res.json().catch(() => null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound Exa response bodies before parsing

If Exa or an intermediary returns an oversized body, res.json() buffers and parses the entire response without a byte limit; the adjacent error path likewise uses unbounded res.text(). A malformed upstream response can therefore consume unbounded memory and stall or terminate the proxy instead of degrading to a sidecar error. Read both success and error bodies through the existing bounded-body helper and reject payloads over MAX_SIDECAR_RESPONSE_BYTES.

AGENTS.md reference: src/AGENTS.md:L15-L19

Useful? React with 👍 / 👎.

@lidge-jun
lidge-jun marked this pull request as ready for review August 21, 2026 08:28
@lidge-jun
lidge-jun merged commit 76e9138 into main Aug 21, 2026
34 of 37 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

lines.push(`- ${title}${dated}: ${snippet || "(no excerpt)"} [${result.url}]`);

P2 Badge Escape Exa content before embedding it in the prompt boundary

When an Exa result title, URL, or snippet contains </web_search_result> followed by newlines, this interpolation closes the untrusted-data block created by formatWebSearchResult and places attacker-controlled page content outside the boundary; titles are also repeated raw in the subsequent Sources list. This is directly reachable from Exa result metadata/content, so sanitize control characters and neutralize boundary tags before constructing the digest, while validating citations through the existing safe-source helper.


const t = await res.text().catch(() => "");

P2 Badge Bound xAI error bodies before reading them

When xAI or an intermediary returns an oversized 4xx/5xx body, res.text() buffers the entire response before the later 200-character slice, allowing a sidecar failure to consume unbounded memory instead of degrading normally. Read this path through readBoundedResponseBytes with MAX_SIDECAR_RESPONSE_BYTES, as the Gemini executor already does.

AGENTS.md reference: src/AGENTS.md:L17-L17


commitReasoningReplayServingRoute();

P2 Badge Defer serving-identity commits until the SSE terminal event

For a passthrough Responses stream that returns HTTP 200 and then emits response.failed, this commits the candidate route before the body reveals that it never served the thread. If route A previously served the conversation, route B fails this way, and the client retries A, the store now reports a B→A identity change and strips A's still-valid encrypted reasoning state. Defer the commit until terminal inspection observes response.completed or response.incomplete, rather than treating the HTTP status alone as success.

AGENTS.md reference: src/AGENTS.md:L17-L17

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants