Treat Telegram HTTP 421 / Misdirected Request responses as retryable transport failures in both the default channel API retry policy and the strict outbound send retry path.
Wire the 421 handling into isSafeToRetrySendError so non-idempotent Telegram send operations can retry this edge-node rejection without enabling broad ambiguous network retries, and add regression coverage for the default retry path plus strict send predicate handling.
* fix(doctor): archive legacy clawd browser profile residue
* Avoid browser cleanup load without residue
Doctor --fix now skips loading the browser doctor facade unless the legacy browser/clawd profile path exists, preventing broad config repair tests from paying the plugin load cost when there is nothing to archive.
* Use structured health check for browser residue
Register the legacy clawd browser profile residue cleanup through the modern doctor health-check contract so doctor --lint can report it and doctor --fix repairs it through structured effects.
OAuth login flow
----------------
- Hard-require refresh_token after the authorization-code exchange in
xai-oauth.ts. Access-only responses persisted credentials that the
downstream usability check later rejected; the new requireRefreshToken
option fails the exchange instead. Error wording explains the missing
refresh_token in OIDC scope terms (offline_access scope rejected),
not a "grant".
- Derive token expiry from the access-token JWT exp claim when
expires_in is missing. id_token exp is intentionally not used as a
fallback because id_token lifetime tracks the OIDC session, not the
access token, and would defer refresh past actual expiry.
- Handle CORS preflight OPTIONS on the loopback OAuth callback in
src/plugin-sdk/provider-auth-runtime.ts. The previous handler treated
any non-callback request as a failed GET, returned "Missing code or
state", and tore the server down before the real GET arrived. The
CORS allowlist is now an optional `corsOriginAllowlist` parameter on
waitForLocalOAuthCallback so the SDK helper stays generic. The xAI
plugin passes ["auth.x.ai", "accounts.x.ai"] from loginXaiOAuth.
Sidecar surfaces
----------------
- speech-provider.ts (POST /v1/tts) honors the xAI OAuth profile in
addition to provider config and XAI_API_KEY. isConfigured now also
reports true when an xAI auth profile is configured (via
isProviderAuthProfileConfigured), so OAuth-only users are no longer
silently filtered out by the selection layer. The bearer resolver
threads req.cfg into resolveApiKeyForProvider so the right xAI auth
profile is picked when a user has multiple.
- realtime-transcription-provider.ts (WSS /stt) gets the same
isConfigured fix, and the lazy headers() resolver threads req.cfg
into the OAuth bearer lookup. createSession stays sync per its
plugin contract.
- stt.ts: drop the plugin-side OAuth fallback. The media-understanding
core already resolves auth (cfg/agentDir-aware) via
resolveProviderExecutionContext before calling transcribeAudio, so
the wrapper was redundant. transcribeAudio is now the registered
hook directly.
User-Agent attribution
----------------------
- New buildXaiAttributionPolicy in src/agents/provider-attribution.ts
injects User-Agent: openclaw/<version>, originator, and version on
/v1/responses and /v1/chat/completions traffic that goes through
resolveProviderRequestHeaders. Gated to xai-native and default
endpoint classes; custom proxy baseUrls remain withheld. reviewNote
is honest about which headers are spec-verified vs mirrored.
- Shared extensions/xai/src/xai-user-agent.ts helper exports
xaiUserAgentHeaderFor(baseUrl) which only emits the User-Agent when
the resolved baseUrl points at the xAI-native API host. Threaded
through TTS and realtime STT (WS upgrade headers) so user-configured
proxy baseUrls do not receive the openclaw identity. OAuth discovery
and token endpoints still send User-Agent unconditionally because
isTrustedXaiOAuthEndpoint already restricts those URLs to *.x.ai.
- Image gen, batch STT, and video gen rely on the attribution policy
alone (no manual User-Agent in defaultHeaders), so attribution
withholding on user-configured proxy baseUrls is preserved
end-to-end.
- UA is bearer-agnostic: same value whether the bearer comes from an
xAI API key or the xAI OAuth flow.
Drop dead api.grok.x.ai alias
-----------------------------
- xAI retired the api.grok.x.ai alias; DNS now returns NXDOMAIN from
xAI's own authoritative nameservers. Drop it from the xai-native
endpoint host set in extensions/xai/openclaw.plugin.json,
extensions/xai/api.ts, extensions/xai/tts.ts, and the
openai-responses payload policy. Update the attribution test to
classify api.grok.x.ai as "custom" (no live user can reach it; the
classification keeps documenting the host's status).
Video generation now matches xAI's actual API behavior
------------------------------------------------------
Previously, real video generation requests failed with
"xAI video generation response malformed" because the poll-status
handler validated against a closed enum that did not match what the
xAI service actually returns. Four fixes:
- Loosen the poll-status handler. xAI returns intermediate strings
outside `["queued", "processing", "done", "failed", "expired"]`
(commonly `submitted`, `pending`, `in_progress`, ...). Treat `done`
as terminal-success, `["failed", "error", "expired", "cancelled"]`
as terminal-failure, and any other string (including empty) as
continue-polling. Also accept `cancelled` as a terminal failure.
- Send default duration/aspect_ratio/resolution on every generate and
reference-image submit. xAI rejects bodies that omit these fields.
Defaults: duration=8s, aspect_ratio="16:9", resolution="720p".
- Accept lowercase resolution input ("480p"/"720p"/"1080p") in
addition to uppercase, normalize to lowercase on the wire.
- Add an `x-idempotency-key` header (fresh `crypto.randomUUID()`) on
every submit so a network retry does not double-charge the user.
Polls intentionally reuse the unmodified `headers` without the key.
Ergonomics
----------
- All "missing xAI credentials" errors (code_execution, lazy
code_execution fallback in extensions/xai/index.ts, x_search,
web_search grok in web-search-provider.runtime.ts, TTS, batch STT,
realtime STT) now mention `openclaw onboard --auth-choice xai-oauth`
first.
- Dedupe the Grok model-id alias table: model-compat.ts re-exports
normalizeXaiModelId from model-id.ts as normalizeNativeXaiModelId.
Test coverage
-------------
- src/plugin-sdk/provider-auth-runtime.test.ts: locks the new pure
buildOAuthCallbackOriginResolver gate (allowlist match,
case-normalization, https-only, non-allowlisted hosts dropped,
multi-Origin handling).
- extensions/xai/xai-oauth.test.ts: locks
XAI_OAUTH_CALLBACK_CORS_ORIGIN_ALLOWLIST so loginXaiOAuth keeps
threading the right hosts to the SDK helper.
- extensions/xai/speech-provider.test.ts: OAuth-only auth profile
flips isConfigured to true; cfg threads into the OAuth fallback
resolver.
- extensions/xai/realtime-transcription-provider.test.ts: same +
upgrade headers carry the OAuth bearer end-to-end.
- extensions/xai/stt.test.ts: explicit assertion that transcribeAudio
trusts the core-resolved apiKey (no plugin-side wrapper).
Verification
------------
- pnpm install: clean
- 154/154 vitest tests pass across 13 touched test files
- pnpm check:changed: typecheck core/ext + tests, oxlint core/ext,
runtime guards, dependency pin guard, package patch guard, runtime
import cycles, sidecar loader guard - all green
- pnpm build: 0 errors, 0 [INEFFECTIVE_DYNAMIC_IMPORT] warnings
Fix Telegram forum-topic OriginatingTo routing for inbound, audio-preflight, and skipped-message hook contexts.
Centralize Telegram inbound origin target construction so real forum topics stay encoded in the routing target while DM thread ids remain metadata-only.
Fixes#83302.
Summary:
- The PR changes Discord reply delivery, sanitizer, and queued follow-up auto-reply paths so explicit verbose tool-progress payloads are delivered while final assistant replies still use the privacy sanitizer.
- Reproducibility: yes. source-level: current main strips tool-looking Discord payload text at the front-chann ... ds compaction events in queued follow-up runs. I did not run a live Discord repro in this read-only review.
Automerge notes:
- Ran the ClawSweeper repair loop before final review.
- Included post-review commit in the final squash: fix: gate queued follow-up progress when verbose is off
- Included post-review commit in the final squash: fix: preserve queued verbose progress under preview suppression
- Included post-review commit in the final squash: ci: rerun discord verbose progress PR
- Included post-review commit in the final squash: fix: preserve Discord verbose progress after rebase
- Included post-review commit in the final squash: fix: serialize discord queued progress
- Included post-review commit in the final squash: Fix Discord verbose tool progress delivery
Validation:
- ClawSweeper review passed for head fd845e773a.
- Required merge gates passed before the squash merge.
Prepared head SHA: fd845e773a
Review: https://github.com/openclaw/openclaw/pull/80042#issuecomment-4414121881
Co-authored-by: Clawsistant <clawsistant@users.noreply.github.com>
Co-authored-by: anyech <anyech@gmail.com>
Co-authored-by: OpenClaw Assistant <assistant@openclaw.local>
Co-authored-by: Shadow <hi@shadowing.dev>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: thewilloftheshadow
Co-authored-by: thewilloftheshadow <35580099+thewilloftheshadow@users.noreply.github.com>
Summary:
- The PR adds a generic inbound debounce `cancelKey`, uses Telegram stop-like controls to cancel same-chat pen ... buffers and bypass debounce, and adds focused Telegram regression coverage plus updated channel test mocks.
- Reproducibility: yes. by source inspection: current main enqueues Telegram text through inbound debounce bef ... nly has flush semantics for pending keyed work. I did not run a live Telegram repro in this read-only pass.
Automerge notes:
- PR branch already contained follow-up commit before automerge: Fix Telegram stop debounce bypass
Validation:
- ClawSweeper review passed for head 19245a341d.
- Required merge gates passed before the squash merge.
Prepared head SHA: 19245a341d
Review: https://github.com/openclaw/openclaw/pull/83248#issuecomment-4472300906
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
* feat(doctor): add --lint mode + structured HealthFinding shape
Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.
Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
types. Findings carry severity per-finding; checks return
readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
Catches throws into synthetic error findings (anchored at check id;
message scrubbed of control chars, capped at 256 bytes). Sorts
findings by severity desc, check id, path. Exports
exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
logic from existing legacy run*Health functions:
core/doctor/gateway-config (warning)
core/doctor/command-owner (info)
core/doctor/workspace-status (info)
core/doctor/final-config-validation (error)
Each was audited safe per the proposal's adapter constraints
(no writes, no repair calls, no prompts, no probes incl. local-bind).
Legacy run*Health contributions in doctor-health-contributions.ts
are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
filters by --severity-min, emits human or JSON output, returns exit
code from unfiltered set so --severity-min hides info findings
without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
--severity-min, --skip, --only flags to existing doctor command.
--lint branches to runDoctorLintCli; without --lint, doctor runs
unchanged.
LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.
* fix: avoid string spread in doctor errors
* chore: refresh plugin SDK API baseline
* docs: clarify doctor lint usage
* feat(doctor): prepare repairs for dry-run reporting
Summary:
- The PR buffers Codex command-output deltas per command item and uses them as a fallback for transcripts, trajectory output, final tool output, and after-tool-call errors when `aggregatedOutput` is empty.
- Reproducibility: yes. A source-level reproduction is clear: send current-turn command-output delta notificat ... aggregatedOutput: null`; current main has no final transcript or trajectory fallback for the streamed text.
Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(codex): preserve streamed command output
Validation:
- ClawSweeper review passed for head 07393a304f.
- Required merge gates passed before the squash merge.
Prepared head SHA: 07393a304f
Review: https://github.com/openclaw/openclaw/pull/83222#issuecomment-4472054629
Co-authored-by: 0x505badc0de <32790662+rozmiarD@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>