All notable changes to this project will be documented in this file.
Fixes the subagent child-session pane fragmenting one flowing answer
into many small messages. Not on latest; install with
npm install @stablekernel/opencode-cursor@next to test.
- Fix: subagent pane shows one growing transcript instead of fragment
messages. Live activity snapshots were posted as a NEW message on
every flush (the 1.5s timer, every tool result, plus up to four more
on finalize), so a single subagent turn rendered as 5–20 fragments —
a paragraph split mid-sentence across messages. The seeded prompt
message's text part now grows in place: each flush PATCHes it via
part.updatewith the FULL cumulative transcript (the endpoint the child session's tool parts already use; opencode publishespart.updated, so live views re-render). Falls back to the old per-flush message only when the seed response carries no parts or the PATCH fails. Tool activity no longer duplicates into the transcript markdown — the child session'stoolparts already render it live on the subagent card — andresultSuffix+conversationSteps+ the activity line merge into the single final transcript instead of three extra messages.
The Cursor agent can now use installed opencode plugins (#104), their
skills mirror into .cursor/skills/, and the Cursor SDK's shell-parser
diagnostic no longer leaks into the opencode TUI prompt (#111).
Consolidates pre-releases 0.9.0-next.0 and 0.9.0-next.1.
-
Fix:
shell-parser: tree-sitter natives are unavailable…no longer appears in the TUI prompt.@cursor/sdk's bundled shell-parser emits a one-shotconsole.warnwhen its vendored tree-sitter natives fail to load (e.g. when opencode runs the plugin under Bun); opencode renders plugin stderr into the prompt, so the line surfaced visually even though it is benign (shell command analysis degrades toparsingFailed, which the SDK handles). The existingconsole.loginterceptor for the SDK's rules/skills load diagnostics now coversconsole.warnon both transports: in-process, known SDK warnings route throughpluginLog("warn")to opencode'sapp.loginstead of stderr; in the Node sidecar, matched lines forward as structured{ev:"log", level:"warn"}events over the JSONL protocol. Unrelatedconsole.warnoutput passes through unchanged, and the message remains visible in opencode logs (serviceopencode-cursor). -
Plugin tools bridge: other plugins' custom tools are now exposed to the Cursor agent. Custom tools from installed opencode plugins (e.g.
opencode-pty'spty_spawn,context-mode'sctx_*) are bridged to Cursor via anopencode-plugin-toolsMCP server (hand-rolled stdio JSON-RPC, no MCP SDK dependency, running under Bun). Permission handling mirrors opencode'sPermission.evaluate— last matching rule wins per ask pattern; a rule resolving toask(which can't prompt from Cursor) or no match fails closed. Controlled byforwardPluginToolsandpluginTools.include/exclude. -
Plugin-bundled skills are mirrored into
.cursor/skills/too. New lowest-priority scan roots indiscoverSkills: the opencode plugin cache (~/.cache/opencode/packages/) andskills//skill/dirs alongside file-based plugins, handling npm and git plugin specs. The per-turn re-sync also merges opencode's live skills inventory at the same priority. No new config surface — folds intoforwardSkills;skills.include/excludeand permission filtering apply unchanged. Project/global skills always win on duplicate ids; user-owned skills are never overwritten. -
Dependency bumps:
@opencode-ai/plugin^1.18.4 → ^1.18.21 (deps),@opencode-ai/sdk^1.18.18 → ^1.18.21 (dev). Consolidates dependabot PRs #105 and #106.
Live Cursor subagent activity: the task card behaves like a native opencode
subagent card — navigable while running, with a live activity subtitle — and
the child session carries the subagent's full transcript (#99).
- Cursor subagent transcripts in the TUI subagent view. The child session
created for a Cursor subagent (
tasktool) is now seeded with the subagent's own activity — its assistant text, thinking, and tool calls with args and results — rendered from Cursor'sconversationSteps, plus the final answer and duration. Previously only a post-completion activity summary appeared. Steps arrive as raw protobuf JSON, whereagent.v1.ConversationStep'smessageoneof serialises to a single camelCase key ({ assistantMessage: … },{ toolCall: { shellToolCall: … } }) rather than the{ type, message }shape of the SDK's public type; both are accepted. Transcript content is never truncated — the child session carries the subagent's full output. - Live activity on the Cursor subagent card. The SDK streams a local
subagent's nested activity via
taskUpdatepayloads on the parent task'stool-call-deltaupdates (text, thinking, tool-start/tool-result with id + name + input). Those events now write realtoolparts into the child session viapart.update(an upsert —session/processor.tscreates parts the same way), so thetaskcard shows a live↳ <Tool> <title>subtitle while the subagent runs (the TUI builds that line purely fromtoolparts in the child session —tui/routes/session/index.tsx:2227-2279). The child session is created up-front when thetaskcall starts and the task card'sstate.metadata.sessionIdis stamped while the subagent is still running (via opencode'spart.updateendpoint, mirroring the native task tool's execute-time metadata publication), so the card is clickable /ctrl+x-navigable live. Tool calls complete when their tool-result event arrives; any call left open is completed at finalize.cursor_delegatealso creates a child session seeded with its transcript, discoverable via the TUI's subagent panel.
The skills bridge (#90), per-model context limits and pricing (#89), and the compaction fixes (#91, #92).
-
Skills bridge: opencode skills are now mirrored into
.cursor/skills/for the Cursor agent. Both project-scoped and global skills are discovered (matching opencode's resolution order:.opencode/skills/,.claude/skills/,.agents/skills/, walked up to the git worktree root, plus global~/.config/opencode/skills/etc.), filtered through opencode'spermissionconfig, and materialised as a git-ignored mirror with agenerated: opencode-cursorsentinel. An<available_skills>catalogue is appended to the generated system rule so the Cursor agent can discover and load skills on demand. Works for the primary agent, Cursor sub-agents, andcursor_delegate(which passessettingSources: ["project"]).ask-permissioned skills are withheld (the ask prompt can't cross the Cursor boundary). Opt out withforwardSkills: false; manual override withskills: { include, exclude }. User-owned.cursor/skills/<id>/directories are never overwritten.config.skills.pathsdirectories are also scanned (lowest priority, first-wins on duplicate ids). Symlinked skill directories and symlinked supporting files are followed (broken links and symlink loops are skipped). Mirror diagnostics (withheld skills, oversized files, write failures) route through opencode's structured plugin logging rather than the terminal, matching 0.7.0's logging change.config.skills.urls(HTTP catalogs) and skills bundled inside opencode plugin packages are not yet supported. Skills bridge contributed by Wayne Simpson (@WayneSimpson). -
Per-model context limits and pricing generated from Cursor's docs. Each resolved model now carries a context-window limit and per-token pricing, emitted on the config channel opencode reads so the TUI can show cost and token counts. High-output frontier models carry a separate output-token limit. The data ships as a generated
src/model-limits.ts, produced from Cursor's published tables bynpm run sync:model-limits; a weekly CI job re-checks the committed data against those tables and fails if it has fallen behind (it verifies only — regenerating is a manual step). -
Dependency bumps:
@ai-sdk/provider3.0.13 → 3.0.14,@types/node26.0.0 → 26.1.2,vitest4.1.9 → 4.1.10 (dev-dependencies group, #88). -
opencode's threshold-triggered auto-compaction is now suppressed for Cursor models by default. The Cursor agent runtime already self-compacts on its own context threshold (
preCompacthook withtrigger: "auto"), so opencode-driven compaction was redundant — and it caused two real failures. First, the compaction turn runs with zero tools declared while the Cursor agent uses its own tools anyway, which opencode rejects (Tool call not allowed while generating summary) — mitigated in 0.7.1-next.1 (#91), and now avoided entirely for the automatic trigger. Second, compaction rewrites the transcript, which classifies as a divergence and mints a fresh Cursor agentId — and every distinct agentId permanently holds a guarded SQLitestore.db/-wal/-shmtriple thatagent.close()cannot release (it only flushes analytics and releases the executor lease). That descriptor growth fed an uncatchableEXC_GUARDprocess kill.Suppression uses a large
limit.input— the value opencode uses as its compaction threshold — leaving the reallimit.contextintact so the TUI context gauge and cost reporting still work. Manual/compactis unaffected and still relies on #91's fix.Tradeoff: this suppresses the proactive threshold trigger only, and opencode has no reactive context-overflow recovery wired up for this provider, so its transcript is no longer trimmed automatically. Ordinary turns send only the new message, but a cold replay (new session, expired agent, changed MCP set) resends everything; if that overflows the model the turn fails and
/compactis the manual recovery. Opt back out withprovider.cursor.options.autoCompaction: true. -
Fixed: auto-compaction (and manual
/compact) failed withTool call not allowed while generating summarywhenever the Cursor agent used a tool while summarizing. opencode declares zero tools on a compaction/summary turn, but the Cursor agent runs its own tools regardless; the provider forwarded that activity as provider-executedtool-callparts, which opencode's summary guard rejects. The provider now routes no-tools turns through the existing"reasoning"tool-display path, so Cursor's tool activity surfaces as reasoning text instead of crossing the tool-execution boundary. Manual/compactwas affected all along; auto-compaction became reachable only in 0.7.1-next.0, because #89 published real per-model context windows — pre-0.7.1 opencode sawlimit.context: 0for every Cursor model, and a zero context limit structurally disables the auto-compaction trigger.
Structured logging (#85), the stream-watchdog tool-phase budget (#86), and the session-pool title-generation race fix (#84).
- Structured logging via
client.app.log()instead of rawconsole.*. The plugin's own diagnostics (transport fallback warnings, per-turn debug traces gated onOPENCODE_CURSOR_DEBUG=1) now route through opencode's plugin logging API (service: "opencode-cursor") rather thanconsole.warn/console.error. Falls back toconsole.*when no client is available (e.g. running the provider standalone). - Cursor SDK's own "rules"/"skills" load diagnostics captured and forwarded.
@cursor/sdk's bundled local-exec runtime writes internal messages likeLocalCursorRulesService load completed meta={durationMs, ruleCount}andAgentSkillsCursorRulesService load completed meta={durationMs, ruleCount, skillCount}straight toconsole.log, with no public logger hook to redirect it. These are now recognized (in-process transport via a narrowly scopedconsole.loginterceptor; sidecar transport via the child process's own interceptor forwarding over the existing JSONL protocol) and re-emitted as structured opencode logs instead of raw terminal noise. Every otherconsole.logcall passes through unchanged. - Fixed: the stream watchdog killed healthy runs during long tool execution. The watchdog
re-armed only on mapped event types, so a long shell command, build, or test suite that streamed
nothing for 60s was cancelled and the turn lost. It now uses two budgets — an idle budget
(
OPENCODE_CURSOR_STALL_MS, default raised to120000) and a larger tool-phase budget (OPENCODE_CURSOR_TOOL_STALL_MS, default600000) applied while a tool call is in flight — and re-arms on any SDK update, including types the plugin doesn't model (progress/heartbeats). A tool-phase stall is terminal and names the in-flight tool.OPENCODE_CURSOR_STALL_MS=0still disables the whole watchdog; the tool-phase bound is independently disabled withOPENCODE_CURSOR_TOOL_STALL_MS=0. Open tool calls are reconciled onturn-endedand on a forced resend, so a dropped completion can't pin a turn to the 10-minute budget. - Fixed: a non-numeric
OPENCODE_CURSOR_STALL_MSstalled every turn immediately.Number("abc")isNaN;NaN <= 0isfalse, so the guard passed andsetTimeout(fn, NaN)fired at once. Env parsing now falls back to the default for non-finite values (an empty string still disables, preserving the historical escape hatch). - Fixed: an over-large stall budget overflowed to a ~1 ms deadline. A
setTimeoutdelay is stored as a signed 32-bit int, so anything above2147483647is silently clamped to1— and the tool-phase stall message tells operators to raiseOPENCODE_CURSOR_TOOL_STALL_MS, making the trap reachable by following the plugin's own advice. Setting it to e.g.999999999999stalled every tool-bearing turn within milliseconds while reportingno events for 999999999999ms. Both budgets are now capped at2147483647. - Fixed: opencode's title-generation call could poison a session's pool entry. opencode forks a
title-generation call on the same
sessionIDas the session's real first turn, concurrently and with an empty system prompt.classifyTurn's side-call detection only fires once a prior pool record exists, so on turn 1 both calls classified as "new" and both wrote to the pool — whichever agent-creation round-trip resolved last silently overwrote the other, leaving the session fingerprinted against the title prompt. Two fixes: the plugin'schat.paramshook now marks opencode'stitleagent call asproviderOptions.cursor.ephemeral = true(the provider already honored this flag but nothing set it), andwithSessionLock(a per-sessionIDasync lock) now wrapsagentRun's classify-then-acquire span so concurrent turns for one session serialize and the second call always observes the first's completed pool write. - Dependency bumps:
@cursor/sdk1.0.24 → 1.0.26,@opencode-ai/plugin(opencode-ai group).
Version-check UX cleanup from #79.
- Fixed: startup toast no longer suspends into the user's first prompt on slow networks.
The version-check toast previously ran
setTimeout(callback, 2000)and thenawait _versionCheckPromiseinside the callback, so a slow npm registry fetch could block the callback until after the user's first message was sent. The delay now runs after the promise resolves:_versionCheckPromise.then(async (result) => { await sleep(2000); showToast() }). The 2 s TUI-init pause is preserved; only the ordering changes. - Removed: terminal
console.warnfor update notifications. ThewarnIfStalefunction previously printed a multi-line warning to stderr on every startup when the plugin was outdated. This message is removed — the UI toast (introduced in 0.4.5) is the sole notification channel, avoiding duplicate noise in the terminal. - New:
scripts/opencode-plugins-refresh. Helper script that compares cached@latestplugin versions against npm and optionally clears outdated caches so opencode re-fetches the latest on next launch. Supports--check(exit 1 if outdated, CI/cron-friendly) and--force(clear without prompting). install.shnow offers to installopencode-plugins-refreshto~/.local/bin(step 4).PLUGIN_CACHE_PATHexported fromsrc/version-check.ts. Single source of truth for the opencode plugin cache path (cross-platform). Used by both the startup warning and thecursor_update_plugintool to build the removal command / actually clear the cache — removes the duplication that could cause them to diverge.warnIfStaleaccepts an optional pre-fetched version string.warnIfStale(prefetchedLatest?)now skips the registry call when the caller has already resolved it. Paired with a single_latestVersionPromisein the plugin that is shared by the console warning, the UI toast, and the system-prompt notice — so only one npm registry fetch happens per startup regardless of how many paths consume it.
- Fixed: reasoning/thinking variants showed as meaningless numbered entries for
most models. Cursor returns every variant of a model with the same
displayName(the model's own name), so the SDK-authoritative variant path (0.5.0) keyed off it and emitted collision-numbered junk — e.g.grok-4.5→cursor-grok-4-5,cursor-grok-4-5-2…-5;claude-opus-4-8→opus-4-8-2…-39— which the global model cache surfaced in the picker of every project. ~20 of 32 models were affected. Same-named presets now fall back to param-derived keys (low/medium/high/xhigh/max/fast); presets with genuinely distinct labels are still honored.
Native Cursor subagents: the Cursor agent's task tool now renders as a
navigable opencode child session instead of a dead "Unspecified Task" card.
- Cursor subagents are now navigable opencode child sessions. When the Cursor
agent runs its
tasktool, the plugin creates a real opencode child session (parentID= the current session) and links it to the task card, so it's clickable and reachable viactrl+x down— like a native subagent. The child session is seeded with the subagent's prompt and Cursor's returned transcript plus a real duration line (posted as user-role messages vianoReply; the public API can't synthesize assistant messages). Best-effort: if the opencode client is unavailable the card degrades to its previous, non-navigable form. The plugin hands its opencode client to the provider stream layer through an in-process bridge (src/provider/subagent-bridge.ts). - Fixed: generic Cursor subagents rendered as "Unspecified Task". Cursor's
proto zero-value
subagentType.kind("unspecified") is no longer forwarded as the agent label; the card now falls back to opencode's "General Task" (or the real subagent name when Cursor provides one).
Native-experience overhaul: in-process HTTP/1.1 transport under Bun, typed-error reliability, and full streaming fidelity.
- HTTP/1.1 in-process transport is now the default under Bun; the Node sidecar
is a fallback. opencode runs on Bun, whose
node:http2client breaks the Cursor SDK's streaming RPC (NGHTTP2_FRAME_SIZE_ERROR; oven-sh/bun#31499). The SDK now runs in-process over HTTP/1.1 + SSE (Cursor.configure({ local: { useHttp1ForAgent: true } })) — no Node child process required. Three transports are selectable via thetransportprovider option orOPENCODE_CURSOR_TRANSPORT:http1(Bun default),http2-direct(Node default), andsidecar(rollback). Resolution order is option →OPENCODE_CURSOR_TRANSPORT→ legacyOPENCODE_CURSOR_SIDECAR(1→sidecar,0→http2-direct) → per-runtime default. Roll back withOPENCODE_CURSOR_TRANSPORT=sidecar. - Typed error classification with per-kind recovery. SDK errors are
classified into
agent-not-found,agent-busy,rate-limit,network,auth,config, andunknown(by errorname/status/code, neverinstanceof— sidecar-forwarded errors arrive as plain objects).agent-busyresends once withlocal.force;rate-limit/networkretry with bounded backoff on the same agent;auth/configfail fast. - Idempotent resends. Every (re)send of a turn carries an idempotency key so a retry is a server-side dedupe, not a duplicate turn.
- Stream watchdog. A wedged run that streams nothing is bounded by
OPENCODE_CURSOR_STALL_MS(default60000): a pre-first-event stall cancels and force-resends once; a stall after partial output surfaces a terminal error rather than re-emitting the already-yielded prefix. Set to0to disable. - Fixed: silent-replay turns dropped their token usage. A multi-message interjection replays leading messages silently and streams only the last; the usage from the silent turns is now summed into the visible turn's reported usage instead of being lost.
- Live tool-input streaming. Cursor's
partial-tool-callupdates are bridged to incremental tool-input parts, so tool arguments stream as they arrive instead of appearing all at once when the call completes. - Thinking duration and compaction metadata.
thinking-completedcarries the reasoning duration, and Cursor's summary/compaction updates are surfaced as compaction events in the stream. - SDK-authoritative model variants. Variant construction prefers the SDK's
own
displayName/isDefaultmetadata rather than deriving it locally. autoReviewoption and multi-root delegation. NewautoReviewprovider option gates tool calls through Cursor's classifier-backed Auto review (best-effort, not a security boundary).cursor_delegategainsadditionalCwdsto combine extra workspace roots into a multi-root agent workspace.- Node floor raised to >=22.13 (
engines.node, from >=22.0), and only needed for thesidecarfallback transport. - Dependency bumps.
@cursor/sdk1.0.23→1.0.24,@opencode-ai/plugin1.17.14→1.18.4,@opencode-ai/sdk1.17.14→1.18.4.
- Fixed: subagents silently ran Cursor's server-side
fastdefault (e.g.composer-2.5in "fast" mode). A subagent inherits its parent agent's model but reached the provider with the model'soptions.paramsdropped, so thefast: "false"opencode default was lost and Cursor's server-sidefast: trueapplied. Each model's default params are now threaded through the provider options and re-applied as a lowest-precedence floor, sofaststays off unless a variant or per-request param explicitly opts in. SetOPENCODE_CURSOR_DEBUG=1to log the resolved model selection per turn (#71).
- Fixed: newly released Cursor models didn't appear locally without a manual
refresh. The auth loader warmed the model cache with a call that respected
the 24h on-disk TTL and no-opped while the cache was still fresh. It now
passes
forceRefresh: true, so the catalog is force-refreshed via a liveCursor.models.list()on every opencode startup (fire-and-forget, no added latency); theconfigandprovider.modelshooks keep serving the existing cache instantly (#65).
- Fixed: Cursor agent rejecting turns as "prompt injection" / "gaslighting."
The provider flattened opencode's system prompt into the user-message transcript;
Cursor's agent (which has its own system prompt) treated that as an injection
attempt. opencode's system prompt is now delivered through Cursor's authoritative
rules channel — written to a git-ignored
.cursor/rules/opencode.mdcand loaded viasettingSources— so opencode keeps control without being flagged. NewsystemPromptoption:"rules"(default),"message"(legacy inline),"omit". An explicitsettingSourcesopt-out of the"project"layer is respected (degrades to"message"delivery), and a failed rule write degrades gracefully instead of failing the turn. The generated rule carries agenerated: opencode-cursorsentinel so a user-ownedopencode.mdcis never overwritten or deleted (#56). - Added: warm resume for multi-message prompt interjections. When a resumed Cursor agent has several queued user messages, the earlier messages are now delivered as silent turns and the final one streams, instead of forcing a cold full-transcript replay. A tail mismatch safely falls back to cold replay so no messages are dropped (#57).
- Fixed: resumed turns against an expired Cursor agent are retried. A pooled
agent whose server-side state expired would pass
resume()locally but error inrun.wait(); the turn now retries once with a fresh agent when nothing was emitted yet, re-pointing the pool (#52). - Fixed: file/dir
@-mentions no longer fail the turn. The local Cursor SDK agent rejects attachment forms, so file/dir mentions are now noted as text (with a filesystem path forfile://sources) instead of attached; raw base64 data without a filename is guarded against inlining a blob into the note (#58). - Added: warn when the installed plugin lags the npm registry latest. A
throttled (24h) startup check compares the installed version against
latestand prints a one-line upgrade hint; skipped underCI/NO_UPDATE_NOTIFIER, never blocks init (#33).
- Fixed: installer fails on
opencode.jsoncfiles with trailing commas. The installer's naive JSON parse broke on JSONC's trailing-comma syntax (common in hand-edited configs). A dedicated JSONC parser (src/jsonc.ts) now strips trailing commas before parsing, so existing JSONC configs are detected and reused instead of clobbering or skipping them (#49). - Fixed: variant enum keys normalized and
'none'dropped for provider parity. Cursor's model params can advertise enum values like'none'for reasoning/effort levels that don't make sense as a model variant. Enum keys are now normalized (lowercased, trimmed) and'none'is excluded so the variant picker doesn't show a no-op variant. This aligns the Cursor provider's variant surface with other opencode providers (#48). - Fixed: redundant thinking variant dropped when an effort enum is present.
When a model advertises both a boolean
thinkingparam and aneffortenum (e.g.low/medium/high), the variant builder previously emitted a standalonethinkingvariant that duplicated one of the effort levels. The standalone thinking variant is now suppressed in favor of the effort enum so the picker isn't cluttered with duplicates (#43). - Dependency consolidation. npm deps bumped in one pass:
@connectrpc/connect-node1.7.0→2.1.2,@cursor/sdk1.0.19→1.0.20,@opencode-ai/plugin1.17.7→1.17.9,@opencode-ai/sdk1.17.7→1.17.9,@types/node25.9.3→26.0.0 (#44). GitHub Actions updates consolidated into a single dependabot group (#45), and@opencode-ai/*packages are now grouped together (#46).
createPlanmapping emits markdown as plain text. Cursor's plan-mode tool returned markdown that opencode rendered as a raw code block. ThecreatePlantool output is now mapped to a plain-text part so the plan reads as formatted prose in the opencode transcript.
- Fixed: missing
@connectrpc/connect-nodedependency.@cursor/sdkrequires@connectrpc/connect-nodeat runtime but didn't declare it as a direct dependency, so installs that hoisted differently could fail with a module-not-found error. It's now an explicit dependency (#31).
readtranscript label surfaces lines-read / total. The Read tool's transcript label now shows how many lines were read out of the total (e.g.read src/foo.ts (50/200)), so partial reads are visible in the conversation.- Removed obsolete sqlite3 native-binding self-heal. The workaround for a Bun/sqlite3 native-binding crash is no longer needed and has been removed.
- Dependency bumps.
@cursor/sdk1.0.18→1.0.19,@opencode-ai/plugin1.17.3→1.17.7, dev-dependencies group bumped.
- Fixed: Cursor's
fasttier is no longer silently forced on. The variant builder only mapped reasoning/effort params and dropped Cursor'sfasttoggle entirely, so it never reachedproviderOptions.cursor. Because Cursor marks the default variant of several models asfast: true(composer-2.5, composer-2, and the gpt-*-codex line), omitting the param meant opencode silently ran the fast tier with no way to opt out. Nowfastdefaults OFF — fast-capable models seedoptions.params.fast = "false"(sent every turn, and pinned into each reasoning variant so picking a reasoning level can't re-enable it) — and afastpicker variant lets you opt back in. Override per model viaprovider.cursor.models.<id>.options.params.fast. - Installer detects and reuses
opencode.jsonc. The installer now recognizes bothopencode.jsonandopencode.jsoncand reuses whichever exists instead of always writingopencode.json. The plugin is also pinned to@latestso opencode re-resolves to the newest release on each startup (#24). - Grep and glob tool blocks get a distinguishing title. Cursor's
grepandglobtools both render as search-result blocks; they now carry distinct titles so you can tell them apart in the opencode transcript (#22).
- Fingerprint-guarded session reuse, now the default (
session: "auto"). Previously the provider created a fresh Cursor agent every turn and re-sent the whole transcript (robust but cache-hostile and increasingly costly as a conversation grows), while opt-insession: trueresumed one agent per session but could drift from opencode's history (edits/reverts/compaction) and was disturbed by non-chat side calls.session: "auto"(the new default) hashes only the parts opencode replays verbatim — the system prompt and the user-message sequence — and classifies each turn: a clean continuation resumes the pooled agent and sends only the new message (maximizing prefix cache hits); a side-call (system prompt differs, e.g. title generation) runs a fresh ephemeral agent without touching the pool; a divergence (edit/revert/compaction/queued messages) or a failed resume falls back to a fresh agent + full transcript and re-pools. Worst case is one self-healing full replay — never worse than the old default.session: trueis now an alias for"auto";session: falsekeeps the always-fresh behavior. SetOPENCODE_CURSOR_DEBUG=1to log per-turn classification and cache usage. - Session reuse survives opencode restarts. The pool's fingerprint records
persist (best-effort) to
~/.cache/opencode-cursor/session-pool.json(7-day TTL, 200-entry LRU cap), so the first turn after a restart resumes the session's Cursor agent — whose conversation lives in Cursor's own checkpoint store — instead of paying a cache-cold full-transcript replay. - MCP servers are re-forwarded live, per turn, with OAuth mapping. The
confighook's startup snapshot meant mid-session MCP enable/disable never reached the Cursor agent. Thechat.paramshook now forwards the live set each turn (client.mcp.status()for runtime truth,client.config.get()for launch specs). Because a resumed agent keeps its original servers, a changed set forces a fresh agent (full-transcript replay, re-pooled) so the new servers take effect — the session fingerprint carries anmcpHashfor this. Remote servers with a registered OAuth client are forwarded with a Cursorauthblock so the agent runs its own OAuth flow; servers needing OAuth without a shareableclientId(dynamic registration) are skipped with a one-time toast instead of forwarding a spec that would 401. - Fixed: text/reasoning streamed after a tool call rendered above the tool block. The earlier ordering fix closed parts on text↔reasoning transitions, but blocks-mode tool parts were emitted while the narration part stayed open — and hosts position a part where it started. Open text/reasoning parts are now closed before tool parts are emitted (except for buffered edit calls, which emit nothing until their result arrives, so narration isn't split needlessly).
- Tool outputs are included (truncated) in flattened transcripts. The
fresh/divergence/
session: falsereplay paths previously dropped Cursor tool results to bare[result of X]placeholders, so a fresh agent re-read a transcript with prior tool outputs missing. Outputs are now inlined and capped (2,000 chars per result, 500 per tool-call args) so context stays faithful without unbounded bloat. - Patched transitive dependabot vulnerabilities via overrides.
undici,tar, andnode-gyppinned via npmoverridesto clear advisories in transitive dependencies (#16).
- More Cursor tools map onto opencode's native tool renderers (blocks mode).
Following the
edit→ diff-viewer mapping, Cursor'sshell,read,write,glob,grep,ls,updateTodos, andtasktool activity is now surfaced under opencode's registeredbash,read,write,glob,grep,list,todowrite, andtasktools, and Cursor's web search (which runs as an MCP tool) maps onto opencode'swebsearchrenderer — so opencode renders its native UI (shell console, file viewer, todo checklist, subagent card, search results, …) instead of genericcursor_*blocks. Cursor's arg shape is translated to opencode's (e.g.path→filePath,globPattern→pattern,fileText→content); calls stay provider-executed (display-only, never re-run on disk). - Cleaner fallback blocks for tools without an opencode counterpart.
readLintsanddeletenow render as formattedcursor_*blocks (a diagnostics list / a one-line confirmation) instead of raw JSON, and every MCP tool'scontentarray is flattened to readable text. Anything else — or a result with an unexpected shape — still falls back to a safecursor_*block with the raw payload.
Pre-releases:
0.1.0-rc.1and0.1.0-rc.2were published to the npmnextdist-tag for validation ahead of this stable release.
Initial public release. A complete opencode integration for Cursor built on the
official @cursor/sdk: a streaming chat provider, an auth/config/model plugin,
and a permission-gated delegation tool surface.
- Cursor provider backed by the official
@cursor/sdk— drives a local Cursor agent (Agent.create/agent.send) and translates itsonDeltacallbacks into AI SDKLanguageModelV3stream parts (text, reasoning, tool activity, usage). Implements bothdoStream()anddoGenerate(). - Per-request controls via
providerOptions.cursor—mode(agent/plan),params, andthinkinglevel; works with opencode's model variant picker. - Model variants auto-generated from
Cursor.models.listparameters: one per reasoning/effort level a model advertises (boolean params collapse to a single on-variant). opencode's plan agent maps to Cursor plan mode. - Session reuse (
session: true) — keeps one Cursor agent per opencode session viaAgent.resume()across turns, with automatic fallback to a fresh agent. A run wedged by a crashed/duplicate process is recovered by retrying the send once with the SDK'slocal.forceescape hatch. (Superseded by the fingerprint-guardedsession: "auto"default; see Unreleased.) - Native diff viewer for Cursor edits (blocks mode). A Cursor
edittool call is now surfaced under opencode's registerededittool with its real unified diff inmetadata.diff, so opencode renders its built-in diff viewer instead of a generic block. The requiredoldString/newString(which Cursor does not expose) are reconstructed from the diff; the call is provider-executed so they are never applied to disk. Any edit without a usable diff (errors, unexpected shapes, or a host without a registerededittool) falls back to a safecursor_editblock. Other Cursor tools (shell/read/mcp/…) remain prefixedcursor_*blocks. toolDisplayprovider option ("blocks"default |"reasoning"):"blocks"(default) emits structured, provider-executed dynamictool-call/tool-resultparts so opencode renders native tool blocks. Names arecursor_-prefixed and sanitized (shell→cursor_shell,myserver/find_symbol→cursor_myserver_find_symbol) so they can't collide with opencode-registered tools, and carryproviderExecuted: true+dynamic: trueso ai v6'sparseToolCallaccepts them without registered-tool validation. Tool-results use the V3-specresult+isErrorfields. A tool call whose completion never arrives (run errored/cancelled mid-tool) is closed with a synthetic error result so the block never dangles as "Tool execution aborted", and a run that ends with statuserrorsurfaces the failure instead of finishing silently."reasoning"renders Cursor's internal tool activity (including the real MCP tool name) as concise[tool] …reasoning lines. Always safe — tool calls never cross opencode's tool-execution boundary; the fallback for older/non-V3 hosts (provider.cursor.options.toolDisplay: "reasoning").
- Automatic Node sidecar — opencode runs on Bun, whose
node:http2client is incompatible with the Cursor SDK's long-lived streaming RPC (NGHTTP2_FRAME_SIZE_ERROR), causing native tool calls to execute but never report completion. When Bun is detected andnodeis onPATH, the SDK agent is hosted in a Node child process and driven over a JSON-lines stdio protocol; the provider is otherwise unchanged. Under Node the SDK runs in-process. Override withOPENCODE_CURSOR_SIDECAR=1(force on) orOPENCODE_CURSOR_SIDECAR=0(force in-process / silence the Bun warning).
- opencode plugin (
@stablekernel/opencode-cursor, resolved via the package's./serverexport): auth hook (API-key login; the key is validated on first use rather than at login), config hook (auto-injectsprovider.cursor),provider.models()(live catalog viaCursor.models.list), and thecursor_refresh_modelstool. The auth loader warms a key-independent catalog cache so the model picker is populated on first authed load (and restart) rather than showing only the fallback snapshot. - MCP server forwarding — opencode's configured
config.mcpentries are translated to CursorMcpServerConfigand passed to the local agent so it can use the same servers. Opt out withprovider.cursor.options.forwardMcp. - Model discovery with a 24-hour cache (keyed by key fingerprint) and a built-in fallback snapshot (composer-2.5, claude-opus-4-8, claude-sonnet-4-6, gpt-5.5) for use without an API key.
cursor_cloud_agent— launch a Cursor cloud (background) agent on a remote repo viaAgent.create({ cloud: { repos, autoCreatePR } }); returns the agent id, terminal status, result, and PR url. Progress is collected fromrun.onDidChangeStatus,onStep, andonDelta.cursor_delegate— run a single local Cursor turn as a permission-gated, auditable opencode tool call (reuses the provider'sacquireAgent+streamAgentTurnplumbing). Both tools honor opencode'spermissionconfig viaToolContext.askand are fail-closed when no permission gate is present.
- Provider debug tracing — opt-in via
OPENCODE_CURSOR_DEBUG=1. - End-to-end CI: unit tests on two Node versions plus a full integration test (opencode loads the plugin, lists models, optionally runs a live chat turn).