Skip to content

feat(agent): Cursor as a runnable second agent — runtime, permission hooks, context + MCP bridge (Agent Adapter steps 2–5)#57

Merged
BotCoder254 merged 5 commits into
mainfrom
feat/cursor-provider-auth
Jul 8, 2026
Merged

feat(agent): Cursor as a runnable second agent — runtime, permission hooks, context + MCP bridge (Agent Adapter steps 2–5)#57
BotCoder254 merged 5 commits into
mainfrom
feat/cursor-provider-auth

Conversation

@BotCoder254

@BotCoder254 BotCoder254 commented Jul 8, 2026

Copy link
Copy Markdown
Owner

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)

  • CursorRuntime spawns cursor-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 documented timestamp_ms/model_call_id delta/buffered/final-flush disambiguation) + errors.ts (classification incl. resume-corruption self-heal: forget token → retry fresh).
  • Chat ids are pre-bound via cursor-agent create-chat and stored provider-keyed in agent_provider_sessions (schema v12), replayed as --resume. Cursor resumes the conversation; Limboo's Resume Pipeline still reconciles repository state — complementary layers.
  • Fixes the stale "Install CLI" status on Windows: the resolver now understands the native installer layout (node.exe + index.js under %LOCALAPPDATA%\cursor-agent, regex-gated version dirs, realpath-contained) instead of relying on a registry-PATH the running GUI never sees.

Permissions (step 3)

  • Deny-first session .cursor/cli.json wraps every run; a capability-gated session hooks.json registers the bundled fail-closed hookRunner.cjs, routing hook events into the shared decideToolUse core — one permission implementation for Claude's canUseTool and Cursor's hooks. Hooks only ever tighten.

Context injection (step 4)

  • Memory/search/resume context moves into a per-run generated .cursor/rules/limboo-context.mdc (restored byte-for-byte in finally; prompt-prepend fallback if the write fails pre-spawn).

