Fixes#158894.
OpenAI image generation over the Codex OAuth route no longer fails outright when the account cannot use the default Responses chat model. On a confirmed model-unavailable 400 (structured HTTP 400 with the exact evidenced error detail), it retries with the configured OpenAI chat models. Other 400s do not retry, and a working default is unchanged. The match lives in the OpenAI plugin; no public SDK surface is added.
Proof: fake OAuth profile, built CLI and a localhost Responses endpoint. main produces no image; this PR retries the configured fallback model and produces a PNG. Regression tests (including request-id-suffixed errors through the real HTTP normalizer) fail before and pass after.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(anthropic): recognize sdk-ts entrypoint in Claude session catalog
Claude Code's bidirectional stream-json subprocess mode (used by the
anthropic plugin to serve adopted web sessions) writes transcript
lines tagged entrypoint: "sdk-ts". CLI_ENTRYPOINTS only recognized
"cli" and "sdk-cli", so discovery treated the first sdk-ts line as an
unrecognized entrypoint and stopped scanning the file, making the
session permanently invisible in the catalog even though its
transcript was otherwise valid.
Add "sdk-ts" to CLI_ENTRYPOINTS alongside the existing values so these
sessions are discovered, listed, and readable like any other Claude
CLI session.
* docs(anthropic): document sdk-ts as a recognized catalog entrypoint
Keep the Claude session catalog discovery docs in sync with the
CLI_ENTRYPOINTS fix: bidirectional stream-json sessions are now listed
alongside interactive and headless CLI sessions.
* docs(anthropic): scope sdk-ts discovery to Gateway reader
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Related: #140086, #141885, #156535, #157838
## What Problem This Solves
A running Gateway doesn't see a newly downloaded hosted model catalog until it restarts. This PR publishes each accepted catalog through the existing prepared-runtime owner, without a restart. Model rows and their prices switch together as one generation, which keeps the invariant from #140086.
## User Impact
- Compatible downloads are adopted at the Gateway's background catalog check, or after an explicit `models.list` refresh. That refresh returns the currently accepted rows right away and runs adoption afterward.
- A turn admitted on catalog N keeps N's rows and prices until it finishes. New turns use N+1. Rows and prices are never mixed.
- A concurrent auth or config publication no longer postpones adoption to the next scheduled check (up to 6 h). Adoption waits for that publication to settle, then retries, up to 3 attempts.
- An owner whose build failed or timed out ends the adoption instead of waiting on unbounded work. Gateway shutdown cancels an adoption that is still preparing.
- Malformed, schema-invalid, too-new (`minVersion`) and older catalogs are rejected, and the previously accepted catalog stays in use.
- Changing `models.catalogRefresh.url` no longer needs a restart: the previous source's catalog stops applying, and the mirror's catalog is adopted at the next catalog check.
**Bad-catalog exposure:** with live apply, a *valid but wrong* published catalog reaches running Gateways at their next catalog check (at most every 6 h) or on the next explicit `models.list` refresh. It no longer waits for a restart. Recovery uses existing mechanisms only:
- Republish a corrected catalog with a newer `generatedAt`; Gateways adopt it the same way.
- Operators can set `models.catalogRefresh.enabled: false`, which withdraws remote rows and prices without a restart (covered by the Gateway integration test).
This PR adds no new kill switch, config option or env knob.
### Compatibility
No config keys, defaults, types, validation, stored rows, protocol or SDK contracts change. The only config-surface change is the `models.catalogRefresh.url` help text, which drops the stale "Changes apply after a Gateway restart" sentence, and its regenerated config-doc baseline hash. Existing configs validate unchanged and need no Doctor migration (maintainer confirmation: https://github.com/openclaw/openclaw/pull/158000#issuecomment-5913462783). Startup behavior is unchanged. Upgrade impact for existing installs: an accepted download activates at the next catalog check instead of the next restart.
## Why This Change Was Made
- `prepared-model-runtime.configured-refresh.ts` builds a complete candidate generation of the configured owners under the new catalog. One serialized commit then publishes rows, the accepted bundle, the pricing context and the reply-dispatch projection together.
- Adoption re-reads the stored catalog until the config it read under is still current, so a stale caller can't cancel a current adoption.
- Each preparation attempt has its own abort signal. A config advance restarts only the attempt; a newer catalog or shutdown ends the whole adoption, including pricing preparation.
- Between attempts, adoption waits only on publication gates: a pending replacement or an owner's pending publication.
- Adopted owners install the same plugin-retirement recovery as configured publication (#161267). After commit, a lost Gateway plugin loan republishes them through the normal recovery. Before commit, it restarts the adoption attempt, and the commit refuses any candidate whose plugin generation retired.
### Why downloads were restart-only, and what this keeps
Restart-only activation was a mechanism, not the goal. #140086 chose it to stop rows and prices from different catalog versions mixing, and #157838 was merged as "the prerequisite for applying new remote catalogs without a Gateway restart (rows and prices must switch together)". This PR is that follow-up. Every requirement those PRs set still holds:
| Original requirement | Source | How it holds here |
|---|---|---|
| Rows and prices from one catalog version; never mixed across reloads or new requests | #140086 | One serialized commit publishes owners, bundle, pricing context and dispatch; pricing contexts are keyed by the exact accepted catalog. Integration test: new rows appear only with new prices |
| Admitted work keeps its pair | #140086, #157838 | Runs carry their plugin generation's catalog; usage operations capture one pricing context. Integration test and live proof: the in-flight turn keeps the old price |
| Startup absence is a real state (no downloaded rows without prices) | #140086 | Absence → catalog goes through the same atomic commit; overlay absence tests unchanged |
| Worker replacement inherits the host's accepted pair, not a later download | #140086 | The commit updates the inherited pair; later workers and a worker-exit recovery keep it (overlay and integration tests) |
| Current enablement and source URL still gate eligibility | #140086, #156535 | Checked on every read and before adoption, including the default-install v1 fallback; disablement withdraws rows and prices together (integration test) |
| Bad or superseded downloads never replace the active pair | #140086, #141885 | Compatibility, `minVersion`, revision and `generatedAt` checks; stale reads can't cancel a current adoption (regression test) |
| Failed catalog checks retry at the remaining fresh interval, not a full TTL | #141885 | Unchanged scheduler behavior; the deleted notice test's retry case is restored for failed adoption (fails if the retry falls back to the full TTL) |
| Operators learn when a downloaded catalog is not yet active | #141885 | No longer needed: downloads activate at the next check. The restart notice and its tests are removed; `models refresh` says when a running Gateway applies the update |
| Billing-route prices switch with their rows | #156535 | `upstreamPricing` and `providerPricing` are part of the accepted catalog pair |
## Evidence
**Regressions.** Each fails with its fix reverted and passes with it:
- *Retries a scheduled adoption when its pending auth owner settles.* Runs through the real Gateway update scheduler. Reverted, it logs `remote model catalog check superseded; deferred to the next check`.
- *Does not let a read under a superseded config cancel the current adoption.* Reverted, both calls end `superseded`.
- *Ends adoption instead of joining a timed-out owner build.* Reverted, adoption never settles.
- *Does not hold Gateway shutdown on an adoption's pricing preparation.* Reverted, shutdown waits on the held preparation until the test times out.
- *Recovers adopted owners when their borrowed Gateway plugin retires after commit / before commit.* Without the recovery, both fail: `Prepared model runtime plugin generation retired` and `prepared reply dispatch runtime owner was not published`.
- *Uses the remaining stored TTL after a fresh startup check when adoption fails.* With the retry reverted to the full TTL, the second check doesn't run.
**Suites:**
| Suite | Result |
|---|---|
| `prepared-model-runtime.remote-publication.test.ts` | 10/10 |
| Gateway integration (`models-list.remote-catalog`) | v1 and v2 pass. Config and auth churn during preparation end `published` on the settled owners. Also covers retained admitted runs, rejected and stale bundles, worker replacement and disablement |
| `prepared-model-runtime*`, `server-plugin-reload*`, `update-startup`, and all PR-touched test files | pass |
| `tsgo:core`, all `tsgo:test:src` shards | pass |
| oxlint and oxfmt on changed files; `config:docs:check`, `config:schema:check`; max-lines, assertion-safety and test-timeout-race ratchets | pass |
Tests wait on owned completion signals (`withinTest`), not wall-clock deadlines.
**Live proof** on an isolated Gateway built from `cb8c9197fd` (no provider mocks). Later commits add plugin-retirement recovery for adopted owners, covered by the regression tests above, and rebases onto `main`. It used a real OpenAI key through `openai/gpt-4.1-mini`, and the build stamp was set before the real catalog's publication date. A client polled `models.list` back to back over one WebSocket for the whole run (1023 polls, no errors). One Gateway process (PID unchanged) and no restart:
1. The stored catalog was seeded with an older revision of the real `catalog.openclaw.ai` v2 catalog: generated 2 days earlier, `gpt-4.1-mini` priced ×10, plus one extra kimi row. `models.list` listed the extra row, and a turn priced **$4.00 / $16.00 per M** input/output.
2. `openclaw models refresh` downloaded the real catalog (`updated`, 1039 models). The listed rows didn't change for the next 7.1 s, and a turn in that window still priced **$4.00 / $16.00 per M**: a download stays inactive until the Gateway adopts it.
3. A long turn was admitted on the older catalog, then `models.list {refresh:true}` returned the older rows (extra kimi row still listed) and started adoption. The new catalog was visible 0.9 s later, while the long turn was still running: the extra kimi row was gone.
4. The in-flight turn finished at **$4.00 / $16.00 per M** (older rows and prices). The next turn priced **$0.40 / $1.60 per M** (real catalog).
5. `models.catalogRefresh.url` was moved to a local mirror of the real catalog through `config.patch`, and the mirror's catalog was adopted without a restart. The mirror then served malformed JSON: `models refresh` failed with `SyntaxError`, the model list was unchanged, and the next turn still priced $0.40 / $1.60 per M.
**Model picker during republication (also on `main`).** Right after the new generation commits, `models.list` shows the new generation's configured and static rows until its full catalog loads, then the full list. In the live run this lasted 109 ms. The same poller against a `main` build shows the same window after a `models.*` config reload (20 → 6 → 16 rows for about 350 ms), so this PR adds a new trigger for an existing behavior. It doesn't change it. The short list comes from the new generation, so rows and prices stay paired.
**Published-driver upgrade cells.** Candidate tarball built from a fresh clone at `6f682c7944` with the canonical Docker packaging script and no build-time overrides. sha256 `46d881a0…ef0ad`; embedded commit `6f682c7944`, version 2026.9.7. `6f682c7944` already includes the shutdown-cancellation and pricing-deadline commits. The current head the current head differs from it only by rebases onto `main`: `main` had moved the scheduled catalog check into `update-startup-catalog.ts`, and this PR's adoption call moved there unchanged (`git range-diff` shows no other production change; a `remoteCatalog: null` test-fixture field moved to main's relocated `cli-compaction.test-support.ts`).
| Driver → candidate | Scenario | Result |
|---|---|---|
| `openclaw@2026.9.6` | base | passed (930 s); updater outcome success, no recovery |
| `openclaw@2026.9.6` | plugin-deps-cleanup | passed (923 s); updater outcome success, no recovery |
| `openclaw@2026.9.7` (latest) | base | passed (813 s); updater outcome success, no recovery |
In every cell:
- Migration, post-Doctor config validation, survival, plugin-dependency cleanup and runtime-deps repair checks passed.
- The candidate Gateway logged ready, then its catalog check fetched and saved the hosted catalog about 0.2 s after starting. The hosted catalog is older than the candidate's build stamp, so adoption ends `unchanged`, which isn't logged. After a 300 s settlement window, `/readyz` (`ready:true`, nothing failing) and a Gateway `status` RPC passed, and the Gateway shut down cleanly.
- To show a logged terminal outcome, each cell was repeated with a loopback mirror serving a newer copy of the same catalog. Each check logged `remote model catalog applied` about 0.26 s after it started, followed by `/readyz` and `status` passing.
Not run: `openclaw@2026.9.7` plugin-deps-cleanup. At the previous head it failed inside the 2026.9.7 driver's retained-runtime verification (`Retained runtime entry does not reference its inventoried file: dist/a2ui-…mjs`), identically for a merge-base control package with none of this PR's commits.
Harness note for the 2026.9.7 cell: unchanged, the upgrade harness can't run against 2026.9.7. It seeds the retired `tools.toolSearch {mode:"code"}` setting, which 2026.9.7 rejects. The 2026.9.7 cell used the harness's existing Tool Search "absent" mode, a one-line local change that skips only that seed and its check. The 2026.9.6 cells used the unchanged harness.
**CI:** fully green on the final head ([run 36818367970](https://github.com/openclaw/openclaw/actions/runs/36818367970)). Earlier heads hit failures that reproduce on `main` in code this PR doesn't touch: `update-cli.target-schema` (main reproduction: https://github.com/openclaw/openclaw/pull/158000#issuecomment-5913464830), the type-suppression inventory, the Windows partition owner test and the Windows backup-rename test. Main has since fixed the last three. Config compatibility confirmation: https://github.com/openclaw/openclaw/pull/158000#issuecomment-5913462783.
No overlap with Pash/Sarah changes.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Summary:
- Moves Agents API onboarding into a dedicated plugin guide and updates navigation, provider references, the plugin README, and release links.
Automerge notes:
- PR branch already contained follow-up commit before automerge: docs: address Agents API onboarding review
Validation:
- ClawSweeper review passed for head af284a9707b38cc6b72094559a4b14ffdac83019.
- Required merge gates passed before the squash merge.
Prepared head SHA: af284a9707b38cc6b72094559a4b14ffdac83019
Review: https://github.com/openclaw/openclaw/pull/162255#issuecomment-5922939944
Co-authored-by: Sarah Fortune <sjf@openai.com>
Approved-by: sjf-oa
Restore supported xhigh and max reasoning choices for Daybreak Blue and Red. Preserve requested aliases in Responses and honor declared account capabilities and Ultra opt-outs in the OpenAI plugin.
Cover capability selection, model materialization, discovery boundaries, and final Responses payloads with focused regressions. Authenticated provider observations and exact-head CI confirm the repaired contract.
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
## What Problem This Solves
Fixes: requests to vendors that document app attribution do not identify OpenClaw. Vercel AI Gateway requests carry no attribution. Perplexity requests send an OpenRouter-style `HTTP-Referer`/`X-Title` pair that Perplexity does not document, rather than the integration header Perplexity does document.
## User Impact
User impact: OpenClaw requests are attributed wherever the vendor documents how:
- **Vercel AI Gateway** (`ai-gateway.vercel.sh`, any provider id): `HTTP-Referer: https://openclaw.ai`, `X-Title: OpenClaw`.
- **Perplexity web search** on `api.perplexity.ai`: `X-Pplx-Integration: openclaw/<version>`.
- **Perplexity web search through OpenRouter**: OpenRouter's standard OpenClaw attribution, replacing the separate "OpenClaw Web Search" title.
Custom proxy base URLs still get no attribution headers.
## Why This Change Was Made
The provider attribution policy stays the single owner. It gains Vercel AI Gateway and Perplexity entries, each with its docs URL and the vendor's own wording:
- Vercel: https://vercel.com/docs/ai-gateway/ecosystem/app-attribution ("AI Gateway reads two request headers when present: http-referer … x-title").
- Perplexity: https://docs.perplexity.ai/docs/getting-started/integrations/opencode (`X-Pplx-Integration: <client>/<version>`).
Vercel is selected by a new `vercel-ai-gateway` endpoint class, the same way OpenRouter is. The class is declared in the plugin manifest and mirrored into the official external provider catalog, so classification still works when the external plugin is not installed. The class exists only for attribution: catalog models routed through the gateway stay route-supported exactly as before, when the URL counted as a custom proxy.
Vercel AI Gateway uses the Anthropic Messages transport, which merges the policy's attribution headers since #160956, so the new policy entry is all it needs.
The Perplexity plugin now builds its headers through the public `resolveProviderRequestHeaders` SDK helper instead of hardcoding them. The isolated session stream forwards any documented (non-hidden) attribution the policy selects, not only OpenRouter's.
Not added:
- **Cohere**: `X-Client-Name` is documented for the native `api.cohere.com` chat API, but OpenClaw's chat path is the compatibility API on `api.cohere.ai`, where the header is not documented.
- **Kilo** `X-KiloCode-Version`: no verified documentation, and the existing Kilo header is duplicated between core and the plugin.
- **Requesty**: OpenClaw has no Requesty provider.
- **No documented attribution**: Cloudflare, Together, Fireworks, Groq, DeepInfra, Mistral, Anthropic, DeepSeek, Z.AI, Hugging Face, Moonshot, Venice, Chutes, Novita, LiteLLM and local runtimes publish none.
## Evidence
Final-head re-proof (rebased on main after #160956, built `dist` at `b179ec195bd`; the final head `d9ff8b2e136` adds only a test fixture type fix and a rebase onto main), `OPENCLAW_TELEMETRY=0`, same capture preload:
- OpenRouter bundled provider, `api: anthropic-messages`, `agent --local` with a real key → `POST openrouter.ai/api/v1/messages` with Referer, `X-OpenRouter-Title: OpenClaw`, `personal-agent,cli-agent`; reply `pong`; generation `gen-1790672764-Foux01snwfakVvYjdiD6` has `app_id` 2725608.
- `agent --local --model vercel-ai-gateway/anthropic/claude-haiku-4.5` (dummy key) → `POST ai-gateway.vercel.sh/v1/messages` with `http-referer: https://openclaw.ai`, `x-title: OpenClaw` (401, as expected).
- `openclaw infer web search --provider perplexity` (dummy key) → `POST api.perplexity.ai/search` with `x-pplx-integration: openclaw/2026.9.6` and no Referer/Title (401, as expected).
Earlier runs on the stacked branch:
Every run used `OPENCLAW_TELEMETRY=0`. Outgoing headers were captured by a throwaway `--import` preload (not committed) on undici's `undici:request:create` diagnostics channel. Base is #160956 (`55824fae493`).
Live OpenRouter runs, built `dist`, `openclaw agent --local` (OpenRouter key; `app_id` from `GET /api/v1/generation`):
| Route | Side | Headers | Generation | `app_id` |
| --- | --- | --- | --- | --- |
| custom id, `api: anthropic-messages`, `https://openrouter.ai/api` | before | none | `gen-1790662480-iGGLpF0HKH4hIBH36Ozu` | **null** |
| same | after | Referer, Title, `personal-agent,cli-agent` | `gen-1790662519-qK0CW0cAUacmTzrNU44D` | 2725608 |
| provider `openrouter` (chat completions) | after | same | `gen-1790662565-tQGEYs8oaJtonKqQU1hM` | 2725608 |
| Perplexity web search runtime via OpenRouter | before | Referer, `X-Title: OpenClaw Web Search` | `gen-1790659036-FuZzYNv2uvXAzytZSdd9` | 2725608 |
| same | after | Referer, `X-OpenRouter-Title: OpenClaw`, categories | `gen-1790659067-7vfkXWHIVn2LIcZQWXp8` | 2725608 |
Header capture only; no live credential was available for these vendors, so the requests got 401 with a dummy key. That is a live-credit gap: attribution could not be confirmed on the vendors' dashboards.
| Path | Before | After |
| --- | --- | --- |
| `agent --local`, `vercel-ai-gateway/anthropic/claude-haiku-4.5` → `POST ai-gateway.vercel.sh/v1/messages` | no attribution | `http-referer: https://openclaw.ai`, `x-title: OpenClaw` |
| `agent --local`, custom provider id with `baseUrl: https://ai-gateway.vercel.sh` | no attribution | same |
| Perplexity web search, `PERPLEXITY_API_KEY` → `POST api.perplexity.ai/search` | Referer + `X-Title: OpenClaw Web Search` | `x-pplx-integration: openclaw/2026.9.6` |
| Perplexity web search, direct key with a model override → `POST api.perplexity.ai/chat/completions` | same as above | `x-pplx-integration: openclaw/2026.9.6` |
Tests and checks:
- Targeted tests pass: provider-attribution, official-external-provider-endpoints (manifest/catalog mirror), provider-request-config, sessions/sdk, perplexity, vercel-ai-gateway, anthropic transport, and the new `model.configured-overrides.test.ts`.
- The route-support test fails without the fix.
- Focused wall times on the final head: `provider-attribution.test.ts` 33 tests 4.9s; `model.configured-overrides.test.ts` 1 test 39.6s cold (95% transform, the test itself runs in milliseconds).
- `pnpm tsgo:core` and `pnpm tsgo:extensions` pass; the `core.test.agents-other` tsgo shard passes on the final head.
- A fresh reviewer found that the new endpoint class dropped route support for Anthropic catalog models routed through the gateway. I reproduced it with the real metadata snapshot (supported `false`, previously `true`), fixed it in `8d86624a53`, and the re-review came back with no findings.
CI note: required ci-gate was red only from failures proven unrelated to this change; evidence: https://github.com/openclaw/openclaw/pull/161018#issuecomment-5888230678
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
## What Problem This Solves
Fixes: OpenRouter requests carry inconsistent or missing app attribution. A custom provider id pointed at `https://openrouter.ai/api/v1` sends no attribution on normal agent turns. The bundled `openrouter` provider sends seven `X-OpenRouter-Categories` values, but OpenRouter honors at most two recognized categories per request and silently drops the rest. Isolated session streams sent a different set (`cli-agent` only) and only when install telemetry was enabled.
## User Impact
User impact: every OpenRouter request OpenClaw makes, whether through the bundled `openrouter` provider or a custom provider id whose `baseUrl` is OpenRouter, now sends the same attribution headers: `HTTP-Referer: https://openclaw.ai`, `X-OpenRouter-Title: OpenClaw` and `X-OpenRouter-Categories: personal-agent,cli-agent`. OpenRouter attribution no longer depends on the install telemetry setting. Custom proxy base URLs still get no OpenRouter attribution headers.
## Why This Change Was Made
The provider attribution policy is now the single owner of the OpenRouter header set. It selects OpenRouter attribution from the endpoint class (`openrouter.ai`), whatever the provider id, which matches how NVIDIA and Google attribution already work. The OpenRouter plugin stream wrapper and its image, video, speech and transcription request builders no longer add their own `HTTP-Referer`/`X-OpenRouter-Title` copies; those paths already go through the policy, which overrides the copies. The Anthropic Messages transport now merges the policy's headers the same way the OpenAI transports do, so removing the plugin wrapper's copies does not drop attribution on OpenRouter `anthropic-messages` requests. The isolated session stream reads the policy's header set as well.
## Evidence
Live runs used the built `dist` of each side (baseline `c34e838e49`, candidate `55824fae49`; the final head only adds the Anthropic Messages transport merge): `node openclaw.mjs agent --local --agent main --model <ref> --thinking off --json -m 'Reply with exactly: pong'` with a temp `OPENCLAW_HOME`/`OPENCLAW_STATE_DIR` and `OPENCLAW_TELEMETRY=0`. A throwaway `--import` preload (not committed) logged outgoing openrouter.ai request headers from undici's `undici:request:create` diagnostics channel. `app_id` comes from `GET /api/v1/generation?id=…`. Every run replied `pong`.
| Path | Side | Categories sent | Generation | `app_id` |
| --- | --- | --- | --- | --- |
| `agent --local`, provider `openrouter` | before | 7 values | `gen-1790655350-bnebR7u8xxS31Gnarann` | 2725608 |
| `agent --local`, provider `openrouter` | after | `personal-agent,cli-agent` | `gen-1790655479-medQeaj13NAK7rNePlW3` | 2725608 |
| `agent --local`, custom id `or`, `baseUrl: https://openrouter.ai/api/v1` | before | no attribution headers at all | `gen-1790655642-fykl8xuEgdCMP93lVYZK` | **null** (origin empty) |
| `agent --local`, custom id `or`, `baseUrl: https://openrouter.ai/api/v1` | after | `personal-agent,cli-agent` (+ Referer, Title) | `gen-1790655522-AadAufRx6Z1cPfLE3l6f` | 2725608 |
Isolated session stream (`createAgentSession` in `src/agents/sessions/sdk.ts`). A scratch script (not committed) sent one real request through `session.agent.streamFn`. Normal authenticated `agent --local` turns switch to the boundary-aware transport, so this stream only serves session-custom fall-through turns and SDK consumers; the script is how to reach it. The run used `api: anthropic-messages` at `https://openrouter.ai/api`, because that transport does not consult the policy and the session stream's headers are the only attribution:
| Side | `OPENCLAW_TELEMETRY` | Headers sent | Generation | `app_id` |
| --- | --- | --- | --- | --- |
| before | 0 | none | `gen-1790653951-OqOd5GB2eQtIoNpOvRNn` | **null** |
| before | 1 | Categories `cli-agent` | `gen-1790654009-mlLEe1AAfy0bfD9SyYih` | 2725608 |
| after | 0 | Referer, Title, `personal-agent,cli-agent` | `gen-1790653981-lchRBOjuKaAFhbUNaZfu` | 2725608 |
| after (custom id `or`, openai-completions) | 0 | Referer, Title, `personal-agent,cli-agent` | `gen-1790654058-P0DK1953pmZI88hZ71Jt` | 2725608 |
Bundled `openrouter` provider on `api: anthropic-messages` (`baseUrl: https://openrouter.ai/api`, model `anthropic/claude-haiku-4.5`), final head `53a26ec6e64`, same `agent --local` command with `OPENCLAW_TELEMETRY=0`: the request to `/api/v1/messages` carried `HTTP-Referer: https://openclaw.ai`, `X-OpenRouter-Title: OpenClaw`, `X-OpenRouter-Categories: personal-agent,cli-agent`; reply `pong`; generation `gen-1790669051-ibr3il50EvrfeHXM55tT` reports `app_id` 2725608. This is the path that previously got Referer/Title only from the plugin wrapper.
OpenRouter plugin media paths. A scratch harness (not committed) called the real speech `synthesize`, `listOpenRouterVideoModelCatalog`, image `generateImage` and audio `transcribeAudio` with a dummy key and captured the outgoing headers. Before this change, all four already sent the policy's header set, so the removed plugin literals were being overridden. After it, all four send `HTTP-Referer: https://openclaw.ai`, `X-OpenRouter-Title: OpenClaw` and `X-OpenRouter-Categories: personal-agent,cli-agent` to `/audio/speech`, `/videos/models`, `/images` and `/audio/transcriptions`.
Tests and checks:
- `node scripts/run-vitest.mjs` on provider-attribution, provider-request-config, stream-wrappers/proxy, sessions/sdk, and openrouter index/image/media/speech tests: all pass. The new custom-provider-id case in `provider-attribution.test.ts` fails on the baseline (`attributionHeaders` undefined).
- Focused local wall times on the final head (`pnpm test <files>`): `provider-attribution.test.ts` 31 tests 3.9s, `provider-request-config.test.ts` 36 tests 3.0s, `stream-wrappers/proxy.test.ts` 15 tests 5.1s, `sessions/sdk.test.ts` 37 tests 16.4s, `extensions/openrouter/` 18 files 285 tests 17.5s, `packages/ai/.../anthropic-transport-stream.test.ts` 144 tests 1.4s. The changed cases themselves each run in under 200ms.
- `pnpm tsgo:core` and `pnpm tsgo:extensions` pass.
Unchanged on purpose: the Cloudflare `User-Agent: openclaw` branch in the same session-stream helper still depends on install telemetry. It is not part of the attribution policy owner, and nothing requires changing it here.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(lmstudio): validate setup URLs before discovery
Reject malformed interactive endpoints in the provider-owned prompt instead
of sending Control UI and CLI operators into an unreachable-server retry.
Retain the authored draft and the existing HTTP(S) host-shorthand support.
Validate before the permissive path/default normalization can hide a missing
host. Reuse the existing host normalizer; no config or SDK surface changes.
The registered setup-entry regression fails against the original validator.
Real Gateway/Chromium proof rejects the invalid draft inline, then configures
the corrected local stub endpoint and sends chat through the selected model.
Owner and sibling coverage: 115 tests, 13.804 seconds wall; new case 2 ms.
Production delta +19 lines, tests +28, docs +3. The added code owns recoverable
prompt validation; existing prompt types move rather than being duplicated.
* fix(llama-cpp): validate server URLs in setup
Run the existing endpoint parser inside the provider-owned URL validator so
Control UI and CLI users can correct an invalid URL without losing setup.
Previously the parser threw after submission, closing the editable prompt
with a raw Invalid URL error. Preserve host shorthand and the existing
HTTP(S) and embedded-credential rules; no config or SDK surface changes.
The setup-entry regression fails against the original nonempty validator.
Real Gateway/Chromium proof keeps the draft with actionable inline guidance,
then activates the corrected local stub endpoint and sends chat through it.
Owner and sibling coverage: 115 tests, 13.804 seconds wall; new case 2 ms.
Production delta +10 lines, tests +29, docs +4. Growth converts the existing
throwing parser into a recoverable prompt result without duplicating policy.
* fix(lmstudio): reject credential-bearing setup URLs inline
* test(setup): use credential-free embedded-user URLs in validation fixtures
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Add API and Claude CLI catalog entries with the published 1M/128K limits and
Sonnet 5 pricing, roll the bare `sonnet` alias to Sonnet 5.5 (`sonnet-5` stays
pinned), and give the model its own contract: `/think off` sends Anthropic's
`between_tools` setting instead of the rejected `disabled`, forced tool choices
relax to `auto`, retained thinking is prefix-bound with append-only runtime
context, a session moving from Sonnet 5 or Claude 4.x onto Sonnet 5.5 keeps its
reasoning while Opus 5, Fable, and Mythos reasoning is dropped, and direct
API-key requests opt into the safety refusal fallback to Sonnet 5. Fable 5.1 no
longer replays Sonnet 5.5 thinking, matching Anthropic's live replay behavior.
Add opt-in Gemini 3.8 Flash and Flash-Lite TTS in the existing Google speech plugin.
Keep the implicit Gemini 3.1 default and older GenerateContent path unchanged. Send Gemini 3.8 transcript text separately from delivery style and structured speaker metadata through stateless Interactions requests, then reuse the existing PCM, WAV, and voice-note outputs.
Preserve the shared Google auth and response helpers, document the model-specific prompting behavior, and cover speaker/no-speaker requests, transcript handling, model selection, and audio output.
Co-authored-by: IWhatsskill <284122573+IWhatsskill@users.noreply.github.com>
* feat(kie): add Kie AI video generation provider
Add 13 documented Market video models with automatic text/image routing, local image uploads, body-level API errors, and bounded polling/downloads. Register API-key onboarding and discovery, wire live QA and CI filters, and document provider capabilities.
Validation: 34 mocked Kie cases pass; core/extension types, bundled registration, inventory, docs, workflow, formatting, and assertion-safety checks pass. Independent review found no actionable issues. The new test command took 186.18s including cold compilation. Live provider proof remains with the lead; Veo awaits API contract reconciliation.
* fix(config): allow Runway and Kie provider credentials in models.providers
Treat runway and kie as bundled provider overlays so apiKey credentials, including SecretRefs, validate without custom baseUrl or models fields. Keep credential resolution in the existing provider auth owner.
Validation: both SecretRef-only schema cases failed before the fix and pass afterward (28 schema cases). All 12 Runway cases pass, including a generated request using the real SDK resolver with a resolved file SecretRef snapshot. Core/extension type checks and the fresh independent review of both requested commits pass.
* feat(zai): add CogVideoX video generation
Extend the Z.AI plugin with regional async CogVideoX text-to-video and image-to-video generation. Reuse provider auth and HTTP helpers, support local image data URIs, and declare duration, geometry, audio, quality, and frame-rate controls.
Wire registration, live provider selection, release shards, generated inventory, and provider documentation. Mocked provider flows, focused contracts, typechecks, inventory, docs, formatting, workflow checks, lint, and assertion safety passed. Live proof remains with the lead.
* feat(novita): add Wan and Hailuo video generation
Extend the Novita plugin with native Wan 2.6 and Hailuo 2.3 async video routes, automatic family image routing, data URI image inputs, and model-specific capabilities. Default Wan output to silent and validate Hailuo duration/resolution combinations before submission.
Wire registration, live provider selection, release shards, generated inventory, and concise provider documentation. Mocked registered-provider flows, focused contracts, typechecks, inventory, docs, formatting, workflow checks, lint, and assertion safety passed. Live proof remains with the lead.
* fix(ci): repair video provider integration checks
Guard the native-live provider list before splitting and add Kie extension and documentation labeler coverage.
Regenerate the config baseline at exactly 4371 plugin entries. The 21 new paths all belong to the shared Kie plugin entry: root/config/enabled (3), hooks (6), llm (8), and subagent (4). Z.AI and Novita add no config entries; existing core, channel, and plugin entries are unchanged.
Focused labeler and native-live shard tests, config baseline, lint, formatting, and diff checks pass. The targeted TS2532 is removed; 19 inherited type diagnostics remain in untouched files. Workflow-planning has the same 17 unique failures and 432 passes on this branch and merge-base 7c346c1c65 because its fixture omits ci-static-step.sh.
Qwen and Alibaba Wan video refused every local reference image with
"requires remote http(s) URLs", so agents could not animate attachments
or workspace images, and the default wan2.6-t2v model rejected a single
reference image instead of using its image-to-video sibling.
DashScope img_url accepts base64 data URIs (live-verified with
wan2.6-i2v), so the shared DashScope Wan path now sends local images as
data URIs under Model Studio's documented size limits (20 MB for Wan
2.5-2.7, 10 MB otherwise, applied to buffers and inline data URIs
alike). Reference videos remain remote-URL only. A Wan text-to-video
model plus exactly one image routes to its known i2v sibling. The model
catalog moves to dashscope-wan-models.ts to stay under the line cap.
Fireworks retired accounts/fireworks/routers/glm-5p2-fast. Onboarding, the bundled catalog and the curated live selector now use the documented GLM 5.3 Fast router (Fast cached-input price 0.39/M); explicit operator pins of the old id are preserved.
Forward-port of release/2026.9.7 066fd58ec8.
OpenAI shut down its Sora video API: sora-2 and sora-2-pro report
shutdown_date 2026-09-24 and POST/GET /v1/videos now return HTTP 404 for
every project. Every video_generate call routed to openai/* failed with an
opaque "OpenAI video generation failed (HTTP 404)", and because the bundled
openai plugin still advertised a configured video provider, agents on
OpenAI-only installs kept selecting it.
Remove the OpenAI video-generation provider, its manifest contract and
metadata, live-test defaults and workflow filters, and the docs that
advertised Sora. Stale openai/sora-* refs need no migration: the media
runtime already skips them with "No video-generation provider registered
for openai" and continues to configured fallbacks or auto-detected
providers.
Emit the existing incomplete-stream contract so partial Interactions replies remain eligible for transient recovery. Centralize reader cancellation, lock release, and pending-work tracking in finally while preserving the original failure.
Regression proof on Blacksmith: six expected failures before the repair; all 101 focused and owner tests pass afterward. The full changed gate passes, and independent Codex review found no actionable P0-P2 findings.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Enable two-way file transfer for the hosted Linux Agents API runtime. Upload admitted attachments to the hosted workspace and return completed files through managed reply media.
Preserve completed assistant replies when artifact transfer fails and enforce live attempt authority at the final outbound write. Document the initial Linux storage scope and deferred platform and failure-presentation limits.
Validation: focused regression tests, type checks, and real Linux ARM64 Docker Gateway transfers through Slack, with CSV, UTF-8, and binary downloads compared byte for byte. ClawSweeper reviewed the published head with no actionable findings.
* feat: register Telnyx as an official external provider
* docs(telnyx): acknowledge non-interactive setup risk
* fix(telnyx): pin the SDK-compatible 0.2.0 provider artifact
@telnyx/openclaw-provider@0.2.0 is built against the batched provider-catalog
SDK and loads on 2026.8.1+ hosts; 0.1.0 failed with sdk-incompatible. Pin the
npm artifact with its registry integrity, raise minHostVersion to >=2026.8.1,
drop the ClawHub pin until 0.2.0 is published there, and remove the docs warning.
* fix(telnyx): restore the ClawHub install source for 0.2.0
@telnyx/openclaw-provider@0.2.0 is now published on ClawHub under the telnyx
publisher with the same npm integrity and source commit, so the catalog can
offer both sources again. npm stays the default choice.
* test(telnyx): keep over-cap suites at their baseline line count
The line-cap ratchet (#149622) rejects growth in files already past the
max-lines cap. Move the Telnyx pin test into a vendor-pins sibling module,
and drop the CLI install and onboarding additions that re-proved what the
wecom pass-through and plugin-install-plan tests already cover.
---------
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
* fix(doctor): retain with warning when catalog successor is authoritatively unavailable
When doctor --fix processes a retired model reference, it resolves the
replacement from the manifest suppression rule. A successor that is
present in the plugin catalog but unavailable on this install (wrong
account tier, region, or plugin version) was previously written silently,
producing an unusable model reference that only fails at inference time.
Previous attempt (cleared-on-no-suppression-rule) was incorrect: an
undefined suppression result means no suppression applies, not that the
successor is absent. Valid catalog-only successors like xai/grok-4.7
have no suppression rule but are perfectly valid migrations.
Fix: evaluate the successor against the same auth-resolution context that
proved the source model's retirement. Only an authoritative negative
(availabilityAuthoritative=true, availability=false) blocks migration.
Unknown or transient availability (undefined) continues to migrate so
the operator can resolve auth after the reference is written.
When a successor is authoritatively unavailable, the repair now:
- Retains the original reference (rather than clearing to an inherited
default, which could silently downgrade to a worse model)
- Emits an actionable warning naming the successor, the reason, and
how to fix it
Fixes#156155
* fix(doctor): validate retired model successors before migration
Keep the saved model reference when its proposed successor is retired on the
selected route or incompatible with the selected account and runtime settings.
Evaluate the settings that repair will preserve without changing the input,
while allowing valid migrations during transient cooldowns or unknown readiness.
Consolidates #156317 into #156435 and repairs its route regression assertion
and unused type export. Related: #156155; native account-catalog exclusion is
not established by this change.
Co-authored-by: Bruce-Yii <298228875+Bruce-Yii@users.noreply.github.com>
Co-authored-by: Jonathan Sieling <jonathan@tailoredmonkey.com>
* fix(doctor): check installed-catalog membership before migrating successor
Add an authoritative installed-catalog membership check to the successor
guard so Doctor retains the saved model reference when the proposed
successor is proven absent from the installed plugin model catalog.
When the provider has catalog entries but the successor model is not among
them, Doctor now preserves the original reference and emits a warning
naming the absent successor and the required manual step. When catalog
membership cannot be determined (no catalog entries for the provider or
resolution failure), migration proceeds as before.
Addresses the remaining ClawSweeper finding at
retired-model-successor-guard.ts:106-110 and the installed-catalog
exclusion case in #156155.
Co-authored-by: Jonathan Sieling <jonathan@tailoredmonkey.com>
* fix(doctor): keep catalog hints non-authoritative
Retain route, account, runtime and retirement checks without treating a
missing manifest/config catalog row as native account exclusion. Preserve
the contributor follow-up in ancestry and the supported migration behavior.
Strengthen the existing config-repair regression; primary/fallback migration,
route-scoped allow-list retention and cooldown controls now pass. Native
account-catalog exclusion remains open under Refs #156155.
Co-authored-by: Bruce-Yii <298228875+Bruce-Yii@users.noreply.github.com>
Co-authored-by: Jonathan Sieling <jonathan@tailoredmonkey.com>
---------
Co-authored-by: Jonathan Sieling <jonathan@tailoredmonkey.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Bruce-Yii <298228875+Bruce-Yii@users.noreply.github.com>
* feat(agentsapi): project native output and reconcile saved history
* fix(agentsapi): retain observed usage after accounting read failures
ClawSweeper: "Retain observed usage when the final REST read fails"
https://github.com/openclaw/openclaw/pull/156182#issuecomment-5789452458
Merge final REST usage with observed usage by admitted turn ID. Canonical
records replace matching contributions, while omitted turns keep observed
usage and repeated accounting does not double count them.
* test(agentsapi): cover projection usage and authoritative session receipts
* refactor(agentsapi): keep projection implementation helpers module-local
* test(agentsapi): use the projection factory and preserve fixture types
---------
Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
Honor the Gemini 3.8 Live model contracts in realtime voice.
Extended Thinking now uses its supported thinking and NON_BLOCKING tool-response shape, keeps filler and tool phases active while interactionStatus is IN_PROGRESS, and completes only at IDLE. The Gateway provider and direct browser Talk transport retain transcript finality, interruption behavior, and the existing contracts for plain Gemini 3.8 and older Live models.
Co-authored-by: IWhatsskill <284122573+IWhatsskill@users.noreply.github.com>
Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Recognize plain, latest-alias, and dated Grok release IDs using xAI's documented capability ranges, so newer releases receive supported thinking levels and image input without another exact-ID update. Keep Grok 4.20 and variant suffixes on their existing conservative paths, and share the release rule with Responses tool defaults.
Validation: 56 focused regression tests passed on the refreshed head; independent review found no P0/P1 issues.
Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Project bounded Desktop metadata before cache and overlay retention, preserving the independent CLI index contract. Detach trimmed strings so short display values cannot keep large backing strings alive.
Synthetic full catalog proof reduced additional retained heap from 64.49 MiB to 0.482 MiB while preserving all 48 visible rows and metadata. Fixes#155754.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
<!--
Related: #155782
Required PR title:
fix: SuperGrok usage shows No usage data when xAI omits creditUsagePercent
-->
Related: #155782
## What Problem This Solves
Fixes: SuperGrok usage cards and `openclaw status --usage` show "No usage data" when xAI returns a valid weekly billing period that omits `creditUsagePercent`.
## User Impact
User impact: OAuth SuperGrok accounts now get a specific "Included usage omitted" snapshot, plus plan and prepaid facts, instead of a generic missing-telemetry error. Grok inference was already working; only quota display was wrong.
## Why This Change Was Made
xAI can omit a default-zero included-usage scalar on an otherwise valid weekly or monthly billing period. The parser now treats that as omitted included usage rather than inventing a percentage or reading on-demand pay-as-you-go counters as SuperGrok subscription quota. Legacy monthly `used` / `monthlyLimit` parsing and explicit `creditUsagePercent: 0` windows stay unchanged.
## Evidence
Exact head: `19aae5939ec429b30333e8db836bf6276d583895`. No code change in this proof pass.
Live usage-only billing probe against `GET https://cli-chat-proxy.grok.com/v1/billing?format=credits` (HTTP 200, 413 bytes). Isolated disposable Gateway on `127.0.0.1:63012` (`openclaw gateway run --dev`). Live operator Gateway on `127.0.0.1:18789` (pid 22298) was not restarted, modified, or used for this proof. No inference requests.
Sanitized live billing payload: valid weekly period, `creditUsagePercent` omitted, no legacy `used` / `monthlyLimit`:
```json
{
"config": {
"currentPeriod": {
"type": "USAGE_PERIOD_TYPE_WEEKLY",
"start": "2026-09-21T12:01:29.688516+00:00",
"end": "2026-09-28T12:01:29.688516+00:00"
},
"onDemandCap": { "val": 0 },
"onDemandUsed": { "val": 0 },
"isUnifiedBillingUser": true,
"prepaidBalance": { "val": 0 },
"billingPeriodStart": "2026-09-21T12:01:29.688516+00:00",
"billingPeriodEnd": "2026-09-28T12:01:29.688516+00:00"
}
}
```
`has_creditUsagePercent`: false. Period recognized as weekly.
PR-head CLI after the same live account, isolated config/port/session store (exit 0):
```text
pnpm openclaw status --usage --timeout 30000
Usage:
SuperGrok (SuperGrok)
Included usage omitted
Prepaid balance: $0.00
```
```text
pnpm openclaw models status
OAuth/token status
- xai usage: Included usage omitted
```
Before (same payload on current main / pre-fix adapter): `SuperGrok: No usage data`.
Focused tests: `node scripts/run-vitest.mjs extensions/xai/usage.test.ts --maxWorkers=1` — 12 passed. File wall 12.38s on one worker; individual cases 1–9ms.
Inspected, sanitized Provider Plans billing-card pair from exact PR-head Control UI (`19aae5939ec429b30333e8db836bf6276d583895`) with a local mocked `usage.status` renderer. Isolated Vite loopback `http://127.0.0.1:51133/`; live operator Gateway `127.0.0.1:18789` (pid 22298) was not used, restarted, or modified. No inference requests.
**Before** (pre-fix SuperGrok snapshot: `error: "No usage data"`):

**After** (PR-head SuperGrok snapshot: plan + prepaid `$0.00` + `Included usage omitted`):

No emails, tokens, account identifiers, or private endpoints. Uploaded via the fork `user-attachments` endpoint after upstream `repository_id` returned 404 (no push on `openclaw/openclaw`).
Remaining semantic uncertainty: omitted `creditUsagePercent` is reported as omitted, not proven 0%. Cross-client reports (including [stablyai/orca#20826](https://github.com/stablyai/orca/issues/20826)) suggest xAI drops default-zero credit fields, but that is not a documented xAI contract.
Made with [Cursor](https://cursor.com)
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
OpenClaw needs an alternative OpenAI harness that owns a hosted agent session instead of connecting to a Codex app-server session.
Add an explicitly selected Agents API plugin for hosted Linux sessions with API-key authentication, text streaming, session reuse, steering, interruption, and native idle settlement. Reuse shared binding and transcript authority, and dispatch the shared completion hooks from the actual attempt result.
Validation: required final-head CI passed; ClawSweeper accepted the real Docker authority proof and reports no remaining findings. Native Linux/arm64 Docker flows verified hosted VM execution, steering, authorized interruption, queued-input cancellation, completion-hook delivery, successor isolation, and strict transcript rejection. No local unit tests, mocks, or fakes were used.
Token accounting is best effort when terminal events omit usage. Apps, connectors, custom executors, file transfer, image generation, and additional configuration remain outside this MVP.
Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
Add API and embedded ChatGPT routing, capability metadata, reasoning levels, and tiered pricing for the two public GPT-6 models. Preserve account-specific native Codex capabilities and Azure sampling. Related: #155937.
* feat(anthropic): support Claude Opus 5.5
Add explicit API and Claude CLI catalog entries with published limits and pricing. Reuse the mandatory adaptive-thinking and prefix-preserving replay contracts, default to medium effort, and account for serving-model prices after fallback. Preserve Opus 5 defaults and aliases.
* fix(anthropic): normalize Opus 5.5 before plugin discovery
Keep cold model parsing aligned with the Anthropic manifest aliases and include Opus 5.5 in the existing live-priority and Claude CLI allowlist expectations.
* feat: enable automatic Code Mode for preferred models
* test: preserve parser boundary and automatic default expectations
* ci: refresh merge proof after upstream tooling type repair
Refresh the merge ref after main restored the tsx CLI shim declaration in 80b9608a25. No source changes.
Reuse the existing model-definition builder for static and dynamic provider projections. Preserve the registered-provider regression and runtime prerequisite without dropping current test consumers.
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Direct Anthropic OAuth requests could fail with claude_code_version_too_old because they advertised Claude Code 2.1.75. Maintain a 2.1.278 client-version floor and prefer a newer installed stable CLI through the Anthropic plugin's shared, bounded probe. Both transports use one selected version for request headers and billing metadata; failed or slow probes fall back to the floor.
Preserve API-key and other provider routes without configuration, credential, dependency, or state migrations. Cover request identity and probe lifecycle, and repair the cold metadata fixture and OS-watch race encountered during validation. Focused tests and exact-head CI passed; candidate live OAuth acceptance remains unverified.
Thanks to @Cyb3rb1ade, @raghidtawil-lab, and @davidcittadini for the reports and controlled comparisons. The single-snapshot and header/billing boundary-test approach builds on @KrasimirKralev's prior work.
Fixes#94716. Related: #154016. Supersedes the approach in #150790 with credit; that PR remains open for maintainer follow-up. The separate model-allowlist issue #144903 is outside this repair.
Co-authored-by: Krasimir Kralev <263465593+KrasimirKralev@users.noreply.github.com>
Enable structured Tool Search when tools.toolSearch is unset, while preserving explicit settings, Code Mode precedence, native Codex discovery, and local-model limits. Repair prompt-policy guidance, callable hook authority, untrusted metadata handling, settlement-safe execution, and saved-result retention. Labs exposes the default and opt-out without rewriting configuration.
The rollout can add discovery turns and does not promise universal latency or token savings. The legacy Node bridge remains an explicit setting.
Validated with focused regressions, admitted OpenAI/SSE cases, actual Gateway and Chromium flows, static gates, independent review, and exact-head hosted CI.
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
OpenClaw-Publication: de7bc1dc-ea60-4fb4-a326-e5725d72be66
Distinguish resolved API, Claude CLI, and unknown configured routes in web and channel model pickers. Preview Default without the outgoing session runtime pin, preserve chat-only accessibility guidance, and explain account-specific billing without changing selection or authentication.
Keep route classification outside the eager startup graph. Apply the explicitly approved 32-byte recorded baseline adjustment for compressed bundle-hash variance; no added raw startup JavaScript or fixed-cap change.
Verified real Telegram Test Server model selection and default reset, full Control UI interactions with inspected before/after screenshots, focused command/UI regressions, and exact-head hosted CI. Source-bound native provider inference is not claimed for this presentation-only change.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>