# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.22.1] - 2026-08-19

### Fixed

- **Tool call ids emitted to OpenCode are now portable across providers.** Cursor can identify a call with a composite `<clientCallId>\n<providerCallId>` string. That id was passed straight through into OpenCode session history, where it exceeded OpenAI's 64-character `call_id` limit — so switching a session from a Cursor model to an OpenAI model failed on replay with `Invalid 'input[N].call_id': string too long`. Oversized or multi-line ids are now emitted as a deterministic 29-character alias, and the upstream id is retained internally for Cursor exec correlation and tool-result pairing.

## [0.22.0] - 2026-08-19

### Added

- **OpenCode v2 plugin support alongside v1.** The package now publishes a dual `./server` entry: OpenCode v1 invokes its `server` function, while v2 decodes and invokes its Promise-domain `setup`. The v2 adapter registers Cursor OAuth through integrations, providers/models through catalog transforms, request routing through `session.model.request`, and read-only codebase search through the v2 tool domain.

### Changed

- **Plugin internals are shared across runtime adapters.** OAuth/token lifecycle, proxy startup, model discovery, index management, and tool implementations moved to `src/plugin-core.ts`; `src/index.ts` remains the thin v1 hooks entry.
- **Setup deduplicates both config generations.** Existing v1 `plugin` string/tuple entries and v2 `plugins` string/`{ package }` entries are recognized without writing a duplicate registration.
- **v2 request routing avoids the placeholder rewrite.** The v2 adapter pins the live `/v1` or `/v1/code` proxy endpoint and session headers through `session.model.request`. Permission-sensitive plugin bridges remain v1-only until v2 exposes equivalent permission, abort, and directory contracts.

## [0.21.2] - 2026-08-16

### Changed

- **Runtime identity and transport artifact clarification**: Model-visible `<cursor_workspace_identity>` and `RequestContext.mcpInstructions` now explicitly instruct models that they are executing in an OpenCode interactive session (not Cursor IDE or Cursor Agent CLI) and that `~/.cursor/projects/...` directories are internal transport artifacts to ignore.
- **Subagent execution contract clarification**: Prompt instructions in `mcpInstructions` now clarify that `task` subagents are orchestrated and executed by OpenCode across its configured multi-model backends rather than constrained by Cursor's internal subagent model catalog.

## [0.21.1] - 2026-08-14

### Added

- **Grok 4.6 and Grok 4.6 Fast** in the offline fallback catalog, setup seed, and required-model pin (`grok-4.6`, `grok-4.6-fast` with `low`/`medium`/`high`/`xhigh`). Live `cursor-grok-4.6-{effort}[-fast]` slugs remap onto those families. Default effort is `high`.

### Fixed

- **AvailableModels gzip bodies were dropped.** Unary discovery advertised `accept-encoding: gzip, br` but parsed the raw body as JSON, so live catalog refresh silently fell back to the static list and never advertised Grok 4.6. Discovery now gunzips unary payloads before JSON/protobuf decode.

### Changed

- **Grok Fast pricing** matches cursor.com: `grok-4.5-fast` / `grok-4.6-fast` are $4/$12/$1; non-fast `grok-4.5` / `grok-4.6` stay $2/$6/$0.5.

## [0.21.0] - 2026-08-10

### Removed

- **Delegated code-mode executor.** Deleted `cursor_code_execute`, the plugin-owned worker (`plugin-code-{contract,executor,worker}`), exact `x-opencode-code-mode-*` marker binding, and the `codeMode.engine` / `CURSOR_OPENCODE_CODE_MODE_ENGINE` knob (`auto`/`local`/`legacy`). Code mode again uses only the in-proxy sandbox (`code-session` / `code-sandbox`). The OpenCode `tool` map is provider-global, so a registered executor leaked onto every non-Cursor model; removing it restores the single model-facing tool name `code` for proxy traffic only.
- **`@modelcontextprotocol/sdk` dependency.** It existed solely for the deleted plugin executor.

### Fixed

- **Code-mode accumulator catalog leak.** Interaction-lane bookkeeping for the outer `code` call was validated against the inner OpenCode client catalog (which never contains `code`), producing `Unknown tool "code". Available: bash, read, … Closest: …` and contradicting the injected doctrine. The streaming accumulator is now inert whenever code mode is active; the exec lane / `CodeSession` owns the call exclusively.
- **Client-stall watchdog killed long pre-wave scripts.** Taking code-session ownership disarmed only the transport watchdog, leaving the 120s client-stall lane armed. Scripts that worked before the first wave (as the one-call doctrine encourages) were force-flushed or checkpoint-retried mid-session. Both lanes are now disarmed; `CodeSession`'s own `CODE_EVENT_TIMEOUT_MS` remains the backstop.

### Changed

- **Code tool description ergonomics.** Non-identifier MCP tool names render as `tools["name"](args)`; budget pressure stubs every Catalog name with `codemode.describe` instead of hard-breaking; Runtime API documents `codemode.call` / `monitor` / `undefine`; `files` documents object map, `[{path,content}]`, and `[[path,content]]` shapes.

### Notes

- **Rollback.** Disable code mode with `provider.cursor.plugin.codeMode.enabled: false` or `CURSOR_OPENCODE_CODE_MODE=0`. The `engine` knob is gone; leftover `codeMode.engine` keys in `opencode.json` are ignored.

## [0.20.6] - 2026-08-07

### Removed

- **Static codebase-search system guidance.** Deleted the provider-specific `<cursor_codebase_search_guidance>` blocks, system-transform and compaction-marker hooks, checkpoint tag parsing, and their dedicated runtime module. Codebase search remains available only through the explicit `cursor_codebase_search` tool, so the plugin no longer mutates any provider's system prompt for search.

## [0.20.5] - 2026-08-08

### Fixed

- **CI: install zsh for sandbox tests.** The `code-sandbox-exec` zsh-specific suites assume `zsh` is present; Ubuntu runners ship only `bash`. Installing `zsh` alongside `ripgrep` in both CI jobs restores the declared environment without skipping the gate.

## [0.20.4] - 2026-08-08

### Fixed

- **CI: raise wall budget for ripgrep install.** After installing `ripgrep` the focused gate’s real `rg` path executes (previously failed fast), increasing wall from 19s to 22s and exceeding the 20s budget. The workflow default budget is now 30s (`CI_BUDGET_SECONDS` and `workflow_dispatch` default) to accommodate the declared tool without flapping.

## [0.20.3] - 2026-08-08

### Fixed

- **CI: install ripgrep for focused gate.** The `tool-bridge focused` suite exercises `plugin_code_executor` grep via `rg`. Ubuntu runners do not ship `rg` by default, so the gate failed with `Executable not found in $PATH: "rg"` (run 31215641104). The workflow now installs `ripgrep` via `apt-get` in both `quality` and `build-and-test` jobs before `bun install`, providing the declared tool without skipping or weakening assertions. No source gate logic was changed.

## [0.20.2] - 2026-08-08

### Removed

- **Automatic dynamic codebase context injection.** Deleted the `CursorContextInjectionManager` implementation (`src/context-injection.ts`), its `provider.cursor.plugin.contextInjection` configuration (`enabled`, `maxBodyChars`, `maxAgeMs`), `CURSOR_OPENCODE_CONTEXT_INJECTION` environment handling, session bookkeeping, `chat.message` capture, and `experimental.chat.system.transform` append path that automatically searched the codebase and injected `<cursor_codebase_context>` for non-Cursor providers. The flapping system-prompt pipeline (94,771 <-> 99,222 bytes) and repeated prefix-drift frames are eliminated. Explicit `cursor_codebase_search` and static `<cursor_codebase_search_guidance>` remain; `experimental.chat.system.transform` now emits only the static guidance block.

### Changed

- **System prompt stability for non-Cursor providers.** Non-Cursor model sessions no longer receive automatic dynamic context; only the bounded static search guidance variant is emitted. Compaction still suppresses guidance and emits no dynamic context.

### Fixed

- **Regression coverage for no automatic injection.** Added `testNoAutomaticContextInjection` and `testCompactionGuides` proving non-Cursor system transforms do not auto-search or append `<cursor_codebase_context>` while explicit `cursor_codebase_search` continues to return bounded local evidence.

## [0.20.1] - 2026-08-08

### Added

- **Plugin-owned colocated code executor.** `cursor_code_execute` now provides the delegated local runtime without an OpenCode fork. Its bounded worker-backed `node:vm` program can invoke permission-aware read, glob, grep, edit, write, bash, plugin-owned, and configured MCP hosts; cancellation terminates the worker and active host calls. Stable runtime/scope markers are attached through `tool.definition` so the proxy can bind it exactly.

### Changed

- **Delegation prefers the plugin runtime.** `/v1/code` selects exact marked `cursor_code_execute` first and retains exact marked upstream `execute` as optional compatibility. `auto`, `local`, `legacy`, `enabled: false`, ordinary MCP result handling, observability, and legacy sandbox fallback keep their existing semantics.

## [0.20.0] - 2026-08-08

### Added

- **Delegated local code execution (compatibility).** When OpenCode advertises a compatible `execute` tool with exact markers `x-opencode-code-mode-runtime: 1` and `x-opencode-code-mode-scope: permission-visible-tools` and a single string `code` property, `/v1/code` delegates `code({script,files?})` to that local `execute({code})` through the ordinary MCP ledger; OpenCode's confined runtime owns filesystem, process, permissions, hooks, truncation, and cancellation. Delegated binding requires the exact `execute` name and both markers; alias or role resolution is never used to bind `execute`.
- **`codeMode.engine` compatibility knob.** `provider.cursor.plugin.codeMode.engine` (`auto` default, `local`, `legacy`) and `CURSOR_OPENCODE_CODE_MODE_ENGINE` with `auto` (delegate when markers present else legacy fallback), `local` (delegate only when present else fail closed), `legacy` (always sandbox). `codeMode.enabled: false` (`CURSOR_OPENCODE_CODE_MODE=0`) remains the normal-mode kill switch and overrides `engine`.
- **Bun-discoverable parity gate.** `test/parity/protocol-surface-parity.ts.test.ts` wraps `test/parity/protocol-surface-parity.ts` so `bun test test/parity/protocol-surface-parity.ts` runs a real test and fails on descriptor or classification drift.

### Changed

- **Code mode execution ownership.** Local `read`, `glob`, `grep`, `edit`, `write`, `bash`, plugin, and MCP tools are now invoked through the real OpenCode registry under the original `Tool.Context` when delegated; legacy sandbox (`code-sandbox.mjs` waves) remains for fallback during the compatibility window.
- **Search is local-always.** `cursor_codebase_search` always returns bounded local evidence; Cursor cloud is optional semantic enrichment only if implementation is available after the concurrent lane. Local lane runs concurrently and does not block on cloud; merged results, when available, deduplicate by canonical relative path and label source truthfully.

### Notes

- **Rollback (compatibility window).** Restore sandbox with `provider.cursor.plugin.codeMode.engine: "legacy"` or `CURSOR_OPENCODE_CODE_MODE_ENGINE=legacy`; disable code mode with `provider.cursor.plugin.codeMode.enabled: false` or `CURSOR_OPENCODE_CODE_MODE=0`. Roll back plugin before OpenCode prerequisite; headers, native allowlists, protobufs, and normal `cursor/*` behavior are unchanged by rollback.
- **Legacy deletion is a later release.** Removal of the sandbox engine and its files occurs after the minimum supported OpenCode version and fallback observation are established (ordered work 5-6). This entry does not claim release or deploy.
- **Validation.** Parity and header gates remain required: `node test/parity/header-parity.mjs` and `bun test test/parity/protocol-surface-parity.ts`.

## [0.19.0] - 2026-08-05

### Added

- **Inbound user images reach the model.** User-message `image_url` content
  parts carrying a strict `data:image/(png|jpeg|webp);base64` URL (object or
  string shape) are now decoded and attached to the live Cursor UserMessage as
  `selectedContext.selectedImages` entries. The model previously reported "I
  can't read image attachments" because `textContent` folded image parts to
  empty strings and the wire field was never populated. Caps: 10 images, 20MB
  each, 32MB aggregate. Rejected parts (http/file URLs, foreign MIME types,
  malformed base64, oversize) append `[image attachment dropped: unsupported
  type or size]` to the user text instead of failing the turn; image bytes are
  never logged. Images ride only the trailing user message - history replay
  and turn-blob serialization stay text-only, so warm caches are unaffected.
  Live-proven end to end: red PNG/JPEG/WebP posted through
  `/v1/chat/completions` were each described correctly by both vision-proven
  model families.

- **Vision-proven families advertise attachment support.** The Cursor catalog
  reports `supportsImages: false` for every model today, which made OpenCode
  strip attachments before the plugin ever saw them. Provider models in the
  proven families (claude-sonnet-5, composer-2.5, including -fast/-thinking
  variants) now advertise `attachment`/`input.image` regardless; the catalog
  flag still wins when Cursor flips it to true. Kill switch:
  `CURSOR_OPENCODE_INBOUND_IMAGES=0` restores catalog-only behavior.

- **Composer-2.5 code-mode steering.** The code tool declaration gains a
  consolidation doctrine prefix and code results gain a post-success
  transition note, both gated to the composer-2.5 family (live A/B: mean code
  calls 1.667 -> 1.333; correct next action 2/6 -> 6/6). Kill switch:
  `CURSOR_OPENCODE_COMPOSER_CODE_GUIDANCE=0`. Exec exit codes now ride wave
  IPC privately (`exitCode` on CodeToolResultInput) so bash-role results
  report honest `isError` without leaking a new property onto the ToolResult
  surface.

## [0.18.2] - 2026-08-05

### Fixed

- **A zero-match search now really is empty.** 0.18.1 added the notes hint but
  returned the placeholder text unchanged, so `.text` stayed `"No files found"`
  (14 bytes). Both guards the change existed to protect were still wrong:
  `!r.text.trim()` returned false and `.lines({ nonEmpty: true }).length`
  returned 1. The placeholder now normalizes to `""` with the hint in
  `.notes`. This also cost turns - a model seeing `No files found` concludes
  the path is wrong and thrashes through `rg`, `find`, and `glob` before
  trusting the result.

- **`codemode.exec` reports the real cwd.** The staged script runs from a
  mktemp dir that symlinks the workspace so relative imports resolve, but the
  interpreter also ran with that mirror as its cwd. Scripts saw
  `/tmp/tmp.XXXX` from `process.cwd()`, and directory listings included the
  injected `script.mjs`, so entry counts were off by one and "am I in the right
  project" answered wrong. The interpreter now runs from the real cwd with the
  script addressed absolutely; relative imports still resolve because an
  interpreter resolves them from the script's own directory.

## [0.18.1] - 2026-08-05

### Fixed

- **A one-line file no longer reaches the model with its line-number gutter.**
  Gutter stripping refused any run shorter than three lines, a guard meant to
  keep prose like `1: item` from being mistaken for a read render. Inside
  OpenCode `<content>` framing every body line is guttered, so the guard only
  ever fired on real reads: a single-line `package.json` arrived as
  `1: {"name":...}` and `JSON.parse` threw. The framing peel now reports
  whether it saw a `<content>`/`<entries>` tag, and a framed body strips at
  any length while unframed text keeps the three-line heuristic.

- **`codemode.exec` works under zsh.** The staged-script command composed its
  cwd-symlink step as `for e in "$c"/* "$c"/.[!.]*`. zsh enables `nomatch`,
  so an unmatched glob is a hard error that aborted the command list before the
  interpreter ran, and any cwd without dotfiles failed. bash tolerates the same
  pattern, which is why it went unnoticed. The loop now runs under `sh -c`,
  keeping the `[ -e ]` guard.

- **A zero-match search says so in `.notes`.** OpenCode renders no matches as
  bare `No files found`, which names files rather than matches and reads like a
  bad path. The hint rides the notes side channel; search `.text` stays
  byte-identical so `!r.text.trim()` and `.lines({ nonEmpty: true }).length`
  keep answering correctly for scripts that branch on them.

## [0.18.0] - 2026-08-05

### Added

- **Code mode reaches parity with the transponder runtime.** The sandbox
  previously advertised a batch-and-retry surface and little else, so scripts
  that reached for anything richer failed at runtime against a helper that was
  never there. `codemode` now answers the full surface: `search`/`describe` for
  tool docs, `poll` for external state, `exec` for a staged node/python script,
  `monitor` for a backgrounded command, `verify` for a receipt assertion,
  `lexMask` for structural scanning, and `define`/`run`/`scripts`/`undefine`
  for storing and replaying script bodies by name.

- **Eight seed skills ship preinstalled.** `exec`, `sh-batch`, `read-windows`,
  `grep-read`, `bridge-status`, `jsonl-fold`, `run-tests`, and `git-snapshot`
  are callable as `skills.<camelCase>(args)` without being defined first, so
  the recurring shapes stop being retyped into every script.

- **`codemode.describe` and `codemode.search` answer from real tool bytes.** The
  session now carries the client tool catalog, so a description is the tool's
  own documentation rather than a name echoed back.

### Fixed

- **A direct `.catch` on a synchronous catalog call no longer throws.**
  `codemode.list()`, `.search()`, `.describe()` and `.scripts()` answer
  synchronously and deliberately return non-thenable values — making them
  thenable would turn `await codemode.list()` into rendered text. A chained
  `.catch`/`.then`/`.finally` therefore compiled and then died on a
  `TypeError`. The call is now wrapped in `Promise.resolve(...)` and the
  rewrite reports itself in band on the result, so the model sees what ran
  instead of believing its own bytes executed.

- **A script cut mid-emission is held instead of executed.** A source that ends
  inside an unterminated string, template, or regex, that carries a
  proof-completable stack of unclosed delimiters, or that trails a dangling
  member access is now provably incomplete. The missing bytes are ambiguous, so
  nothing is closed, no tool calls dispatch, and the hold names the construct
  and the line its first unmatched byte opened. Scripts that merely fail to
  compile keep their existing diagnostics. Delimiter counting runs over a
  lexical mask, so a brace inside a string or regex no longer skews the depth.

- **A snapshotted runtime resolves its own imports.** The sandbox entry imports
  `./seed-skills.mjs`, and the cache snapshot was a single flat file, so the
  relative sibling import could not resolve at spawn. A runtime with
  dependencies is now snapshotted as a directory holding the entry beside every
  dependency under its real name, hashed over all of them so a changed
  dependency busts the directory. A dependency missing from disk declines the
  snapshot rather than emitting one that cannot start.

### Changed

- **Codebase search moves to the RPC the real client uses.** Search was calling
  unary `SearchRepositoryV2`; the decompiled `cursor-agent` calls
  server-streaming `SemSearchFast`, and the request carries only
  `{relativeWorkspacePath, repoName, repoOwner, orthogonalTransformSeed}`. The
  extra fields and the `cursor-state` UUID `repoName` this plugin was sending
  could resolve a different codebase entirely. Streaming requests are now
  framed in a Connect envelope, errors are read from stream trailers, and the
  first data frame carries the results. `topK` moves from 10 to 16 to match.

- **Upload and search now share one repository identity.** `repoOwner` is the
  JWT subject of the access token in use, never the git remote owner — the two
  diverging is what let uploads land under one owner while search queried a
  phantom empty codebase.

## [0.17.1] - 2026-08-01

### Fixed

- **An empty cloud index no longer reports itself as broken.** A zero-length
  body on a 2xx `SearchRepositoryV2` response is a server-asserted empty
  result, not a transport or parse failure: an all-default
  `SearchRepositoryResponse` serialises to exactly zero bytes. It was being
  classified alongside decode failures, so `Cloud readiness` read `unavailable`
  and every degrade hid behind identical local-fallback prose. A received body
  we cannot parse is still `invalid-response`; everything else the cloud
  answers now reports `no-results` with readiness `ready`, and the diagnostic
  keeps the wire shape visible (`response=empty-body, bytes=0` versus
  `response=valid-empty, bytes=N`) so an empty server index stays
  distinguishable from an empty query result.

- **Restored the injected codebase-context block.** Bootstrapping on an empty
  answer awaited the 1s/2s/4s retry ladder on the request path, taking context
  injection from 178ms to 2502ms — past its 2.5s search budget — so the
  `<cursor_codebase_context>` block was silently dropped from the system
  prompt. A served empty answer now returns immediately and bootstraps in the
  background; only `codebase-not-found`, which yields no usable answer, still
  waits.

- **Bootstrap no longer claims uploads it did not perform.** `uploaded` now
  requires `uploadCount > 0`. A zero-upload success on an `OUT_OF_SYNC`
  codebase whose Merkle delta resolved to nothing to send reports
  `already-indexed`, while zero files reaching an `EMPTY` codebase is a
  failure — previously a never-populated codebase could look bootstrapped
  while every subsequent search came back empty. Bootstrap triggered by an
  empty answer is latched to once per session, so repeated empty searches
  cannot re-upload the tree.

- **Wire-harness failures name the failing check.** `fail()` collapsed each
  assertion to its leading token, rendering every prose failure as the single
  word `Expected`; it now keeps the full message and redacts only URLs and
  absolute paths.

### Notes

- Cloud search returning zero hits on some accounts is server-side: live
  probes returned a byte-identical 24-byte zero-result response for the real
  workspace, for the JWT-subject owner, and for a nonexistent repository, on
  both `repo42.cursor.sh` and `api2.cursor.sh`, after a genuinely successful
  upload. This release makes that degrade truthful and machine-readable rather
  than silent; it does not add a transport. A `SemSearchFast` implementation
  was probed and dropped as non-causal.

## [0.17.0] - 2026-07-28

### Fixed

- **Truncated reads are now legible to the model.** 0.16.0 corrected
  `ReadSuccess.total_lines` on the wire, but the model reads the *content*
  string, not the metadata — and normalization peels the client's
  `Use offset=N to continue` trailer off so code-mode scripts get disk-clean
  bytes. The model was therefore handed a window that simply stopped, with no
  indication more file existed. Live probe: reading a 14,454-line file reported
  `TOTAL=1412`.

  A truncated read now re-states one compact line inside the content
  (`(Showing 1412 of 14454 lines. Use offset=1413 to read the rest.)`). Complete
  reads are untouched, and the disk-clean guarantee for code-mode still holds
  for the file body itself. Same live probe now reports `TOTAL=14454` with
  truncation correctly identified.

## [0.16.0] - 2026-07-28

### Fixed

- **Bridged reads now report the file's true total line count.** OpenCode's read
  tool emits one of three trailers, and only two of them carry a real total:
  `(Output capped at LIMIT. Showing lines a-b. Use offset=n to continue.)`,
  `(Showing lines a-b of COUNT. Use offset=n to continue.)`, and
  `(End of file - total COUNT lines)`. The byte-capped form states no total, and
  the previous parser matched neither it nor the line-windowed form, so it fell
  back to counting the returned window. A 14,394-line file read through a capped
  window was reported to the model as being 1,414 lines long — the model was told
  the file ended exactly where its view ended, which is why truncated reads read
  as surprising rather than as "fetch the next page."

  `ReadSuccess.total_lines` is now sourced from the trailer when the client
  states one, and counted from the file on disk when the byte cap hid it, which
  matches Cursor's own native read (it reports full-file `totalLines` even when
  `rangeApplied` is set). `truncated` is driven by the parsed trailer instead of
  a substring guess, and `range_applied` is set when the client hands back a
  continuation offset, so the model can compute its own next `offset`.

  Trailer parsing is single-sourced in `src/read-render.ts` (`totalLines` /
  `nextOffset` on `NormalizedReadResult`); no parallel read path or custom MCP
  read tool was added.

## [0.15.0] - 2026-07-27

### Fixed

- **Native shell metadata no longer terminates a turn.** Under the default
  `nativePolicy: "transparent"`, a Cursor `shellStreamArgs` exec carrying the
  injected `outputNotification` metadata was rejected with "Interactive native
  shell options are unsupported by the OpenCode bash owner." before OpenCode's
  `bash` owner ever ran — even though the adjacent comment said that metadata
  should be ignored. `unsupportedNativeShellOption` now ignores it and the call
  bridges to the shell owner. This is model-agnostic, but it mattered most for
  `composer-2.5`, the family that prefers Cursor's native shell over MCP `bash`.
- **Native shell preflight failures are no longer a dead end.** A recoverable
  argument-normalization failure now rides the client-owned accumulator as a
  `preflightError` instead of returning a terminal rejection with no recourse.
  Invalid working directories stay terminal: OpenCode still owns cwd validation
  and a non-existent directory never reaches the client tool lane.

### Security

- Permission-relevant native shell options remain fail-closed and are unchanged:
  `isBackground`, `requestedSandboxPolicy`, `skipApproval`, `smartModeApproval`,
  and `hookApprovalRequirement` still hard-reject. Only benign output-notification
  metadata became permissive.

### Added

- `scripts/capture-tool-bridge-wire.ts` accepts a diagnostic-only
  `--allow-composer` opt-in so Composer wire traffic can finally be captured.
  `grok-4.5-fast` at low effort stays the default and only implicitly allowed
  capture model, and Composer receipts cannot satisfy release-fixture validation,
  so no immutable release gate can be armed from them.

## [0.14.0] - 2026-07-26

### Fixed

- Stopped multi-minute silent stalls on Cursor turns that emit long runs of
  internal-only frames. The stream inactivity watchdog re-arms on raw bridge
  bytes, so Cursor bookkeeping (interaction updates, checkpoints, contained
  reasoning) refreshed it indefinitely while the client saw nothing render.
  OpenCode's own ~256s ceiling then cancelled the response and replayed the
  whole turn, and the plugin — which only observed the socket drop — logged the
  teardown as `user_cancel`. A second liveness lane now measures silence the
  client can actually observe and escalates first, preferring a checkpoint-safe
  resume over a blind full-transcript retry. Budget defaults to 120s and is
  tunable via `CURSOR_OPENCODE_CLIENT_STALL_MS` (`0` disables the lane).
  Stall diagnostics now carry `lane=` and `clientSilentMs=`.
- Stopped a tool_calls segment from hanging open forever when a turn mixed an
  authoritative call with a bookkeeping-only observation. Such an accumulator
  can never be emitted — doing so would fabricate tool finality the model never
  committed to — and nothing retired it, so the settle test never became true
  and the segment stayed open until the transport died. Terminal flush now
  abandons bookkeeping-only accumulators; started-only observations still never
  become executable calls.

## [0.13.10] - 2026-07-24

### Fixed

- Restored the proven four-worker hosted-runner schedule while retaining native
  Todo coverage in the parallel quality job.

## [0.13.9] - 2026-07-24

### Fixed

- Moved native Todo ownership coverage from the budgeted runtime phase to the
  parallel quality job while retaining it in local `bun run test`, and reduced
  hosted-runner gate concurrency to avoid two-core contention.

## [0.13.8] - 2026-07-24

### Fixed

- Kept the complete runtime suite inside the 15-second CI wall by retaining its
  full failure artifact while removing success-path test-log rendering from the
  timed pipeline.

## [0.13.7] - 2026-07-24

### Fixed

- Consolidated the native Todo and SemSearch capture evidence, applied fixture
  sanitization during validation, and retained checkpoint observations without
  enabling either server-owned native surface.
- Added the native Todo ownership gate to the complete focused bridge suite and
  pinned legacy policy's static native allowlist alongside transparent planning.
- Synchronized the generated runtime and operator guidance with the final
  fail-closed Write, image, Edit, LSP, Skill, Todo, SemSearch, plan, Fetch, and
  background/Await decisions from the current live Cursor build.

## [0.13.6] - 2026-07-24

### Fixed

- Confirmed the pinned Cursor protocol has no executable native carrier for
  OpenCode `skill`: legacy descriptors, `AgentSkill`, and selected skills are
  context-only, while Task/subagent is a different operation. Request contexts
  now explicitly close both Cursor-owned skill catalogs, keep the exact
  lowercase tool on MCP, and ship a live probe that verifies ToolCall, Exec,
  result identity, and model receipt end to end.
- Added a fail-closed native todo ownership probe with status, identity, replace,
  merge, empty-list, filter, error, and continuation coverage. Live
  `grok-4.5-fast` low evidence shows Cursor completes `updateTodosToolCall`
  inside interaction/checkpoint state without a todo Exec/result handoff, while
  `readTodosToolCall` is not exposed on this model surface, so OpenCode
  `todowrite` remains permission-preserving MCP.

## [0.13.5] - 2026-07-24

### Fixed

- Full OpenCode `apply_patch` remains permission-preserving MCP instead of
  enabling an unproven mutation facade. The PI Edit Exec/result pair is present,
  but two low-effort `grok-4.5-fast` attempts produced MCP or no ToolCall; hiding
  raw patching would also remove add, move, and multi-file operations. Standard
  server-owned `editToolCall` remains disabled.
