Skip to content

Add Cursor Agent engine + per-engine model picker#397

Open
hugobiais wants to merge 8 commits intobrowser-use:mainfrom
hugobiais:picker-and-model-listing
Open

Add Cursor Agent engine + per-engine model picker#397
hugobiais wants to merge 8 commits intobrowser-use:mainfrom
hugobiais:picker-and-model-listing

Conversation

@hugobiais
Copy link
Copy Markdown

@hugobiais hugobiais commented May 5, 2026

Summary

  • Adds a Cursor Agent engine adapter (binary agent) alongside Claude Code and Codex: login/auth, stream-json parsing, Connections-pane card, engine-picker logo.
  • Engine picker becomes two-step (Provider → Model); selection only commits once a model is picked.
  • Each adapter exposes listModels(); results cached 24h in engine-model-cache.json (userData) with in-flight de-dup.
  • Selected model flows through SpawnContext / RunEngineOptions so sessions honor the user's choice; per-engine selection persisted in localStorage.

Test plan

  • Cursor Agent appears in the engine picker; Connections card reflects install/auth; login flow opens; a sample task runs and streams output.
  • Pick a provider, then a model. Try different models for the same provider. Restart the app. Refresh the model list.

Risk + rollback

New cache file under userData (auto-rebuilt if deleted). The `model?` field on `SpawnContext` / `RunEngineOptions` is optional; adapters fall back to their CLI default when unset.

hugobiais added 7 commits May 5, 2026 09:34
Wraps `agent -p --output-format stream-json --stream-partial-output` so
sessions can be driven by Cursor's CLI alongside Claude Code and Codex.
Self-registers via the existing engine registry, so the EnginePicker and
engineLogin IPC pick it up with no UI changes.
- Engine adapters expose listModels(); a 24h on-disk cache
  (engine-model-cache.json) lives in userData with in-flight
  request de-duplication
- Picker dropdown gains a two-step Provider → Model navigation,
  per-engine selection persisted via localStorage, and selected
  model rendered as a subline under the engine name
- SpawnContext / RunEngineOptions carry an optional model id
  through to runEngine and the session DB so a chosen model is
  honored end-to-end
Filtering with txt.trim() drops chunks that are just spaces/newlines,
which can run adjacent words together when Cursor splits its partial
output across deltas. Only skip truly empty strings now, and keep
lastNarrative anchored to non-whitespace text.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 25 files

@hugobiais hugobiais changed the title Per-engine model picker + model-listing infra Add Cursor Agent engine + per-engine model picker May 5, 2026
The dropdown previously sized itself to the viewport, which made the
pill's small window clip it and wasted space in the hub. Both views
(provider + model) now share a deterministic 200px box; the inner list
scrolls when entries don't fit.

The picker auto-flips between opening up and opening down based on the
toggle's distance from the viewport top, and exports its menu height as
a constant so the pill can grow its window to exactly the room needed
when the menu is open. Adds the model count to the search placeholder
so the user knows how many models the catalog returned.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

5 issues found across 25 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/src/renderer/pill/pill.css">

<violation number="1" location="app/src/renderer/pill/pill.css:750">
P2: Removing the focus outline here leaves engine picker items without a visible keyboard focus indicator.</violation>
</file>

<file name="app/src/renderer/hub/EnginePicker.tsx">

<violation number="1" location="app/src/renderer/hub/EnginePicker.tsx:362">
P2: `labelMode="model"` hides the engine name but never renders the model label, so the picker toggle can end up with no visible text.</violation>
</file>

<file name="app/src/renderer/hub/TaskInput.tsx">

<violation number="1" location="app/src/renderer/hub/TaskInput.tsx:166">
P2: Selected models can be saved under the wrong engine because `onModelChange` uses the stale `engine` state instead of the engine being selected in the same interaction.</violation>
</file>

<file name="app/src/main/index.ts">

<violation number="1" location="app/src/main/index.ts:1135">
P2: Fallback model loading bypasses the TTL check and can return expired cached model lists whenever listModels() fails.</violation>
</file>

<file name="app/src/renderer/hub/ConnectionsPane.tsx">

<violation number="1" location="app/src/renderer/hub/ConnectionsPane.tsx:278">
P2: Cursor logout does not invalidate the 24h engine model cache, so stale `cursor-agent` model lists can survive sign-out and be reused by the next session.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment on lines +750 to +751
outline: none;
box-shadow: none;
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 5, 2026

Choose a reason for hiding this comment

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

P2: Removing the focus outline here leaves engine picker items without a visible keyboard focus indicator.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/renderer/pill/pill.css, line 750:

<comment>Removing the focus outline here leaves engine picker items without a visible keyboard focus indicator.</comment>

