Commit Graph

16240 Commits

Author SHA1 Message Date
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
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
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
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
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 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
usr-bin-roygbiv b8c5f1544e style(ai): format schema traversal fix
(cherry picked from commit ffa704ed0abdd6f27d0ce56d8c97ccd64d025010)
2026-07-30 01:27:35 +02:00
usr-bin-roygbiv 2ee5a10923 fix(ai): preserve CCA literal payloads
(cherry picked from commit b026a96326e3d7df89771f3224476dc37eeeaa9c)
2026-07-30 01:27:35 +02:00
usr-bin-roygbiv 05d5b8c12d style(ai): format schema normalization
(cherry picked from commit aee3d7e5e26c3a5cb3e950266a2561ece4b1d20c)
2026-07-30 01:27:35 +02:00
usr-bin-roygbiv 34069920a6 fix(ai): normalize map value schemas
(cherry picked from commit 836951121076394b775771b29070f1022d7591c7)
2026-07-30 01:27:34 +02:00
usr-bin-roygbiv 2f579e1ac1 fix(ai): preserve literal schema payloads
(cherry picked from commit 3631bc5bd0a27eabb774bf800e0144523d25a55b)
2026-07-30 01:27:34 +02:00
usr-bin-roygbiv d17ae32b1d fix(ai): preserve enum keys in Google schema defaults
(cherry picked from commit 226f00efb3c330f5ed6b0583c4b65e2fa75b9f5b)
2026-07-30 01:27:34 +02:00
Roy 16e03728f0 fix(ai): omit unsupported Google enum values
(cherry picked from commit 6cc7916f9ff508ee0864229fe83a3c31a585f832)
2026-07-30 01:27:34 +02:00
can1357 ee83520a22 Merge PR #4484: Umans usage provider (@hpost) 2026-07-30 01:27:25 +02:00
can1357 55ac64679e Merge PR #6652: feat(ai): add Exa API key login (@will-bogusz) 2026-07-30 01:26:50 +02:00
can1357 4e1f70bf7d Merge PR #6647: feat(ai): add xAI API key login (@paralin) 2026-07-30 01:26:50 +02:00
can1357 4a05d02f01 test(extensions): cover session async job wiring
(cherry picked from commit e79cc9e79a68b53332eb6ca04f2e555e25d1b7e0)
2026-07-30 01:26:50 +02:00
can1357 5486c8fd1d Merge PR #6939: feat(extensions): expose session async job snapshots (@usr-bin-roygbiv) 2026-07-30 01:26:50 +02:00
can1357 da794ebb24 Merge PR #6938: feat(coding-agent): allow checkpoint/rewind/learn/manage_skill in subagents when explicitly requested (@szavadsky) 2026-07-30 01:26:49 +02:00
can1357 0249fa4715 Merge PR #7036: feat(rpc): expose live fast-mode control and token throughput (@fredluz) 2026-07-30 01:26:49 +02:00
can1357 07ed00ae22 Merge PR #7028: fix(ai): bound Anthropic retry-after waits (@metaphorics) 2026-07-30 01:26:49 +02:00
can1357 65f3743622 feat(coding-agent/session): supported importing session compactions in Codex session store
- Support parsing compacted records with replacement history and conversion to compaction entries.
- Add test coverage for foreign session import of Codex session compactions.
2026-07-30 01:13:37 +02:00
can1357 f11641d5a8 feat(coding-agent): enabled importing foreign sessions from claude and codex
- Implemented session stores and metadata converters to import Claude and Codex sessions into OMP.
- Added `--from-claude` and `--from-codex` CLI flags and `/resume` command arguments for foreign session resolution.
- Updated session selector components and controllers to support listing and picking external agent sessions.
- Added comprehensive unit tests and documentation covering foreign session import functionality.
2026-07-30 00:28:40 +02:00
can1357 47d3317d32 feat(ai): reserved code_mode_tool_names and preserve encrypted_function_args
- Reserve the `code_mode_tool_names` metadata key to prevent caller-supplied client extra collisions.
- Preserve the `encrypted_function_args` plaintext-collaboration marker on replayed function calls.
2026-07-30 00:00:37 +02:00
can1357 93eb95b3b1 chore: rewrite changelogs 2026-07-29 23:34:04 +02:00
can1357 eab5b50834 feat(ai): added parentTurnId support and capture turn-state refreshes in Codex
- Added first-class `parentTurnId` stream option and reserved metadata key for nested Codex requests.
- Captured `x-codex-turn-state` refreshes from `response.metadata` event headers in WebSocket and streaming sessions.
2026-07-29 23:33:29 +02:00
can1357 b7cd8c6582 Merge remote-tracking branch 'origin/main' 2026-07-29 23:19:45 +02:00
can1357 6fb922e5ca style: format codex zstd test 2026-07-29 23:10:58 +02:00
can1357 f2251b7d59 chore: normalize changelogs and formatting after merging open fixes 2026-07-29 23:09:40 +02:00
can1357 6527671c3a fix(lsp): sanitize expanded generic output
(cherry picked from commit 1260d0633a5fb533c492f272ee500fec60c46e22)
2026-07-29 23:09:14 +02:00
can1357 2adf484ef1 Merge PR #7042: fix(lsp): handle quick exits before reader teardown (@roboomp) 2026-07-29 23:09:13 +02:00
can1357 a459878b1e test(ai): cover bare-string Devin system prompt
(cherry picked from commit aa2087a480995e89cc1586e30542131a26db0ff8)
2026-07-29 23:09:13 +02:00
roboomp dca8f44b73 fix(lsp): handled quick exits before reader teardown
Waited briefly for process exit publication after clean stdout EOF so the process handler preserves the real exit code and stderr, while genuine reader errors still tear down immediately.

