Skip to content

feat(chat): account-tier independence, dynamic catalog, redesigned header - #124

Merged
yalexx merged 4 commits into
mainfrom
fix/clawai-account-tier
May 5, 2026
Merged

feat(chat): account-tier independence, dynamic catalog, redesigned header#124
yalexx merged 4 commits into
mainfrom
fix/clawai-account-tier

Conversation

@KrasimirKralev

@KrasimirKralev KrasimirKralev commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Five connected fixes around the chat picker / account tier:

  1. Account-tier independence — ClawKeep + Remote Desktop entitlement no longer flickers when switching active chat provider. A Max-subscription user chatting via OpenAI keeps paid features unlocked.

  2. User-name field — placeholder removed, 5s preference poll gated by a "user is typing" ref, agent instruction to set ui_user_name from conversation.

  3. Disk-backed catalog cache + background warmupopenclaw models list is ~3min on Jetson Orin Nano. Route now serves from data/catalog-cache/<provider>.json with 6h TTL, refreshes happen out-of-band. Boot warmup fires off refreshes for every catalog provider on first import. Picker opens instant after the first ~3min of uptime.

  4. Per-provider reasoning effort + curated openai picker — replaced the universal 9-level dropdown with per-provider sets sourced from each upstream's API docs. OpenAI / OpenAI-Codex pickers curated to the 5.4 + 5.5 family; openai-codex additionally hides -pro variants (ChatGPT-account auth rejects them).

  5. Redesigned chat header — new HeaderDropdown popover component replaces native <select> (compact trigger that truncates with ..., wider popover with full labels + hints). Three loose pills instead of a segmented bar. ClawBox AI consolidated into one provider entry with Flash/Pro selectable via the secondary model dropdown — matches how Anthropic, OpenAI, etc work.

Files changed

  • src/app/setup-api/ai-models/status/route.ts — split clawaiTier (active) from clawaiAccountTier (any profile)
  • src/app/setup-api/ai-models/catalog/route.ts — disk cache, background refresh, per-provider allowlist regex, clawai static catalog
  • src/app/setup-api/chat/model/route.ts — options keyed by provider (one row per provider), normalized provider check
  • src/components/HeaderDropdown.tsx (new) — custom popover dropdown
  • src/components/ChatPopup.tsx — per-provider reasoning levels, deepseek alias handling, header pills
  • src/lib/use-clawbox-login.ts — account-level vs active-provider tier
  • src/lib/provider-models.tsCLAWAI_MODELS, expanded openai-codex fallback, clawai in CATALOG_PROVIDERS
  • Clawbox.md, config/clawbox-workspace-guide.md — agent instruction for ui_user_name
  • src/lib/translations.ts — drop settings.userName.placeholder from 10 locales
  • Tests in src/tests/routes/ai-models/status.test.ts + src/tests/unit/use-clawbox-login.test.ts

Test plan

  • Open chat: provider dropdown shows ClawBox AI / Anthropic / OpenAI / OpenAI Codex / Google / OpenRouter — one row per provider
  • Pick ClawBox AI → secondary dropdown shows Pro Tier + Max Tier; switching tier patches the gateway with deepseek/<model> (no "Selected AI provider is not configured" error)
  • Pick OpenAI Codex → list contains gpt-5.4, gpt-5.4-mini, gpt-5.5; no Pro variants
  • Pick OpenAI (API key) → list contains gpt-5.4 / -mini / -pro, gpt-5.5 / -pro
  • Effort dropdown: each provider shows the levels documented in the commit body; switch providers → effort dropdown swaps to the right set, last-used choice per provider persists in localStorage
  • Log into ClawBox AI Max + add an OpenAI auth profile → switch chat provider to OpenAI → ClawKeep shelf icon stays unlocked, no "Free backup" notification
  • User-name field in Settings: type a name → poll doesn't overwrite; agent says "I'll remember your name as X" and field updates within 5s
  • Restart clawbox-setup → first picker open shows static fallback briefly, ~3min later shows live catalog from disk cache
  • Cache survives restart: stop service, restart, picker open is instant from disk

