Skip to content

feat(agents): Cursor Agent CLI as a builtin agent - #1128

Merged
eneskirca merged 59 commits into
eneskirca:mainfrom
builtbyd3v:feat/cursor-agent
Oct 16, 2026
Merged

eneskirca merged 59 commits into
eneskirca:mainfrom
builtbyd3v:feat/cursor-agent

Conversation

@builtbyd3v

@builtbyd3v builtbyd3v commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Cursor

Cursor on Liquid Glass

New

  • Cursor nodes. Add a Cursor node from the + menu, the Dock, ⌘K or its keybinding. It runs cursor-agent and works like the other agent nodes.
  • Live status. RUNNING, done and unread badges, plus NEEDS YOU when Cursor waits for an approval, when one of its subagents waits for an approval, when it asks you a question in its question box, or when plan mode asks "Ready to build?".
  • Context meter, title and rename. The meter, the node title and the chat name all come from Cursor's own chat store. Renaming the node renames the Cursor chat.
  • Chat view and transfer. ⌘M shows the Cursor conversation and its composer sends messages. Transfer conversation hands a Cursor chat to another agent.
  • Resume. A restart, a reboot, Switch model and Pause/Resume all come back to the same Cursor chat.
  • Orchestration. Context links work in both directions (Claude can read a Cursor chat, Cursor can read a Claude transcript). Cursor can use canvas control, and its subagents show as cards.
  • Permission mode and models. Auto launches with --auto-review, Plan with --mode plan, Bypass with --force, Ask each time with no flag. Switch model lists Cursor's own models (cursor-agent models).
  • Plan usage. The usage pill and popover show your Cursor plan: total, Auto and API, with the reset date. It reads the login cursor-agent already stored and asks Cursor's own usage endpoint, the same one the CLI's /usage uses. On by default, with a toggle in Settings → Usage.
  • SSH projects. Status hooks are installed on hosts that have cursor-agent.

Improved

  • PtyManager.sendText and sendEnvelope send Enter as a separate write for agents that need it (only Cursor today). Cursor drops an Enter that arrives in the same write as a paste, so before this, chat, phone, trigger, canvas write and agent send/reply messages were pasted but never submitted. Before the paste and again before the Enter, the pane is read: if Cursor's approval, question or plan box is on screen, nothing more is written, so a send never answers a dialog. An agent message held back this way is reported as busy (retryable), not as a gone session. After an app restart, a Cursor node that has not been opened yet is recognised from the saved workspace, so it gets the split too.
  • After nodeterm relaunches an agent itself (restart, model switch, wake), the "it exited" flag is cleared at once. Cursor fires no start hook on --resume, so the chat composer used to refuse until the next turn.

Good to know

  • One change outside Cursor: Grok no longer gets the model gateway's default model or the gateway submenu under Transfer. Grok has its own model catalogue, so a gateway model id was never a valid Grok model.
  • Cursor shows up in the agent menus for everyone. Its hooks are only installed when cursor-agent is found, so nothing new runs for people who don't use it.
  • NEEDS YOU for Cursor comes from reading the pane, because Cursor fires no hook when it shows a dialog or its question box. It reads only while a tool call is pending, right after a turn ends, or when a working turn goes quiet: up to 3 reads, one capture in flight per node, and it stops on an unreadable pane.
  • Heads-up from testing: in all 6 test runs, after the question box was answered, cursor-agent ended that turn with status: error ("WritableIterable is closed" in the pane), so the node shows TURN FAILED. This happened in a plain terminal with only a hook logger, outside nodeterm. I haven't found the cause.
  • Not supported yet (written up in docs/cursor-agent.md): on SSH nodes the chat view, meter and context link (the chat store is SQLite on the host), and /loop cards (the Cursor CLI has no loop feature). Windows gets no Cursor hooks until a cmd-safe hook command is measured.
  • Downgrade: an older build shows a saved Cursor node as a plain terminal and launches nothing.
  • Tested on macOS with cursor-agent 2026.10.01, live in the dev app, including Liquid Glass in dark and light (the glass trap probe found nothing). I haven't run it on Linux, Windows, the Server Edition or the phone. @eneskirca the phone app reads the status mirror and chat.page for Cursor the same way as the other agents; worth a look on the iOS side.
  • Known limit: the restart lookup trusts the agent id in .nodeterm/project.json, so a hand-edited file can give a non-Cursor node the split Enter until that node is opened.
  • Typecheck, both builds and every new test pass. The only failing tests also fail on main.

