Skip to content

[WRONG BRANCH] release: promote dev 52c326b479 to main as 2.89.0 - #7372

Merged
lidge-jun merged 30 commits into
mainfrom
release/promote-main-2.89.0
Oct 19, 2026
Merged

lidge-jun merged 30 commits into
mainfrom
release/promote-main-2.89.0

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

This promotes the frozen dev candidate 52c326b479d0d5a7549d35663042f0731861202d to main as stable 2.89.0. The promotion head merges in main ancestry with the ours strategy, and its tree is exactly the candidate tree (99b3f420c3). The candidate already sets the version to 2.89.0 in package.json, desktop tauri.conf.json, Cargo.toml and Cargo.lock. The dev pre-move is #7371 (dev → 2.90.0), merged before this promotion.

2.89.0 carries the fixes and features merged since 2.88.0:

Verification

  • Candidate 52c326b479: manual Cross-platform CI lane=all run 39221379295 completed with success (windows 1–9/9, macos 1–3/3, macos control, Linux test 1–4/4, gates, desktop shell). Service lifecycle run 39221407401 succeeded on the same SHA.
  • Regression verification passed on dev c344c6ca10 and the launcher delta re-verification passed on 7dfc1412b4 (remaining source-invariant oracles fixed by test(launcher): assert the scoped env and versioned bundled runtime in source oracles #7365).
  • Required after merge: push-event Cross-platform CI, Service lifecycle, and catalog contracts on the merge SHA. After those, release.yml runs as a dry run, then live with expected-sha set to that SHA.

Main-review disposition. Promotion from dev to main and npm releases are maintainer-controlled (MAINTAINERS.md). The project owner authorized this release, so the promotion merges with owner authority (--admin). The PR targets main deliberately, so an enforce-target wrong_base failure is expected (as in #7094, #7110, #7249 and #7264).

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.

github-actions Bot and others added 30 commits October 16, 2026 19:30
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…sentation (#7282)

* feat(compatibility): separate custom-tool grammar evidence from representation

Record custom-tool representation and grammar as separate compatibility
claims, and add a second exact subject where grammar is lost.

- canonical Codex forward (gpt-5.6-sol): new request.custom_tools.grammar
  passthrough claim backed by a format assertion with a real Lark
  definition; manifest 1.4.0
- OpenGateway key auth (z-ai/glm-5.3-flash-ultrafast on the Responses
  wire, supportsResponsesCustomTools=false): custom tools translated to a
  string-input function, grammar degraded, custom_tool_call and
  custom_tool_call_output history translated
- manifest test runs each fixture against the production adapter for its
  own subject, pins every subject's route, and adds a catalog evidence
  gate: cited assertion ids must exist in the owning fixture, and a
  passthrough claim may only cite equals assertions whose expected value
  is the caller's own input, so grammar loss cannot be labeled lossless

No runtime behavior change; manifests stay passive.

Refs #2358

* test(compatibility): bind passthrough evidence to the claimed feature

A passthrough claim could cite any preserved caller value, so a grammar
claim backed by an equals assertion on /body/model passed the gate while
the grammar was dropped. Each passthrough-claimable feature now names the
selectors that can prove it, grammar evidence must carry a non-empty
definition, and unbound features cannot be claimed as passthrough.

Refs #2358

* docs(contributing): sync passthrough evidence rule to localized guides

Refs #2358
* fix(antigravity): show the Gemini 4 Argon quota group separately

retrieveUserQuotaSummary reports Gemini 4 Argon as its own group. The
summary parser matched it as "gemini", so its weekly bucket collided with
Gem (Weekly) and was dropped, or replaced the Gemini reading when the Argon
group came first. The model-list fallback had the same order problem.

Each bucket now decides its family from its own id and name (Argon before
Gemini), with the group display name only as a fallback. Argon buckets
become Argon / Argon (Weekly) windows tagged with the canonical pool model
gemini-4-argon, which keeps them out of headroom, exhaustion, reset
ordering and reset notifications like the existing model rows. The
account reset-time selection now skips model-attributed rows as well.

Closes #7272

* docs(structure): state the Antigravity group fallback rules exactly
* fix(google): honor Vertex project endpoints with API keys

* fix(vertex): select API-key project endpoints from provider config only

Keep #7266's project/location endpoint for API keys, but only when the
provider configures project explicitly. Ambient GOOGLE_CLOUD_PROJECT,
GCLOUD_PROJECT and GOOGLE_CLOUD_LOCATION stay ADC fallbacks, so a gcloud
default in the shell cannot move an express-mode key onto another project.

Endpoint selection now lives in src/adapters/google-vertex-endpoint.ts and
also derives the thought-signature replay namespace. Every Vertex branch
rejects a model id that is not a single path segment before credentials
are attached.

* fix(vertex): separate express and project replay namespaces

Prefix the Vertex replay key with the endpoint kind and encode the
project, so a configured project named "api-key" can no longer share
express mode's thought-signature namespace.

* fix(vertex): treat a blank project as unset and sync translated docs

Trim provider.project once so a whitespace-only value keeps API keys on
express mode and lets ADC use its environment fallback, and update the
project/location rows in the seven translated provider references.

---------

Co-authored-by: JUN <jun@lidgeai.com>
)

* fix(google): nest Vertex Argon tool-result images

* fix(google): separate orphan images from Vertex Argon response batches

* docs(google): clarify orphan result ordering guarantee

* test(google): cover Argon signature replay with matched and duplicate tool images

Seeds a real replay signature, then checks on one continuation that the matched image nests in functionResponse.parts, the duplicate image moves to the following marked user turn, and neither image's bytes leak into text parts.

* test(google): cover streaming continuations and error results for Argon tool images

The continuation request now runs with stream on and off, and isError results keep matched-image nesting and orphan-image placement. Both cases fail on the pre-#7280 serializer.

* test(google): cover API-key project endpoints with Argon tool images

Combines #7266 and #7280 on one request: a configured-project API key uses the project endpoint with location global, a projectless key stays on express, and in both the matched image nests in functionResponse.parts while the duplicate image moves to the marked orphan turn. Ambient project/location variables are set to conflicting values and restored.

---------

Co-authored-by: JUN <jun@lidgeai.com>
)

* feat(combos): let a combo set its catalog row description (#7229)

Combos could relabel their Codex picker row with displayName but the row
description was always the generated "Routed via opencodex → combo (combo)."
text. Add an optional combos.<id>.description, validated like displayName,
accepted by the management API (omission keeps the saved value, "" clears)
and by ocx combo set --description <text|->.

Catalog maintenance recognized OpenCodex-authored rows by that generated
prefix, so an overridden row now carries opencodex_routed_origin: "combo-v1".
src/codex/catalog/ownership.ts owns both signals and replaces every prefix
check (removal, shadow restore, template selection, auto-review, exact-combo
admission, native residue). Rows without an override are byte-identical.

* fix(combos): re-project the current description onto retained combo rows

A combo row kept from the previous catalog when its fresh derivation fails
(for example a native-alias combo whose member provider is disabled)
bypassed deriveEntry, so changing or clearing the description in config did
not reach the picker. Config-aware merges now pass the current combo
descriptions and the retained loop re-applies them; a cleared override
returns the generated combo text and drops the marker.

* fix(combos): reject C1 controls in descriptions; document zh-tw flags

Review follow-up for #7229: the description validator now rejects the full
Unicode Cc class (C1 included), with NEL/CSI regression cases, and the
Traditional Chinese combo guide lists --display-name/--description and the
description field like the other locales.
…uest bodies (#7285)

* feat(responses): opt-in compression of outbound ChatGPT Responses request bodies (#7247)

Codex re-sends the full conversation every turn; over HTTP OpenCodex forwarded it to
chatgpt.com identity-encoded. upstreamRequestCompression ("gzip" | "zstd", default off)
compresses canonical codex responses/compact string bodies of 1 KiB-16 MiB at the final
send in sendWithConnectionPolicy, sets Content-Encoding and drops stale Content-Length.
WebSocket frames, other destinations, pre-coded or non-string bodies and codec failures
keep today's representation. Compression is synchronous so no await separates dispatch
admission from the physical send; the 16 MiB ceiling bounds the event-loop block. The
setting is read per send from the live server config installed by setLiveStateStoreConfig.

* test(responses): make request-compression assertions prove nesting, fallback and caller immutability

Count codec calls, assert Buffer fallbacks with Buffer.isBuffer, snapshot caller headers independently, and route the nested case back through sendWithConnectionPolicy with a barely compressible body so a missing idempotence guard double-codes (driven red once). Also asserts the dispatch receipt precedes executor entry with compression active.

* fix(config): refuse unknown upstreamRequestCompression values on live writes

The schema keeps degrading a hand-edited unknown codec to identity, but validateConfigCandidate now rejects it so ocx config set and imports report the error instead of persisting success and dropping the value (Codex/CodeRabbit review on #7285). Docs name the CLI setter and the restart requirement.
* feat(accounts): opt in to auto-pause permanent account refusals

* fix(accounts): register pause route and extracted Responses owners

* fix(accounts): inject refusal observer across transport leaf boundary

* Preserve pause ownership through distributed revisions and pacing rewraps

* test(responses): cover replay ownership after auto-pause rotation

* test(responses): supply the replay conversation header

* fix(responses): rebind replay owners after auto-pause rotation

* test(responses): require the exact sibling replay identity

* fix(accounts): fence auto-pause against policy changes and address review

Persist a per-provider accountAutoPausePolicyRevisions UUID, minted only by
persistConfigUnlocked from the pre-write file when a tracked provider's
effective policy fingerprint changes. Attempts capture revision, local
generation, threshold and route; OAuth, API-key and Codex pauses commit only
while the in-memory and fresh persisted fence still match, with the OAuth
automatic publication serialized under the config mutation lock.

Also preserve key pause fields across dual-auth OAuth upserts, keep an
omitted accountAutoPause on full provider saves, accept the policy as a
canonical-provider overlay, reject non-object pause bodies, degrade
malformed pause display metadata, and fix CLI/dashboard pause messages,
translations and accessible names.

* fix(accounts): count auto-pause refusals only for the credential on the wire

sendWithConnectionPolicy now reports the final destination and headers,
after plugin rewrites and queued rebuilds, through an init symbol. Auto-pause
observers count a response only when that wire credential and destination
match the captured attempt; a Codex WebSocket send, which has no HTTP
final-send report, counts only while no plugin upstream rewriter is
registered. Native Chat, native Messages and the Responses providerFetch
path all report the final send.

* fix(accounts): report oauthDispatch final sends and fence WebSocket attribution

The Responses oauthDispatch send now attaches its auto-pause observer's
final-send report. A send without an HTTP report (Codex WebSocket) counts
only when no upstream rewriter was registered at capture and the rewriter
registration epoch is unchanged at the response, so a rewriter that
registers or unregisters mid-send can no longer make a rewritten dial look
attributable.

* fix(accounts): count a nested physical response once per account

A providerFetch observer and the dispatchOverride observer inside it see the
same physical response. Count it once per account key, and never let an
observer without a final-send report attribute a response that another
observer reported as sent with a different credential.

* fix(accounts): keep live auto-pause fences across detached config loads

A loadConfig() snapshot now adopts only the disk revision; fingerprints and
local generations start there only for providers not yet observed, so a
detached snapshot that differs from the live config can no longer keep
invalidating in-flight fences. API-key quota rows also keep the pause flag
and known pause details, matching the plain key listing.

* fix(accounts): fence observed disk policy changes across detached loads

Track the last observed disk fingerprint separately from the live one. A
loadConfig() snapshot advances the local generation when the disk policy
differs from the previously observed disk policy, so an external change
followed by a restore still invalidates older attempts, while a snapshot
that merely differs from the live config does not.

* fix(accounts): bind the Codex workspace into auto-pause ownership and reclaim deleted-provider epochs

Codex pool and main-pool attempts now own a response only when the final
wire request carries the captured bearer, the context's chatgpt-account-id
and unchanged organization/project selectors, at capture and in the
final-send report. Policy revision records are kept only for providers that
exist after disk-only preservation, observed records for deleted providers
are dropped, and every new record or advance draws from one process-wide
generation counter so a recreated provider cannot revive an older fence.

* fix(accounts): never re-enroll an observed-deleted provider from a stale capture

Once a config path has been observed, captureAccountAutoPauseFence no longer
seeds a missing provider from the caller's config object; only load, reload
or save adoption can enroll it. A capture taken from a stale live config
while its provider is observed deleted therefore cannot survive an identical
restore.

* test(accounts): auto-pause attribution reads post-compression wire headers

With opt-in gzip request compression enabled, the auto-pause final-send
report sees the compressed send's headers (content-encoding set, stale
content-length removed) and still attributes a refusal to the credential
actually sent.
* feat(providers): explicit single-model completion test

Add a user-confirmed, bounded completion diagnostic for one provider and
one model, next to the existing no-completion connectivity probe:
POST /api/providers/test-completion, `ocx provider test <name> --model <id>`,
and a dashboard control in the provider workspace.

The runner talks to the upstream through the provider's normal
credential, destination and egress path and reports TTFT, total latency
and the raw upstream model metadata read before response.model is
rewritten. Comparison is metadata only and never claims identity.

Refs #2834

* fix(providers): dispose diagnostic translator budget, resolve aliases before wire, count real sends in ingress tests

Refs #2834

* fix(providers): admit local cleartext providers and keep gateway routing in completion test

Refs #2834

* fix(providers): bound completion-test body reads, round latency, align CLI model check

Refs #2834
…7286)

* feat(codex): opt-in OS desktop notification for low-quota protection

Low-quota protection already pauses Codex Pool accounts and records a
"logged" alert, but never reached the operating system. A new opt-in
codexPool.lowQuotaProtection.actions.desktop flag (requires notify) hands
each notice to the proxy host's notifier: /usr/bin/osascript on macOS,
notify-send from a literal system path on Linux. Windows and hosts without
a notifier stay "logged".

The notice text is a separate argv item (constant AppleScript, "--" for
notify-send) and names the account only by its pseudonymous log label.
Each in-flight notice is abortable: every episode end (reset boundary,
recovery, account removal, policy change, shutdown) kills the child and
records one "cancelled". Delivery failures and timeouts record "failed"
and retry on a later eligible observation. Armed test processes never
reach a real notifier without an injected spawn.

Refs #5649

* test(codex): cover notifier cleanup and adapter-composed low-quota delivery

Refs #5649

* fix(codex): back off failed low-quota notices and log once per episode

A notifier that keeps failing (for example notify-send without a session bus) was respawned and logged on every eligible observation. A failed notice now retries no sooner than five minutes later, and each episode writes its log line once.

Refs #5649

* docs(codex): state that low-quota notice cancellation is observation-driven

Refs #5649
…6814)

* feat(combos): opt-in advisory quota signals for JEV decisions

Add per-Combo opt-in decisionQuotaSignals/decisionQuotaTiers so the JEV
decision backends can see loaded-only, in-memory advisory quota evidence
(limited / nearly exhausted) for the candidate targets. Unset Combos are
byte-identical to before; no level or pin behavior is added.

Evidence is published by the existing quota producers into a snapshot
bound to the credential generation, goes stale after 30 minutes, treats
unknown as healthy, and marks a pool nearly exhausted only when every
usable account is. Credits are never treated as quota windows.

The second Anthropic instance pool is excluded explicitly: only the
primary instance publishes or is read, covered by a regression test.

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

* fix(combos): address review on advisory quota publication and document touched functions

- Keep main-account advisory publication explicit opt-in: setAccountQuotaFromParsed no longer
  defaults decisionRaw to the policy observation, so a live main writer that omits it publishes
  no codex-main evidence. Adds a regression test (red before the change).
- Parse the usage body once in the reserve capability read and reuse the observation.
- Add docstrings to the functions touched by this change (docs only).

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

* fix(oauth): keep advisory quota publication from affecting auth-store load and persist

Roster publication ran inside the load try, so a publication failure would
reach the invalid-config backup path and return an empty store. Publish after
the parse, make the auth-owned publisher non-throwing, and withdraw the
roster (unknown, not stale) when projection fails.

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

* fix(codex): keep advisory quota publication from affecting Codex account-store load and persist

Roster publication ran inside the load parse try, so a publication failure
reached the invalid-config backup path (spurious codex-accounts.json.invalid-*
backup) and propagated. Publish after the parse, make the Codex-owned and
main-credential publishers non-throwing, and withdraw the roster (unknown,
not stale) when projection fails.

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

* test(routing): realign the sweeper generation before the invalid-reset quota tests

An earlier file in a shared bun process can leave a failed (incomplete-context) reconciliation behind: owners that ran before the failing one keep the raised fence while the shared generation stays behind, so default-generation Codex quota writers are discarded as stale and the invalid-reset tests saw no advisory window. Reconcile with a complete empty context in beforeEach, and capture the real generation instead of a literal 0 in the WHAM commit test.

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

* test(layout): keep the layout registry under the file-size cap after merging dev

Both this branch and dev added registry lines; the merged file reached exactly
2000 lines (the ratchet threshold). Put the two entries this branch adds on
existing lines, as the neighbouring entries already do. No cap or baseline
changed; the registry is back to 1998 lines.

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

* fix(jev-quota): register advisory quota publication only while a combo opts in

Maintainer review (2026-10-15) asked that core credential stores stop publishing decision-quota
rosters for every install. Publication is now activation-owned:

- src/lib/decision-quota-sink.ts: core slot; every producer helper is a no-op without a sink.
- src/combos/jev-quota-activation.ts: sole runtime importer of the decision snapshot; registers the
  snapshot-backed sink while a live JEV combo has decisionQuotaSignals true, replays stores once via
  observe-only reads, detaches and withdraws on the last disable, deletion or config-root change.
- Auth/Codex stores, native-main cache and quota producers call the slot; raw-window binding and
  projection are skipped while inactive, and unbound observations never fall back to display values.
- Live-config observer slot in state-store-registrations (live adoption, live reconciliation and
  published-but-unreconciled combo writes); started/stopped with the optional listener set.
- Native-main decision row is owned by the config root that observed the credential; unknown otherwise.
- Tests: signals-off zero writes, activation/deactivation/root switch/reentrancy/published-error
  paths, and a static guard that keeps the snapshot off the stores/router/lifecycle/core graph.

* fix(jev-quota): keep a credential-less native main known-unusable across roots

CodeRabbit (src/combos/jev-quota.ts:60): root ownership made a confirmed-absent main unknown, so a pool with every account at 95% read as healthy instead of nearly_exhausted. Root ownership now applies only to a retained credential; with none the row is known-unusable as before. Regression added.

* fix(codex): keep the core main credential binding on presence loss; retire advisory state separately

Presence loss (including transient failed credential reads) cleared mainQuotaCredential and advanced its generation, which dev never did; policy checks that match the observed main bearer (hard lock, caller cooldowns) could then stop matching for every install. The core binding is restored to dev behavior. Decision evidence now uses its own generation and a fresh-observation flag, so absence still retires advisory windows and stale writers. Regressions: signals-off binding survives presence loss; active presence loss retires the main row's windows and writers.

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: JUN <jun@lidgeai.com>
* feat(messaging): add explicit enrolled Codex SSH peers

- Added an opt-in foreground owner, private directional enrollment and authenticated same-connection queued delivery with return leases.
- Reused shared SSH policy without activating messaging during normal proxy startup or changing local Unix-only defaults.
- Added isolated lifecycle, recovery, transport and native-contract regressions with public CLI and ownership documentation.
- Qualified isolated loopback SSH and stock Codex 0.160.1; integrated suite and independent security review are being completed before review readiness.

* fix(messaging): join unpublished remote tunnel cleanup

- Join both owned helpers before propagating startup configuration changes or cleanup failures.
- Bound native qualification fixture requests by their cancellation budget and keep test registrations within the existing layout size cap.
- Passed typecheck and six corrected lifecycle regressions; independent delta review found no new security issue. Final rebased integration qualification remains separate.

* docs(messaging): record rebased contribution qualification boundary

- Refresh the individual-peer contribution baseline and completed preparation sequence.
- Record the full-suite resource exception without claiming a passing run or relaxing gates.
- Preserve hosted CI, macOS interoperability and maintainer acceptance as separate outstanding requirements.
- Verified range-diff equivalence, typecheck, privacy, ownership, generated surfaces and 259 focused regressions after rebase.

* test(messaging): wait for native turn history after queue acceptance

- Assert the stock daemon pre-history state before submitting a fixture message.
- Poll only exact known read-only history errors within the existing deadline; keep queue submission outside the polling loop and sanitize other errors.
- Verified the isolated stock 0.160.1 gateway queue, completed synthetic turn, correlated reply and owned cleanup: one pass, six assertions.
- Independent fixture-delta review found no concern; no production behavior changed.

* fix(messaging): enforce remote admission and cleanup ownership

- Bind enrollment completion and recovery cleanup to the current transaction under one store lock; invalidate recovery before publishing revocation.
- Revalidate source authority before remote submission without retiring unrelated peers or replaying unknown outcomes.
- Retain withdrawn tunnel cleanup joins and bound logical helper settlement while charging unresolved physical children.
- Reserve eight persistent and two transient helper slots; cover Darwin listener polling with isolated fixtures.
- Verified 283 focused tests, real loopback SSH and stock Codex round trips, typecheck, privacy, structure, surface and docs checks; broader changed-test qualification remains incomplete.

* fix(docs): refresh remote messaging contribution base

- Record the latest dev base and unchanged five-patch rebase for the remote messaging contribution.
- Update the handover publication boundary to reflect user authorization while keeping deployment separate.
- Revalidated 283 focused tests, isolated SSH and stock Codex round trips, typecheck, privacy, structure, surface and docs build; broad changed-test selection remains incomplete at its documented ceiling.

* fix(messaging): recover enrollment and retire stale remote routes

- Add exact-transaction enrollment abandonment with explicit unconfirmed remote cleanup and correlated receiver-capacity refusal.
- Accept trusted configuration parents while keeping messaging storage private; tolerate only a missing Linux IPv6 listener table.
- Fence return-lease replacement against live generations and retire unexpected tunnel loss after joined cleanup.
- Synchronize CLI metadata, generated references and architecture/user documentation.
- Validate with 294 passing focused regressions, six skipped cases, typecheck, privacy, structure, surface checks and the 569-page docs build.

* fix(messaging): scope cleanup and refresh leases before readiness

- Guard receipt-driven cleanup by the exact enrollment transaction under the mutation lock so old receipts cannot revoke replacement peers.
- Renew return leases within the shared startup deadline before readiness, and join helper exit with natural pipe EOF without signalling historical process groups.
- Add stale-receipt, slow-startup, renewal-failure and descendant-pipe regressions; synchronize CLI references and lifetime documentation.
- Verified 302 focused tests, typecheck, privacy, structure, generated surfaces, the docs build and disposable SSH enrollment/duplex delivery; broader suite and macOS qualification remain outstanding.

* fix(messaging): harden enrollment durability and helper draining

- Keep malformed recovery journals from blocking peer revocation while preserving matching-journal invalidation safeguards.
- Sync published and removed journal entries before dependent work; report uncertain crash durability explicitly.
- Drain discarded helper output to natural EOF, preserve first failures, and retain incomplete-cleanup evidence and capacity.
- Add regression and fault-injection coverage; update storage, cleanup and CLI documentation.
- Verified 318 focused tests, typecheck, privacy, structure, surface and docs build; the 180-second changed-graph run remains incomplete.

* fix(messaging): isolate revocation keys and normalize SSH probes

- Preserve alias-prefixed private host-key records despite HashKnownHosts user defaults, without changing pinned trust policy or normal known_hosts files.
- Isolate each revocation attempt and report committed local removal even when preparing or sending remote cleanup fails; remove owned scratch files in finally.
- Complete the structure source map, regenerate its index, and update messaging and Remote Link documentation.
- Added five targeted regressions: all failed against the old implementation and pass after the fix; the disposable SSH fixture now enables HashKnownHosts.

* fix(messaging): classify malformed local enrollment journals

- Share strict journal interpretation between enrollment retries and recovery metadata reads, preserving original evidence and storage/lock errors.
- Cover malformed JSON, outer/nested records and invalid capabilities at the enrollment and CLI boundaries; document explicit operator recovery.
- Verify 43 enrollment tests and strict typecheck; broader messaging and documentation qualification follows before publication.

* fix(messaging): bind remote admission to peer consent

- Reconstruct envelopes against authenticated peer identity and separate outbound enrollment from explicit local inbound consent.
- Bound one-shot correlated reply routes across consent changes, native rejection and uncertain delivery.
- Isolate pre-auth capacity and clean attempt-owned enrollment pins without disturbing recovery journals.
- Add regression and contract documentation coverage; 388 focused tests pass under Bun 1.4.3, including isolated native and SSH interop.
- Verify typecheck, privacy, structure, generated surfaces and docs; broad changed-graph lane remains incomplete at its 180-second ceiling.

* refactor(messaging): clarify remote admission and cleanup branches

- Expanded connection retirement, queue rejection and reply-grant settlement into explicit ordered blocks.
- Split envelope validation into readable guards while preserving the sanitized public error and canonical attribution checks.
- Clarified enrollment journal publication and scratch cleanup without changing lock or recovery semantics.
- Validated 105 focused regressions, 114 import-connected integration tests and nine file-size checks; typecheck, privacy and structure checks passed.

* fix(test): clean up partially started messaging owners

- Move both fixture owner starts into the cleanup scope and await all initialized owners on failure.
- Preserve the original startup error and successful fixture behavior.
- Verified Bun 1.4.3 typecheck, whitespace checks and 138 focused tests; two opt-in integration tests skipped.

* fix(messaging): keep owner connection slots out of enrolled peers' reach

Review item 3 asked that unauthenticated sockets not starve the owner. The
pre-auth cap covers silent clients, but every authenticated peer socket still
drew from the same 16-slot pool that local owner control, outgoing probes and
lease renewal use, so enrolled peers holding sockets could make the next
renewal fail and retire the owner.

Authenticated peer sockets now share twelve connections; four stay reserved
for the owner's own paths. There is deliberately no per-peer share: capping a
peer at its fair share (three) let three legitimate in-flight sends refuse
that peer's own lease exchange and retire the owner. Docs and structure state
the remaining fail-fast contract: one enrolled peer can still occupy the peer
share, and an initiated peer can end the owner by failing lease renewal.

* test(messaging): prove the review guards are not vacuous

Adds regressions for receiver-bound attribution variants, concurrent replies
on one route, consent withdrawn during a suspended lookup, repeated RPC ids,
cross-socket replay of a captured authorize proof, lost-outcome grant
consumption, and messaging SSH consumers going through the shared argv
builder. The overlapping-enrollment scratch test now records observations in
the SSH runner and asserts outside it, so a shared scratch name fails an
assertion instead of surfacing as enrollment_unknown.

---------

Co-authored-by: JUN <jun@lidgeai.com>
…pic (#7341)

* feat(anthropic): default prompt-cache TTL to 1h on first-party Anthropic

Unset cacheRetention now resolves to long (ttl 1h) for api.anthropic.com (API key and OAuth pools) on the translated adapter, the native /v1/messages passthrough, and the translated /v1/messages lane. Anthropic-compatible endpoints keep 5m. Explicit config and caller-pinned 5m breakpoints still win. 1h cache writes reported by Anthropic are priced at 2x base input.

* fix(anthropic): carry 1h cache-write share through merges and Messages output

* fix(anthropic): keep Zed on 5m and carry the 1h write share through every usage path

Zed borrows the api.anthropic.com body builder but sends to its own gateway; thread cacheRetention through and keep the omitted default at 5m. Parse cache_write_1h_tokens from Responses usage, claude-cli stream usage, the combo summary projection and the native steering log. Treat tool input_examples as caller data.

* fix(responses): parse the 1h cache-write share from Responses upstream usage
)

With Claude Code CLI first-party on, ~/.claude/settings.json sets
HTTPS_PROXY to OpenCodex's own intercept listener
(http://opencodex:<token>@127.0.0.1:<port>), and every process Claude Code
starts inherits it. Installing or restarting the service from such a shell
baked that URL into the service definition, so the proxy sent its own
outbound traffic back through its intercept listener and the per-install
token ended up in the wrapper script.

resolvedProxyEnv now skips the token-bearing intercept URL and falls
through to the lower-case spelling when it holds a real proxy. A tokenless
loopback proxy such as a local Clash or v2rayN port is still baked.
…7352)

* fix(sidecar): pass pacing-queue overload through image and web-search loops

When an image-bridge or web-search loop iteration hit a full request-pacing
queue (or an exhausted key pool), the header-phase catch wrapped the
RequestPacingQueueOverloadError into LoopError(502, "Provider unreachable").
The caller saw a 502 upstream failure instead of the 429 overload answer
with Retry-After that every other route returns. Rethrow the overload like
the shared transport classifier already does, so the handler's
requestPacingOverloadResponse maps it.

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

* fix(bridge): keep the adapter's usage on a translator-overflow terminal

When an adapter raised `translation_buffer_limit` as an error event, the bridge
handed it to `terminateForTranslatorOverflow`, which ignored its argument and
synthesized a usage-less `response.failed`. A turn the provider had already
billed was therefore logged with zero usage and cost.

The overflow terminal now reports the event's usage through `onUsage` and on
the `response.failed` frame, as the ordinary error terminal already does. The
client-facing error is unchanged.

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

* fix(sidecar): map provider input overflow to context_length_exceeded

An upstream 413 (or a 400/422 carrying a definite context-overflow message,
or an adapter's own context_length_exceeded event) behind the web-search or
image sidecar leaked out as a generic upstream failure, so Codex resent the
identical oversized payload through its retry budget instead of compacting.

One classifier in context-overflow.ts now serves both loops: the sidecar
boundary maps a first-iteration failure to the main path's streaming or JSON
overflow response, and both loops end a later iteration's overflow with the
same terminal event. Combo attempts keep their raw failure for the combo
layer, which already maps a final 413 itself.

Closes #7316

* fix(sidecar): keep combo evidence and non-replayable failures on overflow

Review follow-up for #7316:
- A worded in-stream overflow keeps its status and provider wording beside
  context_length_exceeded, so combo stream preflight can still hop to a
  larger-context target; only a bare 413 gets the proxy-owned message.
- Rate-window wording ("too many tokens per minute") and an explicit
  rate_limit_exceeded code are never classified as an overflow.
- A first-iteration failure marked x-should-retry: false keeps its status
  and header instead of inviting a compacted resend.
- Document the sidecar overflow contract in structure/transports/responses.md.

* fix(sidecar): classify overflow where the loop still has the evidence

Round-2 review follow-up for #7316:
- The loops classify each failure with its status, wording, upstream
  error.code and non-replayable mark, and tag a qualifying first-iteration
  JSON failure; the sidecar boundary maps only tagged responses instead of
  re-parsing the loop's envelope.
- A non-replayable failure (HTTP or adapter event) keeps its own verdict
  mid-stream too, so it never invites a compacted resend.
- An upstream rate_limit_exceeded code survives into classification, so a
  "too many tokens" rate refusal is not read as an overflow.
- A 413 with definite overflow wording keeps that wording for combo
  failover; only a status-only 413 gets the proxy message.

* fix(sidecar): keep a non-overflow verdict typed through the SSE bridge

Round-3 review follow-up for #7316: when the sidecar classifier keeps a
failure (non-replayable, or an upstream rate_limit_exceeded) but its wording
would match the bridge's context-window heuristics, the in-stream event now
carries an explicit code, so classifyError cannot rename it
context_length_exceeded on the wire.

---------

Co-authored-by: Vadym O <bolein95@gmail.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
* ci(release): sign Windows binaries and MSI via SignPath (#7207)

Submit the standalone ocx.exe and the desktop MSI to SignPath Foundation from the
release workflow, verify the returned Authenticode signatures on the Windows runner,
and regenerate the Tauri updater signature over the signed MSI before the assets are
renamed, checksummed and verified.

Signing never blocks a release: missing or partial configuration, SignPath outages,
timeouts, rejections and verification failures all keep the unsigned file with a
warning, and a test-signing policy never signs a real release. A dispatch-only
Windows signing test workflow exercises test-signing end to end without publishing.

* docs: Windows code signing policy and verification (#7207)
…) (#7353)

* fix: project hosted-search history during native Lite compaction

* fix(responses): project hosted-search history when no search tool is declared (#7250)

Native Sol answers a replayed web_search_call without a matching web_search declaration with a generic server_error. Project unreferenced cells into the existing bounded reference notes whenever the final body declares no hosted search, on every Responses destination; declared requests keep structural history. Also translates the comments carried over from #7268 into English.

* fix(web-search): replay sidecar search results to routed models (#7269)

Codex replays a hosted web_search_call cell without its result text, and the parser dropped it for routed providers, so a model whose turn ended on a client tool call forgot it had searched and repeated the search and the tool. Rebuild the cell as a web_search call/result pair when search is declared, and let the sidecar loops record the result under the emitted cell id so the next turn restores it, scoped to caller and conversation.

* fix(web-search): rebuild replayed searches at the input level and drop them without a sidecar

Review round 1: the parse-time pair could call an undeclared web_search tool when no sidecar plan resolves, split a parallel client batch, and collide with client call ids. Rewrite cells into function_call/output items before the item loop, defer the output past the following call batch, allocate collision-free call ids mapped back to cell ids, and remove the pairs when the search plan is undefined.

* fix(web-search): answer a replayed search before the next client batch

Review round 2: a deferred search output crossed into the following assistant turn when consecutive client batches followed the cell, and a restored failed search lost its error flag. Flush the deferred output when a call follows the batch outputs, and set the flag independently of the restored text.

---------

Co-authored-by: Junming Feng <12311031@mail.sustech.edu.cn>
* refactor(providers): remove opencode-free with config migration

* docs(providers): document OpenCode Free removal across locales

* docs(providers): recount presets after OpenCode Free retirement

* fix(config): keep retired OpenCode references pruned across saves

* docs(providers): clarify retired OpenCode reference migration
…safe (#7298) (#7354)

* fix(combos): keep tool loops on one target and make failover history safe (#7298)

A combo re-picked its target on every request, so the request carrying a
tool result could land on a different model than the one that issued the
call. A GPT turn leaves no reasoning item, and DeepSeek's thinking mode
refuses a bare tool continuation with HTTP 400 ("The reasoning_text in the
thinking mode must be passed back to the API").

- Pin a tool-output continuation to the lane's previous combo target while
  it is enabled, still a member, out of cooldown and eligible; skip the JEV
  decision for it and record reason combo-tool-loop-pin. New user turns
  re-pick as before.
- When a switch is unavoidable, give every bare assistant tool-call turn a
  reasoning_text " " item on strict plaintext-replay Responses destinations
  (the Responses twin of the #5421 Chat placeholder).
- Apply the Gemini-only thought-signature sentinel on AI Studio, as
  Antigravity and Vertex already do, for calls another model issued.

Closes #7298

* test(deepseek): expect the bare-turn reasoning placeholder on the strict wire (#7298)
) (#7355)

* fix(combo): retry zero-output response.incomplete with no reason

A combo target that ends with `response.incomplete` before any output and
without `incomplete_details.reason` was accepted as the turn's answer, so
the caller got an empty incomplete turn instead of failover to the next
target. Treat a missing reason like the transport reasons already listed;
semantic reasons such as max_output_tokens still commit the target.

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

* fix(combos): hold the turn lease across the failover ladder

Each combo child relay binds the turn admission lease to its own controller
and unbinds it when its stream ends; the lease releases once nothing is
bound. When target 1 returned 200 SSE and then failed before output, the
combo cancelled that relay, its unbind released the parent's lease, and
target 2 was aborted on bind with "turn already settled" (502).

handleComboResponses now binds its own controller for the whole ladder and
unbinds it on return. The accepted child's stream is bound before that, so
it keeps the lease until it ends.

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

* fix(responses): count Combo recovery sends in the shared send budget

* fix(responses): preserve reserved Anthropic pool recovery sends

* test(adapters): assert exact combo cooldown rejection and single refund

* fix(combo): log the upstream refusal reason on combo target failure

The combo hop warning printed only `<target> failed with 401 after Nms`,
so a revoked credential or plan downgrade logged hundreds of identical
lines that read like a provider outage. Append the upstream error code and
parsed message (falling back to the normalized body text), collapsed to one
line, redacted and capped at 200 characters via sanitizeLogMetadataString.

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

* fix(combo): log only the parsed upstream code and message on a hop failure

Drop the classificationText fallback so an unparsed upstream body never
reaches the warning line; the log boundary is now the two parsed fields.

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

* fix(combo): redact failure fields before flattening log metadata

Preserve credential framing until the shared redactor has removed credential values. Cover multipart framing in a parsed upstream code at the actual combo hop warning boundary.

Co-authored-by: Vadym O <bolein95@gmail.com>

* fix(combo): retain output carried by reasonless incomplete snapshots

A terminal-only upstream can include text or tool items in its final snapshot. Keep that response committed, and cover explicit semantic/unknown reasons and output-first streams as well.

Co-authored-by: Vadym O <bolein95@gmail.com>

* fix(combo): unbind only registered turn lease controllers

Match registerTurn capability checks for plain admission leases so the ladder does not report a spurious release miss. Add accepted fallback stream completion, cancellation and caller-abort coverage with exactly-once attached lease settlement.

Co-authored-by: Vadym O <bolein95@gmail.com>

* docs(combo): describe incomplete, lease and recovery-budget boundaries

* docs(structure): split combo streaming boundaries within the line budget

---------

Co-authored-by: Vadym O <bolein95@gmail.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Epinephrine <luvs01@hanmail.net>
… assertions (#7358)

The pricing-cache case asserted warm-cache reuse while startServer's
unawaited config-dir ACL harden was still rewriting inherited ACEs; on
NTFS that moves usage.jsonl ChangeTime and the aggregate cache correctly
rebuilds. Drain the flight first.

AUTH-11 billed each writer's icacls work (registration hardening plus
the exit drain, 30 s budget each) to the parent's 15 s registration
deadline. ACL policy has its own suites; the fixture now pins the ACL
platform gate off.
…MP aliases (#7359)

* test(temp): resolve TEMP and the locked path with one native resolver

Hosted Windows runners export TEMP as C:\Users\RUNNER~1\... while test
paths arrive long-form. containedRunTemp used the JS realpath, which keeps
8.3 spellings, so the containment check never matched and the final-EPERM
quarantine from #6861 never ran on CI: a 15 s external hold failed the case
instead. Resolve both sides through diagnostic.realpath, defaulting to
realpathSync.native.

* test(temp): pin the native resolver as the containment default
… resolver (#7361)

The #7359 default-resolver case compared realpathSync.native output with a
fixture path resolved by the JS realpath, which keeps the 8.3 RUNNER~1
spelling on hosted Windows (run 39215956943, windows 9/9).
…7363)

Removing the handshake data listener does not pause a flowing socket, so a
TLS ClientHello that arrived before the fixture's upstream connected was
dropped and the dial stalled until its 10 s deadline (macOS run 39215956943).
Pause until pipe() resumes; add a delayed-upstream regression.
… it starts (#7362)

* fix(launcher): scope an inherited transpiler cache to the Bun version it starts

The CI shard cache is shared by the test-runner Bun (1.4.3) and the bundled
runtime the launcher starts (1.4.2). Bun keys .pile entries by source hash and
deletes entries written in another release's format, so each version evicted
the other's on every start and launcher children loaded the CLI graph cold
(15 s Windows spawnSync timeout, run 38697651428). Pass the child
<cache>/bun-<version>; leave disabled/unset caches and overrides untouched.
Drop the attest test's cold budget now that the cause is gone.

* fix(launcher): match the cache variable case-insensitively on Windows
…ng idempotent (#7364)

* fix(runtime): scope server children's transpiler cache and keep scoping idempotent

Server spawns of the bundled runtime (startup-health probe, dashboard startup
install, Windows tray) bypassed the launcher and still evicted the test
runner's cache. Scope them through durableBunChildEnv when the child is a
different Bun version; re-scope an already scoped path in place; accept
prerelease/build versions; type the helper.

* fix(runtime): keep the root when re-scoping a root-level transpiler cache

* fix(runtime): re-scope relative and drive-relative transpiler caches in place
…n source oracles (#7365)

#7362 changed the bundled-runtime return and the spawn env line that two
source-invariant oracles pinned literally (dev test 4/4, run 39218333499).
Assert the new lines; the scoped inheritedEnv must sit inside the spawn call.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner October 19, 2026 08:57
@lidge-jun
lidge-jun merged commit dd2bdd2 into main Oct 19, 2026
17 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T08:59:23.580014Z 8b3a10b PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@lidge-jun
lidge-jun deleted the release/promote-main-2.89.0 branch October 19, 2026 08:58
@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@github-actions github-actions Bot changed the title release: promote dev 52c326b479 to main as 2.89.0 [WRONG BRANCH] release: promote dev 52c326b479 to main as 2.89.0 Oct 19, 2026
@github-actions

github-actions Bot commented Oct 19, 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.

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.

6 participants