- Added a fail-closed native SemSearch capture gate and acceptance coverage for
  cloud success, local fallback, no-results versus unavailable diagnostics,
  scope/result bounds, workspace identity, and code mode. The pinned protocol
  exposes only inline `semSearchToolCall` args/result and no client Exec/result
  handoff; live low-effort Grok attempts hit the account usage gate before a
  ToolCall, so `cursor_codebase_search` remains the sole permission-preserving
  MCP facade.
- Added an exact, fail-closed capture adapter from OpenCode `lsp` diagnostics to
  Cursor ReadLints. It preserves general LSP as MCP, tool/Exec identity, and
  typed file/range/severity/empty/error/permission outcomes. Production remains
  disabled: OpenCode 1.18.4 has no public diagnostics operation, and the pinned
  live Grok low probe completed `readLintsToolCall` without the client
  `diagnosticsArgs`/`diagnosticsResult` handoff.

## [0.13.4] - 2026-07-24

### Fixed

- Native Write capture now sends the pinned `grok-4.5-fast` low-effort request,
  advertises both standard and PI carriers while hiding MCP Write, and rejects
  incomplete ToolCall/Exec/result identities or writes outside the isolated
  capture workspace. Native Write remains disabled on the current live evidence.
- Replaced C-MEDIA's reference-tree import with an installed OpenCode process
  roundtrip and recorded the current Cursor build's lack of image-capable models.
  GenerateImage and media projection remain immutable-gated off.
- Native background/Await capture now drives the pinned current Cursor client and
  requires complete identity, bounds, Await, completion-action, cancellation,
  replay, terminal, and code-mode evidence. The account usage gate stopped the
  live run before inference, so native background remains disabled and the MCP
  background/Await pair is unchanged.
- Native Fetch stays MCP-visible: the pinned low-effort Grok lane neither listed
  nor emitted the complete `fetchToolCall` carrier, and approval-only WebFetch
  cannot return OpenCode's result.
- CI now overlaps the declaration-free build with runtime gates, packages only
  after a successful build, and runs the native-background gate in its focused
  Bun suite while preserving the 15-second wall budget.

## [0.13.3] - 2026-07-23

### Fixed

- Native Glob now decodes OpenCode's newline-delimited path output into Cursor's
  files-with-matches result shape, including total-file and truncation metadata.

## [0.13.2] - 2026-07-23

### Fixed

- Native Glob now uses the live-proven `globToolCall` carrier. Cursor encodes
  it as an empty-pattern, files-with-matches `grepArgs`; the proxy discriminates
  that shape, delegates to exact OpenCode `glob`, and returns typed `grepResult`.
- Native Task now projects OpenCode's sorted dynamic agent catalog through
  `RequestContext.customSubagents`, preserving exact agent names and falling
  back to MCP Task when the catalog is absent, ambiguous, or malformed.
- The native Write capture can now bypass the immutable production gate only
  through a non-serializable capture token, preserve its continuation identity,
  and observe native ToolCall/exec/result events directly. Current live evidence
  still shows no native Write tool, so default Write remains MCP.
- The live mapping probe now validates complete native Glob and custom-agent
  Task interaction, exec, typed-result, and terminal sequences.

## [0.13.1] - 2026-07-23

### Fixed

- Native Shell now ignores Cursor's standard output/timeout/close-stdin metadata
  and delegates valid commands to OpenCode `bash` instead of rejecting every call.
- `glob`, `task`, and default `write` remain MCP-visible. The live model surface
  does not expose PI Find/Write, and native Task drops OpenCode's dynamic agent
  contract; hiding those tools caused false "tool unavailable" reports and
  invalid task-agent guesses.
- Checkpoint reuse replaces the complete frozen system prompt with the current
  model, agent, workspace, capability, skill-route, and reasoning instructions.
- Thinking deltas are counted once, preventing premature exhaustion nudges, and
  live tool-result resumes deliver pending reasoning continuity through Cursor's
  dedicated hook-additional-context channel.
- The live transparent-mapping probe now covers native Shell plus residual Glob
  and Write, closing the validation gap that let the regressions ship.

## [0.13.0] - 2026-07-23

### Added

- Added one immutable per-Run tool plan that maps exact compatible OpenCode
  `read`, `grep`, `bash`, `glob`, `question`, `task`, and `write` tools onto
  Cursor's native carriers while preserving OpenCode execution and permissions.
- Added request-aware native/MCP regression coverage and a low-effort live probe
  for native-only, mixed, custom-only, empty, and schema-incompatible catalogs.

### Changed

- `provider.cursor.plugin.nativePolicy` now defaults to `transparent`. Only
  residual custom or unmappable tools are MCP-declared; `legacy` remains the
  immediate rollback and `dedupe-canary` retains its prior behavior.
- Allowed-tools headers, inline and top-level MCP declarations, refresh replies,
  retry/resume state, and native dispatch now share the same physical-Run
  capability snapshot. Empty residual catalogs omit the top-level MCP wrapper.
- Cursor-native WebSearch is suppressed when OpenCode advertises lowercase
  `websearch`, and unsupported native-only options now return typed errors.

## [0.12.9] - 2026-07-23

### Fixed

- Cursor-native Task fanouts now map Cursor's default `generalPurpose` agent
  type to OpenCode's `general` subagent, so parallel calls do not fail as an
  unknown agent and fall back to retry turns.

## [0.12.8] - 2026-07-23

### Fixed

- Cloud bootstrap now matches Cursor Agent's native indexer: a 10,000-file
  Merkle bound, authoritative native snapshots, uploads batched up to 32
  changes across eight workers, and safe Merkle recomputation when sensitive
  paths are excluded. Large workspaces no longer collapse to a small Git-only
  fallback snapshot.

## [0.12.7] - 2026-07-23

### Fixed

- Cloud bootstrap now falls back to the complete filtered upload snapshot when
  Cursor's native Merkle enumerator reaches its file cap, instead of rejecting
  the workspace before the first handshake.

## [0.12.6] - 2026-07-23

### Fixed

- `cursor_codebase_search` now renders a secret-safe diagnostic that separately
  identifies unavailable authentication, unindexed workspaces, transport
  failures, empty responses, and parse failures while preserving truthful local
  fallback behavior.

## [0.12.5] - 2026-07-22

### Fixed

- Production AgentService Connect parsing now bounds partial and declared frames
  before retention, caps gzip/Brotli expansion, and aborts the owned bridge
  fail-closed.

## [0.12.4] - 2026-07-22

### Fixed

- Live-wire artifact scanning now sorts directory entries so scan order and
  byte-cap thresholds are deterministic across platforms.
- Cloud-index freshness smoke coverage uses a test-only watcher delay seam so
  CI stays within the runtime budget without weakening assertions.

## [0.12.3] - 2026-07-22

### Fixed

- Live-wire artifacts now redact usage and cost values, normalize server error
  codes, bound Connect-frame accumulation before concatenation, and fail closed
  when artifact scanning exceeds its byte budget.
- Added behavioral coverage for current-at-bootstrap and stale-after-local-change
  cloud-index freshness transitions.

## [0.12.2] - 2026-07-22

### Fixed

- Added direct regression coverage for completeness detection when a cached
  `git ls-files` snapshot reaches the configured upload cap.

## [0.12.1] - 2026-07-22

### Fixed

- Dependency-free cloud bootstrap now rejects incomplete bounded file scans
  before publishing a repository root, while the installed Cursor native path
  retains its exact file-cap behavior.
- Live-wire protobuf evidence now rejects field-zero payloads and accumulates
  tiny response chunks in linear time with bounded storage.

## [0.12.0] - 2026-07-22

### Added

- Added provider-aware `cursor_codebase_search` guidance for normal and
  `cursor-code` sessions, with bounded suppression during OpenCode compaction.
- Added structured cloud readiness, freshness, query outcome, serving source,
  and scope fields to every codebase-search result.

### Fixed

- Cloud search responses now fail closed on empty, truncated, malformed,
  compressed, multi-frame, or trailing-byte responses instead of conflating
  invalid bytes with valid zero results.
- Cloud bootstrap now uses current-token singleflight, success-only memoization,
  bounded retry cooldown, and lifecycle aborts; request diagnostics remain local
  to each concurrent search.
- `targetDirectories` now post-filters cloud top-K and cached local records under
  one workspace-containment and sensitive-file policy without broadening an
  all-invalid scope.
- Escaped context-injection bodies now remain within `maxBodyChars`, including
  their truncation marker.
- Cursor checkpoint reuse now rebases workspace identity and one authoritative
  search-guidance block, rebuilds after compacted-history divergence, and rejects
  unsafe same-Run resume state.
- `chat.params` can start the local proxy before `auth.loader`; OAuth remains
  lazily required by the first request rather than proxy binding.

### Changed

- The live wire harness now accepts `--candidate` and `--expected-version`,
  validates one candidate root, hashes candidate files, observes compaction and
  checkpoint/resume contracts from those bytes, and omits raw server details
  from evidence. Server-empty cloud search passes only as typed local fallback;
  the opt-in cloud-results gate still requires a real code result.
- Local fallback remains available when cloud-index results are unavailable.
  Cache/auth safety is preserved: cleanup removes only the exact plugin cache
  directory and never removes `~/.local/share/opencode/auth.json`.

## [0.11.1] - 2026-07-21

### Added

- Added a standalone OpenAI-compatible proxy entrypoint for local LaunchAgent or
  Factory-style callers, with shared OpenCode OAuth storage and token refresh.

## [0.11.0] - 2026-07-21

### Added

- **Cursor 2026.07.20 protocol compatibility:** advanced the manifest pin to
  `cli-2026.07.20-8cc9c0b` and regenerated the descriptor with an exhaustive
  parity inventory for 32 messages, 42 fields, 6 enums plus extensions,
  7 corrected field types, and 17 RPC additions. AgentService Run remains
  unchanged.
- **OpenCode-owned adapters for newly reachable protocol families:** mini-SWE
  bash, redacted read, Delete, Fetch, PI read/bash/edit/write/grep/find/list,
  MCP resource list/read, MCP state, subagent/task, bounded subagent await, and
  current-workspace conversation search now translate through advertised
  OpenCode tools or workspace-scoped SDK calls. Unsupported cloud/agent-store/
  SCM/PR/VM/environment/auth/custom-mode surfaces stay fail-closed.

### Changed

- **Tool-result metadata continuity:** OpenCode before/after tool hooks feed a
  bounded, scoped ledger so Cursor result adapters can preserve exit status,
  truncation, diffs, child-session ids, and permission metadata that the
  OpenAI-compatible tool-result body cannot carry by itself.
- MCP declarations now carry the July 20 `input_schema_json` representation in
  addition to the binary protobuf Value and use either representation for
  argument normalization.

### Tests

- Added protocol-surface, adapter, metadata-ledger, conversation-index, and
  subagent-await regression coverage, including external/relative filesystem,
  permission, binary, timeout, cap, and unsupported-path edges.

## [0.10.4] - 2026-07-21

### Fixed

- **Native filesystem permissions stay with OpenCode:** Cursor-native `Ls` now
  delegates directory reads to OpenCode `read`, and native `Grep` delegates to
  OpenCode `grep`; both translate the client result back to Cursor without
  proxy-local filesystem access. Existing external shell cwd values are
  canonicalized and passed to OpenCode's `external_directory` permission flow,
  while missing/phantom directories still fail or use the existing workspace
  suffix rescue.
- **Native Read protocol edges:** `encoding_hint` is retained and used for
  post-permission decoding, image/PDF reads return byte output, binary failures
  map to `invalid_file`, and permission denials map to `permission_denied`
  separately from user rejection.
- **External native Write verification:** the dormant native Write adapter can
  verify the exact external file OpenCode reports writing instead of rejecting
  it after approval. Read/list/grep ownership metadata now identifies the
  client as the permission owner.

## [0.10.3] - 2026-07-21

### Fixed

- **Cursor-native Read honors OpenCode external-directory access:** native
  `Read` execs now delegate to the advertised OpenCode `read` tool and translate
  its result back to the required native `readResult`. Existing absolute paths
  outside the workspace therefore use OpenCode's `read` / `external_directory`
  permission flow instead of failing early with `Path outside workspace` and
  pushing the model toward shell workarounds.

## [0.10.2] - 2026-07-21

### Fixed

- **Phantom shell cwd is rejected before OpenCode spawn:** shell-role calls on native, MCP, interaction-update, and code-wave routes now resolve `workdir`/`cwd` only against the captured workspace. Existing/rescuable directories are canonicalized; missing, outside-workspace, file, symlink-escape, and scope-less values fail preflight instead of reaching `FileSystem.access` or silently relocating the command.
- **Preflight-rejected execs settle as normal turns:** when Cursor consumes an internal typed tool error and continues with text, the settled exec no longer leaves a false pending `tool_calls` segment that turns a clean `turnEnded` into a stream error.
- **Immutable RequestContext on checkpoint resume:** `ResumeAction` now reuses the original physical Run's request-context snapshot and inline-context rollback decision. Inline-off Runs still omit action context; refresh remains available (`ResumeAction.requestContext` reclassified `typed_rejected` → `handled`).

### Changed

- **Vendored proto caught up to cursor-agent 2026.07.08-0c04a8a:** added 31 messages, 26 shared-message fields, and 3 enums. The complete names/signatures now have an executable parity inventory; new interaction cases are classified by direction, descriptor/provenance and tool-bridge fixture fingerprints are refreshed, request-context refresh reports `served_from_disk_cache=false`, and Git repo context populates `is_origin_backed` with cursor-agent semantics.

## [0.10.1] - 2026-07-20

### Fixed

- **Clean-install bridge tests:** schema assertions now use the Zod instance owned by `@opencode-ai/plugin`, avoiding a Zod 3 / Zod 4 composition failure, and the sanitizer fixture exercises macOS path detection deterministically on Linux CI. Runtime bridge behavior is unchanged.

## [0.10.0] - 2026-07-20

### Added

- **Opt-in tool bridges (default off):** `provider.cursor.plugin.toolBridges` with `generateImage`, `nativeWrite`, and `backgroundShell` (env: `CURSOR_OPENCODE_GENERATE_IMAGE`, `CURSOR_OPENCODE_GENERATE_IMAGE_MODEL`, `CURSOR_OPENCODE_NATIVE_WRITE`, `CURSOR_OPENCODE_BACKGROUND_SHELL`). Paired MCP `cursor_background_shell` / `cursor_await` when background is enabled (`bash` / `external_directory` permissions, project `terminals/<taskId>.txt`, output/runtime/job/await bounds, same-session await, process-group cleanup, 6 h TTL, restart loss). Sole plugin search tool remains `cursor_codebase_search` (cloud/local labels). Lowercase MCP-only `webfetch` / `edit` / `skill` / `glob`; native WebFetch / GenerateImage / Edit / Glob / Delete / background / Await stay disabled. Code mode stays exactly one `mcpToolCall` / `code` lane.