MCP reuse (step 5)

  • Session .cursor/mcp.json (defensively merged over repo-authored servers) points limboo_memory/limboo_search at mcpBridge.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

  • Dynamic model discovery (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

  • SecretStore hardening (review finding): secret tmp files are now opened with 'wx' + mode 0o600 and written via the fd, so restrictive permissions apply atomically at open(2); stale tmp files are removed first (an O_TRUNC reuse would keep whatever permissions a crashed write left behind); fsync before rename; the secrets/ dir is forced 0o700.
  • All child processes remain argv-only (no shell: true); pipe token + endpoint ride the child env, never argv; every generated session file is containment-checked, atomic, and restored so git status is clean after each run; .cmd shims stay refused for runs; repo-authored hooks.json is replaced, never merged.

Verification

  • npm run lint clean; npx vite build --config vite.renderer.config.mts builds.
  • SecretStore write sequence exercised standalone: stale loose-permission tmp is not reused, 'wx' refuses pre-existing files, final file carries the create-time mode.
  • Windows CLI detection verified against the native %LOCALAPPDATA%\cursor-agent install (previously showed "Install CLI" despite an authenticated install).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Cursor support alongside the existing agent provider, including sign-in, API key setup, model selection, and update controls.
    • Introduced a new Cursor settings card, troubleshooting panel, and provider icon in the UI.
    • Added support for discovering and using additional Cursor models.
  • Bug Fixes

    • Improved secret handling and redaction so sensitive credentials are better protected in logs and storage.
    • Expanded agent session recovery and provider-specific status handling for more reliable connections.
  • Documentation

    • Updated setup, configuration, security, and API reference docs for the new Cursor workflow.

BotCoder254 and others added 2 commits July 8, 2026 13:30
…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>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@BotCoder254, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 33 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: de5909b2-699b-475b-b571-81dceabd25ac

📥 Commits

Reviewing files that changed from the base of the PR and between 53cb9ba and a34773b.

📒 Files selected for processing (5)
  • src/main/managers/cursor/exec.ts
  • src/main/managers/resume/ResumeManager.ts
  • src/renderer/features/resume/ResumeDeltaDialog.tsx
  • src/renderer/features/workspace/ComposerControls.tsx
  • src/shared/constants.ts
📝 Walkthrough

Walkthrough

This 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.

Changes

Cursor Provider Adapter

Layer / File(s) Summary
Shared contracts and settings
src/shared/types.ts, src/shared/constants.ts, src/shared/ipc-channels.ts, src/main/managers/SettingsManager.ts, src/main/managers/cursor/types.ts
Adds Cursor settings schema, CursorAuthState/CursorUpdateResult types, AgentProvider/AGENT_MODELS extensions, CURSOR_LIMITS/regex constants, new IPC channel names, and settings normalization for the cursor block.
Encrypted secret storage
src/main/secrets/SecretStore.ts
Implements safeStorage-backed atomic encrypted persistence, decryption, and metadata-only exposure for secrets like the Cursor API key.
Cursor CLI exec and auth manager
src/main/managers/cursor/exec.ts, src/main/managers/cursor/CursorAuthManager.ts
Resolves/spawns the cursor-agent CLI safely and implements CursorAuthManager for auth classification, login/logout, API key handling, model discovery, and CLI self-update.
Runtime, streaming and translation
src/main/managers/cursor/CursorRuntime.ts, .../stream.ts, .../translate.ts, .../errors.ts
Executes print-mode runs, parses bounded NDJSON output, translates Cursor events into Limboo tool identities, and classifies Cursor-specific errors.
Session config and per-run bridge
src/main/managers/cursor/{sessionFile,permissions,hooks,rules,mcpConfig}.ts, src/main/managers/cursor/bridge/*, forge.config.ts, vite.main.config.ts
Generates session-scoped .cursor/* config, runs a token-authenticated local pipe server, and bundles standalone hook/MCP bridge scripts with correct packaging.
AgentManager integration and persistence
src/main/managers/AgentManager.ts, src/main/managers/SessionManager.ts, src/main/db/database.ts
Adds Cursor lifecycle gating, runCursorOnce, provider-specific recovery/error handling, and an agent_provider_sessions table replacing Claude-only SDK session storage.
IPC wiring and preload
src/main/ipc/*, src/main/index.ts, src/main/logger.ts, src/preload/index.ts
Registers Cursor IPC handlers, wires managers into bootstrap/shutdown, extends secret redaction, and exposes Cursor methods via preload.
Renderer store and provider UI
src/renderer/stores/*, src/renderer/features/agent/*, src/renderer/features/settings/panels/*, src/renderer/features/workspace/*
Adds Cursor auth/bridge state to stores, provider-aware model/status helpers, a Cursor provider settings card, troubleshooting panel, and provider-aware composer messaging.
Memory/search tool refactor
src/main/managers/memory/memoryTools.ts, src/main/managers/search/searchTools.ts
Extracts transport-neutral PlainTool arrays shared between the SDK MCP server and the Cursor bridge dispatcher.
Documentation
docs/**, CLAUDE.md
Documents the Cursor adapter architecture, security model updates, and user-facing connection guidance.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • BotCoder254/limboo#56: Introduces the same Cursor auth-only foundation (CursorAuthManager, SecretStore, IPC/preload wiring, secret redaction) that this PR builds on and extends.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: Cursor becomes a runnable second agent with runtime, permissions, context, and MCP bridge work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cursor-provider-auth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 call padStart on 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.


⚠️ This PR contains more than 30 files. Amazon Q is better at reviewing smaller PRs, and may miss issues in larger changesets.

Comment on lines +305 to +309
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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 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.

Suggested change
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;
}

BotCoder254 and others added 2 commits July 9, 2026 00:27
…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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The job was not started because the account is locked due to a billing issue.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 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 win

Textarea/mode-switch/controls still gate on Claude-only installed, not the new connected.

The blocked, placeholder, and StatusHint logic were updated to use provider-aware connected (Line 110-112), but the textarea (disabled={disabled || !installed}, Line 284), ComposerModeSwitch (Line 357), and ComposerControls (Line 359) were left on the old Claude-only installed flag. For a Cursor-model session where Claude Code isn't installed, these controls stay disabled even though the session is otherwise connected — 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 win

Use the provider that set the banner state
agentName/isCursor are read from the current settings model, but lifecycle is global. If the model changes after a rate-limited or auth-required transition, 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 win

Consider constraining the mcp__<server>__<tool> identity components.

server/tool are taken verbatim from wire args with 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 value

Minor: StatusHint's installed prop now actually carries connected.

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 value

Document executablePath and discoveredModels in the agent.cursor section.

The main process reads agent.cursor.executablePath and agent.cursor.discoveredModels from settings (lines 222–223 in src/main/index.ts), but neither field is documented here. Users who manually edit settings.json or 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 value

Add cursor.updateCli() and clarify the "no run capability yet" phrasing.

Two gaps in the API documentation:

  1. cursor.updateCli() is exposed in the preload (line 313–314) and has a corresponding IPC handler, but is missing from this list.
  2. "auth only; no run capability yet" is technically accurate for the cursor.* surface (runs go through agent.send()), but is misleading in a PR titled "Cursor as a runnable second agent." Consider clarifying that the run capability is accessed through the existing agent.send() API, not cursor.*.
📖 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 win

Add an ESLint override for .cjs bridge scripts to unblock lint.

@typescript-eslint/no-var-requires fires here, but this standalone asset must use require (it runs via ELECTRON_RUN_AS_NODE outside the bundle and cannot use ESM import). 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 value

Consider 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.bridgeMaxConnections for 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4da94e5 and 53cb9ba.

📒 Files selected for processing (62)
  • CLAUDE.md
  • docs/agents/cursor-integration.txt
  • docs/architecture/security-model.md
  • docs/architecture/subsystems/agent-manager.md
  • docs/getting-started/configuration.md
  • docs/guides/using-the-agent.md
  • docs/reference/ipc-channels.md
  • docs/reference/settings.md
  • docs/reference/window-limboo-api.md
  • forge.config.ts
  • src/main/db/database.ts
  • src/main/index.ts
  • src/main/ipc/cursorHandlers.ts
  • src/main/ipc/index.ts
  • src/main/logger.ts
  • src/main/managers/AgentManager.ts
  • src/main/managers/SessionManager.ts
  • src/main/managers/SettingsManager.ts
  • src/main/managers/cursor/CursorAuthManager.ts
  • src/main/managers/cursor/CursorRuntime.ts
  • src/main/managers/cursor/bridge/bridgeAssets.ts
  • src/main/managers/cursor/bridge/hookRunner.cjs
  • src/main/managers/cursor/bridge/mcpBridge.cjs
  • src/main/managers/cursor/bridge/pipeServer.ts
  • src/main/managers/cursor/bridge/plainTool.ts
  • src/main/managers/cursor/bridge/toolDispatch.ts
  • src/main/managers/cursor/errors.ts
  • src/main/managers/cursor/exec.ts
  • src/main/managers/cursor/hooks.ts
  • src/main/managers/cursor/mcpConfig.ts
  • src/main/managers/cursor/permissions.ts
  • src/main/managers/cursor/rules.ts
  • src/main/managers/cursor/sessionFile.ts
  • src/main/managers/cursor/stream.ts
  • src/main/managers/cursor/translate.ts
  • src/main/managers/cursor/types.ts
  • src/main/managers/memory/memoryTools.ts
  • src/main/managers/search/searchTools.ts
  • src/main/secrets/SecretStore.ts
  • src/preload/index.ts
  • src/renderer/components/brand/ProviderIcon.tsx
  • src/renderer/features/activity/AgentConsolePanel.tsx
  • src/renderer/features/activity/PlanPanel.tsx
  • src/renderer/features/agent/models.ts
  • src/renderer/features/agent/status.ts
  • src/renderer/features/settings/catalog.tsx
  • src/renderer/features/settings/controls.tsx
  • src/renderer/features/settings/panels/AgentPanel.tsx
  • src/renderer/features/settings/panels/AgentTroubleshooting.tsx
  • src/renderer/features/settings/panels/AttachmentsPanel.tsx
  • src/renderer/features/settings/panels/CursorProviderCard.tsx
  • src/renderer/features/settings/panels/ProviderCard.tsx
  • src/renderer/features/workspace/CenterWorkspace.tsx
  • src/renderer/features/workspace/Composer.tsx
  • src/renderer/features/workspace/ComposerBanner.tsx
  • src/renderer/features/workspace/ComposerControls.tsx
  • src/renderer/stores/useAgentStore.ts
  • src/renderer/stores/useSettingsStore.ts
  • src/shared/constants.ts
  • src/shared/ipc-channels.ts
  • src/shared/types.ts
  • vite.main.config.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 win

Textarea/mode-switch/controls still gate on Claude-only installed, not the new connected.

The blocked, placeholder, and StatusHint logic were updated to use provider-aware connected (Line 110-112), but the textarea (disabled={disabled || !installed}, Line 284), ComposerModeSwitch (Line 357), and ComposerControls (Line 359) were left on the old Claude-only installed flag. For a Cursor-model session where Claude Code isn't installed, these controls stay disabled even though the session is otherwise connected — 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 win

Use the provider that set the banner state
agentName/isCursor are read from the current settings model, but lifecycle is global. If the model changes after a rate-limited or auth-required transition, 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 win

Consider constraining the mcp__<server>__<tool> identity components.

server/tool are taken verbatim from wire args with 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 value

Minor: StatusHint's installed prop now actually carries connected.

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 value

Document executablePath and discoveredModels in the agent.cursor section.

The main process reads agent.cursor.executablePath and agent.cursor.discoveredModels from settings (lines 222–223 in src/main/index.ts), but neither field is documented here. Users who manually edit settings.json or 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 value

Add cursor.updateCli() and clarify the "no run capability yet" phrasing.

Two gaps in the API documentation:

  1. cursor.updateCli() is exposed in the preload (line 313–314) and has a corresponding IPC handler, but is missing from this list.
  2. "auth only; no run capability yet" is technically accurate for the cursor.* surface (runs go through agent.send()), but is misleading in a PR titled "Cursor as a runnable second agent." Consider clarifying that the run capability is accessed through the existing agent.send() API, not cursor.*.
📖 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 win

Add an ESLint override for .cjs bridge scripts to unblock lint.

@typescript-eslint/no-var-requires fires here, but this standalone asset must use require (it runs via ELECTRON_RUN_AS_NODE outside the bundle and cannot use ESM import). 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 value

Consider 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.bridgeMaxConnections for 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4da94e5 and 53cb9ba.

📒 Files selected for processing (62)
  • CLAUDE.md
  • docs/agents/cursor-integration.txt
  • docs/architecture/security-model.md
  • docs/architecture/subsystems/agent-manager.md
  • docs/getting-started/configuration.md
  • docs/guides/using-the-agent.md
  • docs/reference/ipc-channels.md
  • docs/reference/settings.md
  • docs/reference/window-limboo-api.md
  • forge.config.ts
  • src/main/db/database.ts
  • src/main/index.ts
  • src/main/ipc/cursorHandlers.ts
  • src/main/ipc/index.ts
  • src/main/logger.ts
  • src/main/managers/AgentManager.ts
  • src/main/managers/SessionManager.ts
  • src/main/managers/SettingsManager.ts
  • src/main/managers/cursor/CursorAuthManager.ts
  • src/main/managers/cursor/CursorRuntime.ts
  • src/main/managers/cursor/bridge/bridgeAssets.ts
  • src/main/managers/cursor/bridge/hookRunner.cjs
  • src/main/managers/cursor/bridge/mcpBridge.cjs
  • src/main/managers/cursor/bridge/pipeServer.ts
  • src/main/managers/cursor/bridge/plainTool.ts
  • src/main/managers/cursor/bridge/toolDispatch.ts
  • src/main/managers/cursor/errors.ts
  • src/main/managers/cursor/exec.ts
  • src/main/managers/cursor/hooks.ts
  • src/main/managers/cursor/mcpConfig.ts
  • src/main/managers/cursor/permissions.ts
  • src/main/managers/cursor/rules.ts
  • src/main/managers/cursor/sessionFile.ts
  • src/main/managers/cursor/stream.ts
  • src/main/managers/cursor/translate.ts
  • src/main/managers/cursor/types.ts
  • src/main/managers/memory/memoryTools.ts
  • src/main/managers/search/searchTools.ts
  • src/main/secrets/SecretStore.ts
  • src/preload/index.ts
  • src/renderer/components/brand/ProviderIcon.tsx
  • src/renderer/features/activity/AgentConsolePanel.tsx
  • src/renderer/features/activity/PlanPanel.tsx
  • src/renderer/features/agent/models.ts
  • src/renderer/features/agent/status.ts
  • src/renderer/features/settings/catalog.tsx
  • src/renderer/features/settings/controls.tsx
  • src/renderer/features/settings/panels/AgentPanel.tsx
  • src/renderer/features/settings/panels/AgentTroubleshooting.tsx
  • src/renderer/features/settings/panels/AttachmentsPanel.tsx
  • src/renderer/features/settings/panels/CursorProviderCard.tsx
  • src/renderer/features/settings/panels/ProviderCard.tsx
  • src/renderer/features/workspace/CenterWorkspace.tsx
  • src/renderer/features/workspace/Composer.tsx
  • src/renderer/features/workspace/ComposerBanner.tsx
  • src/renderer/features/workspace/ComposerControls.tsx
  • src/renderer/stores/useAgentStore.ts
  • src/renderer/stores/useSettingsStore.ts
  • src/shared/constants.ts
  • src/shared/ipc-channels.ts
  • src/shared/types.ts
  • vite.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 login and loosen the key wording docs/agents/cursor-integration.txt:53-67
agent login should be cursor-agent login, and crsr_ 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.ts

Repository: BotCoder254/limboo

Length of output: 227


Update SETTINGS_VERSION in the docs to 15. src/shared/constants.ts sets SETTINGS_VERSION to 15, so docs/reference/settings.md is 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.

ComposerBanner wires its auth-required recovery action to retryAuth for both providers:

action = { label: 'Re-check sign-in', onClick: retryAuth };

But retryAuth() only calls this.probeHealth(true). For a Cursor-model session, probeHealth short-circuits to this.reconcileCursorLifecycle(), which only reads this.cursorAuth.getCachedState() — it force-probes only when the status is 'unknown'. Since CursorAuthManager.probe(false) is a no-op when already probed (if (this.probed && !force) return this.state;), a session stuck in not-authenticated never gets re-classified when the user signs in and clicks this button — they'd have to use the separate Providers card agent:cursorRefreshAuth instead, 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 does if (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.cjs

Repository: BotCoder254/limboo

Length of output: 4174


Wait for stdout to drain before exiting process.exit() immediately after process.stdout.write() can drop the decision JSON when stdout is a pipe. Use the write callback or process.exitCode so 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 -S

Repository: 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 -n

Repository: BotCoder254/limboo

Length of output: 8463


Close the single-flight window in loginStart.

this.loginChild is checked before await 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 a loginStarting flag cleared in finally).

🤖 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:


🏁 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/**' || true

Repository: 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]. The commandBase represents 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 of commandBase is 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 rule Shell(git) will match git status, git diff, and git 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 the command:args syntax [1][3]. For instance, Shell(npm:install*) allows npm install and its variations, but would not match npm 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.json files, 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 the command:args syntax to avoid inadvertently allowlisting a broad set of commands (like git or npm) when you only intend to permit a subset [5].

Citations:


Shell(rm) only covers the first command token. Path-qualified or wrapper-based invocations like /bin/rm or bash -c "rm …" still evade this deny list, so keep the hooks/decideToolUse layer 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, cursorAuth stays null forever (no fallback is set), and CursorProviderCard only renders ProviderStatusRow with "Checking the Cursor CLI…" — every action block there is gated on auth?.status === '...', so none render when auth is null. The user is left with no visible recovery path short of restarting the app (the cursorRefresh button that could self-heal is also gated behind a resolved auth).

🩹 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>
@BotCoder254 BotCoder254 merged commit 3cbed58 into main Jul 8, 2026
3 of 9 checks passed
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