Launches cursor-agent; a prompt rides behind the agent subcommand because
the CLI ignores -- for subcommand dispatch (measured on 2026.09.23).
Joins no capability list yet: spawn, terminal title and process status.
Monochrome mark from @lobehub/icons-static-svg 1.95.1 (MIT), inlined in
currentColor like grok and copilot so it follows the theme in menus,
node headers, the RUNNING badge and the notch HUD.
Flags go before the agent subcommand (measured: root options reach the
session from either side). plan -> --mode plan, bypass -> --force; auto
and acceptEdits emit nothing so the default never widens. Cursor and grok
share OWN_MODEL_CATALOGUE so gateway models and the gateway default model
never reach them.
Joins AGENT_HOOK_TARGETS: normalizeCursor (conversation_id recorded as the
session id), a merge installer for the shared ~/.cursor/hooks.json, a silent
gate-safe hook command, fixtures captured from cursor-agent 2026.09.28 and
docs/cursor-agent.md with the device checklist.
Resolved: kept the measured flag-placement comment, merged both doc halves,
and unified the two cursor capability test blocks into one.
…sfer and title

Reads cursor-agent's SQLite chat store with node:sqlite (read-only, located strictly by
chat id, no cwd fallback). Joins CHAT_CAPABLE, CHAT_LOCAL_ONLY, CONTEXT_LINK_CAPABLE,
TRANSFER_SOURCE_CAPABLE and TITLE_READ_CAPABLE. SSH-remote nodes degrade honestly and
never read the local disk. Fixtures are synthesized; an opt-in real-store test prints
counts only.
Measured live in the dev app: a single read 1.5 s after preToolUse found
no dialog yet, so a node waiting on Cursor's approval stayed RUNNING. Read
again at 4 s and 10 s while the call is still pending; blocked is emitted
once, and a long approved command costs at most three reads.
A bare cursor-agent asks before every command (allowlist default), which
made a new Cursor node prompt for each task. Map nodeterm's Auto (the
default) to Cursor's Auto-review classifier, the same shape as claude's
auto mode. Measured: a safe command ran unprompted, footer Auto-review.
Bypass still maps to --force; Ask each time stays bare.
- Route cursor raw hook events through one applyCursorRaw in both shells, so the
  subagent tracker runs and a child tool event cannot re-point the node's session.
- Approval watch skips child tool calls by isCursorChildToolEvent.
- PtyManager.sendText sends Enter as a separate write for SEPARATE_SUBMIT_AGENTS, so
  chat, phone and trigger sends submit in cursor.
- Renderer retries a cursor model catalogue that came back empty.
- installCursorHooks writes nothing on Windows until a cmd-safe command is measured.
- docs/cursor-agent.md: drop stale local-only and single-read claims.
…ents, feed the activity line

The claude raw branch recorded Cursor's (often child) chat id from claude.sh, so pty:destroy
released the wrong session and the parent tail leaked. The subagent tracker gains release(nodeId),
called by both shells' releaseNodeTails. applyCursorRaw records parent tool calls and turn ends
for the phone's activity line; Shell reads like Bash.
readAgentSessionName resolves capabilityAgentId, so a custom agent on cursor reads the cursor
store. performExitPhase asks submitsSeparately and waits EXIT_KEY_GAP_MS after Ctrl-U: measured
in tmux, Ctrl-U+/quit in one burst exited 0/10, with the gap 10/10.
…ings search

The --model sentence derives from MODEL_SWITCH_CAPABLE labels. Cursor gets the
get-linked-context skill wording. The context menu probes cursor models only for cursor nodes.
Settings search finds cursor.
sessionStart fires before any prompt on a plain launch but not on --resume <minted id>, which
is how nodeterm launches every node, so it stays unsubscribed.
…base transfer

- Cursor's colour moves from #6b7280 (OKLab 0.018 from Grok) to #d9468f,
  at least 0.12 from every swatch; a test pins agent swatch distance.
- LINKED_CONTEXT_SKILL_AGENTS (claude, cursor) replaces the per-agent fork
  in noteLink, so claude-base and cursor-base custom agents match.
- handoff.build resolves a custom agent's base before picking its reader,
  so a cursor-base node's Transfer works.
…Cursor in README

- Shortcut markers this branch added now read note:, and its added em
  dashes are commas or colons.
- CursorApprovalWatchDeps.delayMs had no caller; readsMs stays.
- Comments say up to three pane reads (CURSOR_APPROVAL_READS_MS), the
  stop status note matches the captured fixture, and the docs record the
  live meter check, the blocked state and the relaunch exit clear.
