feat(agent): Cursor as a runnable second agent — runtime, permission hooks, context + MCP bridge (Agent Adapter steps 2–5)#57
Conversation
…ure step 1
Adds Cursor as a second, authentication-only provider beside Claude Code
(build-order item 1 of the Agent Adapter Architecture; no run capability
yet — AGENT_MODELS deliberately has no Cursor entries).
Main process:
- CursorAuthManager: lazy, fully local auth classification (not-installed /
not-authenticated / authenticated-cli / authenticated-api-key) honoring
settings.agent.cursor.preferredAuth; single-flight cursor-agent login
child with manual-browser mode (NO_OPEN_BROWSER=1) and a Cursor-host
allowlist on the captured login URL; logout; API key set/remove;
getSpawnEnv() decrypt-at-spawn seam for the future runtime adapter.
- exec.ts: argv-only cursor-agent runner (never shell:true), Windows
where.exe + ComSpec-bridged .cmd shims gated by a static-literal arg
whitelist, ~/.local/bin/{cursor-agent,agent} install-dir fallback,
bounded output, redactCursor().
- SecretStore: safeStorage-encrypted secrets under userData/secrets/
(atomic 0o600 writes, names-only logging, presence-only metadata).
- 7 validated agent:cursor* IPC channels via the handle() wrapper; the API
key crosses IPC exactly once and is never returned, logged, or echoed.
- Logger + AgentManager redaction for crsr_ tokens and CURSOR_API_KEY.
Renderer:
- CursorProviderCard in Settings › Agent › Providers: CLI sign-in
(incl. manual URL copy/open), encrypted API key, preferredAuth
segmented control, per-state actions.
- Shared ProviderStatusRow + icon-and-label status pills (lucide icons via
lifecycleMeta/cursorStatusMeta) used by both Claude and Cursor rows;
shared ActionButton in settings controls; provider-neutral
Connection & reliability copy; CursorMark glyph; catalog search fields.
- useAgentStore cursorAuth state + actions over window.limboo.agent.cursor.
Settings: agent.cursor { preferredAuth, manualBrowserLogin },
SETTINGS_VERSION 13, CURSOR_LIMITS bounds. Docs updated (CLAUDE.md §3/§6/§8,
reference + architecture + guides).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… Agent Adapter steps 2–5 Turn the Cursor provider from auth-only into a fully runnable second agent behind the existing provider-neutral AgentEvent seam — zero renderer event model changes; the UI never knows which agent is running. Runtime (step 2) - CursorRuntime: argv-only `cursor-agent --print --output-format stream-json --stream-partial-output --workspace <sessionRoot>`; prompt rides stdin; process-tree kill on stop; chat id pre-bound via `create-chat` and stored provider-keyed in agent_provider_sessions (schema v12), replayed as --resume. - stream.ts bounded NDJSON reader; translate.ts maps the Cursor tool union to Claude-shaped tool names and implements the documented timestamp_ms / model_call_id delta disambiguation; errors.ts classifies failures incl. resume-corruption self-heal (forget token, retry fresh). - Native-Windows CLI layout resolver in exec.ts (node.exe + index.js under %LOCALAPPDATA%\cursor-agent, regex-gated version dirs, realpath-contained) fixes the stale "Install CLI" status when the CLI is installed; executablePath override stays fail-closed; .cmd shims still refused for runs. Permissions (step 3) - Deny-first session .cursor/cli.json wraps every run; capability-gated hooks.json registers the bundled hookRunner.cjs (fail-closed, deny on any bridge failure) so preToolUse/beforeShellExecution/beforeReadFile/afterFileEdit flow into the shared decideToolUse core — one permission implementation for both Claude's canUseTool and Cursor's hooks. Hooks only ever tighten. Context injection (step 4) - Memory/search/resume blocks move into a per-run generated .cursor/rules/limboo-context.mdc (alwaysApply), restored byte-for-byte after the run; prompt-prepend stays as the pre-spawn fallback. MCP reuse (step 5) - Session .cursor/mcp.json (defensively merged over repo-authored servers) points limboo_memory/limboo_search at mcpBridge.cjs, forwarding over a token-authenticated per-run local pipe (pipeServer.ts) to transport-neutral plain tools shared with the Claude SDK servers — one index, one DB process. Also - Dynamic model discovery (`cursor-agent models`, TTL-cached, persisted, registered in both processes), sandbox toggle, single-flight CLI self-update, Settings › Agent Troubleshooting section with live resolution diagnostics. - security(SecretStore): fd-based atomic secret writes — tmp opened 'wx' with mode 0o600 so restrictive permissions apply at open(2), stale tmp removed first (O_TRUNC reuse would keep old modes), fsync before rename, secrets dir forced 0o700. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 33 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThis PR adds a Cursor CLI provider adapter alongside the existing Claude Code provider. It introduces encrypted secret storage, Cursor authentication/execution/runtime, session-scoped permission/context/MCP bridging over a local IPC pipe, provider-session persistence, IPC/renderer wiring, and settings UI, plus supporting documentation. ChangesCursor Provider Adapter
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Summary
This PR implements a comprehensive Cursor provider integration (Agent Adapter steps 2-5) with solid security practices throughout. The architecture demonstrates careful attention to security with argv-only spawning, secret handling via environment variables, input validation, and atomic file operations.
Critical Finding
1 blocking issue identified:
- Logic Error in
versionKey: The function will crash on malformed version directory names when attempting to callpadStarton undefined. This prevents the Windows native installer layout resolution from working correctly.
Security Strengths
The implementation demonstrates excellent security practices:
- SecretStore uses atomic writes with restrictive permissions from creation (
wx+0o600) - Runtime execution properly isolates secrets in environment variables, never argv
- Comprehensive input validation with regex gates (MODEL_ID_RE, RESUME_ID_RE, VERSION_DIR_RE)
- Bounded stream processing with size limits
- Session file containment checks and atomic operations
- Proper SQL usage with prepared statements throughout
Recommendation
Address the identified crash risk in versionKey before merge. The security model is well-designed and consistently applied across the codebase.
You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.
| function versionKey(name: string): number { | ||
| const [y, m, d] = name.split('-')[0].split('.'); | ||
| const key = Number(`${y}${m.padStart(2, '0')}${d.padStart(2, '0')}`); | ||
| return Number.isFinite(key) ? key : 0; | ||
| } |
There was a problem hiding this comment.
🛑 Logic Error: The versionKey function will crash if the version directory name doesn't contain periods. When name.split('-')[0].split('.') executes on a malformed name, it could return an array with fewer than 3 elements, causing d to be undefined. Attempting d.padStart(2, '0') on undefined will throw a TypeError, crashing the executable resolution.
Add defensive checks to prevent runtime crashes from malformed version directory names.
| function versionKey(name: string): number { | |
| const [y, m, d] = name.split('-')[0].split('.'); | |
| const key = Number(`${y}${m.padStart(2, '0')}${d.padStart(2, '0')}`); | |
| return Number.isFinite(key) ? key : 0; | |
| } | |
| function versionKey(name: string): number { | |
| const parts = name.split('-')[0].split('.'); | |
| if (parts.length < 3) return 0; | |
| const [y, m, d] = parts; | |
| const key = Number(`${y}${m.padStart(2, '0')}${d.padStart(2, '0')}`); | |
| return Number.isFinite(key) ? key : 0; | |
| } |
…ol names; harden versionKey Resume revalidation (three distinct bugs behind the "revalidate timeout" log spam): - The losing Promise.race timer was never cleared, so every SUCCESSFUL revalidation still rejected an un-awaited promise 10s later — the bare "Error: revalidate timeout" stacks came from the unhandled-rejection handler. The timer is now cleared when the race settles. - Real timeouts: enrichment (a bounded reindex + one `git show` per changed file, up to 30) ran un-budgeted inside the same deadline. enrichDelta is now deadline-cooperative — it yields between stages and per file, keeping the partial enrichment instead of losing the whole delta. The overall deadline rises 10s → 30s for cold-cache git spawns on large Windows repos. - Zombie state writes: Promise.race cannot cancel the losing branch, so a timed-out revalidateInner kept running and could overwrite the degraded 'idle' phase later. A per-session revalidation generation now invalidates the stale run — it stops at its next checkpoint and its writes are dropped. ResumeDeltaDialog: long symbol names in the "Symbol changes" section rendered as non-breaking inline spans and overflowed/overlapped the card. They now sit in a wrapping flex row with per-name break-all — same tokens, sizes, and palette as before. cursor/exec.ts: versionKey no longer assumes VERSION_DIR_RE-shaped input — a malformed version dir name (fewer than 3 dot parts) threw undefined.padStart, which resolveNodeLayout's catch swallowed, silently aborting the whole native layout resolution (an "Install CLI" regression vector). Junk now sorts to 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/renderer/features/workspace/Composer.tsx (1)
280-294: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winTextarea/mode-switch/controls still gate on Claude-only
installed, not the newconnected.The
blocked, placeholder, andStatusHintlogic were updated to use provider-awareconnected(Line 110-112), but the textarea (disabled={disabled || !installed}, Line 284),ComposerModeSwitch(Line 357), andComposerControls(Line 359) were left on the old Claude-onlyinstalledflag. For a Cursor-model session where Claude Code isn't installed, these controls stay disabled even though the session is otherwiseconnected— breaking composer input for Cursor-only setups, which this PR is meant to support.🐛 Proposed fix
<textarea ref={ref} rows={1} value={value} - disabled={disabled || !installed} + disabled={disabled || !connected}- <ComposerModeSwitch mode={mode} onChange={setMode} disabled={disabled || !installed} /> + <ComposerModeSwitch mode={mode} onChange={setMode} disabled={disabled || !connected} /> <span className="hidden h-3.5 w-px shrink-0 bg-line sm:block" /> - <ComposerControls disabled={disabled || !installed} /> + <ComposerControls disabled={disabled || !connected} />Also applies to: 356-359
🤖 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/renderer/features/workspace/Composer.tsx` around lines 280 - 294, The composer still uses the old Claude-only installed check for the textarea, mode switch, and controls, so Cursor sessions can remain blocked even when provider-aware connected is true. Update the disabled/gating logic in Composer.tsx for the textarea, ComposerModeSwitch, and ComposerControls to rely on connected instead of installed, and keep the existing blocked/placeholder/StatusHint behavior aligned with the same session state.src/renderer/features/workspace/ComposerBanner.tsx (1)
49-86: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse the provider that set the banner state
agentName/isCursorare read from the current settings model, butlifecycleis global. If the model changes after arate-limitedorauth-requiredtransition, this banner can show the wrong provider name and retry hint. Persist the provider with the lifecycle state, or derive the copy from the event that set it.🤖 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/renderer/features/workspace/ComposerBanner.tsx` around lines 49 - 86, The banner copy in ComposerBanner is using the current settings model via agentDisplayName(model) and providerForModel(model), but lifecycle is not tied to that model, so the wrong provider name or auth hint can be shown after a settings change. Update the state source that drives lifecycle to also persist the provider/model that triggered it, then have the rate-limited and auth-required branches use that stored provider information instead of the live settings-derived agentName and isCursor values.
🧹 Nitpick comments (6)
src/main/managers/cursor/translate.ts (1)
82-86: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winConsider constraining the
mcp__<server>__<tool>identity components.
server/toolare taken verbatim from wireargswith only a string-type check, then interpolated into the composite tool identity string used for risk-gating/permission decisions elsewhere. If either value ever contains__or other delimiter-breaking characters, the composite name becomes ambiguous to any downstream logic that parses it back into server/tool parts.♻️ Proposed defensive sanitization
if (key === 'mcpToolCall') { const server = strField(args, 'server') ?? strField(args, 'serverName') ?? 'server'; const tool = strField(args, 'tool') ?? strField(args, 'toolName') ?? 'tool'; - return { callId, name: `mcp__${server}__${tool}`, input: { ...args } }; + const safeServer = /^[\w.-]+$/.test(server) ? server : 'server'; + const safeTool = /^[\w.-]+$/.test(tool) ? tool : 'tool'; + return { callId, name: `mcp__${safeServer}__${safeTool}`, input: { ...args } }; }As per path instructions for
src/main/**/*.ts: "Preserve deny-by-default permission handling, navigation lockdown, and other Electron hardening measures."🤖 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/main/managers/cursor/translate.ts` around lines 82 - 86, The mcpToolCall translation currently builds the mcp__<server>__<tool> identity directly from untrusted args, which can make downstream parsing/permission checks ambiguous. Update translate.ts in the mcpToolCall branch to normalize or sanitize the server and tool values before composing the name, preventing delimiter-breaking characters like __ from being preserved. Keep the deny-by-default gating behavior intact by ensuring the resulting identifier remains stable and unambiguous for any logic that consumes it.Source: Path instructions
src/renderer/features/workspace/Composer.tsx (1)
403-421: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMinor:
StatusHint'sinstalledprop now actually carriesconnected.Renaming the prop to
connected(matching the caller) would avoid confusion since the value passed in is no longer Claude's raw install state.🤖 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/renderer/features/workspace/Composer.tsx` around lines 403 - 421, The StatusHint component is using an `installed` prop even though the caller now passes connection state, which is confusing. Rename the prop and its type in `StatusHint` to `connected`, then update the internal `lifecycleMeta` call and any related checks to use the new name so it matches the caller and the actual meaning of the value.docs/reference/settings.md (1)
67-72: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDocument
executablePathanddiscoveredModelsin theagent.cursorsection.The main process reads
agent.cursor.executablePathandagent.cursor.discoveredModelsfrom settings (lines 222–223 insrc/main/index.ts), but neither field is documented here. Users who manually editsettings.jsonor reference the docs won't know these fields exist.📖 Suggested addition
`agent.cursor` (Cursor provider — authentication only): `preferredAuth` `auto` (one of `auto` / `api-key` / `cli-login`), `manualBrowserLogin` `false` (print the login URL instead of auto-opening a browser). **No secrets live here** — the optional Cursor API key is safeStorage-encrypted in a main-only file under `userData/secrets/`, never in `settings.json`. Bounds in `CURSOR_LIMITS`. + +`agent.cursor` also includes `executablePath` `''` (explicit override for the +`cursor-agent` binary; blank = auto-resolve via PATH/where/install-dir) and +`discoveredModels` `[]` (model ids from `cursor-agent models`, persisted for +pre-probe routing at boot).🤖 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 `@docs/reference/settings.md` around lines 67 - 72, The agent.cursor settings docs are missing two fields that the main process already reads: executablePath and discoveredModels. Update the agent.cursor section in settings documentation to mention both settings, using the same naming as in src/main/index.ts, and briefly describe their purpose/expected type so users editing settings.json can discover them.docs/reference/window-limboo-api.md (1)
93-99: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd
cursor.updateCli()and clarify the "no run capability yet" phrasing.Two gaps in the API documentation:
cursor.updateCli()is exposed in the preload (line 313–314) and has a corresponding IPC handler, but is missing from this list.- "auth only; no run capability yet" is technically accurate for the
cursor.*surface (runs go throughagent.send()), but is misleading in a PR titled "Cursor as a runnable second agent." Consider clarifying that the run capability is accessed through the existingagent.send()API, notcursor.*.📖 Suggested revision
-- `cursor.*` — Cursor provider authentication (auth only; no run capability yet). - Capability-based: no method ever returns a credential; the API key crosses IPC - exactly once via `setApiKey` and is safeStorage-encrypted in the main process. - - `cursor.getAuthState() -> CursorAuthState`, `cursor.refreshAuth() -> CursorAuthState` - - `cursor.loginStart(manual?)`, `cursor.loginCancel()`, `cursor.logout()` - - `cursor.setApiKey(key)`, `cursor.removeApiKey()` - - `cursor.onAuthChanged(cb)` — subscription. +- `cursor.*` — Cursor provider authentication and CLI maintenance (the run + capability is accessed through `agent.send()`, not `cursor.*`). Capability-based: + no method ever returns a credential; the API key crosses IPC exactly once via + `setApiKey` and is safeStorage-encrypted in the main process. + - `cursor.getAuthState() -> CursorAuthState`, `cursor.refreshAuth() -> CursorAuthState` + - `cursor.loginStart(manual?)`, `cursor.loginCancel()`, `cursor.logout()` + - `cursor.setApiKey(key)`, `cursor.removeApiKey()` + - `cursor.updateCli() -> CursorUpdateResult` (refused while an agent run is active) + - `cursor.onAuthChanged(cb)` — subscription.🤖 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 `@docs/reference/window-limboo-api.md` around lines 93 - 99, The cursor API docs are missing the exposed `cursor.updateCli()` method and the current “auth only; no run capability yet” wording is confusing. Update the `cursor.*` list in the docs to include `cursor.updateCli()`, and revise the capability note to say that cursor auth/config methods live on `cursor.*` while run capability is available through `agent.send()` rather than `cursor.*`. Use the existing `cursor.*` section and `agent.send()` reference to keep the wording aligned with the preload/IPС surface.src/main/managers/cursor/bridge/hookRunner.cjs (1)
19-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd an ESLint override for
.cjsbridge scripts to unblock lint.
@typescript-eslint/no-var-requiresfires here, but this standalone asset must userequire(it runs viaELECTRON_RUN_AS_NODEoutside the bundle and cannot use ESMimport). Add a targeted override so CI lint doesn't fail on the intentional CommonJS.// eslint config override { "files": ["**/bridge/*.cjs"], "rules": { "`@typescript-eslint/no-var-requires`": "off" } }🤖 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/main/managers/cursor/bridge/hookRunner.cjs` at line 19, The CommonJS bridge script hookRunner.cjs intentionally uses require, but lint is still flagging it under `@typescript-eslint/no-var-requires`. Add a targeted ESLint override for the bridge .cjs assets (for example, files matching **/bridge/*.cjs) and disable that rule there so the standalone Electron bridge scripts can keep using CommonJS without failing CI.Source: Linters/SAST tools
src/main/managers/cursor/bridge/pipeServer.ts (1)
71-116: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueConsider an idle/auth deadline for unauthenticated sockets.
A connection that connects but never sends the token line stays open and consumes a slot toward
CURSOR_LIMITS.bridgeMaxConnectionsfor the whole run. The endpoint is filesystem-gated (0700 dir / named pipe), so exposure is limited to local processes, but a short first-line/auth timeout would harden the bridge against a stuck or hostile local peer exhausting connection slots.🤖 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/main/managers/cursor/bridge/pipeServer.ts` around lines 71 - 116, The unauthenticated socket path in pipeServer.ts can hold a connection slot indefinitely if the client never sends the first token line. Add a short first-line/auth timeout in the net.createServer socket setup so unauthenticated connections are destroyed if they do not authenticate promptly, and clear that timeout once authed becomes true. Use the existing socket handling around sockets.add, socket.on('data'), and the authentication check with parseLine/timingSafeEqual to place the deadline cleanly.
🤖 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 `@CLAUDE.md`:
- Around line 727-729: Update the Cursor status text in the BUILT Authentication
section of CLAUDE.md so it no longer says Cursor is auth-only or cannot run yet.
Replace the stale wording in the Cursor auth layer note with language that
reflects the new runnable adapter work from this PR, keeping the rest of the
build-order item context intact.
In `@docs/agents/cursor-integration.txt`:
- Around line 51-67: The authentication section in cursor-integration.txt should
use the correct CLI name and avoid overstating the key format. Update the
wording around the login flow in the Cursor agent runtime description to refer
to cursor-agent login instead of agent login, and soften the API-key description
so crsr_ is presented as a recognizable/lenient prefix rather than a strict
requirement for exactly 64 hexadecimal characters. Keep the rest of the auth
guidance in this section consistent with the existing Cursor authentication
terms and symbols.
In `@docs/reference/settings.md`:
- Line 13: The documentation is stale because the referenced SETTINGS_VERSION
value no longer matches the source of truth. Update the SETTINGS_VERSION mention
in the settings reference docs to 15 so it stays aligned with
src/shared/constants.ts and any other references to SETTINGS_VERSION in the
docs.
In `@src/main/managers/AgentManager.ts`:
- Around line 874-889: `retryAuth()` currently only calls `probeHealth(true)`,
which does not force a fresh Cursor auth probe, so the Composer “Re-check
sign-in” action can remain a no-op for Cursor sessions. Update `retryAuth()` in
`AgentManager` to branch on the active provider and explicitly call the Cursor
auth refresh path (same behavior as `runWithRecovery` uses for cursor, e.g.
forcing `cursorAuth.probe(true)` or the equivalent cursor recheck method) before
re-running health checks. Also make the `diag('auth', ...)` message
provider-aware instead of always saying “Claude Code authentication.”
In `@src/main/managers/cursor/bridge/hookRunner.cjs`:
- Around line 110-111: The response written in hookRunner.cjs may be lost
because process.exit() is called immediately after process.stdout.write() in the
decision path. Update the logic in the hook runner around the stdout write so it
waits for the write to drain before terminating, using the write callback or
process.exitCode instead of an immediate exit for the allow case. Keep the deny
path failing closed with exit 2, and make sure the JSON decision from the hook
runner is fully flushed before the process ends.
In `@src/main/managers/cursor/CursorAuthManager.ts`:
- Around line 270-281: The single-flight guard in loginStart is still vulnerable
because this.loginChild is only set after await spawnCursorLogin, allowing
concurrent calls to spawn multiple login children. Add a synchronous in-flight
guard in CursorAuthManager.loginStart before the await (for example a dedicated
loginStarting flag or similar state) and clear it in a finally block so only one
login attempt can proceed at a time.
In `@src/main/managers/cursor/permissions.ts`:
- Around line 38-47: The deny list in the cursor permissions rules only blocks
the exact Shell(rm) token, so wrapper or path-qualified forms can still slip
through. Update the enforcement in the hooks/decideToolUse path so destructive
shell detection normalizes or inspects the full command invocation (including
wrappers like bash -c and absolute paths like /bin/rm) and denies those cases
there, using the existing permissions logic in cursor/permissions.ts as the
locator.
In `@src/renderer/stores/useAgentStore.ts`:
- Around line 354-363: The initial Cursor auth probe in useAgentStore is missing
rejection handling, so a failed getAuthState call can leave cursorAuth stuck
null and block CursorProviderCard actions. Update the intakeCursorAuth flow in
useAgentStore to attach error handling to api.cursor?.getAuthState?.() and set a
safe fallback auth state (or otherwise surface the failure) so the UI can
recover instead of remaining on “Checking the Cursor CLI…”. Keep the existing
registerCursorModels and set({ cursorAuth }) behavior for successful responses,
and make sure the live onAuthChanged listener still feeds the same path.
---
Outside diff comments:
In `@src/renderer/features/workspace/Composer.tsx`:
- Around line 280-294: The composer still uses the old Claude-only installed
check for the textarea, mode switch, and controls, so Cursor sessions can remain
blocked even when provider-aware connected is true. Update the disabled/gating
logic in Composer.tsx for the textarea, ComposerModeSwitch, and ComposerControls
to rely on connected instead of installed, and keep the existing
blocked/placeholder/StatusHint behavior aligned with the same session state.
In `@src/renderer/features/workspace/ComposerBanner.tsx`:
- Around line 49-86: The banner copy in ComposerBanner is using the current
settings model via agentDisplayName(model) and providerForModel(model), but
lifecycle is not tied to that model, so the wrong provider name or auth hint can
be shown after a settings change. Update the state source that drives lifecycle
to also persist the provider/model that triggered it, then have the rate-limited
and auth-required branches use that stored provider information instead of the
live settings-derived agentName and isCursor values.
---
Nitpick comments:
In `@docs/reference/settings.md`:
- Around line 67-72: The agent.cursor settings docs are missing two fields that
the main process already reads: executablePath and discoveredModels. Update the
agent.cursor section in settings documentation to mention both settings, using
the same naming as in src/main/index.ts, and briefly describe their
purpose/expected type so users editing settings.json can discover them.
In `@docs/reference/window-limboo-api.md`:
- Around line 93-99: The cursor API docs are missing the exposed
`cursor.updateCli()` method and the current “auth only; no run capability yet”
wording is confusing. Update the `cursor.*` list in the docs to include
`cursor.updateCli()`, and revise the capability note to say that cursor
auth/config methods live on `cursor.*` while run capability is available through
`agent.send()` rather than `cursor.*`. Use the existing `cursor.*` section and
`agent.send()` reference to keep the wording aligned with the preload/IPС
surface.
In `@src/main/managers/cursor/bridge/hookRunner.cjs`:
- Line 19: The CommonJS bridge script hookRunner.cjs intentionally uses require,
but lint is still flagging it under `@typescript-eslint/no-var-requires`. Add a
targeted ESLint override for the bridge .cjs assets (for example, files matching
**/bridge/*.cjs) and disable that rule there so the standalone Electron bridge
scripts can keep using CommonJS without failing CI.
In `@src/main/managers/cursor/bridge/pipeServer.ts`:
- Around line 71-116: The unauthenticated socket path in pipeServer.ts can hold
a connection slot indefinitely if the client never sends the first token line.
Add a short first-line/auth timeout in the net.createServer socket setup so
unauthenticated connections are destroyed if they do not authenticate promptly,
and clear that timeout once authed becomes true. Use the existing socket
handling around sockets.add, socket.on('data'), and the authentication check
with parseLine/timingSafeEqual to place the deadline cleanly.
In `@src/main/managers/cursor/translate.ts`:
- Around line 82-86: The mcpToolCall translation currently builds the
mcp__<server>__<tool> identity directly from untrusted args, which can make
downstream parsing/permission checks ambiguous. Update translate.ts in the
mcpToolCall branch to normalize or sanitize the server and tool values before
composing the name, preventing delimiter-breaking characters like __ from being
preserved. Keep the deny-by-default gating behavior intact by ensuring the
resulting identifier remains stable and unambiguous for any logic that consumes
it.
In `@src/renderer/features/workspace/Composer.tsx`:
- Around line 403-421: The StatusHint component is using an `installed` prop
even though the caller now passes connection state, which is confusing. Rename
the prop and its type in `StatusHint` to `connected`, then update the internal
`lifecycleMeta` call and any related checks to use the new name so it matches
the caller and the actual meaning of the value.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 46c99b54-4e18-4489-a3b9-7108b2db2de8
📒 Files selected for processing (62)
CLAUDE.mddocs/agents/cursor-integration.txtdocs/architecture/security-model.mddocs/architecture/subsystems/agent-manager.mddocs/getting-started/configuration.mddocs/guides/using-the-agent.mddocs/reference/ipc-channels.mddocs/reference/settings.mddocs/reference/window-limboo-api.mdforge.config.tssrc/main/db/database.tssrc/main/index.tssrc/main/ipc/cursorHandlers.tssrc/main/ipc/index.tssrc/main/logger.tssrc/main/managers/AgentManager.tssrc/main/managers/SessionManager.tssrc/main/managers/SettingsManager.tssrc/main/managers/cursor/CursorAuthManager.tssrc/main/managers/cursor/CursorRuntime.tssrc/main/managers/cursor/bridge/bridgeAssets.tssrc/main/managers/cursor/bridge/hookRunner.cjssrc/main/managers/cursor/bridge/mcpBridge.cjssrc/main/managers/cursor/bridge/pipeServer.tssrc/main/managers/cursor/bridge/plainTool.tssrc/main/managers/cursor/bridge/toolDispatch.tssrc/main/managers/cursor/errors.tssrc/main/managers/cursor/exec.tssrc/main/managers/cursor/hooks.tssrc/main/managers/cursor/mcpConfig.tssrc/main/managers/cursor/permissions.tssrc/main/managers/cursor/rules.tssrc/main/managers/cursor/sessionFile.tssrc/main/managers/cursor/stream.tssrc/main/managers/cursor/translate.tssrc/main/managers/cursor/types.tssrc/main/managers/memory/memoryTools.tssrc/main/managers/search/searchTools.tssrc/main/secrets/SecretStore.tssrc/preload/index.tssrc/renderer/components/brand/ProviderIcon.tsxsrc/renderer/features/activity/AgentConsolePanel.tsxsrc/renderer/features/activity/PlanPanel.tsxsrc/renderer/features/agent/models.tssrc/renderer/features/agent/status.tssrc/renderer/features/settings/catalog.tsxsrc/renderer/features/settings/controls.tsxsrc/renderer/features/settings/panels/AgentPanel.tsxsrc/renderer/features/settings/panels/AgentTroubleshooting.tsxsrc/renderer/features/settings/panels/AttachmentsPanel.tsxsrc/renderer/features/settings/panels/CursorProviderCard.tsxsrc/renderer/features/settings/panels/ProviderCard.tsxsrc/renderer/features/workspace/CenterWorkspace.tsxsrc/renderer/features/workspace/Composer.tsxsrc/renderer/features/workspace/ComposerBanner.tsxsrc/renderer/features/workspace/ComposerControls.tsxsrc/renderer/stores/useAgentStore.tssrc/renderer/stores/useSettingsStore.tssrc/shared/constants.tssrc/shared/ipc-channels.tssrc/shared/types.tsvite.main.config.ts
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/renderer/features/workspace/Composer.tsx (1)
280-294: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winTextarea/mode-switch/controls still gate on Claude-only
installed, not the newconnected.The
blocked, placeholder, andStatusHintlogic were updated to use provider-awareconnected(Line 110-112), but the textarea (disabled={disabled || !installed}, Line 284),ComposerModeSwitch(Line 357), andComposerControls(Line 359) were left on the old Claude-onlyinstalledflag. For a Cursor-model session where Claude Code isn't installed, these controls stay disabled even though the session is otherwiseconnected— breaking composer input for Cursor-only setups, which this PR is meant to support.🐛 Proposed fix
<textarea ref={ref} rows={1} value={value} - disabled={disabled || !installed} + disabled={disabled || !connected}- <ComposerModeSwitch mode={mode} onChange={setMode} disabled={disabled || !installed} /> + <ComposerModeSwitch mode={mode} onChange={setMode} disabled={disabled || !connected} /> <span className="hidden h-3.5 w-px shrink-0 bg-line sm:block" /> - <ComposerControls disabled={disabled || !installed} /> + <ComposerControls disabled={disabled || !connected} />Also applies to: 356-359
🤖 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/renderer/features/workspace/Composer.tsx` around lines 280 - 294, The composer still uses the old Claude-only installed check for the textarea, mode switch, and controls, so Cursor sessions can remain blocked even when provider-aware connected is true. Update the disabled/gating logic in Composer.tsx for the textarea, ComposerModeSwitch, and ComposerControls to rely on connected instead of installed, and keep the existing blocked/placeholder/StatusHint behavior aligned with the same session state.src/renderer/features/workspace/ComposerBanner.tsx (1)
49-86: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse the provider that set the banner state
agentName/isCursorare read from the current settings model, butlifecycleis global. If the model changes after arate-limitedorauth-requiredtransition, this banner can show the wrong provider name and retry hint. Persist the provider with the lifecycle state, or derive the copy from the event that set it.🤖 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/renderer/features/workspace/ComposerBanner.tsx` around lines 49 - 86, The banner copy in ComposerBanner is using the current settings model via agentDisplayName(model) and providerForModel(model), but lifecycle is not tied to that model, so the wrong provider name or auth hint can be shown after a settings change. Update the state source that drives lifecycle to also persist the provider/model that triggered it, then have the rate-limited and auth-required branches use that stored provider information instead of the live settings-derived agentName and isCursor values.
🧹 Nitpick comments (6)
src/main/managers/cursor/translate.ts (1)
82-86: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winConsider constraining the
mcp__<server>__<tool>identity components.
server/toolare taken verbatim from wireargswith only a string-type check, then interpolated into the composite tool identity string used for risk-gating/permission decisions elsewhere. If either value ever contains__or other delimiter-breaking characters, the composite name becomes ambiguous to any downstream logic that parses it back into server/tool parts.♻️ Proposed defensive sanitization
if (key === 'mcpToolCall') { const server = strField(args, 'server') ?? strField(args, 'serverName') ?? 'server'; const tool = strField(args, 'tool') ?? strField(args, 'toolName') ?? 'tool'; - return { callId, name: `mcp__${server}__${tool}`, input: { ...args } }; + const safeServer = /^[\w.-]+$/.test(server) ? server : 'server'; + const safeTool = /^[\w.-]+$/.test(tool) ? tool : 'tool'; + return { callId, name: `mcp__${safeServer}__${safeTool}`, input: { ...args } }; }As per path instructions for
src/main/**/*.ts: "Preserve deny-by-default permission handling, navigation lockdown, and other Electron hardening measures."🤖 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/main/managers/cursor/translate.ts` around lines 82 - 86, The mcpToolCall translation currently builds the mcp__<server>__<tool> identity directly from untrusted args, which can make downstream parsing/permission checks ambiguous. Update translate.ts in the mcpToolCall branch to normalize or sanitize the server and tool values before composing the name, preventing delimiter-breaking characters like __ from being preserved. Keep the deny-by-default gating behavior intact by ensuring the resulting identifier remains stable and unambiguous for any logic that consumes it.Source: Path instructions
src/renderer/features/workspace/Composer.tsx (1)
403-421: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMinor:
StatusHint'sinstalledprop now actually carriesconnected.Renaming the prop to
connected(matching the caller) would avoid confusion since the value passed in is no longer Claude's raw install state.🤖 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/renderer/features/workspace/Composer.tsx` around lines 403 - 421, The StatusHint component is using an `installed` prop even though the caller now passes connection state, which is confusing. Rename the prop and its type in `StatusHint` to `connected`, then update the internal `lifecycleMeta` call and any related checks to use the new name so it matches the caller and the actual meaning of the value.docs/reference/settings.md (1)
67-72: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDocument
executablePathanddiscoveredModelsin theagent.cursorsection.The main process reads
agent.cursor.executablePathandagent.cursor.discoveredModelsfrom settings (lines 222–223 insrc/main/index.ts), but neither field is documented here. Users who manually editsettings.jsonor reference the docs won't know these fields exist.📖 Suggested addition
`agent.cursor` (Cursor provider — authentication only): `preferredAuth` `auto` (one of `auto` / `api-key` / `cli-login`), `manualBrowserLogin` `false` (print the login URL instead of auto-opening a browser). **No secrets live here** — the optional Cursor API key is safeStorage-encrypted in a main-only file under `userData/secrets/`, never in `settings.json`. Bounds in `CURSOR_LIMITS`. + +`agent.cursor` also includes `executablePath` `''` (explicit override for the +`cursor-agent` binary; blank = auto-resolve via PATH/where/install-dir) and +`discoveredModels` `[]` (model ids from `cursor-agent models`, persisted for +pre-probe routing at boot).🤖 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 `@docs/reference/settings.md` around lines 67 - 72, The agent.cursor settings docs are missing two fields that the main process already reads: executablePath and discoveredModels. Update the agent.cursor section in settings documentation to mention both settings, using the same naming as in src/main/index.ts, and briefly describe their purpose/expected type so users editing settings.json can discover them.docs/reference/window-limboo-api.md (1)
93-99: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd
cursor.updateCli()and clarify the "no run capability yet" phrasing.Two gaps in the API documentation:
cursor.updateCli()is exposed in the preload (line 313–314) and has a corresponding IPC handler, but is missing from this list.- "auth only; no run capability yet" is technically accurate for the
cursor.*surface (runs go throughagent.send()), but is misleading in a PR titled "Cursor as a runnable second agent." Consider clarifying that the run capability is accessed through the existingagent.send()API, notcursor.*.📖 Suggested revision
-- `cursor.*` — Cursor provider authentication (auth only; no run capability yet). - Capability-based: no method ever returns a credential; the API key crosses IPC - exactly once via `setApiKey` and is safeStorage-encrypted in the main process. - - `cursor.getAuthState() -> CursorAuthState`, `cursor.refreshAuth() -> CursorAuthState` - - `cursor.loginStart(manual?)`, `cursor.loginCancel()`, `cursor.logout()` - - `cursor.setApiKey(key)`, `cursor.removeApiKey()` - - `cursor.onAuthChanged(cb)` — subscription. +- `cursor.*` — Cursor provider authentication and CLI maintenance (the run + capability is accessed through `agent.send()`, not `cursor.*`). Capability-based: + no method ever returns a credential; the API key crosses IPC exactly once via + `setApiKey` and is safeStorage-encrypted in the main process. + - `cursor.getAuthState() -> CursorAuthState`, `cursor.refreshAuth() -> CursorAuthState` + - `cursor.loginStart(manual?)`, `cursor.loginCancel()`, `cursor.logout()` + - `cursor.setApiKey(key)`, `cursor.removeApiKey()` + - `cursor.updateCli() -> CursorUpdateResult` (refused while an agent run is active) + - `cursor.onAuthChanged(cb)` — subscription.🤖 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 `@docs/reference/window-limboo-api.md` around lines 93 - 99, The cursor API docs are missing the exposed `cursor.updateCli()` method and the current “auth only; no run capability yet” wording is confusing. Update the `cursor.*` list in the docs to include `cursor.updateCli()`, and revise the capability note to say that cursor auth/config methods live on `cursor.*` while run capability is available through `agent.send()` rather than `cursor.*`. Use the existing `cursor.*` section and `agent.send()` reference to keep the wording aligned with the preload/IPС surface.src/main/managers/cursor/bridge/hookRunner.cjs (1)
19-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd an ESLint override for
.cjsbridge scripts to unblock lint.
@typescript-eslint/no-var-requiresfires here, but this standalone asset must userequire(it runs viaELECTRON_RUN_AS_NODEoutside the bundle and cannot use ESMimport). Add a targeted override so CI lint doesn't fail on the intentional CommonJS.// eslint config override { "files": ["**/bridge/*.cjs"], "rules": { "`@typescript-eslint/no-var-requires`": "off" } }🤖 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/main/managers/cursor/bridge/hookRunner.cjs` at line 19, The CommonJS bridge script hookRunner.cjs intentionally uses require, but lint is still flagging it under `@typescript-eslint/no-var-requires`. Add a targeted ESLint override for the bridge .cjs assets (for example, files matching **/bridge/*.cjs) and disable that rule there so the standalone Electron bridge scripts can keep using CommonJS without failing CI.Source: Linters/SAST tools
src/main/managers/cursor/bridge/pipeServer.ts (1)
71-116: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueConsider an idle/auth deadline for unauthenticated sockets.
A connection that connects but never sends the token line stays open and consumes a slot toward
CURSOR_LIMITS.bridgeMaxConnectionsfor the whole run. The endpoint is filesystem-gated (0700 dir / named pipe), so exposure is limited to local processes, but a short first-line/auth timeout would harden the bridge against a stuck or hostile local peer exhausting connection slots.🤖 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/main/managers/cursor/bridge/pipeServer.ts` around lines 71 - 116, The unauthenticated socket path in pipeServer.ts can hold a connection slot indefinitely if the client never sends the first token line. Add a short first-line/auth timeout in the net.createServer socket setup so unauthenticated connections are destroyed if they do not authenticate promptly, and clear that timeout once authed becomes true. Use the existing socket handling around sockets.add, socket.on('data'), and the authentication check with parseLine/timingSafeEqual to place the deadline cleanly.
🤖 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 `@CLAUDE.md`:
- Around line 727-729: Update the Cursor status text in the BUILT Authentication
section of CLAUDE.md so it no longer says Cursor is auth-only or cannot run yet.
Replace the stale wording in the Cursor auth layer note with language that
reflects the new runnable adapter work from this PR, keeping the rest of the
build-order item context intact.
In `@docs/agents/cursor-integration.txt`:
- Around line 51-67: The authentication section in cursor-integration.txt should
use the correct CLI name and avoid overstating the key format. Update the
wording around the login flow in the Cursor agent runtime description to refer
to cursor-agent login instead of agent login, and soften the API-key description
so crsr_ is presented as a recognizable/lenient prefix rather than a strict
requirement for exactly 64 hexadecimal characters. Keep the rest of the auth
guidance in this section consistent with the existing Cursor authentication
terms and symbols.
In `@docs/reference/settings.md`:
- Line 13: The documentation is stale because the referenced SETTINGS_VERSION
value no longer matches the source of truth. Update the SETTINGS_VERSION mention
in the settings reference docs to 15 so it stays aligned with
src/shared/constants.ts and any other references to SETTINGS_VERSION in the
docs.
In `@src/main/managers/AgentManager.ts`:
- Around line 874-889: `retryAuth()` currently only calls `probeHealth(true)`,
which does not force a fresh Cursor auth probe, so the Composer “Re-check
sign-in” action can remain a no-op for Cursor sessions. Update `retryAuth()` in
`AgentManager` to branch on the active provider and explicitly call the Cursor
auth refresh path (same behavior as `runWithRecovery` uses for cursor, e.g.
forcing `cursorAuth.probe(true)` or the equivalent cursor recheck method) before
re-running health checks. Also make the `diag('auth', ...)` message
provider-aware instead of always saying “Claude Code authentication.”
In `@src/main/managers/cursor/bridge/hookRunner.cjs`:
- Around line 110-111: The response written in hookRunner.cjs may be lost
because process.exit() is called immediately after process.stdout.write() in the
decision path. Update the logic in the hook runner around the stdout write so it
waits for the write to drain before terminating, using the write callback or
process.exitCode instead of an immediate exit for the allow case. Keep the deny
path failing closed with exit 2, and make sure the JSON decision from the hook
runner is fully flushed before the process ends.
In `@src/main/managers/cursor/CursorAuthManager.ts`:
- Around line 270-281: The single-flight guard in loginStart is still vulnerable
because this.loginChild is only set after await spawnCursorLogin, allowing
concurrent calls to spawn multiple login children. Add a synchronous in-flight
guard in CursorAuthManager.loginStart before the await (for example a dedicated
loginStarting flag or similar state) and clear it in a finally block so only one
login attempt can proceed at a time.
In `@src/main/managers/cursor/permissions.ts`:
- Around line 38-47: The deny list in the cursor permissions rules only blocks
the exact Shell(rm) token, so wrapper or path-qualified forms can still slip
through. Update the enforcement in the hooks/decideToolUse path so destructive
shell detection normalizes or inspects the full command invocation (including
wrappers like bash -c and absolute paths like /bin/rm) and denies those cases
there, using the existing permissions logic in cursor/permissions.ts as the
locator.
In `@src/renderer/stores/useAgentStore.ts`:
- Around line 354-363: The initial Cursor auth probe in useAgentStore is missing
rejection handling, so a failed getAuthState call can leave cursorAuth stuck
null and block CursorProviderCard actions. Update the intakeCursorAuth flow in
useAgentStore to attach error handling to api.cursor?.getAuthState?.() and set a
safe fallback auth state (or otherwise surface the failure) so the UI can
recover instead of remaining on “Checking the Cursor CLI…”. Keep the existing
registerCursorModels and set({ cursorAuth }) behavior for successful responses,
and make sure the live onAuthChanged listener still feeds the same path.
---
Outside diff comments:
In `@src/renderer/features/workspace/Composer.tsx`:
- Around line 280-294: The composer still uses the old Claude-only installed
check for the textarea, mode switch, and controls, so Cursor sessions can remain
blocked even when provider-aware connected is true. Update the disabled/gating
logic in Composer.tsx for the textarea, ComposerModeSwitch, and ComposerControls
to rely on connected instead of installed, and keep the existing
blocked/placeholder/StatusHint behavior aligned with the same session state.
In `@src/renderer/features/workspace/ComposerBanner.tsx`:
- Around line 49-86: The banner copy in ComposerBanner is using the current
settings model via agentDisplayName(model) and providerForModel(model), but
lifecycle is not tied to that model, so the wrong provider name or auth hint can
be shown after a settings change. Update the state source that drives lifecycle
to also persist the provider/model that triggered it, then have the rate-limited
and auth-required branches use that stored provider information instead of the
live settings-derived agentName and isCursor values.
---
Nitpick comments:
In `@docs/reference/settings.md`:
- Around line 67-72: The agent.cursor settings docs are missing two fields that
the main process already reads: executablePath and discoveredModels. Update the
agent.cursor section in settings documentation to mention both settings, using
the same naming as in src/main/index.ts, and briefly describe their
purpose/expected type so users editing settings.json can discover them.
In `@docs/reference/window-limboo-api.md`:
- Around line 93-99: The cursor API docs are missing the exposed
`cursor.updateCli()` method and the current “auth only; no run capability yet”
wording is confusing. Update the `cursor.*` list in the docs to include
`cursor.updateCli()`, and revise the capability note to say that cursor
auth/config methods live on `cursor.*` while run capability is available through
`agent.send()` rather than `cursor.*`. Use the existing `cursor.*` section and
`agent.send()` reference to keep the wording aligned with the preload/IPС
surface.
In `@src/main/managers/cursor/bridge/hookRunner.cjs`:
- Line 19: The CommonJS bridge script hookRunner.cjs intentionally uses require,
but lint is still flagging it under `@typescript-eslint/no-var-requires`. Add a
targeted ESLint override for the bridge .cjs assets (for example, files matching
**/bridge/*.cjs) and disable that rule there so the standalone Electron bridge
scripts can keep using CommonJS without failing CI.
In `@src/main/managers/cursor/bridge/pipeServer.ts`:
- Around line 71-116: The unauthenticated socket path in pipeServer.ts can hold
a connection slot indefinitely if the client never sends the first token line.
Add a short first-line/auth timeout in the net.createServer socket setup so
unauthenticated connections are destroyed if they do not authenticate promptly,
and clear that timeout once authed becomes true. Use the existing socket
handling around sockets.add, socket.on('data'), and the authentication check
with parseLine/timingSafeEqual to place the deadline cleanly.
In `@src/main/managers/cursor/translate.ts`:
- Around line 82-86: The mcpToolCall translation currently builds the
mcp__<server>__<tool> identity directly from untrusted args, which can make
downstream parsing/permission checks ambiguous. Update translate.ts in the
mcpToolCall branch to normalize or sanitize the server and tool values before
composing the name, preventing delimiter-breaking characters like __ from being
preserved. Keep the deny-by-default gating behavior intact by ensuring the
resulting identifier remains stable and unambiguous for any logic that consumes
it.
In `@src/renderer/features/workspace/Composer.tsx`:
- Around line 403-421: The StatusHint component is using an `installed` prop
even though the caller now passes connection state, which is confusing. Rename
the prop and its type in `StatusHint` to `connected`, then update the internal
`lifecycleMeta` call and any related checks to use the new name so it matches
the caller and the actual meaning of the value.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 46c99b54-4e18-4489-a3b9-7108b2db2de8
📒 Files selected for processing (62)
CLAUDE.mddocs/agents/cursor-integration.txtdocs/architecture/security-model.mddocs/architecture/subsystems/agent-manager.mddocs/getting-started/configuration.mddocs/guides/using-the-agent.mddocs/reference/ipc-channels.mddocs/reference/settings.mddocs/reference/window-limboo-api.mdforge.config.tssrc/main/db/database.tssrc/main/index.tssrc/main/ipc/cursorHandlers.tssrc/main/ipc/index.tssrc/main/logger.tssrc/main/managers/AgentManager.tssrc/main/managers/SessionManager.tssrc/main/managers/SettingsManager.tssrc/main/managers/cursor/CursorAuthManager.tssrc/main/managers/cursor/CursorRuntime.tssrc/main/managers/cursor/bridge/bridgeAssets.tssrc/main/managers/cursor/bridge/hookRunner.cjssrc/main/managers/cursor/bridge/mcpBridge.cjssrc/main/managers/cursor/bridge/pipeServer.tssrc/main/managers/cursor/bridge/plainTool.tssrc/main/managers/cursor/bridge/toolDispatch.tssrc/main/managers/cursor/errors.tssrc/main/managers/cursor/exec.tssrc/main/managers/cursor/hooks.tssrc/main/managers/cursor/mcpConfig.tssrc/main/managers/cursor/permissions.tssrc/main/managers/cursor/rules.tssrc/main/managers/cursor/sessionFile.tssrc/main/managers/cursor/stream.tssrc/main/managers/cursor/translate.tssrc/main/managers/cursor/types.tssrc/main/managers/memory/memoryTools.tssrc/main/managers/search/searchTools.tssrc/main/secrets/SecretStore.tssrc/preload/index.tssrc/renderer/components/brand/ProviderIcon.tsxsrc/renderer/features/activity/AgentConsolePanel.tsxsrc/renderer/features/activity/PlanPanel.tsxsrc/renderer/features/agent/models.tssrc/renderer/features/agent/status.tssrc/renderer/features/settings/catalog.tsxsrc/renderer/features/settings/controls.tsxsrc/renderer/features/settings/panels/AgentPanel.tsxsrc/renderer/features/settings/panels/AgentTroubleshooting.tsxsrc/renderer/features/settings/panels/AttachmentsPanel.tsxsrc/renderer/features/settings/panels/CursorProviderCard.tsxsrc/renderer/features/settings/panels/ProviderCard.tsxsrc/renderer/features/workspace/CenterWorkspace.tsxsrc/renderer/features/workspace/Composer.tsxsrc/renderer/features/workspace/ComposerBanner.tsxsrc/renderer/features/workspace/ComposerControls.tsxsrc/renderer/stores/useAgentStore.tssrc/renderer/stores/useSettingsStore.tssrc/shared/constants.tssrc/shared/ipc-channels.tssrc/shared/types.tsvite.main.config.ts
🛑 Comments failed to post (8)
CLAUDE.md (1)
727-729: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update the Cursor status text.
Line 727 still says Cursor is auth-only and cannot run yet, which is stale after the runnable adapter work in this PR.
Suggested doc patch
- Cursor is the second provider, currently authentication only — it cannot run yet (`AGENT_MODELS` has no Cursor entries, so it is structurally unselectable as the running agent). + Cursor is the second provider; Phase 1 covered authentication, and the current adapter also supports running Cursor sessions.📝 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.- **BUILT — build-order item (1), Authentication.** The Cursor auth layer is live (auth only — Cursor still cannot *run*; `AGENT_MODELS` deliberately has no Cursor entries, so it is structurally unselectable as the running agent): **BUILT — build-order item (1), Authentication.** Cursor is the second provider; Phase 1 covered authentication, and the current adapter also supports running Cursor sessions.🤖 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 `@CLAUDE.md` around lines 727 - 729, Update the Cursor status text in the BUILT Authentication section of CLAUDE.md so it no longer says Cursor is auth-only or cannot run yet. Replace the stale wording in the Cursor auth layer note with language that reflects the new runnable adapter work from this PR, keeping the rest of the build-order item context intact.docs/agents/cursor-integration.txt (1)
51-67: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail # Locate the referenced doc and any auth-related implementation/docs. git ls-files | rg -n '(^docs/agents/cursor-integration\.txt$|cursor-agent|CURSOR_API_KEY|NO_OPEN_BROWSER|agent login|crsr_)' echo '--- docs/agents/cursor-integration.txt (around the cited lines) ---' sed -n '45,75p' docs/agents/cursor-integration.txt echo '--- search for exact auth wording across repo ---' rg -n 'agent login|cursor-agent login|CURSOR_API_KEY|NO_OPEN_BROWSER|crsr_' . echo '--- file list for likely implementation/doc references ---' git ls-files | rg 'cursor|agent|auth|login|cursor-integration|claude'Repository: BotCoder254/limboo
Length of output: 11168
Use
cursor-agent loginand loosen the key wordingdocs/agents/cursor-integration.txt:53-67
agent loginshould becursor-agent login, andcrsr_should be described as a lenient prefix rather than a strict 64-hex format.🤖 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 `@docs/agents/cursor-integration.txt` around lines 51 - 67, The authentication section in cursor-integration.txt should use the correct CLI name and avoid overstating the key format. Update the wording around the login flow in the Cursor agent runtime description to refer to cursor-agent login instead of agent login, and soften the API-key description so crsr_ is presented as a recognizable/lenient prefix rather than a strict requirement for exactly 64 hexadecimal characters. Keep the rest of the auth guidance in this section consistent with the existing Cursor authentication terms and symbols.docs/reference/settings.md (1)
13-13: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash rg -n 'SETTINGS_VERSION' src/shared/constants.tsRepository: BotCoder254/limboo
Length of output: 227
Update
SETTINGS_VERSIONin the docs to 15.src/shared/constants.tssetsSETTINGS_VERSIONto 15, sodocs/reference/settings.mdis stale.🤖 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 `@docs/reference/settings.md` at line 13, The documentation is stale because the referenced SETTINGS_VERSION value no longer matches the source of truth. Update the SETTINGS_VERSION mention in the settings reference docs to 15 so it stays aligned with src/shared/constants.ts and any other references to SETTINGS_VERSION in the docs.src/main/managers/AgentManager.ts (1)
874-889: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
retryAuth()never forces a fresh Cursor probe — the composer's "Re-check sign-in" is a no-op for Cursor.
ComposerBannerwires itsauth-requiredrecovery action toretryAuthfor both providers:action = { label: 'Re-check sign-in', onClick: retryAuth };But
retryAuth()only callsthis.probeHealth(true). For a Cursor-model session,probeHealthshort-circuits tothis.reconcileCursorLifecycle(), which only readsthis.cursorAuth.getCachedState()— it force-probes only when the status is'unknown'. SinceCursorAuthManager.probe(false)is a no-op when already probed (if (this.probed && !force) return this.state;), a session stuck innot-authenticatednever gets re-classified when the user signs in and clicks this button — they'd have to use the separate Providers cardagent:cursorRefreshAuthinstead, which this code path never calls. The diag message is also hardcoded to "Claude Code authentication" even when Cursor is the active provider.Compare with
runWithRecovery, which correctly doesif (provider === 'cursor') void this.cursorAuth?.probe(true);on an auth failure — that pattern is missing here.🐛 Proposed fix
/** Force a fresh auth probe — invoked after the user signs in again. */ retryAuth(): AgentInstall { - this.diag('auth', 'info', 'Re-checking Claude Code authentication'); - return this.probeHealth(true); + if (providerForModel(this.settings.getAll().agent.model) === 'cursor') { + this.diag('auth', 'info', 'Re-checking Cursor authentication'); + void this.cursorAuth?.probe(true); + return this.state.install; + } + this.diag('auth', 'info', 'Re-checking Claude Code authentication'); + return this.probeHealth(true); }📝 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./** Force a fresh auth probe — invoked after the user signs in again. */ retryAuth(): AgentInstall { if (providerForModel(this.settings.getAll().agent.model) === 'cursor') { this.diag('auth', 'info', 'Re-checking Cursor authentication'); void this.cursorAuth?.probe(true); return this.state.install; } this.diag('auth', 'info', 'Re-checking Claude Code authentication'); return this.probeHealth(true); } /** The model at the last settings read — a provider flip re-gates the composer. */ private lastModel: string | null = null; /** Re-read connection settings and restart the heartbeat with new cadence. */ reconfigure(): void { this.startHeartbeat(); const model = this.settings.getAll().agent.model; if (this.lastModel !== null && this.lastModel !== model) this.probeHealth(true); this.lastModel = model; }🤖 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/main/managers/AgentManager.ts` around lines 874 - 889, `retryAuth()` currently only calls `probeHealth(true)`, which does not force a fresh Cursor auth probe, so the Composer “Re-check sign-in” action can remain a no-op for Cursor sessions. Update `retryAuth()` in `AgentManager` to branch on the active provider and explicitly call the Cursor auth refresh path (same behavior as `runWithRecovery` uses for cursor, e.g. forcing `cursorAuth.probe(true)` or the equivalent cursor recheck method) before re-running health checks. Also make the `diag('auth', ...)` message provider-aware instead of always saying “Claude Code authentication.”src/main/managers/cursor/bridge/hookRunner.cjs (1)
110-111: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
sed -n '1,180p' src/main/managers/cursor/bridge/hookRunner.cjsRepository: BotCoder254/limboo
Length of output: 4174
Wait for stdout to drain before exiting
process.exit()immediately afterprocess.stdout.write()can drop the decision JSON when stdout is a pipe. Use the write callback orprocess.exitCodeso the allow response reaches Cursor intact; the deny path can still fail closed with exit 2.🤖 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/main/managers/cursor/bridge/hookRunner.cjs` around lines 110 - 111, The response written in hookRunner.cjs may be lost because process.exit() is called immediately after process.stdout.write() in the decision path. Update the logic in the hook runner around the stdout write so it waits for the write to drain before terminating, using the write callback or process.exitCode instead of an immediate exit for the allow case. Keep the deny path failing closed with exit 2, and make sure the JSON decision from the hook runner is fully flushed before the process ends.src/main/managers/cursor/CursorAuthManager.ts (1)
270-281: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail # Map the target file, then inspect the relevant range and nearby state. ast-grep outline src/main/managers/cursor/CursorAuthManager.ts --view expanded || true wc -l src/main/managers/cursor/CursorAuthManager.ts sed -n '1,360p' src/main/managers/cursor/CursorAuthManager.ts | cat -n # Find all references to loginStart / loginChild / any in-flight guard. rg -n "loginStart\\(|loginChild|loginStarting|spawnCursorLogin|loginTimer|setLogin\\(" src/main/managers/cursor/CursorAuthManager.ts src/main -SRepository: BotCoder254/limboo
Length of output: 25511
🏁 Script executed:
#!/bin/bash set -euo pipefail # Inspect the login spawn helper and the IPC handler that invokes loginStart. sed -n '380,460p' src/main/managers/cursor/exec.ts | cat -n sed -n '1,120p' src/main/ipc/cursorHandlers.ts | cat -n # Read the state-update helpers around login transitions / child cleanup. sed -n '380,430p' src/main/managers/cursor/CursorAuthManager.ts | cat -nRepository: BotCoder254/limboo
Length of output: 8463
Close the single-flight window in
loginStart.
this.loginChildis checked beforeawait spawnCursorLogin(...), so two concurrent IPC calls can both pass the guard, spawn separate login children, and let the later one overwrite the earlier process/timer state. Add a synchronous in-flight guard before the await (for example aloginStartingflag cleared infinally).🤖 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/main/managers/cursor/CursorAuthManager.ts` around lines 270 - 281, The single-flight guard in loginStart is still vulnerable because this.loginChild is only set after await spawnCursorLogin, allowing concurrent calls to spawn multiple login children. Add a synchronous in-flight guard in CursorAuthManager.loginStart before the await (for example a dedicated loginStarting flag or similar state) and clear it in a finally block so only one login attempt can proceed at a time.src/main/managers/cursor/permissions.ts (1)
38-47: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Cursor CLI cli.json permissions Shell rule matching how command names are matched💡 Result:
The Cursor CLI utilizes a dedicated permissions system to control shell command access, distinct from the IDE-based permission settings [1][2]. Permissions for the CLI are configured in a configuration file (cli-config.json) [3][4]. Configuration Files Permissions are defined in two primary locations: 1. Global: ~/.cursor/cli-config.json (macOS/Linux) or $env:USERPROFILE.cursor\cli-config.json (Windows) [3][4]. 2. Project-specific: /.cursor/cli.json [3][4]. Permission entries are defined within an object containing allow and deny arrays [3][4]. Shell Rule Matching Shell command permissions use the format Shell(commandBase) [5]. - commandBase represents the first token in the command line (the executable name) [5]. - Glob patterns (e.g., ) are supported for flexible matching [5]. - Specific command-argument matching is possible using the command:args syntax (e.g., Shell(curl:) to match specific utilities with arguments) [5]. - Deny rules take precedence over allow rules [5]. Matching Logic The CLI evaluates these permissions against the shell command being executed. When a command is triggered, the agent identifies the base command and matches it against the configured tokens. If a command does not match an allowed pattern—or is explicitly denied—it will be blocked or require further authorization [5]. Note that this CLI permission system is separate from the terminal shell hooks or the permissions.json file used by the Cursor IDE [1][2]. Important Considerations - The system is case-sensitive and relies on exact string matching for the defined tokens [3][4]. - Changes to the configuration file require that the file be valid JSON and have correct write permissions [3][4]. - Corrupted configuration files are typically backed up as.bad and regenerated by the CLI [3][4].
Citations:
- 1: https://cursor.com/docs/reference/permissions
- 2: https://gist.github.com/alejo4373/ea9bc4dc47c0d13ab64a926b5e44019f
- 3: https://cursor.com/docs/cli/reference/configuration
- 4: https://cursor.com/docs/cli/reference/configuration.md
- 5: https://cursor.com/docs/cli/reference/permissions.md
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n== file map ==\n' git ls-files 'src/main/managers/cursor/permissions.ts' 'src/main/managers/cursor/*' | sed 's#^`#-` #' printf '\n== outline ==\n' ast-grep outline src/main/managers/cursor/permissions.ts --view expanded || true printf '\n== relevant slice ==\n' sed -n '1,220p' src/main/managers/cursor/permissions.ts printf '\n== search for Shell( usages ==\n' rg -n "Shell\\(" src/main/managers/cursor src/main -g '!**/dist/**' -g '!**/build/**' || trueRepository: BotCoder254/limboo
Length of output: 7221
🌐 Web query:
Cursor CLI permissions Shell(commandBase) exact matching first token /bin/rm bash -c documentation💡 Result:
In the Cursor IDE and CLI permission system, shell access is controlled using
Shell(commandBase)syntax [1][2]. ThecommandBaserepresents the first token (the primary command) of the shell execution string [1][3]. Key details regarding this matching behavior include: 1. Exact Matching and Prefix Semantics: The matching ofcommandBaseis case-sensitive [4][5]. When you define a permission, it acts as a prefix matcher for commands starting with that base [4][5]. For example, a ruleShell(git)will matchgit status,git diff, andgit push, as it allows any command beginning with that string [4][5]. 2. Finer Control: To restrict access to specific subcommands or prevent broad prefix matching, you can use thecommand:argssyntax [1][3]. For instance,Shell(npm:install*)allowsnpm installand its variations, but would not matchnpm publish[4][5]. 3. Security Context: - Deny rules take precedence over allow rules [1][3]. - Users may encounter situations where explicit allowlists are bypassed if "Auto-Run in Sandbox" mode is enabled, as this mode may prioritize sandboxing policies over manual permission allowlists [6]. - While Cursor documentation describes a system for project-level.cursor/permissions.jsonfiles, there are known reports of issues where these files may not merge or load correctly, often requiring fallback to user-level configurations in~/.cursor/permissions.json[7]. For precise security, it is generally recommended to use thecommand:argssyntax to avoid inadvertently allowlisting a broad set of commands (likegitornpm) when you only intend to permit a subset [5].Citations:
- 1: https://cursor.com/docs/cli/reference/permissions
- 2: https://github.com/dyoshikawa/rulesync/blob/54415565/src/features/permissions/cursor-permissions.ts
- 3: https://docs.anyweb.dev/docs/cli/reference/permissions
- 4: https://cursor.com/docs/reference/permissions
- 5: https://startdebugging.net/2026/06/gate-cursor-sdk-tool-calls-with-auto-review-and-permissions-json/
- 6: https://forum.cursor.com/t/command-allowlist-is-silently-ignored-when-auto-run-in-sandbox-is-enabled/152136
- 7: https://forum.cursor.com/t/project-level-cursor-permissions-json-not-loaded-cursor-permissions-json-works/163614
Shell(rm)only covers the first command token. Path-qualified or wrapper-based invocations like/bin/rmorbash -c "rm …"still evade this deny list, so keep the hooks/decideToolUselayer as the real enforcement boundary.🤖 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/main/managers/cursor/permissions.ts` around lines 38 - 47, The deny list in the cursor permissions rules only blocks the exact Shell(rm) token, so wrapper or path-qualified forms can still slip through. Update the enforcement in the hooks/decideToolUse path so destructive shell detection normalizes or inspects the full command invocation (including wrappers like bash -c and absolute paths like /bin/rm) and denies those cases there, using the existing permissions logic in cursor/permissions.ts as the locator.src/renderer/stores/useAgentStore.ts (1)
354-363: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Unhandled rejection on the initial Cursor auth probe can strand the UI.
api.cursor?.getAuthState?.().then(intakeCursorAuth)has no.catch(). If this IPC call rejects,cursorAuthstaysnullforever (no fallback is set), andCursorProviderCardonly rendersProviderStatusRowwith "Checking the Cursor CLI…" — every action block there is gated onauth?.status === '...', so none render whenauthisnull. The user is left with no visible recovery path short of restarting the app (thecursorRefreshbutton that could self-heal is also gated behind a resolvedauth).🩹 Proposed fix
api.cursor?.onAuthChanged?.(intakeCursorAuth); - void api.cursor?.getAuthState?.().then(intakeCursorAuth); + void api.cursor?.getAuthState?.().then(intakeCursorAuth).catch((err) => { + logger?.error?.('Cursor initial auth probe failed', err); + });🤖 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/renderer/stores/useAgentStore.ts` around lines 354 - 363, The initial Cursor auth probe in useAgentStore is missing rejection handling, so a failed getAuthState call can leave cursorAuth stuck null and block CursorProviderCard actions. Update the intakeCursorAuth flow in useAgentStore to attach error handling to api.cursor?.getAuthState?.() and set a safe fallback auth state (or otherwise surface the failure) so the UI can recover instead of remaining on “Checking the Cursor CLI…”. Keep the existing registerCursorModels and set({ cursorAuth }) behavior for successful responses, and make sure the live onAuthChanged listener still feeds the same path.
…, height-capped model picker The composer footer previously exposed only a flat model list as the implicit provider selector — with discovered Cursor models it grew unbounded and there was no direct way to "change the agent". - New Agent MiniSelect (Claude Code / Cursor, provider marks): switching agents jumps to the target provider's first catalog model, and the Model select now offers only the active provider's models — changing the agent changes the model list with it. - MiniSelect gains an optional search input (filters label + id, "No matches" empty state) and a height-capped scrollable option list, so the model popover stays at the thinking-select scale no matter how many Cursor models the account discovers. The selected-option Check tick is unchanged and now always reachable via scroll/search. - Same idiom throughout: existing tokens, pop-in animation, click-outside/Esc handling; ComposerModeSwitch consumes MiniSelect unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
Completes Agent Adapter Architecture build-order steps 2–5, turning the Cursor provider from auth-only (step 1, already on this branch) into a fully runnable second coding agent — with zero changes to the renderer's provider-neutral event model. The UI (streaming conversation, tool chips, plan panel, timeline, diff counts) works identically for Claude and Cursor runs.
Runtime (step 2)
CursorRuntimespawnscursor-agent --print --output-format stream-json --stream-partial-output --workspace <sessionRoot>argv-only; the prompt rides stdin, never argv; stop is a process-tree kill.stream.ts(bounded NDJSON reader) +translate.ts(Cursor tool union → Claude-shaped tool names; the documentedtimestamp_ms/model_call_iddelta/buffered/final-flush disambiguation) +errors.ts(classification incl. resume-corruption self-heal: forget token → retry fresh).cursor-agent create-chatand stored provider-keyed inagent_provider_sessions(schema v12), replayed as--resume. Cursor resumes the conversation; Limboo's Resume Pipeline still reconciles repository state — complementary layers.node.exe+index.jsunder%LOCALAPPDATA%\cursor-agent, regex-gated version dirs, realpath-contained) instead of relying on a registry-PATH the running GUI never sees.Permissions (step 3)
.cursor/cli.jsonwraps every run; a capability-gated sessionhooks.jsonregisters the bundled fail-closedhookRunner.cjs, routing hook events into the shareddecideToolUsecore — one permission implementation for Claude'scanUseTooland Cursor's hooks. Hooks only ever tighten.Context injection (step 4)
.cursor/rules/limboo-context.mdc(restored byte-for-byte infinally; prompt-prepend fallback if the write fails pre-spawn).MCP reuse (step 5)
.cursor/mcp.json(defensively merged over repo-authored servers) pointslimboo_memory/limboo_searchatmcpBridge.cjs, which forwards over a token-authenticated per-run local pipe to transport-neutral plain tools shared with the Claude SDK servers — both agents query the same index/memory, better-sqlite3 stays in one process.Also
cursor-agent models, TTL-cached, persisted, registered in both processes), sandbox toggle, single-flight CLI self-update, and a Settings › Agent Troubleshooting section with live CLI-resolution diagnostics.Security notes
'wx'+ mode0o600and written via the fd, so restrictive permissions apply atomically atopen(2); stale tmp files are removed first (anO_TRUNCreuse would keep whatever permissions a crashed write left behind);fsyncbefore rename; thesecrets/dir is forced0o700.shell: true); pipe token + endpoint ride the child env, never argv; every generated session file is containment-checked, atomic, and restored sogit statusis clean after each run;.cmdshims stay refused for runs; repo-authoredhooks.jsonis replaced, never merged.Verification
npm run lintclean;npx vite build --config vite.renderer.config.mtsbuilds.'wx'refuses pre-existing files, final file carries the create-time mode.%LOCALAPPDATA%\cursor-agentinstall (previously showed "Install CLI" despite an authenticated install).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation