Commit Graph

16340 Commits

Author SHA1 Message Date
Diogo Soares Rodrigues 6254b6e81b fix(cursor): build the pi_edit bridge independently of the session's provider
Every native `pi_edit` failed after a session switched onto Cursor. The
replace-mode `edit` instance the frame needs was built only for sessions
CREATED on Cursor, and the tool roster is built once, at creation - a
session that started elsewhere kept its configured-mode `edit` in the
registry, which `executeTool` resolves before its fallback, so the
frame's `old_text`/`new_text` pairs failed validation against a
`hashline` schema.

The instance is now built from the `edit` grant regardless of the
initial provider, lazily so a session that never reaches Cursor never
constructs one, and `pi_edit` asks for it through a dedicated
`getEditReplaceTool` accessor rather than relying on Cursor sessions
having deleted `edit` from the registry. A session that was never
granted `edit` is still refused.

That accessor also closes an escalation the previous wiring opened up.
The session's device resolver is handed to the bridge as `getTool` and
installed as the agent loop's `resolveFallbackTool`, which runs for ANY
call outside the advertised set - so serving `edit` from it let a
hallucinated call, or one naming a tool the session deselected after
startup, execute a replace-mode edit the model was never offered. It is
device-only again.

Regressions cover both directions at the SDK level, driving a real
unadvertised `edit` through the loop and asserting the surfaced
`Tool edit not found`: an unchanged file alone would also pass if the
fallback had resolved the tool and the edit then failed validation.

(cherry picked from commit 11a28dcf7b995a9e94913269733b3199d6f4790d)
2026-07-30 01:43:02 +02:00
usr-bin-roygbiv 17e678fa2d fix(agent): honor explicit compaction endpoint
(cherry picked from commit 3bc5c578ddd2e626eda2049a660de7bffb674b52)
2026-07-30 01:42:53 +02:00
usr-bin-roygbiv 64234e05c9 fix: align manual native compaction fallback
(cherry picked from commit 2d5397a52f6feaeee4136fe4e7d8271309043b75)
2026-07-30 01:42:53 +02:00
usr-bin-roygbiv 9dd8d3c6ce fix: preserve native compaction failures
(cherry picked from commit d13e9f30a06cad347226d2fa377cab8c086debc3)
2026-07-30 01:42:53 +02:00
usr-bin-roygbiv 7fff8869a0 fix: skip unauthenticated compaction candidates
(cherry picked from commit fa5f7d73ec1f169f9b9952648195057caa24657e)
2026-07-30 01:42:53 +02:00
usr-bin-roygbiv 4d3f2e32b3 fix(compaction): preserve native timeout boundaries
(cherry picked from commit 2a5950036f6c671004f2ddf1d2054a481a53cfe8)
2026-07-30 01:42:34 +02:00
usr-bin-roygbiv d084697fae fix(compaction): retry transient native failures
(cherry picked from commit 293c96dfa479de13995d581caec33b3c90b5b3ca)
2026-07-30 01:42:33 +02:00
usr-bin-roygbiv 792e8a06b1 docs: keep changes under unreleased
(cherry picked from commit 8ac4d11932c01d37881963f7bc97cb9a0436ae11)
2026-07-30 01:42:33 +02:00
usr-bin-roygbiv 66c8f27d23 style(compaction): format fallback repairs
(cherry picked from commit bf909c728adf80b1ad4c70f79701f6057138c6d9)
2026-07-30 01:42:33 +02:00
usr-bin-roygbiv c83e506954 fix(compaction): require native-capable fallbacks
(cherry picked from commit 6ac4efd9d2fe37fb50be6e9ba6664e8924bce602)
2026-07-30 01:42:33 +02:00
usr-bin-roygbiv fe59a27cc8 style(coding-agent): format advisor fallback tests
(cherry picked from commit 09cf89422eaa39319652d55a502412125c94856e)
2026-07-30 01:42:33 +02:00
usr-bin-roygbiv 28e738830d fix(compaction): align advisor provider boundary
(cherry picked from commit 771a9917caa2630b96e8fd8f6f8d73d5462242d3)
2026-07-30 01:42:33 +02:00
Diogo Soares Rodrigues 59434149d1 fix(cursor): gate resource downloads, fix pi_grep cap and MCP transcript
Download-mode resource reads created and overwrote workspace files
without running a registry tool - the same hole the native `delete`
frame had - so a session that withheld `write`/`edit`, or whose `write`
tier is `deny`/`always-ask`, still had files written. Both frames now
share one grant and one policy check, and the download refuses before
the read so a blocked call never fetches the resource.

`allowNativeDelete` is renamed `allowDirectFileMutation`: it now gates
more than deletion. The primary session derives it from the registry
BEFORE its own rewriting (Cursor moves `edit` out of the tool map and
`write` may be auto-registered later, so reading the map at bridge
construction would misjudge both) and unconditionally, since the bridge
is installed for every session and one that starts on another provider
can switch to Cursor later.

`pi_grep` with a match cap: the local tool windows to 20 files and
suggests `skip`, which `PiGrepExecArgs` cannot express - 100 matches
requested over 25 one-match files returned 20, with the cap reported
unreached. A capped search now reads cap+1 files, so a result landing
exactly on the cap is distinguishable from a clipped one, and
`match_limit_reached` is truthful either way.

`read_mcp_resource` synthesized no transcript block and paired no
result, so a read - including a download that mutates the workspace -
was invisible in the UI and stripped from every rebuilt history. It now
synthesizes a `read_mcp_resource` block (not `read`: the name drives
rendering and prune semantics) and pairs success, not-found and error.

(cherry picked from commit 5ff27a3efe8bec522d9d5dbd7763055eb03eae3b)
2026-07-30 01:42:30 +02:00
Diogo Soares Rodrigues 821fe75f5d fix(cursor): close download hardlink escape, MCP mime and read range
Hard link escape: a hardlink inside the workspace is a regular file
that passes containment AND `O_NOFOLLOW` while sharing its inode with
a file anywhere else, so truncating it clobbers that file. Proven
before the fix. The open now drops `O_TRUNC`, checks `nlink`/regular
on the OPEN handle, and truncates only after - the pattern
`autolearn/managed-skills.ts` already uses. `O_NOFOLLOW` covers the
final component only; the parent-swap window is documented, not
claimed shut.

MCP resource discovery: `getServerResources` is async and awaits
`ensureServerResources`, so a frame arriving while a server's catalog
still loads no longer reads the empty cache and reports "advertises
nothing" - a lie the model cannot distinguish from the truth.

Mixed-content reads: the mime type came from `contents[0]` while the
payload came from whichever item supplied it, so an image blob
followed by a text note sent the text as `image/png`.

Ranged `pi_read`: a plain `:N+K` selector pads one leading and three
trailing context lines, so offset 5/limit 20 handed Cursor lines 4-27.
Ranged reads compose `:raw:N+K`, verified against a real `ReadTool`.
The wire result is an opaque string, so the gutter `raw` drops is not
part of the contract.

(cherry picked from commit 679785aa6b3243ea39b26abf4dda9435960019b1)
2026-07-30 01:42:30 +02:00
Diogo Soares Rodrigues 91b703b6af fix(cursor): resolve symlinks when confining resource downloads
A lexical containment check is not containment. `out/config` is
relative and `..`-free, so it passed - while a `ws/out -> /elsewhere`
link inside the workspace sent the write straight out. Proven before
the fix: the download landed in the link's target.

Containment now realpaths three things: the target when it exists, the
immediate link destination when it is a dangling symlink (a write still
follows it), and otherwise the deepest existing ancestor with the
not-yet-created segments re-applied. Each branch has a regression, and
all three fail the suite when individually reverted.

Also moves this branch's ai/catalog changelog entries back under
[Unreleased]; two commits had re-landed them inside the released
[17.1.5] section, which left `packages/ai/CHANGELOG.md` with two.
All three released sections are now byte-identical to upstream/main.

(cherry picked from commit e3ed4035ab8a1781c49a68c1fbe92c88bec3aa25)
2026-07-30 01:42:29 +02:00
Diogo Soares Rodrigues 7a944f1baa fix(cursor): confine MCP resource downloads to the workspace
`download_path` is workspace-relative by contract, but it arrives from
the server and `resolveToCwd` deliberately honors absolute paths, `~`,
and `..` - correct for a path a user typed, a write-anywhere primitive
for one a remote peer supplied. `/etc/cron.d/x` or `../../escape` would
have been written wherever the process can reach.

`confineToWorkspace` accepts only a non-empty relative path resolving
under the live cwd, and the download refuses anything else. The refusal
throws inside the dispatch's existing try, so it reaches the model as a
`ReadMcpResourceError` rather than a silent success or a crash.

(cherry picked from commit 963cfee21a56576033ec115db745bb18ab0a8d06)
2026-07-30 01:42:29 +02:00
Diogo Soares Rodrigues 01be80b9ea fix(cursor): honor download_path on MCP resource reads
`ReadMcpResourceExecArgs.download_path` means "write the resource to
this workspace-relative path and return no model content". The handler
I added forwarded only server and uri, so a download reported success
while creating no file and leaving `ReadMcpResourceSuccess.download_path`
unset - the model was pointed at a path that did not exist.

The path now reaches the handler, the bridge writes the bytes (decoding
a base64 blob, or the joined text) under the session cwd, and the
answer carries the path with the content oneof deliberately unset: a
host that also has the payload on hand must not have it forwarded, or
the download mode puts it right back in context.

(cherry picked from commit f0a6784533201f412529fb0ae5a6542531012197)
2026-07-30 01:42:29 +02:00
Diogo Soares Rodrigues 6d42f6e52e fix(cursor): read pi_ls's entry cap from the shape read actually sets
The bridge looked for a flat `details.resultLimitReached`. `glob` sets
that alongside the structured meta, so `pi_find` worked; `read` - the
tool serving `pi_ls` - records the cap only through `OutputMeta`, at
`details.meta.limits.resultLimit.reached`. Every capped listing
therefore reached Cursor with `entry_limit_reached` unset, which reads
as a complete listing.

Both shapes are now checked, mirroring how `piTruncation` already
handles its two producers. Covered by running the real `ReadTool`
against a directory that trips the per-directory cap and asserting the
wire field, so a move in the producer's shape fails here instead of
silently sending clipped output as whole.

(cherry picked from commit cbfe7ca9d59faa2f967e2724744b58a96a9a1839)
2026-07-30 01:42:29 +02:00
Diogo Soares Rodrigues 0601ee7324 fix(cursor): route MCP resource frames and gate native delete
`list_mcp_resources` / `read_mcp_resource` answered as though this
client hosted no MCP servers - a hardcoded empty catalog and
`not_found`. The same session reads those resources through `mcp://`
via `MCPManager.getServerResources` / `readServerResource`, so a Cursor
model could not see resources its own session was connected to.
`CursorExecHandlers` gained `listMcpResources`/`readMcpResource`, the
bridge answers them from the manager's live connections, and the
no-handler fallback is unchanged. A throwing lookup surfaces as an
error: an empty success claims "asked, none exist", which the model
cannot retry.

The native `delete` frame also bypassed approval. Unlike every other
frame it calls `fs.rmSync` directly rather than running a registry
tool, so no `ExtensionToolWrapper` sat in front of it, and
`allowNativeDelete` only answers whether a mutating tool was granted -
not whether the user's policy allows the call. It now resolves the
write tier against the session's approval mode and per-tool policies,
failing closed on `always-ask`, which this channel cannot prompt in.

(cherry picked from commit 44d36d1e8d35b0038d00e0202454d68fbcc53bce)
2026-07-30 01:42:28 +02:00
Diogo Soares Rodrigues a363f95009 fix(cursor): pair interrupted calls and gate advisor bridge approval
Two independent bugs found in review.

A stream that dies mid-turn takes the terminal-error path: `settleH2`
rejects when the transport closes without `turnEnded`, so the flush on
the success path never runs. `connect_scm` and native todo blocks are
stamped `kCursorExecResolved` at start, so `agent-loop.ts` synthesizes
no placeholder and only their completion frame pairs a result - the call
was left unpaired and its card animating, and `buildSessionContext`
strips a dangling call from every rebuilt transcript. The catch path now
closes open blocks and pairs those server-owned calls with an
interrupted result. Exec-settled MCP blocks are excluded: the dispatch
that ran them owns their result and `drainInFlightDispatches` awaits it,
so pairing here would duplicate against the same id.

Separately, the advisor bridge supplied no `getToolContext`.
`ExtensionToolWrapper` reads the approval mode, per-tool policies and
`autoApprove` only from that execute-time context, so every wrapped
advisor bridge tool resolved as `yolo` with empty policies - a
configured `ask` or `deny` on `edit`/`grep` did not apply to native
frames. Advisors now get the same `ToolContextStore` as the primary
bridge.

Both are covered against the real paths: the interrupted call through
the HTTP/2 fixture server (a helper-level test passes even with the
catch-path flush removed), and approval through real deny policies.

(cherry picked from commit 5ace682578af96708caf88db2d44b9077a1c0e74)
2026-07-30 01:42:28 +02:00
Diogo Soares Rodrigues 5779c762de fix(cursor): give advisors the replace-mode edit pi_edit needs
The primary bridge builds a `replace`-mode `EditTool` because
`PiEditExecArgs` carries `old_text`/`new_text` pairs that no other mode
accepts. The advisor roster passed its own instances straight through,
and those follow the session's configured `edit.mode` - `hashline` by
default, whose schema is a single `input` string - so every native
advisor edit failed validation instead of touching the file.

Both bridge-only tools now come from `cursor-bridge-tools.ts`:
`createBridgeEditTool` builds the wrapped `replace` instance, and
`bridgeToolMap` substitutes it into a granted map. The substitution is
gated on `edit` actually having been granted, since the tool is
constructed rather than looked up - handing one to a read-only roster is
the #5680 escalation. The advisor's own loop keeps its instance; only
the exec map is swapped.

(cherry picked from commit e6cf9f8046c595cab9793b4b2a5795d8488d8d22)
2026-07-30 01:42:28 +02:00
Diogo Soares Rodrigues a4e714279c docs(changelog): move this branch's entries out of released 17.1.5
The rebase onto a tree where 17.1.5 had been cut landed this branch's
entries inside that released section. Released sections are immutable,
so they belong under [Unreleased] — the release script folds them into
whatever version ships next.

Both released sections are now byte-identical to upstream/main, no
bullet was dropped in the move, and the `Added` heading the
coding-agent entry needs came back with it.

(cherry picked from commit c1c6566dccb7255395e463c940cca6c76a707a9d)
2026-07-30 01:42:28 +02:00
Diogo Soares Rodrigues 8737987398 fix(cursor): gate bridge-only edit and grep on the granted tool set
Both are constructed rather than looked up, and `executeTool` prefers a
constructed override over the registry — so a session that withheld
either still got a working frame. Native `pi_edit`/`pi_grep` arrive
regardless of the advertised catalog, so a restricted agent
(`toolNames` without them, or `restrictToolNames: true`) could modify
and search files it was never granted.