Cleared only the matching initialization failure for explicit reloads and added regressions for quick exits, reader errors, ordinary backoff, and immediate reload retries.

Fixes #7041

(cherry picked from commit a76522b759f14421202d4cc437ec611b78be20d1)
2026-07-29 23:09:13 +02:00
can1357 3e379410b4 Merge PR #7038: fix(ai): normalize bare-string systemPrompt in bedrock and devin (@roboomp) 2026-07-29 23:09:12 +02:00
can1357 abf4f3eda8 Merge PR #7033: fix(ai): classify bare resource_exhausted as MODEL_CAPACITY (@roboomp) 2026-07-29 23:09:12 +02:00
roboomp 5578a2203b fix(ai): normalize bare-string systemPrompt in bedrock and devin
buildSystemPrompt hand-rolled systemPrompt?.map(...) and devin's request
builder called (context.systemPrompt ?? []).join(...); both crash when
Context.systemPrompt is a bare string, as legacy @earendil-works/pi-ai
extensions remapped onto the fork pass it. The failure surfaced as
stopReason "error" with "systemPrompt?.map is not a function".

Route both through the existing normalizeSystemPrompts() helper, which
already accepts readonly string[] | string, matching the other providers.

Fixes #7037

(cherry picked from commit d681de5daa7e3316f118e6fb4cf8805ec5318c32)
2026-07-29 23:09:12 +02:00
roboomp ca8fcb25b8 fix(ai): strip every bare resource-exhausted token
parseConnectEndStream repeats the default status phrase in the message
body, so the trailer reads "resource_exhausted: resource exhausted".
Using a global strip removes both occurrences, so the leftover
"exhausted" no longer trips the generic quota branch and reintroduces
the 30-minute credential block for an otherwise bare status.

Fixes #7032

(cherry picked from commit 3d46a042c649e3892d9c68abbc3789a7719f0667)
2026-07-29 23:09:11 +02:00
roboomp f144f5db36 fix(ai): preserved quota details on resource exhaustion
Stripped the resource_exhausted status token before classifying the
remaining provider message. Bare or opaque status errors still use the
short model-capacity backoff, while explicit quota, rate-limit, capacity,
or server details remain authoritative.

Added regression coverage for a Connect resource_exhausted trailer with
an explicit quota-exceeded body.

Fixes #7032

(cherry picked from commit ee04c065692e34ccabe1a07b45b5f5987297ff84)
2026-07-29 23:09:11 +02:00
roboomp 3c7ffa3e08 fix(ai): classify bare resource_exhausted as MODEL_CAPACITY
Connect/gRPC end-streams carry the status name `resource_exhausted`
(underscore), but parseRateLimitReason only matched the space phrase
"resource exhausted" in its MODEL_CAPACITY branch. The underscore form
fell through to the generic includes("exhausted") catch-all and was
classified QUOTA_EXHAUSTED, producing a 30-min credential block and the
retry.maxDelayMs fail-fast at the session layer.

Match both forms via /resource.?exhausted/i, consistent with the
existing resource.?exhausted clause in USAGE_LIMIT_PATTERN, which is left
untouched so stream/session credential rotation is preserved.

Fixes #7032

(cherry picked from commit bc18cbb5b9bcf5c2bf41dc494581ae31061fcfcb)
2026-07-29 23:09:11 +02:00
can1357 672857d238 Merge PR #7025: fix(coding-agent): coalesce models config resource probe (@paralin) 2026-07-29 23:09:10 +02:00