…ader

Account-tier resolution decoupled from active chat provider
* /setup-api/ai-models/status resolves clawaiAccountTier (any clawai
  profile present) separately from clawaiTier (active chat provider).
  Drives ClawKeep + Remote Desktop entitlement so a Max user chatting
  via OpenAI keeps paid features unlocked.
* use-clawbox-login reads account-level tier with fallback to
  active-provider tier for older /status responses.
* Shelf shield colour, shelf click target, and the "Free backup"
  notification all bind to clawboxLogin.loggedIn (account-level)
  instead of provider-equality.

User-name field
* Removed placeholder text; 5s poll of ui_user_name preference gated
  by userNameEditedRef so local typing isn't clobbered.
* Translations: deleted settings.userName.placeholder from all 10
  locales (parity test forbids empty values, removal beats blanking).
* CLAWBOX.md + config/clawbox-workspace-guide.md teach the agent to
  call preferences_set("ui_user_name", "<name>") when offered a name.

Disk-backed catalog cache + background warmup
* /setup-api/ai-models/catalog serves from data/catalog-cache/
  <provider>.json with 6h TTL. Refreshes happen out-of-band — the
  route never waits on the openclaw bin (~3min CPU on Jetson).
* Boot warmup fires off refreshes for every CATALOG_PROVIDERS entry
  on first import, staggered 5s. Subsequent picker opens are instant.
* Static fallbacks updated to mirror current upstream catalogs so
  the warming-state UX is decent on fresh installs.

Per-provider reasoning effort levels
* Replaced the universal 9-level dropdown with REASONING_BY_PROVIDER
  table sourced from each upstream's API docs:
    OpenAI / Codex   Off / Low / Medium / High / X-High  (Medium)
    Anthropic        Low / Medium / High / Max           (High)
    Google           Off / Low / Medium / High / Adaptive (Adaptive)
    DeepSeek         Low / Medium / High                 (High)
    ClawBox AI       Low / Medium / High                 (High)
    OpenRouter       Off / Minimal / Low / Medium / High / X-High (Medium)
* Per-provider localStorage remembers each provider's last choice.

Curated openai / openai-codex catalogs
* ALLOWED_MODEL_RE_BY_PROVIDER filters the live openclaw catalog to
  gpt-5.4 + gpt-5.5 family only. Older gens (4.1, 5.0-5.3) hidden.