Both now check the registry for the grant before building. The edit
check reads it before the Cursor-specific delete, since that delete is
about not advertising the tool, not about revoking it. This is the same
escalation the `delete` frame already guards against (#5680); the
advisor path got the grep gate in the previous commit and the primary
session was missed.

(cherry picked from commit 68f82b0ce08bb93db941e0fbe1bd2a515d45c2cb)
2026-07-30 01:42:28 +02:00
Diogo Soares Rodrigues f785d76bc9 fix(cursor): honor an explicit zero pi_bash timeout
`timeout` is `optional int32` and `bash` documents `0` as "disables the
command deadline". Both the bridge and the provider's synthesized block
gated on `timeout && timeout > 0`, folding a supplied `0` into unset —
so the 300s default applied and the long-running command that asked not
to be killed was killed.

The expression was duplicated across the two sides, which is the drift
the shared translation exists to prevent, so it moves into
`cursor-pi-args` as `piTimeout` alongside the other presence-sensitive
mappings. Negatives have no local meaning and would clamp to `bash`'s 1s
floor, so those still fall back to the default.

Verified against a real BashTool: `timeout: 0` yields
`timeoutDisabled: true`, omitted yields the default, `42` passes
through, and `-5` matches the omitted case rather than the 1s clamp.
Mutation-checked on both branches.

(cherry picked from commit db442ae5aed90dc2268b2d898ef99ef4e0961c10)
2026-07-30 01:42:27 +02:00
can1357 857b70fe99 Merge remote-tracking branch 'refs/remotes/pr/6535' into prep/6535
# Conflicts:
#	packages/coding-agent/src/extensibility/extensions/runner.ts
2026-07-30 01:42:24 +02:00
Diogo Soares Rodrigues 6eaf090ccc fix(cursor): repair pi_edit and close the scoped-grep approval bypass
Three defects the exec bridge shipped with, all found by review.

`pi_edit` never worked. The session removes `edit` from the tool
registry for Cursor so the model is steered to full-file `write`
(8ba0498eb), but that same registry is the bridge's tool source, so the
native frame — which the server sends regardless of the advertised
catalog — resolved nothing and answered `Tool "edit" not available`.
Retaining the instance is not enough either: `PiEditExecArgs` carries
`old_text`/`new_text` pairs, which only `replace` accepts, while the
default mode is `hashline` (`{ input: string }`). `EditTool` now takes
an optional mode, and the bridge resolves a pinned `replace` instance
through its fallback resolver.

A `pi_grep` frame carrying `context` or `limit` escaped the approval
gate. Honoring those needs a per-call tool, and the per-call instance
was built raw while every registry tool is wrapped — so exactly those
calls skipped `tools.approval.grep` and the exec-tier SSH check. Both
callsites now go through one `createBridgeGrepFactory`.

Advisors ignored the same two fields: only the primary session supplied
the factory. They now get it too, gated on the advisor actually holding
`grep` so the factory cannot grant a denied tool.

Also moves the pure Pi arg translation to `providers/cursor-pi-args`.
The legacy shim shares it and is compiled into the bundled virtual
registry, where `./providers/*` cannot match a nested specifier — it
fell through to `Bun.resolveSync`, unsatisfiable under bunfs (#3442) —
and the exec module would have dragged the protobuf graph along.

Verified against real files and the real module graph: `pi_edit` mutates
a temp file, the bundled probe executes the shim's shared module in a
subprocess, and the grep test drives the shared factory. Mutation-
checked: returning a raw tool from the factory, ignoring the pinned edit
mode, dropping the `getTool` fallback, or moving the helpers back to a
nested path each fails a test.

(cherry picked from commit e46ba22b634e449005f7c22b6d0efd19a45ce1f8)
2026-07-30 01:42:23 +02:00
usr-bin-roygbiv b8f8485ee4 style(coding-agent): format compaction fallback tests
(cherry picked from commit 75613b4e82ba839ccb6f4ccb846eb8753672614d)
2026-07-30 01:42:19 +02:00
usr-bin-roygbiv ba3f1898ca fix(compaction): keep native fallback within provider
(cherry picked from commit d7f2cc59eb9138942924259677ff81b9535e4661)
2026-07-30 01:42:19 +02:00
usr-bin-roygbiv eeb542b2ef fix(advisor): stop provider fallback on native compaction failure
(cherry picked from commit c636aa6be3f12eed9475bb2e1aa6a03749049523)
2026-07-30 01:42:19 +02:00
Diogo Soares Rodrigues ab7b457af0 fix(cursor): read pi_bash truncation from the shape BashTool emits
Two truncation records exist locally. `read`/`grep` set
`details.truncation` (`TruncationResult`), which carries an explicit
`truncated` boolean. `bash` sets `details.meta.truncation`
(`TruncationMeta`), which has no such flag — its presence is the signal.

`piTruncation` read only the first and required the boolean, so every
real Bash truncation was dropped: Cursor got clipped output with no
indication it was clipped. Both shapes now translate; `TruncationResult`
stays authoritative when present so an explicit `false` still suppresses.

Also drops the legacy pi shim's copies of the regex-literal escaper and
the path/glob join. Both were verbatim duplicates of the modern bridge's
helpers, which is the drift the shared translation exists to prevent.

Verified producer-to-consumer, not against a hand-built bag: the test
runs a real `BashTool`, asserts its output has no top-level `truncation`
and no `truncated` flag under `meta`, then feeds those exact details to
`piTruncation`. Typed against the producer's own `TruncationMeta`, so a
renamed field fails compilation rather than silently reverting the bug.
Mutation-checked: reverting to the top-level lookup, restoring the flag
requirement, or dropping the null guard each fails a test.

(cherry picked from commit 6699672d52061b832677dd45315f4aba8d330db1)
2026-07-30 01:42:11 +02:00
usr-bin-roygbiv 0c31095d34 fix(agent): skip V2 retries for compaction auth errors
(cherry picked from commit 07a54771834435b7897208ab9d6d42401f0203e1)
2026-07-30 01:42:06 +02:00
Diogo Soares Rodrigues 9436fb5640 feat(cursor): honor pi_grep's context and limit
`pi_grep` carries a context width and a total match cap. Neither is
expressible in the model-facing `grep` schema — context comes from
`grep.contextBefore`/`grep.contextAfter`, fixed when the shared tool is
constructed — so both were dropped.

`GrepTool` now takes them as constructor options. The model-facing
schema is unchanged: this is a seam for wire bridges whose protocol
supplies the values, mirroring `GlobTool`'s existing options bag. The
bridge builds a per-call `grep` only for frames that supply them;
everything else keeps the shared instance and session defaults.

`pi_ls`'s `limit` stays unmapped, now deliberately and documented. It
caps directory entries, while the local `read` renders a depth-2 tree
and slices rendered lines — nested rows, headers and elision summaries
all count — so `:1+K` would cap a different unit while looking honored.

Verified against real files in a temp dir, not captured arguments:
match counts and context lines are asserted from actual search output.
Mutation-checked — ignoring either option, or dropping the scoped tool
in the bridge, fails a test.

(cherry picked from commit 299ded5a274427c2c2d5de27c00a2056a709581c)
2026-07-30 01:42:06 +02:00
usr-bin-roygbiv b927da95e6 fix(agent): preserve native compaction error precedence
(cherry picked from commit 3b1c1a420132485ac4f5a4803615a70d1e05c794)
2026-07-30 01:42:06 +02:00
usr-bin-roygbiv 3abf17e7b9 fix(compaction): stop cross-provider native fallback
(cherry picked from commit 01455dc1e681e385d0a46e64a72009364e8f510e)
2026-07-30 01:42:06 +02:00
usr-bin-roygbiv 4c2b40f8b3 fix: preserve streaming compaction auth errors
(cherry picked from commit 6fe25c6a08e169a15fca057a56e1ce36f46bc641)
2026-07-30 01:42:06 +02:00
usr-bin-roygbiv 2fe31c61b6 fix: preserve native compaction auth errors
(cherry picked from commit ab8a823e1de425735825392db7d98b33905f0dc3)
2026-07-30 01:42:06 +02:00
usr-bin-roygbiv a8349d4dea fix: retain native compaction auth fallback
(cherry picked from commit 164d77ee90ed109b62ed7f1b6afcf644d26abe12)
2026-07-30 01:42:06 +02:00
Roy 5ce80fdedc fix: preserve provider-native compaction semantics
(cherry picked from commit 426ac1e147c08092c7d0b6c4a7af5f2e09eaf941)
2026-07-30 01:42:06 +02:00
Wolfgang Schoenberger 757b0938ce fix(coding-agent): tighten startup changelog contracts
(cherry picked from commit e5490279ca0b400b143514e7ef3e38be0686bf31)
2026-07-30 01:42:01 +02:00
Wolfgang Schoenberger a4dc5a094a feat(coding-agent): add startup changelog display modes
(cherry picked from commit bed594fecd1eae1917f3f047f883da5ba83317f9)
2026-07-30 01:41:57 +02:00
Diogo Soares Rodrigues 7b62fef366 fix(cursor): honor Pi frame arguments and preserve open-block args
Review of the modern exec wire protocol surfaced defects the committed
suite did not pin.

The Pi bridge dropped frame arguments: `pi_read`'s offset/limit (ranged
reads returned whole files), `pi_grep`'s literal (fixed strings ran as
regexes), and the path/glob join emitted `./`-prefixed specs. These are
`optional int32`, so a present `0` is a value, not "unset" — `limit: 0`
now answers empty rather than reading everything, and `pi_find` clamps
to 1 like the reference client.