### Changed

- **Cursor-native WebSearch in normal mode**: `cursor/*` Runs now enable `webSearchEnabled`, allow `webSearchToolCall`, and auto-approve `webSearchRequestQuery` so Cursor can execute live searches server-side. `cursor-code/*` remains MCP-only and does not expose the native search tool.
- **Capture-gated image / native Write remain blocked:** C-IMAGE `NO_IMAGE_CAPABLE_MODEL` / no pass fixture, C-MEDIA `IMPORT_FAILED`, C-WRITE `outside_workspace`. Enabling the bridge flags does not bypass those gates — `cursor_generate_image` and native Write stay unregistered / runtime-unavailable (not shipped enabled).

### Tests

- Normal-mode smoke coverage now pins the WebSearch allowlist, RequestContext flag, typed approval response, and continued WebFetch rejection. A live `grok-4.5-fast` low-effort probe verified an observed `webSearchToolCall` and exact final token.
- Tool-bridge capture/probe scripts and fixtures document gate-failed / unsupported status; MCP background probe is not blocked by native-background unsupported.

## [0.9.27] - 2026-07-19

### Fixed

- **Stale parked-response `proxy_watchdog` retirement**: a parked/closed response wrapper could re-arm its response-local inactivity timer from late bridge bytes during the handoff gap, then abort or `PhysicalRunState.cleanup` the successor continuation on the same shared Run (`bridge exit 1; phase=mid_response reason=proxy_watchdog`). Cleanup-owner handoff now synchronously retires predecessor response-local timers (`watchdogTimer`, tool-flush, retry timers), and watchdog arm/fire requires an open response with a current cleanup lease.

## [0.9.26] - 2026-07-19

### Fixed

- **Mid-response `proxy_watchdog` stalls no longer kill the turn**: the 180s stream-inactivity watchdog previously refused to resume whenever *any* server frame arrived after the last checkpoint — including internal bookkeeping that never reached the client (interaction deltas, unemitted tool accumulation, internal execs). The resume predicate now gates on **client-visible** output (SSE chunks) instead of raw server frames: a stall after server-side-only activity resumes transparently from the checkpoint, because replay cannot duplicate anything the user saw. Client-visible post-checkpoint output and exposed in-flight tool calls still block resume, so no duplication can leak.

## [0.9.25] - 2026-07-19

### Fixed

- **Model cwd hallucination on checkpoint reuse**: when a usable Cursor checkpoint loaded, `buildCursorRequest` reused the frozen `rootPromptMessagesJson` and discarded the freshly baked `effectiveSystemPrompt`. The `<cursor_workspace_identity>` block could therefore be stale or missing while `RequestContext` was rebuilt — models read the text channel, not the protobuf side-channel. Checkpoint system blobs are now patched with the current workspace paths so the outbound Run always carries an authoritative `Working directory` / `Workspace root folder`.
- **Mid-session model switches**: conversation history from other models no longer contaminates path priors via stale checkpoint system blobs; identity is re-baked per request from the current workspace.
- **Incomplete stale-env scrub**: `removeStaleEnvCwd` now strips `Working directory:`, `Workspace root folder:`, and `Workspace root:` lines inside OpenCode `<env>` blocks so only the identity block owns cwd.
- **Checkpoint identity hardening**: every system root-prompt blob is re-baked (even when preceded by a non-system blob), unknown JSON fields are preserved, and malformed/no-system root prompts fall back to rebuilding from current client history instead of silently bypassing the cwd patch.

### Changed

- **RequestContext parity**: `osVersion` now uses `os.platform() + os.release()` (matching cursor-agent) instead of `os.type()`; `shell` is mapped to a kind token (`zsh` | `bash` | `powershell` | `fish` | `naive`) from `$SHELL` rather than the raw path.
- **Native shell cwd diagnostics**: dropped/rescued cwd lines now log the authoritative workspace root alongside the rejected path so models receive one clear corrective signal instead of a silent drop.

### Tests

- Checkpoint reuse: `buildCursorRequest` with a prior checkpoint whose system blob carries a stale path must emit an outbound system blob containing the current `Working directory`.
- Stale env rewrite: OpenCode `<env>` with wrong `Working directory` / `Workspace root folder` / `Workspace root` must yield authoritative identity paths with no stale path remaining.
- Shell kind / osVersion parity asserted via `__test` exports.
- Multi-system-blob, non-system-first, and malformed checkpoint root-prompt fallbacks covered in smoke.

## [0.9.24] - 2026-07-18

### Changed

- **Plugin self-update cache busting**: setup/self-update clears stale OpenCode plugin cache pins so `@latest` installs pick up the published version.
- **CI: protocol-snapshot off the gated wall**: regenerating the enlarged `agent_pb.ts` under 2-core fan-out made `proto:check` the long pole (~14s) and blew the 15s build+test+pack budget. Snapshot now runs in the parallel `quality` job (`bun run proto:check` + quality fixture); runtime gates keep header/surface parity + smoke only.

## [0.9.23] - 2026-07-18

### Changed

- **Cursor protocol pin advanced to `cli-2026.07.08-0c04a8a`**: vendored `proto/agent/v1/agent.proto` from recent public agent.v1 dumps (agent-vibes 2026-07-16, cross-checked against cursor-byok / cursorkit), regenerated `src/proto/agent_pb.ts`, and refreshed the protocol-manifest inventory. New `RequestContextEnv` fields include `process_working_directory` (21) and `is_working_dir_home_dir` (20); `buildRequestContext` now sets both alongside the existing model-visible workspace-identity bake. New InteractionUpdate / InteractionQuery / Exec cases are inventoried (`intentionally_observed` / `typed_rejected` / `controlled_exec_error`) without advertising unsupported capabilities. Regenerate with `bun run proto:from-source`.

## [0.9.22] - 2026-07-18

### Fixed

- **Plugin git/bash spawn storms**: remotes and branch come from `.git/config` + `HEAD` (zero process); workspace refresh is at most one `git status --short --branch`; cloud `ls-files` is cached by index mtime; script-salvage no longer runs `bash -n` / `python3` compile helpers (indicator heuristics only). Spawn attribution lives in `src/spawn-budget.ts` (`CURSOR_OPENCODE_SPAWN_BUDGET=1`). Agent shell tool processes remain OpenCode-owned.

### Changed

- **CI gated wall under 15s**: budgeted build+test+pack defaults to `CI_BUDGET_SECONDS=15`. CI uses transpile-only `build:ci` (`scripts/ci/emit-dist.mjs`), then test ‖ pack. Repository gates run as one parallel phase (concurrency capped at 5); streaming smoke splits into `streaming-proxy` + `streaming-lifecycle`; multi-tool batching uses `setToolCallTurnQuietMsForTest` (scaled sibling gaps) so quiet-window waits no longer dominate the wall.

### Tests

- Spawn-budget smoke now asserts ≤1 git per workspace refresh, filesystem remotes, zero bash on script reroute, and a single `ls-files` spawn under cache hit.

## [0.9.21] - 2026-07-18

### Fixed

- **Git process cascade on workspace context**: workspace/repo probes now short-circuit non-repos via a filesystem `.git` walk (no spawn), collapse remotes+status into at most two `--no-optional-locks` git calls with branch parsed from the `##` status header, share one in-flight warm via a Promise Map (index boot no longer starts a parallel uncached collect), and refresh on a 60s TTL instead of 10s. Cloud upload `ls-files` also passes `--no-optional-locks`.

### Tests

- Added spawn-budget smoke coverage: zero git for non-repos, ≤2 spawns per refresh with `--no-optional-locks`, singleflight under concurrent cache/async callers, and `##` branch header parsing.

## [0.9.20] - 2026-07-17

### Fixed

- **Mid-response `proxy_watchdog` recovery**: when the stream inactivity watchdog fires after Cursor has already delivered output, the proxy can resume from the latest safe conversation checkpoint on a fresh bridge (shared `CURSOR_OPENCODE_RUN_RETRIES` budget) instead of immediately failing with `Cursor stream ended unexpectedly before turnEnded (... reason=proxy_watchdog)`. Unsafe/absent checkpoints, active execs, and exhausted retries still surface the existing `upstream_error`.
- **Open `tool_calls` watchdog refresh**: local flush timers may keep refreshing the inactivity watchdog for an open tool segment regardless of segment age, as long as the current bridge recently delivered accepted bytes. A truly silent bridge still times out.
- **Configurable stream inactivity budget**: operators can set `CURSOR_OPENCODE_STREAM_INACTIVITY_MS` (positive integer ms, max `2147483647`); unset/invalid values keep the previous `180000` ms default.

### Tests

- Added `watchdog-env-override`, `watchdog-tool-segment-long-lived`, and `watchdog-mid-response-checkpoint-retry` streaming regressions covering env parsing, long-lived/silent tool-segment refresh, checkpoint resume, and all ineligible visible-failure branches.

## [0.9.19] - 2026-07-16

### Fixed

- **Concurrent explore `Task cancelled` via proxy_watchdog**: open OpenAI `tool_calls` segments now refresh the stream inactivity watchdog while local flush/quiet timers are still owning the turn (capped by `toolCallWatchdogRefreshMaxMs`, default 120s). Under parallel subagent load, Cursor often gaps between interaction bookkeeping and exec delivery; the old watchdog could kill that gap mid-segment, OpenCode cancelled the child session, and the Task tool surfaced `Task cancelled`. Watchdog teardown also force-flushes any force-ready tool segment once as a last chance, with conv/bridge diagnostics.

### Tests

- Added a short-watchdog regression proving a completed tool observation can finish its quiet window without `proxy_watchdog` aborting the bridge.


### Changed

- **CI budget coverage split**: quality fixture tests now run in the parallel quality job while protocol and smoke runtime gates remain in build-and-test. The same full test set stays required without serializing independent quality work into the 25-second build wall budget.

## [0.9.17] - 2026-07-15

### Fixed

- **Expected parked teardown classification**: abandoned, displaced, expired, and replaced parked turns now carry `parked_teardown`, so their delayed nonzero transport closes no longer emit false `transport_failure` errors or `upstream_error` events.
- **Single-owner physical-Run cleanup**: `PhysicalRunState.cleanup` is now a stable idempotent dispatcher. Response wrappers transfer one explicit cleanup-owner slot only after the previous response retires; overlapping live owners fail immediately instead of silently overwriting teardown responsibility.
- **Stale daemon cause precedence**: proxy-known terminal causes now override conflicting close reasons from older daemons while preserving phase and transport details. The daemon socket protocol moves to v4 so upgraded clients do not reuse a v3 process that reports every proxy abort as `client_abort`.

### Tests

- Added stale-v3 daemon metadata coverage, cleanup-owner handoff/rejection/idempotency checks, delayed parked-teardown log assertions across TTL/displacement/code-session/replacement paths, and end-to-end response cancellation through a real legacy `h2-bridge` subprocess with cause, admission, and no-respawn assertions.

## [0.9.16] - 2026-07-15

### Fixed

- **Cancellation ownership races**: Run admission now carries a unique physical-Run token, so a canceled Run's delayed close or repeated terminal callback cannot erase a newly admitted successor under the same stable bridge key.
- **Post-cancel side effects and code-session zombies**: explicit user cancellation is latched across every response wrapper. Buffered MCP, code, checkpoint, and other transport data is rejected after cancellation, exec lifecycle disposal is permanent only for canceled Runs, and the exact transport's code session is aborted immediately.
- **Nested code continuation cancellation**: all-invalid tool recovery retains its inner composer reader and propagates outer cancellation both before and after reader assignment.
- **Transport cause diagnostics**: daemon and proxy teardown now distinguish `user_cancel`, `proxy_watchdog`, `retry_teardown`, and `transport_failure`. Watchdog failures no longer masquerade as `client_abort`, while unexpected transport failures retain actionable errors.
- **Legacy cancellation noise**: intentional cancellation of the per-request `h2-bridge` subprocess no longer emits a false `h2-bridge exited with code` error; unexpected nonzero exits remain logged.
- **Idempotent physical-Run cleanup**: one shared terminalizer clears watchdogs, retry timers, heartbeats, code sessions, execs, admission ownership, and bridge resources exactly once.

### Tests

- Added ordering regressions for successor admission, buffered post-cancel MCP/code/checkpoint frames, nested continuation cancellation on both assignment orderings, watchdog/retry/transport cause classification, shared code-wave cancel intent, repeated cleanup, daemon cause propagation, and real legacy subprocess cancellation versus unexpected failure.
- Preserved parked tool-turn late-message behavior and asserted both `bridge closed with exit` and `h2-bridge exited with code` log families.

## [0.9.15] - 2026-07-15

### Fixed

- **False-positive `upstream_error` on user cancel**: pressing Esc in OpenCode to cancel a streaming Cursor response no longer surfaces `Cursor stream ended unexpectedly before turnEnded (bridge exit 1; phase=mid_response reason=client_abort)`. The proxy now records client-cancellation intent before tearing down the bridge and silently closes the SSE stream without error or respawn.
- **Zombie bridge respawn on pre-response cancel**: canceling before any response bytes arrive no longer triggers the zero-byte respawn lane, which previously replayed the full request into a dead/canceled stream and burned quota invisibly.

### Tests

- Added regression fixtures for pre-response cancel (no respawn, no error log), mid-response cancel after partial output (no respawn, no error log), and cancel during a pending retry backoff (spawn suppressed).

## [0.9.14] - 2026-07-14

### Changed

- **Fail-closed OpenCode skill policy**: when `provider.cursor.plugin.skills.enabled` is false (`CURSOR_OPENCODE_SKILLS`, default true) or the system prompt has no valid nonempty `<available_skills>` catalog, the proxy strips lowercase `skill` from tool declaration and execution instead of leaving a free-string schema. Present catalogs constrain `name` to catalog ∩ client enum (catalog may narrow, never widen). The last complete valid `<available_skills>` block is authoritative (not a union of stale blocks). Out-of-catalog names fail schema validation before client execution on normal and code routes; code-mode descriptions pin `tools.skill` so budget pressure cannot drop the active skill signature. Cursor-native Skill/Task/Plan remain disabled (`rules: []`, empty `skillDescriptors`, web search off). Redacted skill-policy log lines record only counts/reasons.