- README's agent list names Cursor.
Comment thread src/core/agents/hooks/cursor.test.ts Fixed
Comment thread src/core/agents/hooks/cursor.test.ts Fixed
Comment thread src/core/agents/hooks/cursor.test.ts Fixed
Cursor's AskQuestion form fires no tool hook on show, answer or Esc
(measured again: only afterAgentThought until the turn's stop). The
approval watch now hangs its bounded pane reads on a quiet stretch of a
working turn too (after beforeSubmitPrompt or a tool post, cancelled by
preToolUse), and a box at the bottom of the pane emits waiting once, as
Claude's AskUserQuestion does. The match is structural (footer, Question
N of M, an option row), so the answered summary left in the transcript
never matches. Fixture: live captures and hook payloads.
Reads the cursor-agent login (macOS Keychain via /usr/bin/security, else the
CLI's auth.json) and asks the same endpoint the CLI's /usage uses,
DashboardService/GetCurrentPeriodUsage on api2.cursor.sh. Shows total, Auto
and API percentages with the billing-cycle reset. Visible by default with a
Settings toggle. Not signed in or an expired token shows no row; a network or
5xx failure keeps the last good numbers of the same login.
The pill showed only a provider's worst limit, so Cursor read 100% (the API
bucket) while the plan total was far lower. Cursor now shows total, Auto and
API ("35% Cursor · 33% auto · 100% api"); every other provider keeps its single
worst-limit segment, text unchanged.
@builtbyd3v
builtbyd3v marked this pull request as ready for review October 4, 2026 22:25
…t path

Upstream's typed chat input is claude only, so a Cursor chat prompt still
goes through sendText's paste, then a separate Enter. The per-pane write
chain now serializes every sendText and sendEnvelope, which replaces the
Cursor-only submit chain dropped in the merge. Pin both: cursor pastes and
submits separately, claude takes the typed path, and an envelope waits for
the Cursor Enter.
Cursor's hook installers already run only for a consented agent (the local
registry and the remote installAgentHooks plan). Finish the rest of the
consent model for it:

- local skills go to ~/.cursor/skills, which cursor-agent loads before
  ~/.claude/skills, so canvas control does not depend on claude's consent
- a decline deletes cursor.sh with the other agents' scripts
- on an SSH host its skills ride ~/.agents/skills, and a removal strips only
  our entries from the shared ~/.cursor/hooks.json
…nto a dialog

Resolve the pane's agent from create() or, after a restart, the workspace records when every placement agrees. Agent envelopes now paste, wait, then send a bare Enter inside their serialized write. The bare Enter is skipped while a Cursor approval, AskQuestion or plan prompt shows; an empty or failed capture still submits.
The byte cap let the newest blob through at any size. A blob over the whole budget is now skipped.
The 200 body was read with res.json() and no size limit. It is now read up to 256 KB; past that the read is cancelled and the row is unavailable.
…nvelope as busy

Both Cursor splits now read the screen before pasting and write nothing while an approval, AskQuestion or plan prompt shows. A blind capture still sends. An envelope refused before the paste returns 'dialog', which delivery maps to targetBusy/blocked (retryable, queued) instead of targetGone. Once the paste is in, the envelope counts as written, so a withheld Enter reports stalled.
Comment thread src/core/usage/cursor-usage.ts Dismissed
builtbyd3v and others added 5 commits October 8, 2026 15:21
Conflicts resolved keeping both sides:
- shared/agents/config.ts: cursor joins CANVAS_CONTROL_CAPABLE; main's POSIX_TOOL_SHELL_ON_WINDOWS kept.
- core/agent-integrations.ts: main's Windows .cmd hook files kept; cursor stays POSIX-only (no Windows hook).
- core/agent-launch.ts: copilot's `--session-id=<uuid>` and cursor's `--resume <uuid>` mint grammars both kept.
- core/canvas-control-core.ts: both imports (MODEL_SWITCH_CAPABLE + shim-invocation).
- renderer/lib/verifyPanel.ts: usesLinkedContextSkill (cursor reads the skill) with main's hostPlatform-aware shim command.

Also de-flakes pty-manager.separate-submit.test.ts: the first rig's cold `import('./pty-manager')`
(1-3.5 s, more under a parallel run) ran inside that test's 5 s budget and timed it out. The module
now loads once in a file-level beforeAll with its own timeout; the test drops from seconds to ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main now registers 54 commands and this branch adds node.newAgent.cursor,
so the 54-key pool ran out (expected 54 >= 55). Home and End pass
normalizeBindingForCommand like the other navigation keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@eneskirca
eneskirca enabled auto-merge (squash) October 16, 2026 11:00
review and others added 2 commits October 16, 2026 14:17
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@eneskirca
eneskirca merged commit 295f9ed into eneskirca:main Oct 16, 2026
5 checks passed
eneskirca pushed a commit that referenced this pull request Oct 16, 2026
#1128 landed while this PR was open. It is not a small addition: `cursor`
joined almost every capability list at once — hooks, resume, subagents,
context links, the usage meter, the chat view, rename, title read, canvas
control, permission modes and model switching — so it gets its own paragraph
rather than a clause.

What it did NOT join is stated too: RECURRING_CAPABLE and BRANCH_CAPABLE are
still Claude alone, so `/loop`, `/schedule`, `/cron` and Branch do not appear
on a Cursor node.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

3 participants