feat: v3.0.4 β VNC UTF-8 clipboard + ClawKeep pair UX + bug fixes - #139
Conversation
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.
|
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 selected for processing (3)
π WalkthroughWalkthroughThis PR enhances clipboard bridging between host and guest VNC sessions via a new xclip API, refactors the VNCApp paste/copy flows to use it with toast notifications, improves AI models catalog filtering and provider refresh wiring, redesigns the ClawKeep pairing challenge UI with explicit authorization, updates pairing translations across 10 locales, and improves login state resilience. ChangesVNC Clipboard Bridge and Paste/Copy Refactoring
AI Models Catalog Filtering, Augmentation, and Provider Configuration
ClawKeep Pairing Challenge UI Redesign
Version Bump and Infrastructure Improvements
Estimated code review effortπ― 3 (Moderate) | β±οΈ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
π₯ Pre-merge checks | β 4 | β 1β Failed checks (1 warning)
β Passed checks (4 passed)
βοΈ Tip: You can configure your own custom pre-merge checks in the settings. β¨ Finishing Touchesπ§ͺ Generate unit tests (beta)
Warning Review ran into problemsπ₯ ProblemsStopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a 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 |
β¦de 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).
β¦thout 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.
β¦anel - 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.
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).
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.
There was a problem hiding this comment.
Actionable comments posted: 5
π€ Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/setup-api/vnc/clipboard/route.ts`:
- Around line 47-65: runXclip is currently unboundedly concatenating
proc.stdout/proc.stderr into stdout/stderr which can spike memory; add a hard
cap (e.g. XCLIP_MAX_BYTES) and stop/ignore further data once reached, mark the
result as truncated and terminate the child (proc.kill) and settle the promise;
implement this by tracking bytesRead for stdout and stderr inside the existing
proc.stdout.on("data")/proc.stderr.on("data") handlers, only append up to the
remaining allowed bytes, set a truncated flag (or append a β[xclip] truncatedβ
marker to stderr), clear the timer, kill the process if the cap is hit, and then
call settle(...) with code -1 to ensure the function (runXclip) returns a
bounded-size payload.
- Around line 110-123: Before calling request.json() in POST, read and validate
the Content-Length header: use request.headers.get('content-length'), parse it
to an integer, and if it exists and is greater than MAX_CLIPBOARD_BYTES return
NextResponse.json({ error: "text exceeds 1 MiB cap" }, { status: 413 })
immediately; if the header is present but not a valid positive integer return
400; only then call await request.json() and continue with the existing checks.
Reference: the POST function, request.json(), MAX_CLIPBOARD_BYTES, and
NextResponse.json.
In `@src/components/ClawKeepApp.tsx`:
- Around line 1007-1013: In ClawKeepApp update the JSX that renders the pairing
status so the changing text is announced to assistive tech: keep the existing
phase conditional but wrap the status string (the t(...) output) in an element
with aria-live="polite" (or role="status" and aria-live="polite") so screen
readers get updates, and mark the decorative spinner span (the inline-block w-3
h-3 ... animate-spin element) as aria-hidden="true" so it is ignored by
assistive tech; ensure you only expose the translated string (phase variable) in
the live region and do not interpolate untrusted content into that element to
avoid XSS.
In `@src/components/VNCApp.tsx`:
- Around line 736-780: Wrap the remote-copy toast container (the div that
renders when copyToast && status === "connected") with proper ARIA live-region
attributes so assistive tech announces changes: add aria-live="polite"
role="status" and aria-atomic="true" to that outer div, ensure the
human-readable message nodes (the p elements showing copied state and
copyToast.text) are within that region, and keep existing handlers
(copyToastTextToHost, dismissCopyToast) unchanged; this makes updates to
copyToast (including copied vs. not copied and the remote text) announced
without moving focus.
- Around line 522-534: The sendPaste callback can run concurrently on rapid
submits; add an immediate guard that returns if pasteBusy is true at the top of
sendPaste, and ensure setPasteBusy(false) is called in a finally block so the
busy flag is cleared even on errors. Specifically, in the sendPaste function
(which uses pasteText, setPasteBusy, setPasteError, writeAndPaste, pasteOpenRef,
setPasteOpen, setPasteText, focusVncSurface) check pasteBusy and bail early, set
pasteBusy true before awaiting writeAndPaste, and move the setPasteBusy(false)
into a finally so duplicate clipboard writes/Ctrl+V injections won't occur.
πͺ Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
βΉοΈ Review info
βοΈ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 95102eee-ac71-4383-9b2d-73a53ac881cc
β Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
π Files selected for processing (14)
package.jsonsrc/app/setup-api/ai-models/catalog/route.tssrc/app/setup-api/ai-models/configure/route.tssrc/app/setup-api/vnc/clipboard/route.tssrc/components/ChatPopup.tsxsrc/components/ClawKeepApp.tsxsrc/components/RemoteControlPanel.tsxsrc/components/VNCApp.tsxsrc/lib/clawkeep-translations.tssrc/lib/desktop-translations-part1.tssrc/lib/desktop-translations-part2.tssrc/lib/desktop-translations-part3.tssrc/lib/desktop-translations.tssrc/lib/use-clawbox-login.ts
- 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.
Summary
RemoteControlPanel.tsx).PROVIDER_CATALOGSand filters retired Claude IDs (claude-sonnet-4-20250514,claude-opus-4-20250514) via aDEPRECATED_MODEL_IDSdenylist. Dropdown also surfaces whenallowCustomis set, so users can type their own model ID against Anthropic OAuth (ai-models/catalog/route.ts,ChatPopup.tsx).ai.openAuthPagetranslation key. Removes four orphanedclawkeep.pair.*keys across all 10 locales (ClawKeepApp.tsx,clawkeep-translations.ts)./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. Paste modal posts text through the route then sends Ctrl+V via RFB; copy uses noVNC'sclipboardevent as a change signal and reads the real UTF-8 via the same endpoint. Manual-copy toast for HTTP origins uses the sharedcopyToClipboardhelper. 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. The server already caches portal lookups with proper TTLs, so a 2xx response is the only authoritative signal; anything else is 'unknown', not 'downgraded'. Hook returns the same state ref when no logical change so React bails out and consumers don't re-render every 30s on flaky networks (use-clawbox-login.ts).ai-models/configure/route.tsβ already in this branch).Test plan
apt install -y xclip(or wait for the nextinstall.shupdate). Open the Remote Desktop, click the 'Paste to VNC' button, paste Bulgarian/Chinese/emoji into the textarea, hit Send. Text should appear correctly inside Chromium with no mojibake.ΓΒΎΓΒΈΓβ¦). Click Copy β paste somewhere on your host machine and verify the round-trip.claude-sonnet-4-20250514andclaude-opus-4-20250514should not be selectable.package.jsonshows3.0.4.Summary by CodeRabbit
New Features
Bug Fixes
Localization