### Tests

- Added fail-closed skill fixtures for missing/empty/malformed/disabled catalogs, client-enum narrowing, superseded blocks, oversized catalogs, execution-path rejection, and config defaults.

## [0.9.13] - 2026-07-14

### Fixed

- **Invisible checkpoint resume after mid-stream transient failures**: retryable Cursor Connect failures (`resource_exhausted` / `unavailable`) that arrive after output no longer necessarily kill the agent turn. When the latest validated `conversationCheckpointUpdate` is still the newest server activity and no exec remains active, the proxy starts a fresh Run with cursor-agent-compatible empty `ResumeAction`, the checkpoint's `ConversationStateStructure`, the original model/conversation/MCP context, and a fresh request ID. Already-delivered output remains in the same OpenAI stream; only continuation output follows the resume. Activity after the checkpoint, missing/incomplete checkpoints, active execs, terminal turns, and non-retryable errors still fail visibly rather than risking duplicate output or side effects.
- **Cursor-agent retry budget parity**: zero-byte transport replay and retryable Connect recovery now share a maximum of three retries. `CURSOR_OPENCODE_RUN_RETRIES=0` disables retries; `CURSOR_OPENCODE_RUN_RETRY_BACKOFF_MS` keeps the existing 500 ms default backoff. The retry factory is retained across parked native-tool and code-mode composer continuations so safe checkpoints can recover those paths too.

### Tests

- Added ResumeAction wire-shape coverage, latest-checkpoint continuation recovery, stale-checkpoint rejection, active retry-budget exhaustion, and retry kill-switch coverage.

## [0.9.12] - 2026-07-14

### Fixed

- **Transient `resource_exhausted` stream deaths**: Cursor's backend occasionally rejects a Run with Connect code `resource_exhausted` (its HTTP 429 mapping) or `unavailable` (503) before producing any output, which previously killed the whole agent turn mid-loop. The proxy now replays the identical Run once on a fresh bridge when such a retryable Connect end-stream error arrives before ANY client-visible output (no text, no tool calls, no execs), after a short backoff (`CURSOR_OPENCODE_RUN_RETRY_BACKOFF_MS`, default 500ms). The replay shares the existing one-shot respawn budget with the zero-byte-death lane, so a request never retries twice and delivered output is never duplicated. Errors after visible output remain terminal visible failures.
- Live falsification of the earlier "oversized tool result" theory: a size sweep against real Cursor (`scripts/probe-result-size-sweep.ts`) showed 512 KiB–8 MiB tool results succeed while a 64 KiB result transiently failed, confirming the error is backend capacity, not payload size. No client-side result cap is imposed.

### Added

- **Per-Run request-ID correlation**: every AgentService/Run carries an `x-request-id`, and Connect end-stream errors log it (`plugin.log`) so future upstream failures can be correlated with Cursor's backend.

## [0.9.11] - 2026-07-14

### Added

- **Repository quality ratchet**: `bun run quality:ratchet` gates Cognitive Complexity on handwritten `src/**/*.ts` (excluding generated `src/proto/**`) using the local `@barney-media/cognitive-typescript` binary only. A committed `.github/quality/ratchet.json` baseline enforces `maxComplexity: 15` and `countOver15: 0`. The collector is fail-closed on spawn errors, non-threshold exits, empty/malformed/invalid/negative/nonfinite metrics, and missing baseline. Comparator/collector tests cover exact boundary behavior, regressions, improvements, missing metrics, invalid sentinels, and generated exclusion. CI runs an authoritative parallel quality job that ignores `SKIP_HOOKS`.

### Changed

- **Complexity reduction**: extracted behavior-preserving helpers across `src/proxy.ts`, `src/tool-contract.ts`, `src/config.ts`, `src/auth.ts`, `src/index.ts`, `src/model-variants.ts`, `src/cursor-index*.ts`, `src/context-injection.ts`, `src/script-salvage.ts`, `src/arg-coerce.ts`, `src/tool-roles.ts`, `src/read-render.ts`, and `src/code-session.ts`. The project now measures 993 TypeScript functions with a maximum Cognitive Complexity of 15 and zero functions above the threshold.
- **Bridge-close finalizer semantics**: `finalizeBridgeCloseWithPendingTools` now re-reads live stream-closed state after flushing pending tool calls instead of relying on a snapshot captured before flush.
- **Auth-reader / disk sentinel behavior**: `resolveFromAuthReader` and `resolveFromDiskAuth` return `undefined` on miss/fallthrough instead of falsy strings, preserving the original `resolveCursorAccessToken` control flow.
- **Ratchet scope explicit**: `scripts/quality-ratchet.ts` now passes `targets` and `excludes` explicitly instead of relying on collector defaults.

### Fixed

- **Quality collector fail-open on empty analysis**: `metricsFromReport` now fails closed when the collector returns zero analyzed methods, preventing a discovery/target regression from appearing as a passing improvement.

## [0.9.10] - 2026-07-14

### Fixed

- **Lossless delayed tool siblings**: Cursor tool execs that arrive after an OpenAI `tool_calls` segment closes now remain owned in the parked bridge ledger and are exposed exactly once on the next segment instead of receiving a false retry error. Interaction-first calls retain early client results until their exec arrives, answered calls cannot create empty `tool_calls` rounds, and closed or duplicate calls fail with typed errors rather than hanging agent and subagent sessions.
- **Transport failure isolation and diagnostics**: HTTP/2 and Connect failures no longer open the native-tool policy circuit, while semantic duplicate or unhandled execs still do. The shared daemon now reports allowlisted, 512-byte-bounded close phase/status metadata so pre-response, mid-response, HTTP-status, timeout, and local-abort failures can be distinguished without logging request bodies, response bodies, URLs, or credentials.

## [0.9.9] - 2026-07-13

### Fixed

- **Delayed multi-tool turn loss and hangs**: tool-call turns now close after 500 ms of quiet on the latest tool-lane activity instead of a deadline anchored to the first observation, preserving MCP and native-shell siblings that Cursor serializes hundreds of milliseconds apart. Continuation ownership starts only at terminal `finish_reason`; interaction-first execs remain resumable; duplicate exec identities receive correctly typed errors, including synchronous and async AskQuestion; completion is single-shot; and timing diagnostics expose near-boundary or five-second accumulating segments without logging tool arguments or output.

## [0.9.8] - 2026-07-12

### Fixed

- **Hallucinated OpenCode skill names**: the proxy now reads the authoritative `<available_skills>` system catalog before compression and constrains the existing lowercase `skill` tool's `name` schema to those exact names on both normal and code-mode routes. Grok 4.5 Fast no longer emits nonexistent skill calls such as `codebase-exploration`; production-shaped live probes now fail on any out-of-catalog name.

## [0.9.7] - 2026-07-12

### Fixed

- **Grok discovery omission**: when Cursor's live catalog omits `grok-4.5-fast`, discovery now retains the fallback family and its concrete `medium`/`high`/`xhigh` variant slugs. Base and low-effort requests no longer send the collapsed base slug that Cursor rejects with Connect `not_found`.

## [0.9.6] - 2026-07-12

### Fixed

- **OpenCode tool-result visibility**: Cursor can emit newline-delimited client/provider tool-call IDs while OpenCode returns only the provider `fc_...` component. Unique component IDs now correlate to their pending exec instead of being replaced with `Tool result not provided`; ambiguous and unrelated IDs remain fail-closed.

## [0.9.5] - 2026-07-10

### Fixed

- **Parked-owner Run admission displacement**: a fresh Run in a conversation whose admission was held by a PARKED owner (a parked tool-call bridge or parked code session whose tool results will never arrive because the client moved on — e.g. concurrent subagents in one directory, or a cancelled turn followed by an immediate retry) now displaces that parked owner and admits, instead of failing with `Another Cursor Run is already active for this conversation` until the 10-minute idle TTL. Live streaming owners and code sessions mid sandbox turn still hold their admission and conflict with 409.

## [0.9.4] - 2026-07-10

### Fixed

- **Skill capability grounding**: every Cursor Run now receives one explicit capability boundary in both the model-visible system prompt and RequestContext. Cursor-native `Skill`/`Task`/`Plan` mechanisms stay disabled, while the live OpenCode route is stated exactly as unavailable, lowercase MCP `skill`, or code-mode `tools.skill`; models are told not to invent skill names or claim a load without a successful tool result. The live normal-mode probe now advertises one valid skill and requests a nonexistent one to catch both native-tool and skill-name hallucinations.

## [0.9.3] - 2026-07-10

### Fixed

- **Concurrent subagent recovery ownership**: OpenCode agent identity now crosses the local proxy boundary and participates in bridge and conversation routing. When a subagent Run remained active while OpenCode started a recovery turn under `build`, both previously shared one admission and the recovery loop failed with `Another Cursor Run is already active for this conversation`. Same-session same-agent continuations remain deterministic; different agents are isolated.

## [0.9.2] - 2026-07-10

### Fixed

- **Tool-result resume callback race**: resumed streams now install their data and close handlers before writing tool results back to Cursor. A fast continuation could previously reach the prior already-closed response, be rejected as a late tool call, and eventually surface as `Cursor stream ended unexpectedly before turnEnded (bridge exit 1)`. Smoke coverage injects the next exec synchronously during result delivery.

## [0.9.1] - 2026-07-10

### Changed

- **Non-fast Grok 4.5 removed from the picker.** Only `grok-4.5-fast` (with `low`/`medium`/`high` variants) remains. Live discovery drops the non-fast family; setup removes former `grok-4.5` / `grok-4.5-{medium,high,xhigh}` entries from `opencode.json`.

## [0.9.0] - 2026-07-10

### Added

- **Pinned Cursor protocol version**: advertised `x-cursor-client-version` defaults to the checked-in protocol manifest pin (`cli-2026.01.09-231024f`), not the newest installed cursor-agent. Explicit development override: `CURSOR_OPENCODE_CLIENT_VERSION`. Cursor's private Agent Run protocol remains unsupported as a public stability contract.
- **Inline RequestContext**: initial Runs send MCP tools and RequestContext inline by default (`provider.cursor.plugin.requestContext.inline`, env `CURSOR_OPENCODE_INLINE_REQUEST_CONTEXT`). Set `false`/`0` to roll back to refresh-only context; refresh replies stay enabled.
- **Native policy + catalogs**: `provider.cursor.plugin.nativePolicy` / `CURSOR_OPENCODE_NATIVE_POLICY` — `legacy` (default, full MCP declarations) or `dedupe-canary` (hide model-visible MCP duplicates for native-eligible read/list/grep while retaining the full execution catalog for bridges). Code mode still declares only `code`.
- **Native-policy circuit breaker**: under `dedupe-canary`, unhandled or duplicate/closed execs, or pre-`turnEnded` transport closure open the owning proxy context's circuit so **new** Runs use `legacy` declarations; in-flight execs keep their matching result/error encoding.
- **Controlled exec lifecycle + CancelAction**: per-exec heartbeat, abort owner, typed result or throw, and exactly one `streamClose`; client cancellation sends protocol `CancelAction` and clears owned bridge/exec/timer/sandbox state.
- **Checkpoint authority**: a valid checkpoint with matching workspace identity and client-history prefix is preserved; missing/corrupt/incomplete/cross-workspace/history-diverged checkpoints append one recovery marker and rebuild from the client ledger/history.

### Fixed

- **Strict identity-first tool-result correlation**: partial, mixed, or rewritten IDs cannot cross-wire results; equal batches with all IDs blank retain positional compatibility.
- **Per-conversation Run ownership**: fresh Runs use admission control and owner generations so stale callbacks cannot release or mutate a successor.
- **Parked code-session lifecycle**: composer transport closure cleans up the owned session, and live stream data refreshes admission activity.

## [0.8.1] - 2026-07-10

### Fixed

- **Auth-free provider fetches**: provider-scoped fetch now resolves and authenticates the live proxy only when rewriting the `:65535` setup placeholder. Requests already targeting another URL preserve context/session headers and strip proxy authorization without requiring Cursor credentials, keeping clean CI and custom fetch adapters functional.

## [0.8.0] - 2026-07-10

### Added

- **Request-scoped workspace and session routing**: each plugin instance stamps an immutable context id and the real OpenCode session id onto proxy requests. Conversation, bridge, native-tool, and checkpoint paths use the captured workspace/worktree snapshot; pending tool-call ids recover model-switched continuations without crossing context, workspace, mode, or session ownership. Unknown contexts, ambiguous continuations, and deleted/moved workspaces fail closed with explicit 409 errors.
- **Checkpoint recovery ledger**: checkpoints are bound to their workspace identity and validated through nested blob references before reuse. A bounded eight-turn ledger preserves compact user/assistant decisions, and missing, corrupt, incomplete, or cross-workspace checkpoints append one explicit model-visible recovery marker while client history is replayed.

### Fixed

- **Unified tool-call finality**: exec and interaction/E1 observations now merge into one per-call accumulator keyed by Cursor's upstream call id. Richer final interaction arguments can heal thin exec payloads; malformed interaction-only calls never cross the SSE boundary; mixed siblings retain stable ids; and 60/180/250 ms arrival boundaries are covered with a deterministic finality grace.
- **Workspace identity drift**: stale OpenCode `<env>` cwd prose and duplicate identity blocks are removed with exact matching, same-basename project folders receive path hashes, nested monorepo paths are worktree-relative on the repository protobuf, and native tools use the request's captured root instead of mutable process-global state.
- **Composed tool schemas**: bounded local `$ref` and `allOf` resolution now feeds recursive argument validation. Required/type failures are rejected before emission while empty-string Edit replacements remain valid deletions.
- **Tool-result fidelity and request boundaries**: normalized read coaching stays in `.notes` while `.text` remains byte-clean; code mode rejects direct non-`code` MCP bypasses; and requests combining tools with `stream: false` return an immediate 400 instead of entering an unsupported parked-tool flow.

## [0.7.10] - 2026-07-10

### Fixed

- **Edit missing-argument loops**: incomplete Edit/StrReplace payloads now fail required-key preflight before they cross the OpenCode SSE boundary. Cursor receives missing, expected, and received key names plus explicit guidance to regenerate the complete object instead of retrying unchanged.
- **Tool schema consistency**: shared `ToolContract` extraction keeps top-level properties, required keys, and the full schema together for normal and code-mode normalization. The no-schema fallback now re-keys all known file-tool arguments, including `oldString`, `newString`, `replaceAll`, and write `content`, rather than only `filePath`.
- **Edit recovery diagnosis**: schema and argument-validation failures no longer masquerade as `oldString` match failures. Only genuine missing/ambiguous text matches receive re-read or whitespace-heal coaching.
- **Parallel tool-call finality**: the proxy records key-only argument correlation and grants one bounded batch-window extension when interaction updates are still non-final or richer than `mcpArgs`, reducing late sibling calls without treating the 50 ms timer as protocol finality.