The provider synthesized its transcript block from a second, divergent
translation of the same frame, so the displayed operation differed from
the executed one. Both sides now share one mapper in `exec-modern.ts`.

End-of-transport cleanup reparsed every open block's streamed argument
buffer; blocks whose args arrive whole never set that buffer, and
`parseStreamingJson(undefined)` is `{}`, so a truncated turn erased
their arguments.

All fixes are mutation-verified: reverting each one fails a test.

(cherry picked from commit bb7bcfebce4200d436e17d6e39320da13fc85ca8)
2026-07-30 01:41:55 +02:00
Diogo Soares Rodrigues b6e01c8a3c feat(ai): handle Cursor's modern exec wire protocol
Current Cursor CLI builds emit exec frames this client did not model. A
frame whose oneof number is absent from `agent.proto` decodes with
`message.case` unset, so the dispatcher found no handler, ran no tool and
sent no result — the server was left waiting on an execution that never
happened.

Every recognised frame now gets a typed answer:

- The seven Pi tools (45-51) run their local equivalents. They are a
  separate wire family from the legacy args, not aliases: `pi_grep`'s
  `ignore_case` is the inverse of the local `case` flag, `pi_find`
  searches filenames (so it routes to `glob`, not `grep`), and
  `pi_edit`'s replacements are renamed to snake_case pairs.