<file context>
@@ -701,7 +741,14 @@ html, body {
+
+.engine-picker__item-select:focus,
+.engine-picker__item-select:focus-visible {
+  outline: none;
+  box-shadow: none;
 }
</file context>
Suggested change
outline: none;
box-shadow: none;
outline: 2px solid var(--color-fg-primary);
outline-offset: 2px;
Fix with Cubic

Comment on lines +362 to +364
{!modelOnlyLabel && (
<span className="engine-picker__name">{currentEngine?.displayName ?? '…'}</span>
)}
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 5, 2026

Choose a reason for hiding this comment

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

P2: labelMode="model" hides the engine name but never renders the model label, so the picker toggle can end up with no visible text.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/renderer/hub/EnginePicker.tsx, line 362:

<comment>`labelMode="model"` hides the engine name but never renders the model label, so the picker toggle can end up with no visible text.</comment>

<file context>
@@ -145,38 +323,75 @@ export function EnginePicker({ value, onChange, onOpenChange }: EnginePickerProp
         {currentEngine && <EngineLogo id={currentEngine.id} />}
-        <span className="engine-picker__name">{currentEngine?.displayName ?? '…'}</span>
+        <span className="engine-picker__label">
+          {!modelOnlyLabel && (
+            <span className="engine-picker__name">{currentEngine?.displayName ?? '…'}</span>
+          )}
</file context>
Suggested change
{!modelOnlyLabel && (
<span className="engine-picker__name">{currentEngine?.displayName ?? '…'}</span>
)}
{modelOnlyLabel ? (
<span className="engine-picker__model">{currentModelLabel}</span>
) : (
<span className="engine-picker__name">{currentEngine?.displayName ?? '…'}</span>
)}
Fix with Cubic

}, []);

const onModelChange = useCallback((model: string | undefined) => {
setModelsByEngine((prev) => ({ ...prev, [engine]: model }));
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 5, 2026

Choose a reason for hiding this comment

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

P2: Selected models can be saved under the wrong engine because onModelChange uses the stale engine state instead of the engine being selected in the same interaction.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/renderer/hub/TaskInput.tsx, line 166:

<comment>Selected models can be saved under the wrong engine because `onModelChange` uses the stale `engine` state instead of the engine being selected in the same interaction.</comment>

<file context>
@@ -121,19 +147,26 @@ export const TaskInput = forwardRef<TaskInputHandle, TaskInputProps>(function Ta
   }, []);
 
+  const onModelChange = useCallback((model: string | undefined) => {
+    setModelsByEngine((prev) => ({ ...prev, [engine]: model }));
+    storeSelectedModel(engine, model);
+  }, [engine]);
</file context>
Fix with Cubic

Comment thread app/src/main/index.ts
}
const listed = await adapter.listModels();
if (listed.source === 'fallback' || listed.error) {
const stale = readEngineModelCache().entries[validated];
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 5, 2026

Choose a reason for hiding this comment

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

P2: Fallback model loading bypasses the TTL check and can return expired cached model lists whenever listModels() fails.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/index.ts, line 1135:

<comment>Fallback model loading bypasses the TTL check and can return expired cached model lists whenever listModels() fails.</comment>

<file context>
@@ -1025,6 +1111,43 @@ app.whenReady().then(async () => {
+      }
+      const listed = await adapter.listModels();
+      if (listed.source === 'fallback' || listed.error) {
+        const stale = readEngineModelCache().entries[validated];
+        if (stale && !forceRefresh) {
+          return { ...stale, cached: true, error: listed.error };
</file context>
Fix with Cubic

@@ -3,6 +3,7 @@ import anthropicLogo from './anthropic-logo.svg';
import claudeCodeLogo from './claude-code-logo.svg';
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 5, 2026

Choose a reason for hiding this comment

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

P2: Cursor logout does not invalidate the 24h engine model cache, so stale cursor-agent model lists can survive sign-out and be reused by the next session.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/renderer/hub/ConnectionsPane.tsx, line 278:

<comment>Cursor logout does not invalidate the 24h engine model cache, so stale `cursor-agent` model lists can survive sign-out and be reused by the next session.</comment>

<file context>
@@ -213,6 +240,46 @@ export function ConnectionsPane({ embedded }: ConnectionsPaneProps): React.React
+  const handleCursorLogout = useCallback(async () => {
+    const api = window.electronAPI;
+    if (!api?.settings?.cursor?.logout) return;
+    const res = await api.settings.cursor.logout();
+    if (!res.opened) console.warn('[connections] cursor logout failed', res.error);
+    await refreshCursor();
</file context>
Fix with Cubic

@Cheggin
Copy link
Copy Markdown
Collaborator

Cheggin commented May 5, 2026

will take a look at this in a bit!

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