## [0.7.9] - 2026-07-09

### Fixed

- **Tool-result delivery (non-code mode)**: three seams in the `/v1` tool-result round trip could deliver EMPTY tool output to the model ("The tool returned no output"), observed as parallel Reads whose errors vanished on the first wave:
  - `textContent` silently dropped tool-result content parts not typed exactly `{ type: "text" }` (e.g. `output_text`, `error`, untyped `{ text }`, bare strings) — any string payload is now salvaged.
  - `handleToolResultResume` only positionally paired a single unmatched exec/result; 2+ parallel results whose `tool_call_id`s were all blanked/rewritten by the client degraded to `McpError("Tool result not provided")` for every call. Pairing is extracted into `pairToolResultsWithExecs` with an N:N positional fallback when no id matched but batch sizes agree (ambiguous mismatches still refuse to guess).
  - the mcpResult encoder always sent `isError: false` and shipped blank text verbatim; it now preserves the parsed error flag and substitutes a visible `(tool returned no output)` sentinel for empty success text.
  - Regression coverage in smoke (`testToolResultDeliveryRegression`) plus a live wire probe (`scripts/probe-parallel-toolresults.ts`) exercising string/array/output_text/empty-id/empty-content variants against grok-4.5-fast.
- **Code-mode session lifecycle (silent strand/leak class)**: a deep-scan pass over the code-mode and transport seams closed the remaining members of the empty-output failure class:
  - `CodeSession.abort()` now resolves any parked `nextEvent()` awaiter with an error `done` instead of setting `settled` and killing the child silently (TTL sweep / `stopProxy` racing an in-flight resume previously stranded the awaiting request forever).
  - `nextEvent()` gained a watchdog (default 10 min, `CURSOR_OPENCODE_CODE_EVENT_TIMEOUT_MS`): an alive-but-silent sandbox (wedged script) is killed and the awaiter receives an error `done` instead of parking forever.
  - `deliverWaveResults`/`post` return the stdin write result; all three call sites (resume, zero-wake wave, first-wave all-unknown) now branch on a dead child instead of awaiting a queue that can never fill, and `start()` reports a failed run-script write as an error `done`.
  - The sandbox child exits when the parent closes stdin — parked `callTool` promises after parent death no longer leak a node child forever.
  - `stopProxy()` disposes parked code sessions (sandbox child + composer bridge) — previously both leaked on proxy restart and a stale session could consume the new process's matching tool results.
  - Duplicate `tool_call_id` results (client re-sends) are excluded from positional pairing in both `pairWaveSlotResults` and `pairToolResultsWithExecs` (last copy wins for the owning slot; the stale copy can no longer cross-wire a different slot's answer).
  - Late execs arriving after the tool_calls turn closed are answered with the *kind-correct* protocol reply (`shell`/`shellStream`/`askQuestion`/mcp) via `answerExecWithRetryError` — a blanket mcpResult left shell/askQuestion operations unresolved upstream.
  - Auth: `pollCursorAuth`/`refreshCursorToken` reject a 200 with a missing/empty `accessToken` (schema drift, HTML-that-parses-as-JSON) instead of persisting empty credentials as a successful login; a missing refresh token falls back to the prior one.
  - A Connect frame that fails gzip+brotli decompression is now logged before being skipped (previously an invisible frame drop).
  - Regression coverage in smoke: abort-resolves-parked-awaiter, watchdog kill, dead-child start, duplicate-id pairing (both pairers), malformed-200 auth poll.

## [0.7.8] - 2026-07-09

### Fixed

- **Runtime snapshot fallback**: the Node child runtimes (`code-sandbox.mjs`, `h2-daemon.mjs`, `h2-bridge.mjs`) were spawned by absolute path into the OpenCode plugin cache on every use, so clearing/refreshing `~/.cache/opencode/packages` while a session was live bricked code mode and the transport mid-flight ("Cannot find module .../code-sandbox.mjs"). Each runtime is now snapshotted into the plugin-owned cache dir (`~/.cache/cursor-oauth-opencode/runtime`, content-hashed) at proxy load — while the original is guaranteed present — and every spawn site falls back to the snapshot when the original path has vanished. Smoke covers snapshot, fallback resolution, a real sandbox run off the snapshot, and both-missing/unreadable edges.

## [0.7.7] - 2026-07-09

### Fixed

- **Index manager LRU bound**: the per-workspace manager map (added in 0.7.6) is now a bounded LRU capped at 40 entries. A long-lived OpenCode process that hops across many workspace roots previously accumulated one live `FSWatcher` per root with no eviction; `get`/`start` now refresh recency and evict+`dispose` the least-recently-used manager past the cap, so watchers/FDs cannot leak. Workspace isolation semantics are unchanged.

## [0.7.6] - 2026-07-09

### Fixed

- **Index manager workspace isolation**: `getCursorIndexManager` no longer returns a process-wide singleton that ignored later `directory`/`worktree` options. Managers are now keyed by resolved workspace+worktree, so a shared OpenCode process serving multiple roots cannot leak another repo's index/session into context injection (which previously looked like path hallucinations). Smoke covers cross-workspace search isolation.

## [0.7.5] - 2026-07-09

### Fixed

- **Workspace-identity bake**: outbound system prompts now append a model-visible `<cursor_workspace_identity>` block (`Working directory` / workspace root) after caveman compression. Cursor `RequestContextEnv.process_working_directory` (proto field 21) is a protobuf side-channel — models still invent absolute paths under other users when OpenCode `<env>` cwd is missing/stale. Idempotent replace; smoke covers block, stale-`<env>` override, and `buildEffectiveSystemPrompt`.
- **Connect frame decompression**: `createConnectFrameParser` now honors the Connect compressed flag (`0x01`) and gunzips/brotli-decompresses framed payloads (gzip magic `1f 8b`, else brotli), matching cursor-agent `connect-accept-encoding: gzip, br`. Smoke covers gzip/br round-trip + parser path.
- **H2 header parity**: `h2-daemon` / `h2-bridge` outbound headers aligned with cursor-agent stream/unary contracts (`connect-accept-encoding`, no forbidden `te` / checksum / `local-cli-mode`). `test/parity/header-parity.mjs` gates `npm test`.

## [0.7.4] - 2026-07-09

### Fixed

- Native-mode read/ls/grep: `~/…` and `$HOME/…` paths now expand to the real home directory instead of being joined under the workspace root (which returned `fileNotFound` and made models report "Shell commands and file reads failed").
- Native shell bridge: `workingDirectory` is now tilde-expanded and validated before being passed to the client `bash` tool as `workdir`; nonexistent cwds are rescued to the workspace root instead of failing the command.
- Regression coverage in `test/smoke.ts` for tilde/$HOME reads, ls, grep, and shell cwd resolution.

## [0.7.3] - 2026-07-09

### Fixed

- **Collapsed base ids on the wire**: `ModelDetails.modelId` / `displayModelId` now use the resolved variant public slug (`detailsModelId`), not the OpenCode collapsed base. Sending `grok-4.5-fast` (no effort suffix) caused Connect `not_found`.
- **Grok default effort**: family collapse no longer treats the first-seen discovery variant as default for grok families (often `…-high` / OpenCode medium). Default is OpenCode `low` → Cursor `…-medium` when present.
- **Empty-catalog boot race**: `resolveRequestedModel` reads the discovery cache via `peekCachedCursorModels()` (falls back to `FALLBACK_MODELS`); the duplicate in-proxy `proxyModels` / `updateProxyModels` mirror is removed.

### Changed

- Proxy no longer owns a second model catalog copy. Live discovery still publishes into `src/models.ts`; `/v1/models` and effort→slug resolve peek that cache.

## [0.7.2] - 2026-07-09

### Fixed

- **Normal-mode skill/tool hallucination**: `/v1` Runs now send `x-cursor-agent-allowed-tools` with only the ToolCall cases we bridge or execute (`mcpToolCall`, `readToolCall`, `lsToolCall`, `grepToolCall`, `shellToolCall`, `askQuestionToolCall`). Previously only code mode restricted the catalog, so Cursor still offered Ask/Task/Plan/WebSearch/etc. and models invented tools OpenCode never advertised.
- **AskQuestion → OpenCode `question`**: `interactionQuery.askQuestionInteractionQuery` is bridged to the advertised `question` tool (with Cursor↔OpenCode arg mapping) and answered via `interactionResponse`. Other interaction queries (web search, plan, switch mode, Exa) are rejected so the Run does not stall. If `question` is not advertised, AskQuestion is rejected with a clear error (no Skill/Task invent).
- **Shared dialect heal on normal MCP path**: MCP tool names/args now use the same `normalizeClientToolCall` pipeline as code-mode waves (`AskQuestion`/`ask`/`Shell`/… → advertised names + schema re-key).
- **RequestContext**: explicit `skillOptions: { skillDescriptors: [] }` and `webSearchEnabled: false`, plus mcpInstructions that forbid inventing Cursor-native Skill/Task/Plan tools.

## [0.7.1] - 2026-07-09

### Fixed

- **Setup leaves pre-0.7.0 per-tier picker entries:** `providerModelsWithRepairs` now drops known legacy effort-tier ids (`grok-4.5-fast-high`, `gpt-5.4-medium`, …) from `opencode.json` while preserving unrelated user model keys. Re-run setup after upgrade so the picker shows only collapsed bases.

## [0.7.0] - 2026-07-09

### Fixed

- **`cursor-code` "Cannot connect to API"**: OpenCode builds the AI SDK client from `provider.options.baseURL ?? model.api.url` *before* `chat.params`, and `auth.loader` only stamps the primary `cursor` provider. `cursor-code` therefore kept the setup placeholder `http://127.0.0.1:65535/v1/code` baked into the SDK. The config hook now installs `options.fetch` on both providers to rewrite `:65535` → the live proxy port at request time, and stamps `cursor-code` `options.baseURL` when the primary loader runs.

### Changed

- **BREAKING — reasoning tiers are OpenCode variants, not separate picker slugs.** Effort-suffixed Cursor models (`grok-4.5-fast-high`, `claude-4.6-opus-high`, `gpt-5.4-medium`, …) collapse to one base id (`grok-4.5-fast`, `claude-4.6-opus`, `gpt-5.4`, …). Pick the base model, then choose the effort in OpenCode's Select variant menu. The proxy maps `(base, reasoning_effort)` to the Cursor slug (including the grok display/slug shift: OpenCode `high` → Cursor `…-xhigh`). Legacy full slugs remain aliases. Single-tier reasoning models advertise `variants.default` so OpenCode does not invent a fake low/medium/high menu. Re-run `npx cursor-oauth-opencode setup` and restart OpenCode after upgrading.

## [0.6.2] - 2026-07-09

### Changed

- **Caveman + runaway-reasoning port closed:** P0–P2 (caveman v3, guard, tail, harness frames, thinking exhaustion, savings receipts) documented as complete; AGENTS module map lists `thinking-exhaustion.ts` and `savings.ts`; README covers prompt-shaping knobs.
- Dual-provider user docs: README / AGENTS document `cursor/*` (native) vs `cursor-code/*` (sandbox) and the shared `cursor` login.

### Fixed

- **Context injection on `cursor-code`**: skip index injection for both Cursor-family providers (`cursor` and `cursor-code`); they already receive workspace context via the proxy.
- Dual-provider smoke gaps: live `chat.params` pins for `/v1` and `/v1/code`, advertised-tool ON/OFF with no cross-request bleed, independent array-shaped models repair, OPEN-1 `/v1/code` placeholder safety net.

## [0.6.1] - 2026-07-09

### Added

- **Dual-provider code route**: second OpenCode provider `cursor-code` served from `/v1/code` on the same proxy port. `/v1/code/chat/completions` selects code mode per request (streaming + tools required), `/v1/code/models` shares the catalog, and the global code-mode flag now acts as a kill switch only (`resolveCodeModeActive`). Setup seeds both providers; model entries carry the matching `providerID` and base path.

### Changed

- **BREAKING — `cursor/*` is code mode OFF.** Native MCP tool passthrough is the default for the primary provider. To keep sandbox-collapse behavior, select `cursor-code/<model>` in the picker. Login remains `opencode auth login --provider cursor` (shared token). Kill switch: `CURSOR_OPENCODE_CODE_MODE=0` or `provider.cursor.plugin.codeMode.enabled: false` forces OFF even for `cursor-code/*` (knob is not read under `provider.cursor-code.plugin`). No auto-rewrite of a previously selected `cursor/*` model.

### Fixed

- **Plugin error text painted over the OpenCode TUI**: every `console.error`/`console.warn` in the plugin (proxy diagnostics, model-cost fallback warnings, savings receipts, thinking-exhaustion notes, bridge/daemon lifecycle errors) wrote to the shared stderr that OpenCode's TUI renders on. All diagnostics now go through a file logger (`src/log.ts`) writing to `~/.cache/cursor-oauth-opencode/plugin.log` (override with `CURSOR_OPENCODE_LOG_FILE`, dir via `CURSOR_OPENCODE_DAEMON_DIR`; `CURSOR_OPENCODE_LOG_STDERR=1` mirrors to stderr for local debugging). Nothing writes to stdout/stderr by default.

## [0.6.0] - 2026-07-09

### Added

- **Thinking-exhaustion containment** (`src/thinking-exhaustion.ts`): a gated model's turn that spends its whole budget thinking (zero visible text, zero tool calls, max_tokens or Cursor volume/duration heuristic) arms a single-shot `[thinking exhaustion]` nudge on the next outbound Run telling the model to externalize (text or tool call) instead of re-planning into the same spiral. Streak-aware wording; kill switch `CURSOR_OPENCODE_THINKING_EXHAUSTION=0`, thresholds `_MIN_CHARS` / `_MIN_MS`.
- **Savings receipts** (`src/savings.ts`): process counters for caveman-compression savings on tool descriptions and the system prompt; opt-in stderr receipts under `CURSOR_OPENCODE_SAVINGS=1`.
- `CURSOR_OPENCODE_DEBUG_TOOL_UPDATES=1`: dump raw `toolCallStarted/Delta/Completed/partialToolCall` interaction updates for wire debugging.

### Fixed

- **Interaction-only tool calls dropped** (grok-4.5-fast tiers): some models route MCP tool calls exclusively through `toolCallStarted`/`partialToolCall` interaction updates and never send an `execServerMessage`, so the proxy dropped every call and the turn ended empty (the "tool calling fails on continued threads" transcript failure). The proxy now buffers full `mcpToolCall` payloads from those updates (`captureInteractionToolCall`, args decoded identically to the exec path; a result echo for the same call id evicts the buffer so exec-routed calls can never double-fire) and, when the stream closes with no exec message received, synthesizes them into OpenAI `tool_calls`. No execId exists on this lane, so the bridge is not parked — the follow-up tool-result request takes the existing fresh-Run resume path on the stored checkpoint. Live-validated two-turn tool round-trips on `grok-4.5-fast-medium` and `grok-4.5-fast-xhigh`.
- **False "tool calls may be dropped" warning** (`[proxy] model ... sent N tool-call interaction updates with no exec message and no text`): Cursor echoes every exec-path MCP tool call back as `toolCallStarted/Completed` bookkeeping interaction updates — `toolCallCompleted` even carries the `mcpResult` the proxy already answered with — and a tool-result resume stream begins with those echoes. The E1 counter treated every update as a potential drop, so healthy resume turns (observed on `grok-4.5-fast-*`) logged the warning. The counter now counts only a genuinely unrouted call: a NEW call id carrying args with no result and no matching exec message this stream; exec-routed ids are registered and their echoes suppressed. Live-validated on `grok-4.5-fast-medium` multi-turn tool rounds: zero warnings, tool calls intact.

## [0.5.0] - 2026-07-09

### Added

- **Caveman prose compression** (`src/caveman.ts`, port of transponder's caveman.mjs rules v3): deterministic rule-based compression of client tool-description prose (compress-before-truncate in `code-description.ts`) and the OpenCode-supplied system prompt. Protected spans (code fences, inline code, URLs, paths, quoted literals, tags, ALL-CAPS tokens, JSON-ish lines, edit anchors) are masked before rules run and restored after, with an all-or-nothing fail-safe. Config: `plugin.caveman.level` / `plugin.caveman.system` (default `full`; system inherits), env `CURSOR_OPENCODE_CAVEMAN` / `CURSOR_OPENCODE_CAVEMAN_SYSTEM`.
- **Reasoning guard** (`src/reasoning-guard.ts`): Anthropic's tested anti-overplanning block appended verbatim (never compressed) to the outbound system prompt for runaway-reasoning models (`claude-fable-5`, `claude-opus-4-8`). Kill switch `CURSOR_OPENCODE_REASONING_GUARD=0`.
- **Reasoning-tail fallback** (`src/reasoning-tail.ts`, `src/text-boundary.ts`): when a gated model's turn settles with thinking + tool calls but zero visible text, a caveman-compressed, sentence-snapped tail (default 480 chars, `CURSOR_OPENCODE_REASONING_TAIL` overrides, `0` disables) of that thinking is stashed per conversation and re-presented once on the next outbound Run as a `[reasoning tail]` note. Visible text clears the stash; tool-less thinking never stashes; surrogate-pair-safe trims.
- **Harness frames** (`src/harness-frames.ts`): one coalesced appendix per outbound Run for gated models — interstitial visible-text reminder + `<system-reminder>` overthink nudge on real user turns (`CURSOR_OPENCODE_INTERSTITIAL_TEXT=0` / `CURSOR_OPENCODE_OVERTHINK_NUDGE=0`), throttled reasoning-continuity note on tool-result resumes (wave 1 then every Nth, `CURSOR_OPENCODE_REASONING_CONTINUITY=0` / `_EVERY`).
- Smoke coverage: caveman port fidelity (rules, span protection, fail-safe, config), guard gate + kill switch, harness frame cadence + kill switches, reasoning-tail lifecycle (budget, boundary snap, stash/clear/single-shot consume, model gates), and description-shrink stability.

### Changed

- `code-description.ts` compresses per-tool prose before truncation (signatures and truncation pointers never compressed); `proxy.ts` compresses the system prompt per `caveman.system` and appends the coalesced harness appendix to the outbound user text; per-conversation reasoning-tail/harness state rides `conversationStates` (TTL-swept).

## [0.4.10] - 2026-07-08

### Added

- Gap-coverage smoke tests (12 new cases): direct matrices for `arg-coerce` stringified-arg coercion, `tool-roles` (role map, advertised-name heal, arg rekey, closest-name ranking), `edit-heal` failure classification + fuzzy span + coach notes, warp-sketch edit lowering (`warp-edit.mjs`), `script-salvage` trailing invocation + files-payload shapes, `jsonSchemaToTs` union/allOf/tuple/nested rendering, PKCE S256 generation, mocked `pollCursorAuth`/`refreshCursorToken` (404-continue, error budget, refresh failure), `cursor-index-wire` path crypto round-trip + protobuf string encoding, `validateRepositoryIndexMetadata`, `listProjectLayoutEntries`/`makeIndexSessionId`, and the legacy `h2-bridge` fallback path (daemon marked failed → per-request subprocess).

### Changed

- `src/auth.ts`: `CURSOR_POLL_URL` and `CURSOR_POLL_BASE_DELAY_MS` env overrides (test seam, mirrors `CURSOR_REFRESH_URL`; defaults unchanged).

## [0.4.9] - 2026-07-09

### Fixed

- **Code-mode native tool leakage:** inject `x-cursor-agent-allowed-tools: mcpToolCall` on code-mode AgentService/Run bridges (same contract as cursor-agent `--allowed-tools`). Live probe confirms `NATIVE_EXEC_COUNT=0` while sandbox waves still complete; native read/shell/edit frames no longer arrive under code mode.

## [0.4.8] - 2026-07-09

### Fixed

- **Compaction Aborted / dead Cursor proxy:** `chat.params` could not start the proxy when `auth.loader` had not run yet (compaction/subagent turns), because token resolution depended only on the loader-injected auth reader. Now falls back to `~/.local/share/opencode/auth.json`, eagerly warms the proxy at plugin boot, and logs `chat.params` failures instead of silently leaving requests on `:65535`.

## [0.4.7] - 2026-07-09

### Added

- Code-mode hardening:
  - Files-shape heal ladder (`normalizeFilesShape`): pairs/tuples/nested body keys/JSON-string maps.
  - Empty-script coaching with required shape + truncation hints.
  - Warp-sketch edit lower (`src/warp-edit.mjs`): elision-marker sketches → byte-exact edit spans.
  - `codemode.poll(fn, { intervalMs, timeoutMs })` sandbox helper.
  - Per-tool description prose cap default 700 chars (`CODE_TOOL_DESC_MAX_CHARS`).
  - codeMode continuity across shell-bridge parks (`ActiveBridge.codeMode`).
  - `CODE_ONLY_REASON` on backgroundShell/fetch/writeShellStdin under code mode.

## [0.4.6] - 2026-07-09

### Fixed

- **Cannot connect to API / compaction failure:** when `provider.cursor` was missing from `opencode.json` (setup wiped or never run), auth.loader's live proxy `baseURL` had nothing to merge into and every Cursor request (including `agent.compaction` → `cursor/grok-4.5-fast-*`) hit a dead URL. The plugin now (1) reseeds `provider.cursor` + fallback models via a `config` hook, (2) pins `options.baseURL` on every Cursor turn via `chat.params`, and (3) writes the live proxy URL onto `provider.api` / model `api.url` using `127.0.0.1` (avoids IPv6 `localhost` miss).

## [0.4.5] - 2026-07-08

### Added

- Code mode exclusivity: when code mode is active, Cursor **native** read/ls/grep/write/delete are rejected with coaching to use `code` + `await tools.X()` — the model no longer has a parallel native tool path that skips the sandbox.
- Schema-rich `code` tool description: client tools are advertised as TypeScript-shaped signatures (from their JSON Schema) plus exclusivity/doctrine coaching, not a bare name list.
- Read-result normalization: OpenCode path/type/content framing and line-number gutters are stripped before results reach the sandbox (`.text` is disk-clean for Edit round-trips).
- Edit-heal: a failed edit with whitespace-only old_string drift is auto-retried once with a unique fuzzy disk match; otherwise the error is coached with a file window.
- Wave slot errors: unknown tools become synthetic isError results without a client round-trip.

### Fixed

- Native shell under code mode: bash/shell is resolved from the **real client tool catalog** (`codeMode.toolNames`), not from `mcpTools` (which only lists `code`). Prevents the hang where native shell was rejected because no shell tool appeared in mcpTools.

## [0.4.4] - 2026-07-08

### Fixed

- Code mode: sandbox wave tool_calls now schema-normalize args against the client's real tool definitions before OpenCode sees them. Cursor-native names (`path`, `file_path`, `old_string`, …) map to OpenCode's declared names (`filePath`, `oldString`, …), killing the `SchemaError(Missing key ["filePath"])` loop that made agents narrate "no files found" / "shell unavailable" after a successful listing and fall through to invented recovery tools.
- Code mode: the `code` entrypoint is recognized under provider-qualified names (`mcp_opencode_code`, `opencode_code`) as well as the bare name, so interception cannot miss and leak a non-existent tool call to the client.
- Code mode: sandbox start/nextEvent failures and unhandled session rejections answer composer with an error instead of wedging the SSE turn.
- Legacy (no-schema) arg aliasing is case-insensitive and covers `write`/`edit` path → `filePath` in addition to `read`.

## [0.4.3] - 2026-07-08

### Fixed

- Native read/ls/grep: an absolute path fabricated under the wrong parent ("/Users/<other-user>/Projects/<repo>/src/x.ts") whose trailing suffix exists under the workspace root is now rescued to the workspace file instead of returning fileNotFound. Longest-suffix match, workspace-contained only; existing outside-workspace paths and truly missing tails are untouched. Kills the wasted read + recovery turn from hallucinated cwd/parent paths.

## [0.4.2] - 2026-07-08

### Fixed

- Streaming proxy: a bridge that died with a nonzero exit before ANY response bytes arrived surfaced as "[Error: Cursor stream ended unexpectedly (bridge exit 1). Please resend the message.]", losing the whole turn. The proxy now respawns a fresh bridge and replays the identical request bytes once (budget: one respawn per request; replay is gated on zero bytes received so partial frames or consumed checkpoints can never double-deliver). This covers crashed daemons and legacy bridge processes that the daemon's in-process 0.3.7 replay cannot reach.

## [0.4.1] - 2026-07-08

### Fixed

- Streaming Connect end-stream errors now surface as a real OpenAI-style `error` object on the SSE stream instead of being injected as assistant `content`, so clients render them as errors and they never pollute replayed conversation history.
- `usage.prompt_tokens` is now frozen at the first `conversationCheckpointUpdate` (Cursor's cumulative `usedTokens` minus completion accrued by then) instead of being re-derived subtractively per checkpoint, which inflated prompt counts as completion grew.
- `h2-daemon`: GOAWAY/close no longer drifts pool `refCount` accounting — detached sessions with still-draining channels are tracked so load balancing sees them instead of over-dialing.
- `h2-daemon`: the unix socket and pid file are now uid-keyed (`h2d-v2-u<uid>.sock`) and the daemon refuses to serve from a state directory owned by another user, closing the remaining cross-user token-leak window (dir was already 0o700 and socket 0o600).

### Added

- `h2-daemon`: at pool cap, a session at/over the server's advertised `maxConcurrentStreams` triggers one overflow dial instead of letting `session.request()` refuse streams under extreme parallel-subagent load.
- Debug counter for `toolCallStarted/Delta/Completed` interaction updates: a stream that ends with counted tool-call updates but no exec message and no text logs a warning, so a model routing tool calls only through interaction updates is observable instead of silently dropping calls.
- Shared `src/model-fallbacks.ts`: `bin/setup.ts` now consumes `SETUP_MODEL_METADATA` from the same module as the runtime fallback list (bundled at build time), removing the last hand-maintained duplicate model table.
- Smoke coverage: daemon OPEN-frame `x-cursor-client-version` asserted at the wire on the upstream stream; prompt-token freeze; ignored tool-call update counter; uid-keyed daemon socket path.

## [0.4.0] - 2026-07-08

### Added

- Code mode (on by default): the proxy now advertises a single `code` tool to the Cursor model instead of the full client tool list. The model writes one async JavaScript program; the proxy runs it in a Node child sandbox (`src/code-sandbox.mjs`, `node:vm`) and bridges each `await tools.X()` wave to OpenCode's permissioned tools. The Cursor bridge stays parked across every client tool round-trip, so a K-step dependent tool chain collapses from K model round-trips to 1 (the transponder code-mode model). `Promise.all([...])` batches independent calls into one wave; sequential `await`s stay ordered. Disable via `provider.cursor.plugin.codeMode.enabled=false` or `CURSOR_OPENCODE_CODE_MODE=0`; code mode is streaming-only and falls back to direct tool passthrough when the client declares no tools or the request is non-streaming.

## [0.3.9] - 2026-07-08

### Changed

- HTTP/2 transport now prewarms and tunes the shared daemon path: DNS lookup cache, TCP_NODELAY + TCP keepalive via `createConnection`, H2 PING keepalive with ACK timeout eviction, multi-session pooling, and a `T_PREWARM` control frame so TLS/H2 sessions are dialed off the request path. Protocol-level `PrewarmRequest` remains opt-in via `CURSOR_OPENCODE_PROTOCOL_PREWARM=1`. Cleartext `http://` origins (smoke/local h2c) still use net.connect instead of TLS.

## [0.3.8] - 2026-07-08

### Changed

- Native Cursor read-only tools (`read`, `ls`, `grep`) are now executed locally by the proxy with native success frames instead of being rejected. The model no longer narrates "standard tools are unavailable, using MCP tools" or burns turns falling back; relative paths resolve against the workspace root, and mutating tools (write/delete) plus network fetch remain rejected so side effects still flow through the caller's permissioned MCP tools.

## [0.3.7] - 2026-07-08

### Fixed

- `h2-daemon`: an HTTP/2 stream that died before the first response byte (dead pooled session, GOAWAY race, REFUSED_STREAM, idle-killed TCP) surfaced as "Cursor stream ended unexpectedly (bridge exit 1)". The daemon now buffers request bytes until the first response byte arrives and replays them once on a fresh session, evicting the suspect session from the pool. Mid-response failures are never replayed (no duplicate delivery), and the retry budget is one attempt per channel.

## [0.3.6] - 2026-07-08

### Fixed

- Cursor's Auto router (model id `default`, alias `auto`) now resolves its published fixed rates ($1.25/M input, $6/M output, $0.25/M cache read) instead of warning `no cost entry for model "default"` and falling back to `DEFAULT_COST` on every OpenCode load.

## [0.3.5] - 2026-07-08

### Fixed

- **Plugin failed to load in OpenCode** with `Plugin export is not a function`.
  OpenCode iterates every module export and requires each value to be a plugin
  function (or `{ server }`). The entrypoint's `export const __test` object
  (cost-table helpers added in 0.3.1) broke that contract, so the auth loader
  never ran, the local proxy never started, and every Cursor request hit the
  setup placeholder `http://127.0.0.1:65535/v1` with
  `Cannot connect to API: Unable to connect`. Cost helpers moved to
  `src/model-cost.ts`; the package entrypoint now only exports the plugin.

## [0.3.4] - 2026-07-08

### Added

- **Grok 4.5 Fast tiers.** `grok-4.5-fast-medium`/`-fast-high`/`-fast-xhigh`
  (display names "Grok 4.5 Low/Medium/High Fast", matching Cursor's shifted
  id↔name convention) are now seeded into the picker and runtime fallback.
  They were present in the live catalog but missing from 0.3.3's curated list.

### Fixed

- **Grok 4.5 cache-read pricing** corrected to $0.5/M per cursor.com docs
  (was $0.2/M). Fast tiers share the Grok 4.5 rate until Cursor documents a
  separate fast price.

## [0.3.3] - 2026-07-08

### Added

- **Current headline models in the seeded picker and runtime fallback.**
  `FALLBACK_MODELS` / `SETUP_MODEL_METADATA` / setup's `CURSOR_MODELS` now
  include `grok-4.5-medium`/`-high`/`-xhigh`, `gemini-3.5-flash`,
  `glm-5.2-high`, `kimi-k2.7-code`, `claude-sonnet-5-medium`, and
  `claude-opus-4-8-medium`, so the OpenCode picker shows a useful catalog
  even before live discovery completes.
- **Cost coverage for new families.** Explicit `grok-4.5`, `glm-5.2`,
  `kimi-k2.7-code`, and `gemini-3.5-flash` cost entries/patterns; `glm` no
  longer falls through to `DEFAULT_COST`.

### Fixed

- **Array-shaped `provider.cursor.models` broke the picker.** An array value
  (a hard allowlist to OpenCode) was previously `Object.entries()`'d into
  bogus numeric keys by setup instead of being repaired. Setup now resets any
  non-object `models` value to the seeded map; object-shaped user overrides
  are still preserved.
- **Test-only bridge insert leaked a ref'd heartbeat timer**, which could keep
  the smoke-test process alive after completion.

## [0.3.0] - 2026-07-07

### Added

- **H2 session pool.** The shared daemon now maintains up to 3 (configurable
  1-4 via `CURSOR_OPENCODE_H2_POOL_SIZE`) HTTP/2 sessions per origin with
  least-loaded channel placement, lifting the per-connection
  `maxConcurrentStreams` ceiling and reducing TCP head-of-line blocking
  under heavy parallel agent use. New sessions are dialed lazily and dead
  sessions are pruned from the pool.
- **Daemon prewarm.** The proxy now connects to (and if needed spawns) the
  shared H2 daemon as soon as it binds, so the first chat request no longer
  pays daemon spawn + unix-socket connect latency.

### Fixed

- **Abandoned tool-loop bridges leaked forever.** A bridge paused for tool
  results whose results never arrived (Esc, crashed opencode) kept its h2
  channel, 5s heartbeat timer, and blob store alive indefinitely — the
  heartbeat also defeated the daemon channel's rolling timeout. Idle paused
  bridges are now evicted after 10 minutes
  (`CURSOR_OPENCODE_BRIDGE_IDLE_TTL_MS`), and heartbeat timers are unref'd
  so they can never keep the host process alive.
- **Daemon fallback was permanently sticky.** One transient connect+spawn
  failure demoted the entire session to per-request node subprocesses. The
  daemon path is now retried after a 60s backoff.
- **Unbounded buffering on slow readers.** Daemon↔client socket writes and
  bridge stdout writes now honor backpressure: full buffers pause the
  producing h2 stream/socket until drain instead of buffering without bound.
- **O(n²) framing readers.** All three length-prefixed frame readers (proxy
  daemon client, daemon unix socket, legacy bridge stdin) accumulated via
  `Buffer.concat` per chunk. They now use O(n) chunk-list readers that copy
  each byte exactly once.

## [0.3.0] - 2026-07-07

### Added

- **HTTP/2 session pool.** The shared daemon now maintains up to 3 sessions
  per origin (tunable via `CURSOR_OPENCODE_H2_POOL_SIZE`, capped at 4) and
  routes each channel to the least-loaded live session. This lifts the
  server's per-connection `maxConcurrentStreams` ceiling and stops one lossy
  TCP connection from head-of-line-blocking every concurrent agent stream.
- **Idle tool-loop bridge eviction.** Bridges paused for tool results whose
  results never arrive (Esc, crashed client) are now closed after an idle TTL
  (default 10 minutes, `CURSOR_OPENCODE_BRIDGE_IDLE_TTL_MS`) instead of
  holding their h2 channel, heartbeat timer, and blobs forever. The next
  request falls back to a checkpoint resume.
- **Daemon prewarm.** The proxy connects to (or spawns) the shared h2 daemon
  when it binds, so the first chat request no longer pays daemon spawn +
  unix-socket connect latency.

### Fixed

- **Permanent daemon fallback.** A single transient daemon connect failure
  demoted the whole session to per-request node subprocesses. The daemon path
  is now retried after a 60s backoff.
- **Unbounded write buffering.** All three transports (proxy client socket,
  daemon, legacy bridge) now honor stream/socket backpressure: producers are
  paused until the consumer drains instead of buffering unboundedly.
- **Quadratic frame parsing.** The length-prefixed frame readers in the
  proxy daemon client, the daemon, and the legacy bridge accumulated bytes
  with per-chunk `Buffer.concat` (O(n²) under backlog). All three now use
  O(n) chunk-list readers that copy each byte exactly once.
- Tool-loop heartbeat timers are now `unref()`'d so an abandoned loop can
  never keep the host process alive.

## [0.2.3] - 2026-07-07

### Fixed

- **Conversation amnesia in agentic sessions.** `parseMessages` silently
  dropped every assistant answer that followed a tool-call round (no pending
  user message to pair it with), so the history replayed to Cursor contained
  user messages only. The model "forgot" everything it had said and
  hallucinated paths/content. Post-tool assistant text is now attached to the
  previous turn so the replayed history is faithful.
- **Historical tool results replayed as fresh input.** All tool messages in
  the OpenAI history were collected as pending tool results; after a bridge
  died they were joined into a garbage mega-message. Only the trailing run of
  tool messages (results for the tool calls currently awaiting an answer) is
  now treated as tool results.
- **Esc/abort stranded checkpoints.** Checkpoint blobs were merged into the
  persistent conversation store only in the bridge `onClose` path. The
  `cancel()` path (user pressed Esc) skipped the merge while a checkpoint
  referencing those blobs had already been persisted — the next turn failed
  the blob-availability check and silently dropped the whole checkpoint
  (total amnesia after a wedge). Blobs are now merged at checkpoint time and
  again on cancel.
- **Silent empty stops on bridge death.** When the bridge died before Cursor
  produced any output, the proxy emitted an empty assistant turn with
  `finish_reason=stop`, which looks like a hang/wedge in OpenCode. A visible
  error message is now emitted so the user knows to resend.

### Changed

- Conversation state TTL raised from 30 minutes to 6 hours so long agent
  sessions keep their server checkpoints (memory remains bounded by the
  existing conversation-count and blob-byte caps).

## [0.2.2] - 2026-07-07

### Fixed

- **Cross-workspace conversation leakage.** Conversation and bridge keys were
  derived only from the message fingerprint (system prompt + opening turns).
  Two sessions in different repos with a similar opening prompt could derive
  the same deterministic Cursor conversation ID, so Cursor's server resumed
  the OTHER repo's conversation checkpoint — surfacing as the model
  confidently "remembering" paths and files from a different project
  (hallucinated `/Users/.../transponder/...` paths, etc.). The workspace
  directory is now folded into every fingerprint, so identical prompts in
  different repos can never share conversation state.
- **Wedged sessions after client abort.** When the client aborted a streaming
  response (Esc in OpenCode), the proxy never observed the cancellation: the
  h2 bridge and its 5s heartbeat kept running forever and a stale
  `activeBridges` entry could wedge the session's next request. The SSE
  stream now implements `cancel()` — aborting the bridge, clearing the
  heartbeat, and dropping the stale bridge entry. Clean bridge closes after
  emitted tool calls also proactively remove unreusable entries.

## [0.2.1] - 2026-07-06

### Fixed

- **Schema-aware tool argument normalization.** Cursor-hosted models emit
  Cursor-native snake_case tool parameters (`new_string`, `old_string`,
  `file_path`, ...) while OpenCode tools declare camelCase (`newString`,
  `oldString`, `filePath`, ...), which surfaced as errors like
  "parameter was named new_string instead of newString". The proxy now decodes
  each tool's declared JSON schema from the request and transparently renames
  any argument key whose snake_case/camelCase twin (or known alias such as
  `path` → `filePath`) exists in the schema — for every tool, in both
  directions, invisibly to the model. Keys already matching the schema and
  keys with no schema twin are passed through untouched; nothing is ever
  dropped or overwritten on collision.

## [0.2.0] - 2026-07-06

### Added

- **Shared HTTP/2 daemon** (`h2-daemon.mjs`): one long-lived Node process per
  machine now owns persistent HTTP/2 sessions to Cursor and multiplexes every
  request from every opencode instance over a unix domain socket. Replaces the
  per-request `node h2-bridge` subprocess model, which exhausted system
  pty/process limits under heavy concurrent agent use and paid Node startup +
  TLS handshake on every request. The daemon self-starts on demand, survives
  proxy restarts, exits after 10 minutes idle, and is shared safely across
  concurrent opencode processes (single-flight startup, stale-socket recovery).
  Set `CURSOR_OPENCODE_NO_DAEMON=1` to fall back to the legacy per-request
  bridge; `CURSOR_OPENCODE_DAEMON_DIR` overrides the socket directory.

### Fixed

- Identical-template parallel subagents no longer collide on shared
  conversation/bridge state: the conversation fingerprint now covers the full
  leading user/assistant prefix (not just the first user message) and folds in
  the OpenAI `user` request field as a request-level discriminator.
- Conversation state is now bounded: LRU caps on conversation count
  (`CURSOR_OPENCODE_MAX_CONVERSATIONS`, default 64) and total stored blob bytes
  (`CURSOR_OPENCODE_MAX_TOTAL_BLOB_BYTES`, default 256MB), plus a periodic TTL
  sweep instead of eviction only on request arrival.
- The local index scan no longer blocks the event loop: directory walks use
  async fs with periodic yielding, per-file stored text is capped, and the
  synchronous cold paths (git via `spawnSync`, sqlite reads of Cursor state)
  were converted to async equivalents.
- Context injection search is budgeted (2.5s) and cached (60s TTL) so a slow
  cloud index search cannot stall message starts; budget misses populate the
  cache in the background. Default `searchTimeoutMs` lowered 8s → 4s.

## [0.1.11] - 2026-07-05

### Fixed

- `composer-2.5-fast` (and other Composer models) no longer hang when invoked as
  an OpenCode subagent. Composer prefers Cursor's **native** shell exec
  (`shellStreamArgs`) over the MCP-registered `bash` tool. Native streaming shell
  is an *attachable execution*: after the proxy bridges it to the caller's shell
  tool and replies with the stream (`start` → `stdout` → `exit`), the server keeps
  the tool call open until the exec stream is explicitly closed. The proxy now
  sends an `ExecClientControlMessage.streamClose` control frame to finalize the
  execution, so the model resumes generation instead of stalling on heartbeats.
  Non-streaming native shell (`shellArgs`) is answered with a unary `ShellResult`.

## [0.1.10] - 2026-07-05

### Fixed

- Concurrent subagents no longer stall. A burst of parallel requests (e.g. a
  parent agent fanning out many subagents) previously blocked the proxy's single
  event loop and piled up orphaned `h2-bridge` processes. Three root causes were
  addressed:
  - **Non-blocking workspace context.** `collectWorkspaceContext()` ran three
    synchronous `git` subprocesses plus filesystem walks on every
    `requestContextArgs`, blocking every in-flight stream and heartbeat. The
    request hot path now reads a TTL-cached snapshot (10s), warmed at proxy
    startup and refreshed in the background via async `git` so the event loop is
    never blocked.
  - **Single-flight OAuth refresh.** An expired token no longer triggers N
    parallel `refreshCursorToken` calls; concurrent requests now share one
    in-flight refresh.
  - **Collision-proof conversation keys.** Bridge and conversation keys are now
    derived from the full system prompt plus the full first user message instead
    of the first 200 characters, so parallel sessions that share a boilerplate
    prompt prefix no longer clobber each other's bridge or conversation state.

### Added

- `h2-bridge` stderr is now captured and logged on non-zero exit, so bridge
  failures are diagnosable instead of silent.

## [0.1.9] - 2026-07-04

### Changed

- Removed dead legacy Composer models.
- Exposed Cursor model variants from the live catalog.

### Fixed

- Declared provider input limits.

## [0.1.6] - 2026

### Added

- Cursor workspace context, index adapter, and prompt injection.
- Wired Cursor cloud index defaults.

## [0.1.4] - 2026

### Fixed

- Provide Cursor workspace context.

### Added

- Improved Cursor plugin startup performance.

## [0.1.2] - 2026

### Added

- Cursor setup command.

### Fixed

- Normalize Cursor read tool args.

[0.1.11]: https://github.com/jaredboynton/cursor-oauth-opencode/releases/tag/v0.1.11
[0.1.10]: https://github.com/jaredboynton/cursor-oauth-opencode/releases/tag/v0.1.10
[0.1.9]: https://github.com/jaredboynton/cursor-oauth-opencode/releases/tag/v0.1.9
[0.1.6]: https://github.com/jaredboynton/cursor-oauth-opencode/releases/tag/v0.1.6
[0.1.4]: https://github.com/jaredboynton/cursor-oauth-opencode/releases/tag/v0.1.4
[0.1.2]: https://github.com/jaredboynton/cursor-oauth-opencode/releases/tag/v0.1.2
