Skip to content

fix(ai-provider): sweep sessions + show switch progress + green banner - #83

Merged
KrasimirKralev merged 2 commits into
ID-Robots:betafrom
KrasimirKralev:fix/configure-route-sweeps-sessions
Apr 19, 2026
Merged

fix(ai-provider): sweep sessions + show switch progress + green banner#83
KrasimirKralev merged 2 commits into
ID-Robots:betafrom
KrasimirKralev:fix/configure-route-sweeps-sessions

Conversation

@KrasimirKralev

@KrasimirKralev KrasimirKralev commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Changing the primary AI provider via Settings (e.g. OpenAI β†’ ClawBox AI) had three visible problems:

  1. Chat kept responding as the OLD provider. Configure route updated agents.defaults.model.primary but left existing sessions' per-session overrides pointing at the previous model. OpenClaw's resolver respected those sticky overrides on the next turn.
  2. Chat "froze" during the gateway restart β€” popup only had skill-install-aware retry + overlay logic, so a provider-change restart dropped the WS silently and user had to refresh.
  3. No affirmative feedback after the switch β€” dropdown already shows a green "Switched chat to X" banner when switching models from inside the chat pane (PR fix(chat): model-dropdown switches actually stickΒ #73); provider changes via Settings deserved the same.

Changes

  • configure/route.ts β€” after setting agents.defaults.model.primary, sweep per-session overrides via applyModelOverrideToAllAgentSessions with source: "user" so OpenClaw's resolver keeps the new primary bound across existing sessions. Mirrors PR fix(chat): model-dropdown switches actually stickΒ #73's sweep. Only runs when the call actually set a new primary (skips local-only scopes).
  • ChatPopup.tsx β€” listens for clawbox:primary-ai-configured (already dispatched by SettingsApp) alongside clawbox-skill-installed. Reuses the overlay + progress bar + quadrupled retry budget. Banner label adapts ("Switching AI provider..." vs "Reloading skills...") via a new reloadReason state + escape-hatch reloadReasonRef (one-time-captured WebSocket hello resolve closure can't see state changes). After reconnect on a provider change, auto-"my skills were updated" prompt is skipped and a green "Switched chat to <label>" banner is pushed using refreshed chat/model state.
  • chat/model/route.ts β€” PROVIDER_LABELS was missing "openai-codex", so labelForProvider fell back to the generic "AI Provider" placeholder, making the new banner read "Switched chat to AI Provider" when switching to or from openai-codex. Added "openai-codex": "OpenAI Codex".

/simplify pass

Converted the inline fetch().then().then() chain in ChatPopup's reload branch to async/await (matches the existing refreshChatModelState pattern in the same file + CLAUDE.md preference). Semantics unchanged.

Test plan

  • Jetson: switch OpenAI Codex β†’ ClawBox AI β†’ chat shows "Switching AI provider..." overlay β†’ reconnects β†’ green "Switched chat to ClawBox AI" banner β†’ next message answers from new provider (or falls back cleanly when the portal quota is hit)
  • Chat no longer needs a page refresh after provider change
  • No erroneous "my skills were just updated" auto-prompt
  • CodeRabbit review

Summary by CodeRabbit

  • New Features

    • Added OpenAI Codex provider support
    • Improved messaging when switching AI providers with confirmation feedback
  • Enhancements

    • Agent sessions now automatically synchronize when primary AI provider is changed
    • Enhanced user experience during AI provider switching with clearer status messaging

Changing the primary AI provider in Settings (e.g. OpenAI β†’ ClawBox AI)
had three user-visible problems:

1. Chat kept responding as the OLD provider after the switch.
   The configure route updated agents.defaults.model.primary but
   left every existing session's per-session override pointing at
   the previous model. OpenClaw's resolver respected those sticky
   overrides on the next turn, so the chat stayed bound to the old
   provider until the user cleared the session β€” or refreshed the
   page and noticed it still answered wrong.

2. Chat 'froze' during the gateway restart. The chat popup only
   had skill-install-aware retry logic, so a provider-change gateway
   restart dropped the WS and the user saw no progress; only the
   bare retry loop slowly reconnected.

3. No affirmative 'switched to X' feedback β€” when reconnect finally
   landed, the chat looked identical to before. The dropdown already
   shows a green 'Switched chat to X' banner when switching models
   from within the chat pane (PR ID-Robots#73); provider changes via Settings
   deserved the same.

Changes:

- src/app/setup-api/ai-models/configure/route.ts: after
  'agents.defaults.model.primary' is written, sweep per-session
  model overrides via applyModelOverrideToAllAgentSessions with
  source: 'user' so OpenClaw's resolver keeps the new primary
  bound across existing sessions. Mirrors the sweep in
  /setup-api/chat/model from PR ID-Robots#73. Only runs when this call
  actually set a new primary (skips local-only scopes).

- src/components/ChatPopup.tsx: listens for the existing
  clawbox:primary-ai-configured event (dispatched by SettingsApp
  after provider configure succeeds) alongside
  clawbox-skill-installed. Reuses the reloadingSkill overlay +
  progress bar + quadrupled retry budget β€” makes the gateway
  restart visible instead of letting the chat look frozen. Banner
  label adapts ('Switching AI provider...' vs 'Reloading skills...')
  via a new reloadReason state; a matching reloadReasonRef escapes
  the frozen closure captured by the one-time WebSocket hello
  resolve callback. After reconnect on a provider change, the
  auto-'my skills were just updated' prompt is skipped (no skill
  changed, nothing to confirm) and a green 'Switched chat to
  <label>' banner is pushed using the refreshed chat/model state.

- src/app/setup-api/chat/model/route.ts: PROVIDER_LABELS map was
  missing 'openai-codex', so labelForProvider fell back to the
  generic 'AI Provider' placeholder β€” which made the new banner
  read 'Switched chat to AI Provider' when switching to or from
  openai-codex. Added 'openai-codex: "OpenAI Codex"'.

Post-fix /simplify pass converted the inline fetch().then().then()
chain in ChatPopup's reload branch to async/await to match the
existing refreshChatModelState pattern in the same file. Semantics
unchanged.
@KrasimirKralev
KrasimirKralev requested a review from a team as a code owner April 18, 2026 23:51
@KrasimirKralev

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 18, 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 50 minutes and 29 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 50 minutes and 29 seconds.

βŒ› 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: ec111e46-bc07-4313-b430-ba178fa30b81

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between bbccfd9 and 1867c70.

πŸ“’ Files selected for processing (1)
  • src/components/ChatPopup.tsx
πŸ“ Walkthrough

Walkthrough

The changes implement AI model provider change synchronization across the system. When a primary AI model is configured, all existing agent sessions are updated with the new provider and model before the gateway restarts. The chat component independently tracks provider changes versus skill reloads and updates the UI accordingly.

Changes

Cohort / File(s) Summary
Backend Configuration & Session Sync
src/app/setup-api/ai-models/configure/route.ts
Added pre-restart sweep logic that parses the configured default model and invokes applyModelOverrideToAllAgentSessions to synchronize all active agent sessions with the new primary provider/model. Wrapped in non-fatal try/catch to allow configuration to proceed if sweep fails.
Provider Label Mapping
src/app/setup-api/chat/model/route.ts
Added "openai-codex" to PROVIDER_LABELS mapping with display label "OpenAI Codex".
Chat UI Provider Change Handling
src/components/ChatPopup.tsx
Added reloadReason state and ref to distinguish provider changes from skill reloads. Enhanced WebSocket completion flow to skip skill-update prompts on provider changes and instead fetch updated model state, displaying a green success message. Updated reload overlay text to show context-specific messaging.

Sequence Diagram

sequenceDiagram
    actor User
    participant ConfigAPI as Configure API
    participant SessionDB as Session Storage
    participant Gateway as Gateway
    participant ChatClient as Chat Client
    participant WebSocket as WebSocket

    User->>ConfigAPI: POST primary model change
    ConfigAPI->>SessionDB: applyModelOverrideToAllAgentSessions<br/>(new provider/model)
    alt Override Success
        SessionDB-->>ConfigAPI: sessions updated
    else Override Fails
        SessionDB-->>ConfigAPI: error (logged, non-fatal)
    end
    ConfigAPI->>Gateway: restart gateway
    Gateway-->>ChatClient: connection reset
    ChatClient->>WebSocket: reconnect
    WebSocket-->>ChatClient: hello/connect
    alt Provider Change Reload
        ChatClient->>ChatClient: fetch updated model state
        ChatClient->>ChatClient: append success message<br/>"Switched chat to <label>"
    else Skill Reload
        ChatClient->>ChatClient: auto-send skill update prompt
    end
    ChatClient->>User: show updated UI
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A provider hops in, the sessions align,
Gateway blinks bright with a new design.
"Switched chat to OpenAI!" the popup declares,
While whispers of models float through the air. 🌟

πŸš₯ Pre-merge checks | βœ… 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
βœ… Passed checks (2 passed)
Check name Status Explanation
Title check βœ… Passed The PR title accurately summarizes the three main changes: sweeping sessions, showing switch progress, and displaying a green banner after provider switch.
Description check βœ… Passed The PR description is comprehensive and covers all required template sections with detailed problem context, code changes, and test plan, though some checkboxes remain unchecked.

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

✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests

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

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

@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown
βœ… Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 1

Caution

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

⚠️ Outside diff range comments (2)
src/components/ChatPopup.tsx (2)

387-416: ⚠️ Potential issue | 🟠 Major

Don’t clear the transcript for provider-only reloads.

Line 391 runs before wasProviderChange is checked, so a Settings provider switch replaces the current visible chat with only the success banner. Keep the reset for skill changes only.

πŸ› Proposed fix
           if (skillInstalledRef.current) {
             const wasProviderChange = reloadReasonRef.current === 'provider'
             skillInstalledRef.current = false
             reloadReasonRef.current = 'skill' // reset for next reload
-            setMessages([])
-            greetedRef.current = true // prevent auto-greet
+            if (!wasProviderChange) {
+              setMessages([])
+              greetedRef.current = true // prevent auto-greet
+            }
             const evt = skillEventRef.current
             skillEventRef.current = null
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ChatPopup.tsx` around lines 387 - 416, The code clears the
chat transcript unconditionally (skillInstalledRef/reloadReasonRef logic) before
checking wasProviderChange, causing provider-only switches to wipe messages;
modify the flow in the block that handles skillInstalledRef.current so you only
call setMessages([]) and set greetedRef.current = true when
reloadReasonRef.current is not 'provider' (i.e., when handling actual skill
install/uninstall/enable/disable events), leaving the existing success banner
behavior for provider changes; ensure you still reset skillInstalledRef.current,
reloadReasonRef.current (to 'skill' only when you cleared messages), and clear
reloadTimerRef.current / setReloadProgress(100) / setReloadingSkill(false) as
currently done.

818-849: ⚠️ Potential issue | 🟠 Major

Make provider reload completion independent of a future WebSocket hello.

The Settings event is emitted after configuration, while the configure route restarts the gateway before returning. If the socket has already reconnected by the time this handler runs, this only starts the overlay and no later hello clears it.

πŸ› Proposed fix direction
-    const providerHandler = makeHandler('provider')
+    const providerReloadHandler = makeHandler('provider')
+    const providerHandler = (e: Event) => {
+      providerReloadHandler(e)
+      // Provider events can arrive after the gateway restart already completed;
+      // force a fresh handshake so the provider completion branch always runs.
+      void connect()
+    }
     window.addEventListener('clawbox-skill-installed', skillHandler)
     window.addEventListener('clawbox:primary-ai-configured', providerHandler)
     return () => {
       window.removeEventListener('clawbox-skill-installed', skillHandler)
       window.removeEventListener('clawbox:primary-ai-configured', providerHandler)
       if (reloadTimerRef.current) clearInterval(reloadTimerRef.current)
     }
-  }, [])
+  }, [connect])
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ChatPopup.tsx` around lines 818 - 849, The providerHandler
started via makeHandler('provider') can start the reload overlay even when the
gateway/socket has already reconnected, so change providerHandler to complete
the reload independently of waiting for a future WebSocket hello: after running
the existing makeHandler setup, immediately check the current connection state
(e.g. inspect your socket readyState or the app-level connected flag) and if
already connected, clear reloadTimerRef, setReloadProgress(100),
setReloadingSkill(false), reset retryCountRef and
skillInstalledRef/reloadReasonRef as needed; otherwise keep the existing
behavior but also subscribe to the connection/hello event to finalize the reload
(clear timer and setReloadProgress(100)/setReloadingSkill(false)) when that
event arrives. Use the existing symbols makeHandler, providerHandler,
skillHandler, reloadTimerRef, setReloadProgress, setReloadingSkill,
retryCountRef, skillInstalledRef and reloadReasonRef to locate and update the
logic.
πŸ€– Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ChatPopup.tsx`:
- Around line 421-425: The fetch response from '/setup-api/chat/model' should be
validated before casting and setting state; inside the try block where you call
fetch and parse JSON, check res.ok (and/or validate that parsed object matches
ChatModelState shape and has options array) and handle non-OK or malformed
responses by throwing or returning early (e.g., log error and do not call
setChatModelState). Update the logic around setChatModelState and the subsequent
label derivation (the const label = state.activeLabel ...) to run only when the
response is valid so that later uses like chatModelState.options.map cannot
crash. Ensure you reference the existing symbols:
fetch('/setup-api/chat/model'), setChatModelState, ChatModelState, and
chatModelState.options.map when applying the checks.

---

Outside diff comments:
In `@src/components/ChatPopup.tsx`:
- Around line 387-416: The code clears the chat transcript unconditionally
(skillInstalledRef/reloadReasonRef logic) before checking wasProviderChange,
causing provider-only switches to wipe messages; modify the flow in the block
that handles skillInstalledRef.current so you only call setMessages([]) and set
greetedRef.current = true when reloadReasonRef.current is not 'provider' (i.e.,
when handling actual skill install/uninstall/enable/disable events), leaving the
existing success banner behavior for provider changes; ensure you still reset
skillInstalledRef.current, reloadReasonRef.current (to 'skill' only when you
cleared messages), and clear reloadTimerRef.current / setReloadProgress(100) /
setReloadingSkill(false) as currently done.
- Around line 818-849: The providerHandler started via makeHandler('provider')
can start the reload overlay even when the gateway/socket has already
reconnected, so change providerHandler to complete the reload independently of
waiting for a future WebSocket hello: after running the existing makeHandler
setup, immediately check the current connection state (e.g. inspect your socket
readyState or the app-level connected flag) and if already connected, clear
reloadTimerRef, setReloadProgress(100), setReloadingSkill(false), reset
retryCountRef and skillInstalledRef/reloadReasonRef as needed; otherwise keep
the existing behavior but also subscribe to the connection/hello event to
finalize the reload (clear timer and
setReloadProgress(100)/setReloadingSkill(false)) when that event arrives. Use
the existing symbols makeHandler, providerHandler, skillHandler, reloadTimerRef,
setReloadProgress, setReloadingSkill, retryCountRef, skillInstalledRef and
reloadReasonRef to locate and update the logic.
πŸͺ„ 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: 8bed6678-47b6-490e-99b8-f1ef59b09869

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between bcfaf9a and bbccfd9.

πŸ“’ Files selected for processing (3)
  • src/app/setup-api/ai-models/configure/route.ts
  • src/app/setup-api/chat/model/route.ts
  • src/components/ChatPopup.tsx

Comment on lines +421 to +425
try {
const res = await fetch('/setup-api/chat/model', { cache: 'no-store' })
const state = await res.json() as ChatModelState
setChatModelState(state)
const label = state.activeLabel ?? state.primary?.label ?? 'the new AI provider'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate the chat-model response before committing it to state.

A non-OK JSON response like { error: ... } is currently cast to ChatModelState; the next render can crash when it reads chatModelState.options.map.

πŸ›‘οΈ Proposed fix
                 try {
                   const res = await fetch('/setup-api/chat/model', { cache: 'no-store' })
-                  const state = await res.json() as ChatModelState
+                  if (!res.ok) throw new Error('Failed to refresh chat model state')
+                  const state = await res.json() as ChatModelState
+                  if (!Array.isArray(state.options)) {
+                    throw new Error('Invalid chat model state response')
+                  }
                   setChatModelState(state)
                   const label = state.activeLabel ?? state.primary?.label ?? 'the new AI provider'
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const res = await fetch('/setup-api/chat/model', { cache: 'no-store' })
const state = await res.json() as ChatModelState
setChatModelState(state)
const label = state.activeLabel ?? state.primary?.label ?? 'the new AI provider'
try {
const res = await fetch('/setup-api/chat/model', { cache: 'no-store' })
if (!res.ok) throw new Error('Failed to refresh chat model state')
const state = await res.json() as ChatModelState
if (!Array.isArray(state.options)) {
throw new Error('Invalid chat model state response')
}
setChatModelState(state)
const label = state.activeLabel ?? state.primary?.label ?? 'the new AI provider'
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ChatPopup.tsx` around lines 421 - 425, The fetch response from
'/setup-api/chat/model' should be validated before casting and setting state;
inside the try block where you call fetch and parse JSON, check res.ok (and/or
validate that parsed object matches ChatModelState shape and has options array)
and handle non-OK or malformed responses by throwing or returning early (e.g.,
log error and do not call setChatModelState). Update the logic around
setChatModelState and the subsequent label derivation (the const label =
state.activeLabel ...) to run only when the response is valid so that later uses
like chatModelState.options.map cannot crash. Ensure you reference the existing
symbols: fetch('/setup-api/chat/model'), setChatModelState, ChatModelState, and
chatModelState.options.map when applying the checks.

…rce hello on provider change

Two Major findings on PR ID-Robots#83:

1. Provider switches were wiping the chat transcript. setMessages([])
   ran unconditionally in the skillInstalledRef branch before the
   wasProviderChange check, so a Settings provider change cleared
   the user's visible history. Skill installs still want the reset
   (new tools may change the assistant's behavior), but provider
   changes don't β€” the conversation semantics are unchanged.
   Guarded the reset + greetedRef bump behind !wasProviderChange.

2. Provider-reload overlay could get stuck on fast gateway restarts.
   The configure route restarts the gateway before returning its
   response, and the primary-ai-configured event fires only after
   that response lands. If the WS retry loop has already
   reconnected by then, no future hello triggers the reload branch
   and the overlay stays up indefinitely.

   The provider handler now, after seeding state:
   - closes any currently-open WebSocket (so the resolve-branch
     fires on a fresh connect rather than relying on a stale hello
     that missed skillInstalledRef=true)
   - resets retryCountRef and calls connect() directly

   This makes the completion independent of any in-flight hello's
   timing relative to when the event is dispatched.
@KrasimirKralev

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 19, 2026

Copy link
Copy Markdown
βœ… Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@KrasimirKralev
KrasimirKralev merged commit 4c8e4c5 into ID-Robots:beta Apr 19, 2026
4 of 5 checks passed
KrasimirKralev added a commit to KrasimirKralev/clawbox that referenced this pull request Apr 19, 2026
14 tests had been red on beta since before the Gemma/provider-switch
work in PRs ID-Robots#82/ID-Robots#83 landed. Three separate root causes:

1. Test mocks drifted from the configure route's real imports.
   The route now calls `applyModelOverrideToAllAgentSessions` and
   `parseFullyQualifiedModel` (added in ID-Robots#83 for session sweeping),
   plus four functions from `@/lib/llamacpp` and one from
   `@/lib/local-ai-runtime`. Tests mocked none of these, so the
   first request-time call to any of them threw and the route
   returned 500 β€” producing the "expected 200 to be 500" cascade.
   Added the missing mocks with real-shape implementations.

2. vitest-under-bun clears mock implementations along with call
   history in `vi.clearAllMocks()`. Factory defaults set inside
   `vi.mock(...)` survive `vi.resetModules` but not `mockClear`.
   Tests passed in isolation, failed in sequence. Re-apply the
   implementations in `beforeEach` so each test starts with a
   consistent mock surface.

3. The ai-models-step component test relied on `llamaCppIsActive`
   defaulting true, but the panel only sets it true when
   `currentProviderId === "llamacpp"`. Without that prop the
   "Gemma 4 is already configured" pill never rendered. Added the
   prop.

Extras during simplifier pass:
- Mirror real `parseFullyQualifiedModel` logic byte-for-byte
  (`idx <= 0 || idx === fq.length - 1`) so trailing-slash inputs
  reject as expected. Fixed drift in `chat-model.test.ts` too.
- Extract shared proxy-URL constant via `vi.hoisted` to avoid
  duplicating the magic string across two mock factories.

Result: 93/93 files, 1069/1069 tests pass β€” CI signal restored.
KrasimirKralev added a commit that referenced this pull request Apr 19, 2026
…90)

14 tests had been red on beta since before the Gemma/provider-switch
work in PRs #82/#83 landed. Three separate root causes:

1. Test mocks drifted from the configure route's real imports.
   The route now calls `applyModelOverrideToAllAgentSessions` and
   `parseFullyQualifiedModel` (added in #83 for session sweeping),
   plus four functions from `@/lib/llamacpp` and one from
   `@/lib/local-ai-runtime`. Tests mocked none of these, so the
   first request-time call to any of them threw and the route
   returned 500 β€” producing the "expected 200 to be 500" cascade.
   Added the missing mocks with real-shape implementations.

2. vitest-under-bun clears mock implementations along with call
   history in `vi.clearAllMocks()`. Factory defaults set inside
   `vi.mock(...)` survive `vi.resetModules` but not `mockClear`.
   Tests passed in isolation, failed in sequence. Re-apply the
   implementations in `beforeEach` so each test starts with a
   consistent mock surface.

3. The ai-models-step component test relied on `llamaCppIsActive`
   defaulting true, but the panel only sets it true when
   `currentProviderId === "llamacpp"`. Without that prop the
   "Gemma 4 is already configured" pill never rendered. Added the
   prop.

Extras during simplifier pass:
- Mirror real `parseFullyQualifiedModel` logic byte-for-byte
  (`idx <= 0 || idx === fq.length - 1`) so trailing-slash inputs
  reject as expected. Fixed drift in `chat-model.test.ts` too.
- Extract shared proxy-URL constant via `vi.hoisted` to avoid
  duplicating the magic string across two mock factories.

Result: 93/93 files, 1069/1069 tests pass β€” CI signal restored.
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.

1 participant