* openai-codex additionally hides -pro variants (ChatGPT-account auth
  rejects them with "model not supported when using Codex with a
  ChatGPT account").

Redesigned chat header dropdowns
* New HeaderDropdown component replaces native <select>: compact
  trigger pill that truncates with "..." when squeezed, wider
  popover showing full labels + hints, coral-highlighted active
  option, click-outside / Esc to close.
* Three loose rounded pills with 6px gap (no segmented bar).
* Compact provider labels on the trigger ("Codex" instead of
  "OpenAI Codex"); popover still shows full names.
* ClawBox AI consolidated into one provider entry with Flash/Pro
  selectable via the secondary model dropdown — matches how
  Anthropic, OpenAI, etc work. chat/model/route options now keyed
  by provider (one row per provider) instead of by model id.
@KrasimirKralev
KrasimirKralev requested a review from a team as a code owner May 4, 2026 18:20
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@KrasimirKralev has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 13 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0edc4c42-e4b4-4b90-a204-759b413e4907

📥 Commits

Reviewing files that changed from the base of the PR and between 67c5370 and d4c33b8.

📒 Files selected for processing (12)
  • .gitignore
  • Clawbox.md
  • config/clawbox-workspace-guide.md
  • e2e/chat-popup.spec.ts
  • src/app/setup-api/ai-models/catalog/route.ts
  • src/app/setup-api/ai-models/status/route.ts
  • src/app/setup-api/chat/model/route.ts
  • src/components/ChatPopup.tsx
  • src/components/SettingsApp.tsx
  • src/tests/routes/ai-models/status.test.ts
  • src/tests/routes/chat-model.test.ts
  • src/tests/unit/provider-models.test.ts
📝 Walkthrough

Walkthrough

This PR adds user name persistence, implements ClawAI as an account-level entitlement tier separate from chat provider selection, introduces a custom header dropdown UI component, refactors chat header controls with per-provider reasoning configuration, and redesigns the model catalog system with disk-backed caching and background refresh mechanics.

Changes

User Name Preferences & Settings Polling

Layer / File(s) Summary
Preference Documentation
Clawbox.md, config/clawbox-workspace-guide.md
New guidance specifying when to persist user's preferred name via preferences_set("ui_user_name", ...), clear on anonymity requests, and avoid echoing confirmation.
Settings Sync Implementation
src/components/SettingsApp.tsx
Settings app polls /setup-api/preferences?keys=ui_user_name every 5s, conditionally updates state while user is typing (gated by userNameEditedRef), and cleans up timers on unmount.
Translation Updates
src/lib/translations.ts
Removed settings.userName.placeholder entries from all locales (en, bg, de, es, fr, it, ja, nl, sv, zh).

ClawAI Account-Level Entitlement System

Layer / File(s) Summary
Model Definitions
src/lib/provider-models.ts
Added CLAWAI_MODELS curated list (DeepSeek v4-flash and v4-pro); updated OPENAI_MODELS to GPT 5.5/5.4 generations; added "clawai" to CATALOG_PROVIDERS and PROVIDER_CATALOGS with allowCustom: false; changed OpenAI default from gpt-5 to gpt-5.4.
Account Tier Resolution
src/app/setup-api/ai-models/status/route.ts
Compute clawaiAccountTier from stored clawai config and optional portal lookup; derive clawaiTier (badge-facing) only when active provider is clawai; expand response to include clawaiAccountTier and clawaiConfigured fields.
Login Hook Account Detection
src/lib/use-clawbox-login.ts
Derive loggedIn from clawaiConfigured (account-level) with fallback to legacy provider === "clawai"; compute tier as clawaiAccountTier ?? clawaiTier; update inline documentation for account-level semantics.
E2E Mock Setup
e2e/helpers/clawbox.ts
Mock /setup-api/ai-models/status response now includes clawaiAccountTier ("flash" when configured, null otherwise) alongside clawaiTier.
ChromeDesktop Auth Wiring
src/app/page.tsx
clawAiAuthenticated now derives from useClawboxLogin().loggedIn instead of setup-status provider detection; showClawAiOfferNotification gated by dedicated effect that waits for login loading to settle.
Tier Resolution Tests
src/tests/routes/ai-models/status.test.ts
Added test suite validating clawaiAccountTier and clawaiConfigured behavior when OpenAI is active, when no clawai profile exists, when Free user has paired token, and when clawai is active provider.
Login Hook Account Tests
src/tests/unit/use-clawbox-login.test.ts
Updated tests to assert login state driven by clawaiConfigured (independent of active provider); added coverage for paid clawai account with OpenAI chat; added fallback test for legacy provider === "clawai" when clawaiConfigured is absent.

Header Dropdown Component & Chat UI Refactoring

Layer / File(s) Summary
Dropdown Component
src/components/HeaderDropdown.tsx
New reusable React component rendering a compact trigger pill and options popover with open/close state management, accessibility attributes, optional onPointerDown handler for drag integration, and configurable sizing/truncation.
Dropdown Styling
src/app/globals.css
Added .chat-header-pills for aligned pills; .header-dropdown-trigger with focus/disabled/hover states; .header-dropdown-popover with absolute positioning and @keyframes header-dropdown-pop entrance animation; .header-dropdown-option with active/disabled/hover states and hint layout.
Provider Selection & Labeling
src/components/ChatPopup.tsx
Added getProviderPillText() for compact provider names in header; replaced native <select> provider switcher with HeaderDropdown pill-based UI.
Per-Provider Reasoning Config
src/components/ChatPopup.tsx
Introduced REASONING_BY_PROVIDER mapping supported thinking levels per provider (not global DeepSeek-only); added per-provider localStorage persistence (clawbox:chat:thinkingLevel:<provider>); refactored UI to show only provider-supported levels via visibleThinkingLevels and effectiveThinkingLevel; added effect to re-sync reasoning level when provider changes.
Model Selection Wiring
src/components/ChatPopup.tsx
Updated model dropdown to map active model with clawaideepseek wire-provider fallback; model onChange routes requests using correct wire provider prefix.
Header Control Refactoring
src/components/ChatPopup.tsx
Replaced reasoning-effort native <select> with HeaderDropdown showing provider-specific levels; updated gateway sessions.patch call to send effectiveThinkingLevel directly (no normalization).

Model Catalog Caching & Route Updates

Layer / File(s) Summary
Provider Normalization
src/app/setup-api/chat/model/route.ts
Changed primary dropdown construction to be keyed by provider (not model ID), ensuring one row per provider; updated "power-user" validation to normalize provider (normalizeProvider(parsed.provider)) before comparing against options, enabling deepseek/clawai aliasing.
Catalog Infrastructure
src/app/setup-api/ai-models/catalog/route.ts
Added disk-cache read/write helpers with atomic temp-file-rename pattern; introduced per-provider single-flight refreshing guard and in-memory memCache; added bootWarmup() to trigger staggered background refreshes on module load.
Model Transformation
src/app/setup-api/ai-models/catalog/route.ts
Extracted transformOpenclawEntries() (applies provider-specific ID allowlist regex, filters deprecated models); refactored transformOpenRouterEntries() and fetchOpenRouterCatalog() for OpenRouter model mapping with hint truncation and modality joining.
Streaming JSON Parsing
src/app/setup-api/ai-models/catalog/route.ts
OpenClaw execution now uses streaming stdout parsing with timeout enforcement, resolving as soon as buffered JSON becomes syntactically complete (no need to wait for process exit).
Cache & Refresh Control
src/app/setup-api/ai-models/catalog/route.ts
GET handler prefers memCache, falls back to disk, computes staleness against REFRESH_INTERVAL_MS, triggers background refresh when forced/stale/missing, returns cached payload (optionally stale: true) or empty { warming: true } payload when no cache exists (client uses static fallback).

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant ChatUI as Chat UI
    participant Status as /setup-api/ai-models/status
    participant Portal as ClawBox Portal
    participant Config as Config Store
    
    User->>ChatUI: Select provider (e.g., OpenAI)
    ChatUI->>Status: GET /setup-api/ai-models/status
    Status->>Config: Read stored clawai/deepseek models
    alt clawai model configured
        Status->>Portal: Fetch account tier (if claw_ token exists)
        Portal-->>Status: Account tier (e.g., "pro")
        Status->>Status: clawaiAccountTier = "pro"
        Status->>Status: clawaiTier = null (provider is OpenAI, not clawai)
    else no clawai model
        Status->>Status: clawaiAccountTier = null
        Status->>Status: clawaiConfigured = false
    end
    Status-->>ChatUI: { clawaiAccountTier, clawaiTier, clawaiConfigured }
    ChatUI->>ChatUI: useClawboxLogin derives loggedIn from clawaiConfigured
Loading
sequenceDiagram
    participant User as User
    participant ChatPopup as ChatPopup Component
    participant HeaderDropdown as HeaderDropdown
    participant LS as localStorage
    participant Session as Gateway /sessions/patch
    
    User->>ChatPopup: Switch provider (e.g., DeepSeek)
    ChatPopup->>ChatPopup: activeProvider changes
    ChatPopup->>LS: Read persisted thinkingLevel for new provider
    LS-->>ChatPopup: Provider-specific level (or default fallback)
    ChatPopup->>ChatPopup: Update effectiveThinkingLevel
    ChatPopup->>HeaderDropdown: Render reasoning dropdown with provider levels
    User->>HeaderDropdown: Click reasoning level option
    HeaderDropdown->>ChatPopup: onChange(selected level)
    ChatPopup->>LS: Persist thinkingLevel for this provider
    ChatPopup->>Session: PATCH /sessions with effectiveThinkingLevel
    Session-->>ChatPopup: { updated session }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ID-Robots/clawbox#83: Modifies ChatPopup provider selection and labeling logic alongside this PR's header dropdown refactoring.
  • ID-Robots/clawbox#119: Updates src/app/setup-api/ai-models/status/route.ts tier resolution and caching, overlapping with this PR's ClawAI account tier logic.
  • ID-Robots/clawbox#108: Implements ui_user_name persistence and updates ClawBox login/status fields similarly to this PR's user name and entitlement changes.

Suggested labels

enhancement, ui, backend, chat, settings

Poem

🐰 A rabbit hops through dropdown pills,
Per-provider reasoning fills the ills,
ClawAI tiers stack with account-wise grace,
While disk caches warm the catalog's space.
Your name persists, preferences bloom—
Smart chat awaits you in the room!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the three main changes: account-tier independence, dynamic catalog caching, and header UI redesign. It directly reflects the primary objectives of the PR.
Description check ✅ Passed The PR description comprehensively covers all required sections: a detailed summary of five connected changes, explicit file listing with their roles, concrete test plan with checkboxes, and proper issue linkage conventions. All template sections are present and well-populated.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/clawai-account-tier

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.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 25 minutes and 13 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

- Extract compareCatalogModels helper to share the sort comparator
  between the openclaw and openrouter transforms.
- Convert refreshInBackground's nested ternary to an if/else chain
  per CLAUDE.md (no nested ternaries).
- Replace bootWarmup's index-based for loop with forEach((p, i) => ...).
- Drop the now-trivial labelForThinkingLevel useCallback wrapper and
  inline the THINKING_LEVEL_LABELS lookup at its single call site.
- Untangle the activeModelId clawai/deepseek alias resolution into a
  small if-block so the picker no longer relies on a nested ternary
  inside a ?? chain.
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

CI Summary

✅ E2E Install

✅ Tests

  • Result: passed
  • View run
  • Coverage: statements 70.51%, branches 60.49%, functions 65.85%, lines 72.58%

✅ E2E

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/app/setup-api/ai-models/catalog/route.ts (1)

225-236: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep ClawBox AI non-custom in the route payload too.

buildPayload() and the empty warming response both hardcode allowCustom: true. After the first background refresh, the live clawai catalog will therefore contradict PROVIDER_CATALOGS.clawai.allowCustom = false and re-enable arbitrary model ids in any UI that trusts this route.

Suggested fix
+const ALLOW_CUSTOM_BY_PROVIDER: Record<string, boolean> = {
+  clawai: false,
+  anthropic: true,
+  openai: true,
+  "openai-codex": true,
+  google: true,
+  openrouter: true,
+};

 function buildPayload(provider: string, models: CatalogModel[]): CatalogResponse {
   const fallbackDefault = DEFAULT_MODEL_BY_PROVIDER[provider];
   const defaultModelId = models.find((m) => m.id === fallbackDefault)?.id
     ?? models[0]?.id
     ?? fallbackDefault
     ?? "";
   return {
     provider,
     models,
     defaultModelId,
-    allowCustom: true,
+    allowCustom: ALLOW_CUSTOM_BY_PROVIDER[provider] ?? true,
     fetchedAt: Date.now(),
   };
 }
   const empty: CatalogResponse = {
     provider,
     models: [],
     defaultModelId: DEFAULT_MODEL_BY_PROVIDER[provider] ?? "",
-    allowCustom: true,
+    allowCustom: ALLOW_CUSTOM_BY_PROVIDER[provider] ?? true,
     fetchedAt: 0,
     warming: true,
   };

Also applies to: 392-399

🤖 Prompt for 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.

In `@src/app/setup-api/ai-models/catalog/route.ts` around lines 225 - 236, The
payload currently forces allowCustom: true which re-enables custom model IDs for
providers like "clawai"; update buildPayload(provider, models) to set
allowCustom based on the provider catalog config (e.g., use
PROVIDER_CATALOGS[provider]?.allowCustom or a sensible default) instead of
hardcoding true, and apply the same change to the empty/warming response path so
both the real and fallback responses respect PROVIDER_CATALOGS for the given
provider (refer to buildPayload and the warming/empty response construction).
src/app/setup-api/ai-models/status/route.ts (1)

197-204: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize provider aliases before choosing the active profile.

Line 197 derives the provider hint from the raw model slug, and Line 203 compares it to the raw profile provider. If the active model is deepseek/... but the matching profile is stored as clawai (or the reverse), activeKey misses and this route falls back to profileKeys[0], which can report the wrong provider, mode, and clawaiTier.

Suggested fix
-    const primaryProviderHint = model ? model.split("/")[0] : null;
+    const primaryProviderHint = normalizeProvider(model ? model.split("/")[0] : null);
     let activeKey: string | undefined;
     if (primaryProviderHint) {
       activeKey = profileKeys.find((key) => {
         const entry = profiles[key];
-        const entryProvider = entry?.provider ?? key.split(":")[0];
+        const entryProvider = normalizeProvider(entry?.provider ?? key.split(":")[0]);
         return entryProvider === primaryProviderHint;
       });
     }
🤖 Prompt for 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.

In `@src/app/setup-api/ai-models/status/route.ts` around lines 197 - 204, The
provider comparison when selecting activeKey uses raw strings
(primaryProviderHint and entry?.provider) which misses aliases like "deepseek"
vs "clawai"; normalize both sides before comparing: add/use a normalization
function (e.g., normalizeProviderAlias) and call it on primaryProviderHint and
on entryProvider (derived from entry?.provider ?? key.split(":")[0]) inside the
profileKeys.find in the activeKey assignment so aliases map to the same
canonical provider name while preserving the existing fallback to
profileKeys[0].
🤖 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 `@Clawbox.md`:
- Line 111: The docs show preferences_set as two positional args but the MCP
exposes a single parameter named "preferences" which is a JSON string parsed
with JSON.parse; update the documentation/example for preferences_set to show
calling it with a single object whose "preferences" field is a JSON-stringified
map containing the ui_user_name key (i.e., JSON.stringify of { "ui_user_name":
"<name>" }) so the schema and the handler's JSON.parse will succeed;
alternatively, if you prefer API change, modify the preferences_set
handler/schema (the preferences parameter and its JSON.parse usage) to accept a
direct key/value payload, but keep the code and docs consistent (refer to
preferences_set, the "preferences" parameter and the JSON.parse call).

In `@config/clawbox-workspace-guide.md`:
- Around line 76-78: Add a language identifier to the fenced code block
containing the preferences_set invocation to satisfy the MD040 lint rule; locate
the fenced block that contains preferences_set("ui_user_name", "<name>") and
change the opening fence from ``` to ```text (or ```python) so the snippet is
fenced with a language identifier.

In `@src/app/setup-api/ai-models/catalog/route.ts`:
- Around line 169-172: The "openai-codex" regex in ALLOWED_MODEL_RE_BY_PROVIDER
is too permissive (the pattern /^gpt-5\.[45](-mini)?$/ also matches
gpt-5.5-mini); update the "openai-codex" entry so it only permits the explicit
SKUs allowed (gpt-5.5, gpt-5.4, and gpt-5.4-mini) by replacing its RegExp with
one that enumerates those variants (e.g., an alternation that matches gpt-5.5 or
gpt-5.4 with optional -mini) so the unsupported gpt-5.5-mini cannot pass
validation.

In `@src/app/setup-api/chat/model/route.ts`:
- Around line 179-194: The fallback logic in route.ts occasionally uses
defaultModelForProvider(rawProvider) which relies on the DEFAULT_PROVIDER_MODELS
map that still contains the old "openai/gpt-5" value; update the default to the
new curated OpenAI family id ("openai/gpt-5.4") so legacy/sparse configs pick
the correct pill. Locate DEFAULT_PROVIDER_MODELS (or the implementation of
defaultModelForProvider) and replace the OpenAI entry from "openai/gpt-5" to
"openai/gpt-5.4" (or have defaultModelForProvider return the new id for
rawProvider === "openai") ensuring providerDefinitions/definedModels logic
remains unchanged. Ensure tests or code paths that normalize provider/model
strings (normalizeProviderFromModel) still accept the new id.

In `@src/components/SettingsApp.tsx`:
- Around line 575-591: The polling still runs after the user edits because
tick()'s .finally() only checks cancelled, so add a guard for
userNameEditedRef.current there; inside tick()'s .finally() only schedule the
next setTimeout(tick, 5_000) when !cancelled && !userNameEditedRef.current (so
network requests stop once the user has edited), referencing the existing tick,
userNameEditedRef, cancelled and timer/setTimeout symbols to locate and update
the logic.

In `@src/tests/routes/ai-models/status.test.ts`:
- Around line 270-303: The test currently allows both local picker and portal to
return "pro", masking whether clawaiAccountTier comes from the portal; update
the test around the "returns clawaiAccountTier=pro alongside..." case so it
asserts the portal was consulted: after calling GET() assert fetchSpy was called
exactly once (or at least called) and/or change mockGetConfigValue to return a
distinct value (e.g. "free") while keeping the portal fetchSpy response
deviceTier:"pro" so the only way body.clawaiAccountTier === "pro" is if GET()
used the portal; reference the same mocks used in the test (mockGetConfigValue,
fetchSpy) and the GET() invocation when making the assertion or value change.

---

Outside diff comments:
In `@src/app/setup-api/ai-models/catalog/route.ts`:
- Around line 225-236: The payload currently forces allowCustom: true which
re-enables custom model IDs for providers like "clawai"; update
buildPayload(provider, models) to set allowCustom based on the provider catalog
config (e.g., use PROVIDER_CATALOGS[provider]?.allowCustom or a sensible
default) instead of hardcoding true, and apply the same change to the
empty/warming response path so both the real and fallback responses respect
PROVIDER_CATALOGS for the given provider (refer to buildPayload and the
warming/empty response construction).

In `@src/app/setup-api/ai-models/status/route.ts`:
- Around line 197-204: The provider comparison when selecting activeKey uses raw
strings (primaryProviderHint and entry?.provider) which misses aliases like
"deepseek" vs "clawai"; normalize both sides before comparing: add/use a
normalization function (e.g., normalizeProviderAlias) and call it on
primaryProviderHint and on entryProvider (derived from entry?.provider ??
key.split(":")[0]) inside the profileKeys.find in the activeKey assignment so
aliases map to the same canonical provider name while preserving the existing
fallback to profileKeys[0].
🪄 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: 100a583a-b2b8-47a2-91b4-9ca56f314589

📥 Commits

Reviewing files that changed from the base of the PR and between 43a081b and 67c5370.

📒 Files selected for processing (16)
  • Clawbox.md
  • config/clawbox-workspace-guide.md
  • e2e/helpers/clawbox.ts
  • src/app/globals.css
  • src/app/page.tsx
  • src/app/setup-api/ai-models/catalog/route.ts
  • src/app/setup-api/ai-models/status/route.ts
  • src/app/setup-api/chat/model/route.ts
  • src/components/ChatPopup.tsx
  • src/components/HeaderDropdown.tsx
  • src/components/SettingsApp.tsx
  • src/lib/provider-models.ts
  • src/lib/translations.ts
  • src/lib/use-clawbox-login.ts
  • src/tests/routes/ai-models/status.test.ts
  • src/tests/unit/use-clawbox-login.test.ts

Comment thread Clawbox.md Outdated
Comment thread config/clawbox-workspace-guide.md Outdated
Comment thread src/app/setup-api/ai-models/catalog/route.ts
Comment thread src/app/setup-api/chat/model/route.ts
Comment thread src/components/SettingsApp.tsx
Comment thread src/tests/routes/ai-models/status.test.ts
CodeRabbit findings (all major/minor in PR #124 review):

* Clawbox.md / config/clawbox-workspace-guide.md: preferences_set takes
  a single JSON-string parameter, not two positional args. Examples
  updated to preferences_set('{"ui_user_name": "<name>"}'). Without
  this an agent following the docs would either fail schema validation
  or hit JSON.parse on a non-JSON string. Added text language hint to
  the workspace-guide fenced block to silence MD040.
* catalog/route.ts ALLOWED_MODEL_RE_BY_PROVIDER: openai-codex regex
  /^gpt-5\.[45](-mini)?$/ accidentally allowed gpt-5.5-mini (not on
  the Codex auth path, would 400). Replaced with explicit alternation
  /^(?:gpt-5\.5|gpt-5\.4(?:-mini)?)$/.
* catalog/route.ts allowCustom: was hardcoded to true in buildPayload
  and the warming-state empty payload, contradicting
  PROVIDER_CATALOGS.clawai.allowCustom = false. Added
  ALLOW_CUSTOM_BY_PROVIDER override consulted by both code paths so
  ClawBox AI cant accept arbitrary slugs Mike's gateway wouldnt route.
* chat/model/route.ts DEFAULT_PROVIDER_MODELS.openai: still pointed at
  openai/gpt-5 even though the curated picker moved to gpt-5.4. Legacy
  configs without an explicit models.providers.openai.models block
  would surface a stale GPT pill. Updated to openai/gpt-5.4 to match.
* SettingsApp.tsx user-name poll: tick().finally() rescheduled even
  after userNameEditedRef was set, leaving the network requests firing
  every 5s with results discarded. Added the ref check to .finally()
  so polling actually stops.
* status.test.ts clawaiAccountTier-via-OpenAI test: mockGetConfigValue
  returned "pro" and the portal also returned deviceTier:"pro", so
  the test couldnt tell which source the route used. Local picker now
  returns "flash" (distinct), and we assert fetchSpy was called once
  to lock in that the portal must be consulted.

Test updates for the consolidated chat picker (PR's own behavior change):

* chat-model.test.ts "surfaces every model": ClawBox AI is now one row
  per provider, not one per model. Updated expected options to a
  single ClawBox AI entry with the active model on it; activeLabel
  drops the Pro/Flash suffix. Same shape change for "lists every
  configured cloud provider".
* chat-model.test.ts default OpenAI model expected as openai/gpt-5.4
  (was openai/gpt-5).
* provider-models.test.ts default OpenAI model expected as gpt-5.4.
* e2e/chat-popup.spec.ts: native selectOption() doesnt work on the new
  custom HeaderDropdown (it's button + popover, not <select>). Two
  tests updated to click the trigger by aria-label, then click the
  option by accessible name.

Hygiene:
* .scratch/ added to .gitignore — these are per-session debug scripts
  that shouldnt show up as untracked clutter or get accidentally
  committed by `git add -A`.
* status/route.ts:197-204 — primaryProviderHint comparison now flows
  through normalizeProvider so the deepseek/clawai alias collapses.
  Without this, a primary model of clawai/deepseek-v4-pro never
  matched a profile recorded under the wire-format `deepseek`
  provider, and we silently fell back to profileKeys[0]. Caught by
  CodeRabbit on the prior pass; missed in my first sweep.

* e2e/chat-popup.spec.ts — dropped the Escape sanity-check between
  opening the provider dropdown and picking an option. The check was
  specific to native <select> behavior (escape dismisses the browser
  dropdown but keeps the page state). With the custom HeaderDropdown
  popover, Escape both closes the popover AND can be handled higher
  up (chat popup itself), so the trigger button stops being available
  for the second click. The test now just exercises open + select,
  which is the user-visible path.

@yalexx yalexx left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Алл гоод

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants