Conversation
* feat: refresh model catalog when provider gets configured
Symptom: a device's AI picker shows only 1 (or 0) models for a
provider the user just configured. Example from a live device that
added Anthropic credentials after first boot:
models.providers.anthropic = {}
catalog cache: 1 anthropic model (a default placeholder)
picker: only the placeholder selectable
chat: silently falls back to ClawBox AI / deepseek
Root cause: the catalog refresh job only runs at clawbox-setup boot
warmup. When the user adds an API key after boot (the normal path —
the wizard prompts for credentials hours/days after the device
first comes online), no refresh fires for that provider. The
catalog stays on whatever the pre-auth snapshot found, which for
Anthropic is the single fallback model. ClawBox AI gets configured
as the fallback at the same time and silently absorbs every
Claude-selected chat request.
Fix: trigger a single-provider catalog refresh at the end of
configure/route.ts, right before the gateway restart. The refresh
runs out-of-band (single-flight guarded, no await), so the
configure response doesn't slow down. Once the refresh completes
(seconds to a couple minutes on Jetson) the live model list is
written to the disk cache and the picker sees it on next open.
Implementation:
- Export `refreshInBackground` from `catalog/route.ts` (was already
designed for fire-and-forget use; just needed the export).
- Import it from `configure/route.ts` and call after the existing
step 8b (anthropic-plugin gating) and before step 9 (gateway
restart). Map `ocProvider === "deepseek"` → `clawai` because the
catalog uses the user-facing label, not the openclaw provider id.
Gate on `isCatalogProvider()` so local-only providers (llamacpp)
don't trip the refresh.
This means the `models.providers.<p>` block in openclaw.json (and
the picker UI that reads from it) reflects whatever the upstream
provider actually exposes — no hard-coded model lists ever, not
even temporarily.
Verified on a live device: re-saving Anthropic now triggers a
`[catalog] refreshed anthropic: <N> models` log line within seconds
of the configure call returning, and the picker subsequently shows
all available Claude variants instead of the single boot-time
placeholder.
* feat(picker): merge static catalog into live + filter deprecated Claude IDs + show dropdown for allowCustom providers
Three picker-experience fixes uncovered on a device whose Claude.ai
OAuth scope only enumerates `claude-sonnet-4-20250514` (a model
Anthropic has scheduled for retirement on 2026-06-15).
## 1. Merge static PROVIDER_CATALOGS into the live catalog response
When `openclaw models list --provider anthropic` returns just one
model — which is what Claude.ai consumer OAuth scopes do today —
the picker had nothing to offer. The curated list in
`src/lib/provider-models.ts` already tracks the current Anthropic
lineup (Opus 4.7, Sonnet 4.6, Haiku 4.5 per
https://platform.claude.com/docs/en/about-claude/models), so we
now union the live response with the static list. Live entries
take precedence (their `contextWindow`, `input`, and `label`
reflect what the gateway negotiated); ids in the static list but
not the live response get appended as fallback entries with
`contextWindow` from a small known-good lookup table.
Net effect: the picker always shows the curated current lineup,
plus anything extra the upstream actually exposes.
## 2. DEPRECATED_MODEL_IDS denylist
Anthropic's docs list `claude-sonnet-4-20250514` and
`claude-opus-4-20250514` as retiring on 2026-06-15. The openclaw
plugin does not tag them as deprecated (so the existing
`entry.tags?.includes("deprecated")` filter doesn't catch them),
which means a fresh device hitting Claude.ai OAuth gets these as
the *only* selectable Claude model — i.e. the picker silently
defaults users to a soon-to-be-dead model. Hardcode the two
known-retired ids in a denylist that filters them out at
catalog transform time.
When Anthropic publishes the next deprecation notice, add the
new ids to this set.
## 3. Show dropdown for `allowCustom: true` providers with only 1 model
`ChatPopup.tsx` previously hid the model dropdown when
`catalog.models.length < 2`. With Anthropic's single-model OAuth
catalog and `allowCustom: true`, the user had no UI affordance to
switch models at all — even though typing a custom id was the
documented escape hatch. Relax the gate: show the dropdown when
the catalog has multiple models OR allows custom ids.
## Verified on a live device
- `openclaw models list --provider anthropic --all --json` returns
the single deprecated model.
- `/setup-api/ai-models/catalog?provider=anthropic` now returns
the three curated current Claude models (Opus 4.7 / Sonnet 4.6 /
Haiku 4.5) and the disk cache at
`data/catalog-cache/anthropic.json` is consistent.
- The chat picker shows the dropdown with those three entries. The
deprecated `claude-sonnet-4-20250514` is no longer reachable from
the curated list (still typeable as a custom id if a user really
needs it before the 2026-06-15 retirement, since
`allowCustom: true` is preserved).
* fix(remote-control): keep polling tunnel status so the URL appears without a manual refresh
The Cloudflare Quick Tunnel panel exited its poll loop as soon as the
service entered the 'active' state without a URL yet — leaving the
'Negotiating tunnel with Cloudflare...' spinner up until the user
hit Refresh by hand. Restructure the loop to always reschedule with
an adaptive cadence (2s while negotiating, 15s when settled) so the
URL appears the moment cloudflared prints it.
* feat(clawkeep): redesign pair card to match the ClawAI subscription panel
- Replace the auto-window.open with an explicit 'Open authorization
page' button so the device code is visible before focus shifts to
the portal tab.
- Add a 'Get a new code' affordance for when the code expires or
the user wants to retry against a different account.
- Reuse the existing 'ai.openAuthPage' translation key instead of a
near-duplicate; remove four pair-flow keys (enterCode, reopenPortal,
typeCodeOnPortal, waitingApproval) that the redesigned card no
longer references, in all 10 locales.
* feat(vnc): bidirectional UTF-8 clipboard via xclip with toast fallback
Both copy directions used the basic RFB ClientCutText path, which is
Latin-1 only — Cyrillic / CJK / emoji round-tripped as mojibake. Route
both directions through xclip on the guest X CLIPBOARD instead, since
xclip handles UTF-8 natively.
- New /setup-api/vnc/clipboard route: GET reads the guest CLIPBOARD,
POST writes it. xclip's write daemon-forks to keep the selection
alive, so the write side uses 'ignore' stdio to avoid the parent
hanging on inherited pipes.
- Paste modal posts text to the route, then sends Ctrl+V over RFB so
the focused field inside Chromium pastes from the freshly-updated
CLIPBOARD.
- Copy uses noVNC's 'clipboard' event as a change signal only — the
Latin-1 payload is ignored; we GET the real UTF-8 text via xclip.
An in-flight guard + pending flag collapses rapid copy stampedes.
- On insecure HTTP origins where navigator.clipboard is gated, a
toast with a manual 'Copy' button uses the shared copyToClipboard
helper (execCommand fallback inside a user gesture).
- One new translation key, 'vnc.copyToast.fromRemote', in 10 locales.
Note: requires 'xclip' installed on the device (apt install xclip).
* fix(clawai-tier): preserve last-known tier on transient failure
The 'You are on the free plan' modal was popping up every time the
network blipped or the gateway WebSocket reconnected. Cause: the
useClawboxLogin poll cleared loggedIn + tier to null on any non-2xx
response or fetch throw, which TierUpgradeCelebration then detected
as a downgrade (currentRank=0 while seenRank>0).
The server-side /status route already has portal-failure resilience
(it caches the last-known tier with TTLs), so a 2xx response is the
authoritative signal — anything else is 'I don't know right now',
not 'you've been downgraded'.
- On transient failure: return the previous state ref so React bails
out and downstream consumers don't re-render every 30s on a flaky
network.
- On 2xx: keep trusting the server response as before. A confirmed
Paid → Free transition still fires the modal exactly once.
* chore(release): bump version to 3.0.4
* fix: address CodeRabbit review on #139
- VNC clipboard route: cap xclip stdout buffering at MAX_CLIPBOARD_BYTES
and SIGTERM the child when exceeded, returning HTTP 413 — prevents a
giant guest selection from spiking Jetson RAM during the JSON hop.
- VNC clipboard route: reject oversized POST bodies via Content-Length
pre-check, before request.json() buffers the whole payload.
- ClawKeep PairChallengeCard: mark the phase indicator with role=status
+ aria-live=polite, hide the decorative spinner with aria-hidden, so
assistive tech announces waiting/configuring transitions.
- VNCApp sendPaste: add a pasteBusy early-return guard and move
setPasteBusy(false) into a finally — kills the duplicate-paste race
on rapid Ctrl+Enter / double-click.
- VNCApp copy toast: wrap the remote-clipboard toast in
role=status + aria-live=polite + aria-atomic=true so screen readers
announce that remote text is available without focus motion.
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (17)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…th loss (#138) * fix: codex peer-dep self-heal + don't flip badge to Free on portal auth loss Two related post-v3.0.3 hardenings observed live on a customer device and a dev box. ## 1. gateway-pre-start.sh: re-install codex if the peer-dep symlink is missing Symptom on a customer device: chat fails with `Error: Cannot find package 'openclaw' imported from .../@openclaw/codex/dist/shared-client-…js`. Root cause: `openclaw plugins install codex` writes both the package directory AND a `<codex>/node_modules/openclaw -> <global openclaw>` symlink. If something later removes the nested node_modules (partial install, openclaw upgrade churn, manual cleanup), the package.json remains but the peer dep is gone. Our v3.0.3 auto-installer only checked `$CODEX_PLUGIN_DIR/package.json`, declared the plugin "installed", and skipped repair. The plugin then loaded but every import via the openclaw package failed at runtime. Add a check on the nested peer dep too. `-e` follows symlinks, so both "missing symlink" and "dangling symlink" trigger a `--force` reinstall, which rebuilds the link without redownloading the package contents. ## 2. status/route.ts: portal 401/403 no longer flips badge to Free Symptom: when ClawKeep auth disconnects (or the bearer is otherwise rejected by the portal), the device flips its tier badge to Free and fires the downgrade-celebration popup with a Re-subscribe CTA — even though the user might still be on a paid plan. Root cause: fetchPortalTier treated 401/403 as a definitive "user is Free" verdict and cached it for the full 120 s TTL. But the portal returns 401/403 for several distinct cases: - User is genuinely Free. - Token was revoked (admin action, account event, plan transition). - Token migrated to a new format the device hasn't refreshed yet. - Backend cleanup pruned the device record. The response code alone can't distinguish them. Fall through to the existing `unreachable` branch on 401/403 instead. clawaiAccountTier then preserves `localTier` (last known good), tierSource is "picker", the downgrade-celebration popup doesn't fire spuriously, and — because `unreachable` is uncached — the very next poll re-tries the portal, so a recovered token is picked up immediately instead of waiting out a stale 120 s null. A user who legitimately downgrades to Free still flips correctly: the portal returns 200 + `tier: "free"` for that case, which is the path that goes through mapPortalTier and returns null. ## Tests - Replaced "returns clawaiTier=null on portal 403" with two new tests: - "preserves localTier on portal 401/403 (auth lost, might still be paid)" - "does not cache the auth-failure verdict — recovers on next poll" ## Verified - Codex resilience: reproduced the broken state on a dev Jetson (`@openclaw/codex` present, nested node_modules removed), confirmed the new check triggers `--force` reinstall and the chat recovers. - Auth-loss handling: covered by the two new unit tests above. * refactor(simplify): negative-cache unreachable + trim duplicated rationale Applies the three actionable findings from /simplify: 1. Efficiency: previously, removing the 401/403 positive-null cache meant every 30 s status poll hit the portal during sustained auth failure or outage (4× sustained per-device load vs. the prior 120 s cache). Add a separate short negative cache (portalUnreachableCache, 30 s TTL — matches the poll cadence so we hit the portal at most every other poll). Successful 200 clears it so recovery is responsive. The "401/403 isn't Free" semantic from the previous commit is preserved. 2. Quality: comment block in fetchPortalTier was 16 lines, the "also: deliberately not cached" tail was redundant with the test name. Trimmed to 6 lines (the irreducible "401/403 is ambiguous" reasoning). 3. Quality: dropped the duplicated prose comments inside the two new test cases — the test titles already document the behaviour and the rationale belongs in route.ts, not both places. Tests updated: - Renamed "does not cache the auth-failure verdict" → "negative- caches an unreachable verdict so back-to-back polls don't hammer the portal". Same shape, now asserts fetchSpy was called once (not twice). Verifies the new cache hit. - "preserves localTier on portal 401/403" unchanged in behaviour; comment stripped.
# Conflicts: # package.json
Promotes the v3.0.3 + v3.0.4 work from
betato production. All commits have been live onbetaand validated on physical Jetson hardware (krasi.local). CodeRabbit + CI green on each component PR before merge.v3.0.4 bundle (#139)
PROVIDER_CATALOGSand filters retired Claude IDs via aDEPRECATED_MODEL_IDSdenylist. Dropdown surfaces whenallowCustomis set so users can type their own model ID against Anthropic OAuth.ai.openAuthPagetranslation key./setup-api/vnc/clipboardroute that shells out toxclipon the guest X CLIPBOARD. Replaces the basic RFB ClientCutText path that mangled Cyrillic / CJK / emoji as Latin-1 mojibake. Stdout capped at 1 MiB and Content-Length pre-checked on POST to protect the Jetson from memory spikes. Requiresxclipinstalled on the device./setup-api/ai-models/statusfailures so the 'You are on the free plan' modal stops popping every time the gateway WebSocket blips. Hook returns the same state ref when no logical change so React bails out.Post-3.0.3 hardening (#138)
@openclaw/codexif the nested peer-dep symlink toopenclawis missing or dangling. Repro'd live on a customer device where the chat failed withCannot find package 'openclaw'.-efollows symlinks, so both 'missing' and 'dangling' trigger a--forcereinstall, which rebuilds the link without redownloading the package.unreachablebranch instead —clawaiAccountTierpreserveslocalTier, the downgrade-celebration popup doesn't fire spuriously, and the next poll re-tries the portal immediately so a recovered token is picked up without a 120 s wait.portalUnreachableCache, 30 s TTL — matches the poll cadence) so back-to-back polls during sustained auth failure don't hammer the portal. Successful 200 clears it so recovery stays responsive.v3.0.3
Note: PR #138 + PR #139 both contain tier-flicker fixes — #139 is the client-side preservation in
useClawboxLogin, #138 is the server-side preservation in/setup-api/ai-models/status. They're complementary; together they fix the modal-flicker symptom from both ends.Test plan
mainon a Jetson, run the standard update flow, verify version reports 3.0.4.tier: "free").@openclaw/codexinstalled, manually delete~/.openclaw/npm/node_modules/@openclaw/codex/node_modules/openclaw, restart the gateway service, confirm the peer dep is restored and chat works.