- Hooks, subagents, prechecks, MCP state, smart-mode, canvas,
  conversation search and agent-store answer with the error, not-found or
  empty-but-valid variant that is true of this client.
- Unnameable frames raise `ExecClientControlMessage.throw`
  (`unknown_exec_variant`); recognised frames with no truthful answer —
  `git_diff_request`, whose `GetDiffResponse` has no error variant —
  raise `exec_variant_unsupported`.

Four frames previously answered `create(XSchema, {})`. In proto3 that is
not an empty result: the oneof is unset and the server reads it as "the
tool ran and produced nothing", indistinguishable from success. They now
send real variants.

`connect_scm` lost its repository (the target rides in a oneof, so the
flat property was always undefined) and settled on a fixed failure at the
announcement, before the server's `success`/`error`/`rejected` verdict
arrived on the completion frame.

The stream decoder tracked a single "current" tool-call block and settled
it on any `toolCallCompleted`, ignoring the envelope `call_id`: an
unrelated completion paired the wrong block, and `start A, start B`
orphaned A so nothing ever paired it — which strips the whole interaction
from every rebuilt transcript. Blocks are now retained per envelope id.

`lsp` is advertised as MCP again; the native `diagnostics` frame covers
one of ~10 actions.

(cherry picked from commit 4d269724a3a448886d13b4323ac02aadbfe38de3)
2026-07-30 01:41:55 +02:00
Diogo Soares Rodrigues cdbe7c4ed2 feat(ai): model modern Cursor exec wire protocol in agent.proto
(cherry picked from commit 8f26f5f04c64ce2fd198a1526b1a2a26729afef9)
2026-07-30 01:41:55 +02:00
can1357 fcc8cce9d9 Merge remote-tracking branch 'refs/remotes/pr/6858' into prep/6858
# Conflicts:
#	packages/coding-agent/src/modes/components/status-line/component.ts
2026-07-30 01:41:54 +02:00
can1357 091f670ea0 style: apply biome formatting to merged changes 2026-07-30 01:28:07 +02:00
can1357 038d8372d7 chore: normalize changelogs after merging open fixes 2026-07-30 01:27:44 +02:00
can1357 d3dc42c369 Merge PR #6734: fix(ai): omit unsupported Google enum values (@usr-bin-roygbiv) 2026-07-30 01:27:35 +02:00
usr-bin-roygbiv 47ac33aa1f fix(ai): inspect snake-case schema children
(cherry picked from commit 41590a873ca838f9ac3b54e1008fd7c97424c3d3)
2026-07-30 01:27:35 +02:00
usr-bin-roygbiv 6dcd28c302 fix(ai): preserve negated enum semantics
(cherry picked from commit c96aa0d61445de12597abf76c397ce5f9707bcb8)
2026-07-30 01:27:35 +02:00
usr-bin-roygbiv b9b17a6cbc docs: keep enum fix unreleased
(cherry picked from commit c90b6302e8e7eab30f5a0c635e6373bae1fdc90d)
2026-07-30 01:27:35 +02:00