pi-cursor-sdk 0.1.14 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.1.15 - 2026-05-21
6
+
7
+ ### Added
8
+
9
+ - Add the default-on local pi MCP tool bridge, which exposes bridgeable active pi tools to local Cursor agents while executing calls through pi's normal tool path.
10
+ - Add `cursor_ask_question` through the bridge so Cursor can ask users through pi UI as `pi__cursor_ask_question`.
11
+ - Add `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1` for opting in to overlapping built-in pi tools that are hidden from the Cursor bridge by default.
12
+ - Add Cursor SDK MCP tool-call timeout overrides via `PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS` and `PI_CURSOR_MCP_TOOL_TIMEOUT_MS` for long-running local MCP tools, including bridged pi tools.
13
+ - Replay Cursor SDK `grep` activity through native pi `grep` cards and `glob` activity through native pi `find` cards, so search activity matches built-in tool UX in interactive TTY sessions.
14
+
15
+ ### Changed
16
+
17
+ - Load Cursor setting sources with `PI_CURSOR_SETTING_SOURCES=all` by default while filtering direct Cursor SDK startup logs so settings, rules, plugins, and configured Cursor MCP servers are available without corrupting pi's TUI.
18
+
19
+ ### Fixed
20
+
21
+ - Replay recorded Cursor tool errors, including nonzero shell exits and timeout-backgrounded shell commands, as native pi tool errors instead of successful green cards.
22
+ - Format zero-match Cursor grep results as `(no matches)` instead of raw `{ "totalMatches": 0 }` JSON in native replay and transcript output.
23
+ - Strip trailing colons from Cursor grep file-list replay output.
24
+ - Make native Cursor read replay closer to pi's built-in read cards by displaying session-relative paths and 20-line continuation hints.
25
+ - Convert Cursor SDK shell timeouts from milliseconds to seconds in native bash replay cards instead of rendering `30000ms` as `30000s`.
26
+ - Use the pi session cwd for Cursor `Agent.create`, not only native tool replay display. Completes the 0.1.10 cwd work that previously updated replay registration but left the Cursor agent runtime on `process.cwd()`.
27
+ - Replay path-only Cursor `write` activity through neutral recorded Cursor activity instead of invalid native pi `write` calls.
28
+ - Preserve literal `cursor_edit`, `cursor_write`, and `cursor_mcp` text in user messages, assistant text, tool args, and tool results while still relabeling structured replay tool names.
29
+ - Avoid hiding unrelated MCP activity whose result payload merely contains a bridge tool name, while still suppressing real bridge-owned Cursor MCP replay by invocation identity and call ID.
30
+ - Clean up pending native replay waits when abort signals are already aborted or abort before listener registration.
31
+ - Suppress direct Cursor SDK settings/skills startup noise, including late `managed_skills.removed` lines, without swallowing unrelated non-startup stdout/stderr output.
32
+
3
33
  ## 0.1.14 - 2026-05-18
4
34
 
5
35
  ### Changed
package/README.md CHANGED
@@ -165,7 +165,7 @@ For Claude models with both `thinking` and `effort`, pi thinking `off` sends `th
165
165
 
166
166
  In `pi --list-models`, `thinking=no` means pi cannot control the model's thinking level with `--thinking`, a final `:medium` model suffix, or shift+tab. It does not mean the Cursor model cannot think.
167
167
 
168
- Some Cursor SDK models do not expose a `reasoning`, `effort`, or `thinking` parameter for the extension to set. Cursor thinking is still enabled/supported by the model, and Cursor may still emit thinking deltas. The extension does not disable Cursor's default reasoning behavior.
168
+ Some Cursor SDK models do not expose a `reasoning`, `effort`, or `thinking` parameter for the extension to set. Cursor thinking is still enabled/supported by the model, and Cursor may still emit thinking deltas. The extension surfaces those deltas through pi's native thinking rendering when the SDK emits them.
169
169
 
170
170
  ## Fast mode
171
171
 
@@ -197,6 +197,31 @@ If you do not see `cursor fast`, fast mode is off.
197
197
 
198
198
  Images from the latest user message are forwarded to Cursor. Historical images are kept out of the transcript and appear only as `[image omitted from transcript]` placeholders, so follow-up questions about an earlier image should reattach the image or include a textual description. The extension advertises `text` and `image` input for Cursor models because Cursor's SDK accepts image messages and Cursor models are expected to support them.
199
199
 
200
+
201
+ ## Tools and local pi bridge
202
+
203
+ Cursor runs use local Cursor SDK agents. Cursor's own local-agent tools, Cursor settings, plugins, and configured Cursor MCP servers remain available through the SDK.
204
+
205
+ In addition, pi-cursor-sdk exposes the current pi session's bridgeable active tools to Cursor through a local loopback MCP bridge by default. The bridge snapshots `pi.getActiveTools()` and `pi.getAllTools()` for each Cursor run, excludes internal Cursor replay activity names, and hides overlapping built-in pi tools (`read`, `bash`, `write`, `edit`, `grep`, `find`, `ls`) by default because Cursor local agents already have native equivalents. Non-overlapping active tools and extension/custom tools present in pi's active tool registry are exposed as MCP tools with collision-safe names such as `pi__sem_reindex`, and calls map back to the real pi tool names. When Cursor calls a bridged tool, pi executes it through the normal pi tool path, so confirmations, tool hooks, renderers, session history, and abort behavior stay pi-native. The bridge also exposes `cursor_ask_question` as `pi__cursor_ask_question` when enabled, allowing Cursor to ask the user through pi UI instead of silently choosing a default. The bridge does not call tool `execute()` handlers directly.
206
+
207
+ Bridge controls:
208
+
209
+ ```bash
210
+ # Roll back to Cursor SDK tools/settings/MCP only; do not expose active pi tools through the bridge.
211
+ PI_CURSOR_PI_TOOL_BRIDGE=0 pi --model cursor/composer-2.5
212
+
213
+ # Opt in to also expose overlapping pi tool names through the bridge.
214
+ PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 pi --model cursor/composer-2.5
215
+
216
+ # Override Cursor SDK MCP tool-call timeout, including bridged pi tools and configured Cursor MCP servers.
217
+ PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS=7200 pi --model cursor/composer-2.5
218
+ PI_CURSOR_MCP_TOOL_TIMEOUT_MS=7200000 pi --model cursor/composer-2.5
219
+ ```
220
+
221
+ `PI_CURSOR_PI_TOOL_BRIDGE=0` is the supported rollback flag and disables the bridge entirely. The bridge also treats `false`, `off`, `none`, `no`, and `disabled` as off; `1`, `true`, `on`, `yes`, and `enabled` as on. `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1` opts in to exposing overlapping pi tool names that Cursor already has native equivalents for (`read`, `bash`, `write`, `edit`, `grep`, `find`, and `ls`). By default those names are hidden even when pi's Cursor replay wrapper has registered them as extension tools. The Cursor MCP timeout override defaults to 3600 seconds because the installed Cursor SDK has a 60-second MCP request default that is too short for some local MCP tools, including bridged pi tools and configured Cursor MCP servers.
222
+
223
+ Cursor-native tool replay is separate from the bridge. Replay only displays completed Cursor SDK tool activity as pi-native-looking cards with recorded results, using pi's normal success/error card shell for neutral Cursor activity too. It never re-runs Cursor-side commands, reapplies Cursor edits, calls MCP servers, or mutates pi state. See [Cursor native tool replay](docs/cursor-native-tool-replay.md).
224
+
200
225
  ## Fallback models
201
226
 
202
227
  If no key is available from `/login`, `CURSOR_API_KEY`, or `--api-key`, model discovery fails, or discovery returns no models, the extension registers a bundled fallback snapshot of the latest reviewed Cursor SDK model catalog and notifies interactive users when possible.
@@ -207,11 +232,11 @@ Actual Cursor runs still need a key from `/login`, `CURSOR_API_KEY`, or `--api-k
207
232
 
208
233
  ## Limits
209
234
 
210
- - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents.
211
- - **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, `ls`, `edit`, and `write` activity through pi's native tool-call path with recorded results (for example green `read`, `$ ...`, and Cursor edit/write cards) without forcing Cursor to call pi tools or rerun commands. Cursor edit/write activity uses replay-only `cursor_edit` and `cursor_write` tool cards because Cursor's file-editing schema is not the same as pi's built-in `edit`/`write` schemas; those replay tools only display recorded Cursor results and never mutate files directly. If a Cursor read completion reports no content, the extension may include a bounded local file preview for safe in-workspace paths; that preview is explicitly labeled as a local preview captured at transcript time, not guaranteed Cursor-observed content. Native replay wrappers are registered only for tool names not already owned by another extension; skipped tools fall back to the scrubbed Cursor activity transcript. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text.
212
- - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor.
213
- - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls.
214
- - **Cursor setting sources are opt-in.** The extension does not pass `local.settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. To load configured Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities, start pi with `PI_CURSOR_SETTING_SOURCES=all`. To narrow loading, set a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`.
235
+ - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. Cloud pi tool bridging is out of scope because it needs a separate auth, transport, lifetime, and remote trust design.
236
+ - **The pi tool bridge is local and MCP-backed.** Bridgeable active pi tools are exposed to local Cursor agents through a tokenized `127.0.0.1` MCP endpoint; internal Cursor replay activity names are excluded, and overlapping built-in pi tools are hidden by default. Set `PI_CURSOR_PI_TOOL_BRIDGE=0` to disable it or `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1` to expose overlapping built-ins too.
237
+ - **Cursor native tool replay is display-only.** Replay renders recorded Cursor SDK activity and never re-runs Cursor-side commands, reapplies Cursor edits, calls MCP servers, or mutates pi state. Workflow tools such as Cursor `SwitchMode` and Cursor todo state are not pi workflow controls. See [Cursor native tool replay](docs/cursor-native-tool-replay.md) for supported replay cards, ordering, conflict handling, and opt-out flags.
238
+ - **Cursor run state can span tool-use turns.** A new Cursor SDK agent starts for a new provider run. When Cursor calls bridged pi tools or emits replayed Cursor tool activity, the same Cursor SDK run can stay alive across pi `toolUse` turns so results resume in the original Cursor run.
239
+ - **Cursor setting sources default to all.** The extension passes `local.settingSources: ["all"]` by default so configured Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities are available like they are in Cursor. To narrow loading, set a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. To disable ambient setting sources, set `PI_CURSOR_SETTING_SOURCES=none`. Direct Cursor SDK startup logs are suppressed so setting/skill loading messages do not pollute the TUI.
215
240
  - **Max Mode is not a manual pi variant.** Cursor's SDK may enable Max Mode automatically for models that require it. This extension only advertises exact context-window variants that the SDK catalog exposes and otherwise uses conservative SDK-derived default/non-Max context windows.
216
241
  - **Output token limits are conservative.** Cursor SDK model metadata does not currently expose output token limits directly.
217
242
  - **Token usage is approximate in pi.** Cursor SDK usage events include internal agent/tool/cache work, so the extension reports an approximate replayable pi prompt/output size for context display and compaction decisions.
@@ -251,7 +276,7 @@ pi install npm:pi-cursor-sdk
251
276
 
252
277
  ### `pi --list-models` shows `thinking=no`
253
278
 
254
- That does not mean the model cannot think. It means the Cursor SDK does not expose a pi-controllable thinking parameter for that model. The model may still think internally and may still emit thinking deltas.
279
+ That does not mean the model cannot think. It means the Cursor SDK does not expose a pi-controllable thinking parameter for that model. The model may still think internally and may still emit thinking deltas that pi renders natively.
255
280
 
256
281
  ### I do not see `cursor fast` in the footer
257
282
 
@@ -259,21 +284,38 @@ Fast mode is currently off. The footer only shows `cursor fast` when fast mode i
259
284
 
260
285
  ### My Cursor app settings or rules do not seem to apply
261
286
 
262
- Cursor setting sources are not loaded by default because the Cursor SDK can print settings/skills loading output directly to the terminal. Start pi with `PI_CURSOR_SETTING_SOURCES=all`, or choose a narrower list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`.
287
+ Cursor setting sources are loaded with `PI_CURSOR_SETTING_SOURCES=all` by default. To narrow loading, set `PI_CURSOR_SETTING_SOURCES=project,user,plugins` or another comma-separated list. If you explicitly disabled sources with `PI_CURSOR_SETTING_SOURCES=none`, remove that override.
263
288
 
264
289
  ### Cursor does not call my web search MCP/tool
265
290
 
266
- Cursor SDK local agents load MCP servers from Cursor setting sources and inline SDK config. This extension leaves Cursor setting sources off by default to avoid startup log noise, so a web search tool needs to be configured in Cursor and settings sources need to be enabled with `PI_CURSOR_SETTING_SOURCES=all` or a narrower list.
291
+ Cursor SDK local agents load MCP servers from Cursor setting sources and inline SDK config. This extension enables all Cursor setting sources by default, so a missing web search tool usually means it is not configured in Cursor or the run was started with a narrowing/disable override such as `PI_CURSOR_SETTING_SOURCES=none`.
267
292
 
268
- ### Cursor native tool cards conflict with another extension
293
+ ### Cursor does not call my pi extension tool
294
+
295
+ The local pi bridge only exposes tools that are active in the current pi session and present in pi's tool registry at Cursor run start. By default, it does not expose overlapping pi tool names that Cursor already has native equivalents for (`read`, `bash`, `write`, `edit`, `grep`, `find`, and `ls`). Opt in if you intentionally want Cursor to see both the Cursor-native tool and an overlapping built-in pi tool:
296
+
297
+ ```bash
298
+ PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 pi --model cursor/composer-2.5
299
+ ```
300
+
301
+ To disable the bridge for rollback or isolation, start pi with:
269
302
 
270
- Cursor native replay is a UI enhancement for interactive TTY sessions. If another extension already owns `read`, `bash`, `ls`, `cursor_edit`, or `cursor_write`, this extension skips only the conflicting native replay wrapper and uses the scrubbed Cursor activity transcript for that tool instead. To disable Cursor native replay registration entirely, start pi with:
303
+ ```bash
304
+ PI_CURSOR_PI_TOOL_BRIDGE=0 pi --model cursor/composer-2.5
305
+ ```
306
+
307
+ ### A Cursor MCP tool times out
308
+
309
+ The extension raises Cursor SDK's MCP tool-call timeout from 60 seconds to 3600 seconds by default for Cursor SDK MCP `callTool` requests, including the local pi bridge and configured Cursor MCP servers. For longer local MCP tools, set one override:
271
310
 
272
311
  ```bash
273
- PI_CURSOR_NATIVE_TOOL_DISPLAY=0 pi --model cursor/composer-2.5
312
+ PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS=7200 pi --model cursor/composer-2.5
313
+ PI_CURSOR_MCP_TOOL_TIMEOUT_MS=7200000 pi --model cursor/composer-2.5
274
314
  ```
275
315
 
276
- `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is also accepted as a registration-only opt-out.
316
+ ### Cursor native tool cards conflict with another extension
317
+
318
+ Cursor native replay is a UI enhancement for interactive TTY sessions. See [Cursor native tool replay](docs/cursor-native-tool-replay.md) for conflict behavior and opt-out flags.
277
319
 
278
320
  ## Development
279
321
 
@@ -16,10 +16,16 @@ Current implementation notes:
16
16
  - Image payload forwarding sends images only from the latest user message. If the latest user turn is plain text after an earlier image turn, the transcript keeps an `[image omitted from transcript]` placeholder but no image bytes are sent to Cursor. The prompt explicitly tells Cursor that prior image bytes are unavailable and to ask the user to reattach or describe a prior image when needed. Carrying images forward across turns remains a future product decision because it affects token cost, privacy, stale visual context, and expected multimodal follow-up behavior.
17
17
  - `@cursor/sdk` is a package dependency of this extension; users should not need a global SDK install.
18
18
  - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults.
19
- - Local agents do not pass `settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. Users can opt in with `PI_CURSOR_SETTING_SOURCES=all` or narrow loading with a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`.
19
+ - Local agents pass `settingSources: ["all"]` by default so Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities are available. Users can narrow loading with a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`, or disable ambient setting sources with `PI_CURSOR_SETTING_SOURCES=none`. The provider suppresses direct Cursor SDK startup writes around agent creation so setting/skill loading logs do not pollute pi's TUI.
20
20
  - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model.
21
- - Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, `ls`, `edit`, and `write` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands/file edits. Cursor edit/write activity is replayed through `cursor_edit` and `cursor_write` cards rather than pi's built-in `edit`/`write` names because Cursor's edit/write schemas differ from pi's schemas; these replay-only tools display recorded Cursor results and fail closed if called without a recorded result. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active.
21
+ - Cursor-side thinking remains visible through pi's native thinking rendering when the Cursor SDK emits thinking or summary deltas.
22
+ - Local Cursor agents get two tool paths. First, Cursor keeps the Cursor SDK local-agent tool surface plus configured Cursor settings, plugins, and Cursor MCP servers. Second, pi-cursor-sdk exposes active pi tools through a default-on, tokenized loopback MCP bridge when `pi.getActiveTools()` and `pi.getAllTools()` contain exposable tools. Cursor sees collision-safe MCP names such as `pi__sem_reindex`, while pi emits and executes the real pi tool name. Overlapping built-in pi tools (`read`, `bash`, `write`, `edit`, `grep`, `find`, `ls`) are hidden by default because Cursor local agents already have native equivalents; `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1` opts into exposing them too. The provider also registers `cursor_ask_question` for Cursor models when the bridge is enabled; Cursor sees it as `pi__cursor_ask_question`, and pi executes it through the normal tool path so interactive users can choose options from pi UI. In non-UI modes it reports that UI is unavailable so Cursor can state a default assumption instead. The bridge queues MCP calls, emits provider `toolcall_*` events, waits for matching pi `toolResult` messages by `toolCallId`, resolves the result back to the same Cursor SDK run, and never calls tool `execute()` handlers directly. `PI_CURSOR_PI_TOOL_BRIDGE=0` disables this local bridge, including question bridging. Cloud Cursor agents remain out of scope for the bridge.
23
+ - Cursor SDK MCP tool calls use a guarded timeout override because installed `@cursor/sdk` 1.0.13 has a 60-second MCP request default with no public per-server timeout option. The extension extends that Cursor SDK MCP `callTool` timeout path to 3600 seconds by default. Users can override it with `PI_CURSOR_MCP_TOOL_TIMEOUT_MS` or `PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS`.
24
+ - Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, `grep`, `find`, `ls`, `edit`, `write`, diagnostics, delete, todo/plan, task, image generation, and MCP activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native-looking cards without rerunning Cursor's reads/shell commands/file edits. Cursor `glob` activity is replayed through native `find` cards. Cursor write activity is replayed through native-looking `write` cards, and Cursor StrReplace/edit activity uses native-looking `edit` only when recorded arguments truthfully satisfy pi's `edit` schema; path-only Cursor edit and notebook edit replay falls back to neutral Cursor activity before pi validation. Diagnostics, delete, todos/plans, task, image, and MCP activity use neutral Cursor activity cards with pi's default success/error shell. Neutral Cursor activity calls include `activityTitle` and, when available, `activitySummary` so partial/collapsed cards preserve identity such as `Cursor plan`, `Cursor todos`, `Cursor MCP`, or `Cursor edit`. Replay-only tools display recorded Cursor results, normalize workspace-local paths/diff headers for display, use pi diff colors for edit previews and path-inferred syntax highlighting for write previews, and fail closed if called without a recorded result. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. Cursor workflow tools such as `SwitchMode` and Cursor todo state are not pi workflow controls; reported todo/plan events are displayed as Cursor activity only. Plan/todo replay cards can be followed by Cursor's final plan text, selected from `run.wait().result` when Cursor provides one and trimmed against already-emitted text. Started Cursor SDK tool calls that never receive a completion event are discarded without synthetic replay errors; explicit failures remain visible when Cursor reports them through completed tool calls or step results. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When bridge or native replay cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK activity arrives: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later tool batches as further `toolUse` turns, then Cursor's final assistant answer. For shell replay, completed `stdout` / `stderr` are primary; unambiguous `shell-output-delta` data is used only as display-only fallback for empty successful shell completions, and overlapping shell calls drop ambiguous deltas instead of guessing. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when no live-run turn split is active.
25
+ - Synthetic replay names are internal compatibility details. New model-facing prompt text and user-visible cards use native tool names when renderer-compatible, or neutral Cursor activity labels when not. Legacy sessions containing old internal replay names are sanitized before prompt/display. Bridge MCP names such as `pi__sem_reindex` are MCP-only; pi session output uses real pi tool names.
22
26
  - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. When native replay splits one Cursor SDK run into multiple pi turns, prompt input is counted once for the run; later synthetic replay turns report `input: 0` and only their own output estimate.
27
+ - Audit observation, 2026-05-19, superseded by the 2026-05-21 replay pass: a missing-file read with Composer 2.5 emitted `tool-call-started` for Cursor `read`, then streamed final text `Error: File not found`, but did not emit `tool-call-completed` or an `onStep` `toolCall` error result. Leftover started calls are now discarded at run completion instead of becoming synthetic replay errors. Cursor-reported completed/step errors remain visible.
28
+ - Maintainer visual verification for replay-card changes should follow [Cursor Native Tool Visual Audit Workflow](./cursor-native-tool-visual-audit.md): offscreen PTY-driven pi run, xterm.js/Playwright screenshot rendering, and JSONL inspection before accepting commits or PRs.
23
29
  - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup.
24
30
  - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.13 documentation says the SDK may enable Max Mode automatically when a selected model requires it, but the public local-agent `ModelSelection` path still does not expose a manual Max Mode selector. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends.
25
31
  - `@cursor/sdk` 1.0.13 adds latest-style `ModelListItem.aliases`. The extension registers only unambiguous aliases as pi model IDs (with the same context suffixes when applicable) and sends the alias back in `ModelSelection.id`, while sharing Cursor-only state such as fast defaults with the underlying catalog `id`. Aliases shared by multiple base models, such as generic family aliases, are skipped because the pi row metadata would otherwise imply one base model while Cursor may resolve the alias to another.
@@ -236,7 +242,7 @@ Important distinction:
236
242
 
237
243
  - **Cursor thinking support** applies to all Cursor SDK models. The extension should assume Cursor models can think and may emit thinking deltas.
238
244
  - **Pi-controllable thinking** means Cursor exposes a `reasoning`, `effort`, or `thinking` parameter that the extension can set from pi's native thinking level. These models register `reasoning: true` and show `thinking=yes` in `pi --list-models`.
239
- - **Cursor SDK thinking-control gap** means the model can still think, but the SDK does not expose a user-controllable thinking parameter for that model. These models register `reasoning: false` and show `thinking=no` in `pi --list-models` because pi cannot control a level for them. The extension still parses Cursor `thinking-delta` events if they are emitted.
245
+ - **Cursor SDK thinking-control gap** means the model can still think, but the SDK does not expose a user-controllable thinking parameter for that model. These models register `reasoning: false` and show `thinking=no` in `pi --list-models` because pi cannot control a level for them. The extension still surfaces Cursor `thinking-delta` and summary events through pi's native thinking rendering when they are emitted.
240
246
 
241
247
  Do not mark a model `reasoning: true` only because it can think. That would make pi show controls such as `--thinking`, `:medium`, and shift+tab even though the extension cannot translate them into Cursor SDK params.
242
248
 
@@ -655,3 +661,11 @@ Before calling done:
655
661
  - `pi --model cursor/gpt-5.5@272k --thinking xhigh -p "Say ok only"`
656
662
  - `pi --model cursor/gpt-5.5@1m --cursor-fast -p "Say ok only"`
657
663
  - confirm requests use selected context, pi thinking, and fast flag state
664
+
665
+ 4. Tool bridge and replay:
666
+ - `npm test -- test/cursor-pi-tool-bridge.test.ts test/cursor-provider.test.ts test/cursor-mcp-timeout-override.test.ts`
667
+ - confirm `Agent.create()` gets `mcpServers.pi_tools` when active pi tools exist and omits it when `PI_CURSOR_PI_TOOL_BRIDGE=0` or the active snapshot is empty
668
+ - confirm bridged MCP requests emit real pi tool calls and resolve matching pi tool results back to the same Cursor SDK run
669
+ - confirm bridge MCP activity is suppressed from Cursor replay while non-bridge Cursor MCP activity remains visible
670
+ - confirm `PI_CURSOR_MCP_TOOL_TIMEOUT_MS` and `PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS` override the Cursor SDK MCP callTool timeout seam
671
+ - run the visual audit workflow when replay card visuals or bridge card visuals change; JSONL should show real pi tool names for bridged calls and no duplicate MCP replay for bridge calls
@@ -0,0 +1,88 @@
1
+ # Cursor native tool replay
2
+
3
+ pi-cursor-sdk has two separate tool paths:
4
+
5
+ 1. **Local pi MCP bridge:** default-on for local Cursor agents. It exposes the current pi session's bridgeable active tools to Cursor through a tokenized `127.0.0.1` MCP endpoint, excluding internal Cursor replay activity names and, by default, overlapping built-in pi tools (`read`, `bash`, `write`, `edit`, `grep`, `find`, `ls`). When Cursor calls one of those MCP tools, pi executes the real pi tool through the normal pi tool path.
6
+ 2. **Cursor native tool replay:** display-only. It renders completed Cursor SDK tool activity as pi-native-looking cards using recorded Cursor results.
7
+
8
+ This document is about replay. Replay is not execution and is not the local pi bridge.
9
+
10
+ ## Local pi bridge summary
11
+
12
+ The bridge is enabled by default when bridgeable active pi tools exist. Cursor sees bridge-owned MCP names such as `pi__sem_reindex`, while pi history and tool cards use the real pi tool name such as `sem_reindex`. The bridge hides overlapping built-in pi tools by default because Cursor already has native equivalents; extension/custom tools and non-overlapping active tools present in pi's active tool registry normally remain exposed. pi-cursor-sdk also registers `cursor_ask_question` for Cursor models when the bridge is enabled, exposed to Cursor as `pi__cursor_ask_question`, so Cursor can ask the user to choose instead of silently defaulting when the pi UI is available. The bridge does not call pi tool `execute()` handlers directly; it queues the request, emits a real pi `toolCall`, waits for the matching pi `toolResult`, and resolves the Cursor MCP call back into the same Cursor SDK run.
13
+
14
+ Rollback and timeout controls:
15
+
16
+ ```bash
17
+ PI_CURSOR_PI_TOOL_BRIDGE=0 pi --model cursor/composer-2.5
18
+ PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 pi --model cursor/composer-2.5
19
+ PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS=7200 pi --model cursor/composer-2.5
20
+ PI_CURSOR_MCP_TOOL_TIMEOUT_MS=7200000 pi --model cursor/composer-2.5
21
+ ```
22
+
23
+ `PI_CURSOR_PI_TOOL_BRIDGE=0` disables the bridge, including `pi__cursor_ask_question`. `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1` opts in to exposing overlapping pi tool names that Cursor already has native equivalents for (`read`, `bash`, `write`, `edit`, `grep`, `find`, and `ls`). By default those names are hidden even when pi's Cursor replay wrapper has registered them as extension tools; non-overlapping active built-ins remain bridgeable by default. Cursor-native tools, Cursor settings, plugins, and configured Cursor MCP servers still come from the Cursor SDK local agent path. Cloud Cursor agents are out of scope for this bridge.
24
+
25
+ ## What gets replayed
26
+
27
+ When Cursor reports completed tool activity, the extension can display recorded results for:
28
+
29
+ - `read`
30
+ - `bash`
31
+ - `grep`
32
+ - `find`
33
+ - `ls`
34
+ - `edit`
35
+ - `write`
36
+ - diagnostics
37
+ - delete
38
+ - todos and plans
39
+ - tasks
40
+ - image generation
41
+ - MCP activity
42
+
43
+ Cursor `glob` activity is displayed through native `find` cards.
44
+
45
+ Edit and write activity replays through pi-facing `edit` and `write` cards only when replay arguments truthfully satisfy the matching pi schema, but still uses recorded Cursor results only. The adapter passes through truthful Cursor paths, content when Cursor reported it, and recorded diff/details; it does not pretend Cursor's editing schema is pi's schema and it fails closed if a recorded replay result is missing. Cursor `StrReplace` with recorded replacement text displays as native-looking `edit`; path-only Cursor `edit` and notebook edit activity fall back to neutral Cursor activity so pi does not reject the replay before recorded-result handling. Cursor `write` displays as native-looking `write`. Diagnostics, delete, todos/plans, task, image, and MCP activity use neutral Cursor activity cards with pi's default success/error tool shell. Neutral Cursor activity cards carry display metadata such as `activityTitle` and `activitySummary`, so partial/collapsed cards can say `Cursor plan`, `Cursor todos`, `Cursor MCP`, or `Cursor edit` instead of only `Cursor activity`. These replay tools only display recorded Cursor results; they never mutate files or execute tool work directly. Replay paths are normalized to workspace-relative paths when possible. Collapsed replay cards include bounded previews for diffs and text details so small edits, todos, task output, and MCP results are visible without expanding; edit previews omit raw unified diff headers and show compact numbered changed/context lines using pi's native diff added/removed/context colors, and write previews use syntax highlighting when pi can infer a language from the path. Image generation replay cards show the saved image path in the collapsed summary and render the image inline when pi terminal image display is enabled and the generated file is still readable.
46
+
47
+ ## What replay does not do
48
+
49
+ Native replay is display-only:
50
+
51
+ - pi does not re-run Cursor-side commands.
52
+ - pi does not apply Cursor-side edits or deletes.
53
+ - pi does not call Cursor-side MCP servers.
54
+ - replay-only cards do not update pi state or generate images.
55
+ - replay does not expose pi tool schemas to Cursor; the local pi MCP bridge is the separate path that exposes active pi tools.
56
+ - Cursor workflow tools such as `SwitchMode` and Cursor todo state are not pi workflow controls; reported todo/plan events are displayed as Cursor activity only. Plan/todo replay cards do not drive pi plan-mode state.
57
+
58
+ If a Cursor read completion reports no content, the extension may include a bounded local file preview for safe in-workspace paths. That preview is labeled as a local preview captured at transcript time, not guaranteed Cursor-observed content.
59
+
60
+ Other unsupported Cursor SDK tools may still be described through a bounded scrubbed activity transcript when the SDK reports completed tool-call data. Started Cursor SDK tool calls that never receive a completion event are discarded without a synthetic replay error; missing completion is not itself treated as a Cursor tool failure. Explicit failures remain visible when Cursor reports an error through a completed tool call or step result. Some Cursor-internal workflow actions may only appear in Cursor's own thinking stream or not be reported as replayable SDK tool completions.
61
+
62
+ ## Ordering and non-interactive output
63
+
64
+ As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results, then continuing with live post-tool Cursor thinking/text, later Cursor tool batches, or Cursor's final answer as the next assistant turn. For plan-mode runs, neutral Cursor plan/todo cards can therefore appear before the final Cursor plan text.
65
+
66
+ Bridged pi tool calls follow the same visible pi `toolUse` turn shape, but they are real pi tool executions rather than replayed Cursor results.
67
+
68
+ For shell replay, completed `stdout` / `stderr` remain the primary source. If a successful completed shell result is empty and Cursor emitted unambiguous `shell-output-delta` data while exactly one shell call was active, the replay card uses that delta as display-only fallback data. Overlapping shell calls make delta attribution ambiguous, so those fallback deltas are dropped rather than guessed. `(no output)` is kept only when no completed output or safe delta fallback is available.
69
+
70
+ Non-interactive and session consumers still receive bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text.
71
+
72
+ ## Synthetic-name policy
73
+
74
+ Synthetic replay names are internal compatibility details. New model-facing prompt text and user-visible cards use native tool names when renderer-compatible, or neutral Cursor activity labels when not. Legacy sessions that already contain old internal replay names are rewritten to safe labels in prompt text and display surfaces.
75
+
76
+ Bridge MCP names are also not pi tool names. Cursor may see names such as `pi__sem_reindex` inside the local MCP bridge, but pi session output uses the real pi tool name.
77
+
78
+ ## Conflicts and opt out
79
+
80
+ Native replay wrappers are registered only for tool names not already owned by another extension. If another extension already owns a wrapper name needed for replay, pi-cursor-sdk skips only the conflicting wrapper and uses the scrubbed Cursor activity transcript for that tool instead. Legacy replay wrappers remain registered for old sessions, but their model-facing and user-visible labels are sanitized.
81
+
82
+ Disable native replay registration entirely:
83
+
84
+ ```bash
85
+ PI_CURSOR_NATIVE_TOOL_DISPLAY=0 pi --model cursor/composer-2.5
86
+ ```
87
+
88
+ `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is also accepted as a registration-only opt-out.
@@ -0,0 +1,183 @@
1
+ # Cursor Native Tool Visual Audit Workflow
2
+
3
+ This workflow verifies Cursor SDK tool replay the way a human sees it in pi's interactive TUI, without stealing macOS focus.
4
+
5
+ Use it before accepting replay-card commits or PRs. Text logs and JSONL are necessary, but they are not enough when the claim is visual parity: always keep before/after PNGs for the exact prompt.
6
+
7
+ ## When to use this
8
+
9
+ Use this workflow when changing or reviewing:
10
+
11
+ - Cursor native tool replay cards.
12
+ - Tool-call turn ordering.
13
+ - Tool-result error styling.
14
+ - Truncation, continuation hints, timeout labels, or path display.
15
+ - Any PR claiming native TUI parity.
16
+
17
+ Do not use this for ordinary unit-only logic changes.
18
+
19
+ ## Why this workflow exists
20
+
21
+ Earlier manual verification used a visible Terminal window plus `screencapture`. That worked, but it stole system focus and made it easy for the user to type into the audit window by accident.
22
+
23
+ The preferred workflow is now offscreen:
24
+
25
+ 1. Spawn `pi` in a pseudo-terminal at a fixed size.
26
+ 2. Feed the prompt programmatically.
27
+ 3. Save raw ANSI output and plain text output.
28
+ 4. Render the terminal buffer through xterm.js in headless Playwright.
29
+ 5. Save a PNG screenshot.
30
+ 6. Inspect the session JSONL for exact persisted `toolCall` / `toolResult` data.
31
+
32
+ This gives human-like visual evidence without activating Terminal, iTerm, or a browser window.
33
+
34
+ ## Tool stack
35
+
36
+ Install the harness outside this repo so generated assets and temporary dependencies do not pollute commits:
37
+
38
+ ```bash
39
+ HARNESS=/tmp/pi-visual-harness
40
+ rm -rf "$HARNESS"
41
+ mkdir -p "$HARNESS"
42
+ cd "$HARNESS"
43
+ npm init -y
44
+ npm install node-pty @xterm/xterm playwright
45
+ npm rebuild node-pty
46
+ ```
47
+
48
+ `npm rebuild node-pty` is useful after Node upgrades; without it, `node-pty` may fail with `posix_spawnp failed`.
49
+
50
+ ## Runner contract
51
+
52
+ A runner script should:
53
+
54
+ - Spawn `pi -e <extension-dir> --model cursor/composer-2.5` with:
55
+ - `PI_CURSOR_NATIVE_TOOL_DISPLAY=1`
56
+ - `TERM=xterm-256color`
57
+ - fixed PTY size, for example `150x45`
58
+ - cwd set to the target audit repo.
59
+ - Wait for startup.
60
+ - Write the exact prompt and carriage return to the PTY.
61
+ - Wait a bounded amount of time.
62
+ - Save:
63
+ - `<label>.ansi` raw terminal bytes.
64
+ - `<label>.txt` stripped text for quick search.
65
+ - `<label>.png` rendered xterm screenshot.
66
+ - `<label>.jsonl.path` pointing to the latest pi session JSONL.
67
+ - Kill the PTY child after capture.
68
+ - Check for leftover commands when prompts can background work, especially shell timeout tests.
69
+
70
+ Example invocation shape:
71
+
72
+ ```bash
73
+ node /tmp/pi-visual-harness/run-pi-visual.mjs \
74
+ --label after-shell-nonzero \
75
+ --ext /path/to/pi-cursor-sdk \
76
+ --cwd /path/to/test-workspace \
77
+ --prompt "Run \`printf 'cursor-shell-stderr\\n' >&2; exit 7\` using only the shell/terminal tool. Do not use read, grep, glob, find, ls, edit, or write. Print the command result exactly, then stop." \
78
+ --wait-ms 30000 \
79
+ --out-dir /tmp/pi-visual-harness/review-current
80
+ ```
81
+
82
+ Keep the runner in `/tmp` unless the project explicitly decides to check in a maintained audit harness.
83
+
84
+ ## Before/after comparison
85
+
86
+ Use a clean worktree for the baseline and the active worktree for the candidate change:
87
+
88
+ ```bash
89
+ BASE=/tmp/pi-cursor-visual-review
90
+ BEFORE_WT=$BASE/before-main
91
+ AFTER_WT=/path/to/pi-cursor-sdk
92
+ TARGET=/path/to/test-workspace
93
+
94
+ rm -rf "$BASE"
95
+ git fetch origin main
96
+ BASE_COMMIT=$(git merge-base origin/main HEAD)
97
+ git worktree add --detach "$BEFORE_WT" "$BASE_COMMIT"
98
+
99
+ # Optional speedup when the before worktree has no install of its own.
100
+ ln -s "$AFTER_WT/node_modules" "$BEFORE_WT/node_modules"
101
+ ```
102
+
103
+ Then run the same prompt against both extension dirs:
104
+
105
+ ```bash
106
+ node /tmp/pi-visual-harness/run-pi-visual.mjs \
107
+ --label before-glob-single \
108
+ --ext "$BEFORE_WT" \
109
+ --cwd "$TARGET" \
110
+ --prompt "Find files matching \`src/tools/reindex.ts\` using only the glob/file-search tool. Do not use shell, bash, grep, read, or ls. Print the matched files exactly as found, then stop." \
111
+ --wait-ms 16000 \
112
+ --out-dir /tmp/pi-visual-harness/review-current
113
+
114
+ node /tmp/pi-visual-harness/run-pi-visual.mjs \
115
+ --label after-glob-single \
116
+ --ext "$AFTER_WT" \
117
+ --cwd "$TARGET" \
118
+ --prompt "Find files matching \`src/tools/reindex.ts\` using only the glob/file-search tool. Do not use shell, bash, grep, read, or ls. Print the matched files exactly as found, then stop." \
119
+ --wait-ms 16000 \
120
+ --out-dir /tmp/pi-visual-harness/review-current
121
+ ```
122
+
123
+ For review, create a simple HTML/PNG gallery that places `before-*.png` and `after-*.png` side by side. Keep the generated gallery in `/tmp` unless explicitly asked to commit visual artifacts.
124
+
125
+ ## JSONL inspection
126
+
127
+ For each visual claim, inspect the JSONL path written by the runner. Confirm at least:
128
+
129
+ - `toolCall.name` is the expected pi-facing replay tool name.
130
+ - `toolCall.arguments` show the expected user-facing args.
131
+ - `toolResult.toolName` matches the call.
132
+ - `toolResult.content[0].text` contains the recorded body expected in the card.
133
+ - `toolResult.isError` matches the visual card state.
134
+
135
+ For local pi MCP bridge claims, also confirm:
136
+
137
+ - Bridged calls appear as the real pi tool name (for example `sem_reindex`), not the MCP bridge name (for example `pi__sem_reindex`; or `read`/`pi__read` when overlapping built-ins are explicitly exposed).
138
+ - The JSONL has no second Cursor MCP replay card for the same bridged call.
139
+ - Non-bridge Cursor MCP activity, if present, still renders as neutral Cursor activity instead of being suppressed.
140
+
141
+ Small helper pattern:
142
+
143
+ ```bash
144
+ python3 - <<'PY'
145
+ import json, pathlib
146
+ path = pathlib.Path('/tmp/pi-visual-harness/review-current/after-shell-nonzero.jsonl.path').read_text().strip()
147
+ for line in pathlib.Path(path).read_text().splitlines():
148
+ obj = json.loads(line)
149
+ msg = obj.get('message', {})
150
+ if msg.get('role') == 'assistant':
151
+ for part in msg.get('content', []):
152
+ if part.get('type') == 'toolCall':
153
+ print('CALL', part.get('name'), part.get('arguments'))
154
+ if msg.get('role') == 'toolResult':
155
+ text = msg.get('content', [{}])[0].get('text', '')
156
+ print('RESULT', msg.get('toolName'), 'isError=', msg.get('isError'), repr(text[:160]))
157
+ PY
158
+ ```
159
+
160
+ ## Safety rules
161
+
162
+ - Prefer the offscreen PTY renderer. Do not use `osascript`, visible Terminal windows, or `screencapture` unless a user explicitly asks for a real desktop screenshot.
163
+ - Keep generated screenshots, HTML galleries, ANSI logs, and temporary harness dependencies out of the repo by default.
164
+ - Use short, deterministic prompts with bounded wait times.
165
+ - For timeout/background prompts, always check for leftovers:
166
+
167
+ ```bash
168
+ ps -axo pid,etime,command | rg "sleep 2|should-not-print|<audit-session-label>" || true
169
+ ```
170
+
171
+ - If the model uses a different tool than requested, record it as model/provider behavior unless JSONL shows replay lost or misrendered a completed Cursor tool event.
172
+ - Visual output can differ slightly from macOS Terminal fonts because xterm.js renders offscreen. Treat this workflow as evidence for card class, color state, labels, ordering, truncation, and content. Use a real terminal screenshot only for pixel-level terminal-specific bugs.
173
+
174
+ ## Required evidence before commit or merge
175
+
176
+ Before accepting a replay-card change, provide:
177
+
178
+ - Before and after PNG paths.
179
+ - The prompt used for each pair.
180
+ - JSONL paths for each run.
181
+ - A short statement of what changed visually.
182
+ - The relevant JSONL `toolCall` / `toolResult` facts.
183
+ - `npm test` and `npm run typecheck` results, unless the change is documentation-only.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cursor-sdk",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "pi provider extension backed by @cursor/sdk local agents",
5
5
  "author": "Mitch Fultz (https://github.com/fitchmultz)",
6
6
  "license": "MIT",
@@ -26,6 +26,8 @@
26
26
  "scripts/refresh-cursor-model-snapshots.mjs",
27
27
  "README.md",
28
28
  "docs/cursor-model-ux-spec.md",
29
+ "docs/cursor-native-tool-replay.md",
30
+ "docs/cursor-native-tool-visual-audit.md",
29
31
  "LICENSE",
30
32
  "CHANGELOG.md"
31
33
  ],
@@ -40,7 +42,8 @@
40
42
  "refresh:cursor-snapshots": "node scripts/refresh-cursor-model-snapshots.mjs"
41
43
  },
42
44
  "dependencies": {
43
- "@cursor/sdk": "^1.0.13"
45
+ "@cursor/sdk": "^1.0.13",
46
+ "@modelcontextprotocol/sdk": "^1.29.0"
44
47
  },
45
48
  "peerDependencies": {
46
49
  "@earendil-works/pi-ai": "*",
package/src/context.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Context, Message, ToolCall } from "@earendil-works/pi-ai";
2
2
  import type { SDKImage } from "@cursor/sdk";
3
+ import { getCursorReplayPromptLabel } from "./cursor-tool-names.js";
3
4
 
4
5
  export interface CursorPrompt {
5
6
  text: string;
@@ -58,8 +59,26 @@ function formatContentBlocks(content: string | { type: string; text?: string; da
58
59
  }
59
60
 
60
61
  function formatToolCall(toolCall: ToolCall): string {
61
- const args = JSON.stringify(toolCall.arguments);
62
- return `Tool call (${toolCall.name}, call ${toolCall.id}): ${args}`;
62
+ const args = JSON.stringify(toolCall.arguments) ?? "";
63
+ return `Tool call (${getCursorReplayPromptLabel(toolCall.name)}, call ${toolCall.id}): ${args}`;
64
+ }
65
+
66
+ function sanitizeSystemPromptForCursor(systemPrompt: string): string {
67
+ let sanitized = systemPrompt;
68
+ sanitized = sanitized.replace(
69
+ /Available tools:\n[\s\S]*?\n\nIn addition to the tools above, you may have access to other custom tools depending on the project\.\n\n/g,
70
+ "Pi tool catalog omitted: Cursor can call only Cursor SDK tools exposed in this run.\n\n",
71
+ );
72
+ sanitized = sanitized.replace(
73
+ /Guidelines:\n[\s\S]*?\n\nPi documentation /g,
74
+ "Guidelines:\n- Be concise in your responses.\n- Show file paths clearly when working with files.\n\nPi documentation ",
75
+ );
76
+ sanitized = sanitized.replace(
77
+ /\n\nThe following skills provide specialized instructions for specific tasks\.[\s\S]*?<\/available_skills>/g,
78
+ "",
79
+ );
80
+ sanitized = sanitized.replace(/\n+Semantic code intelligence priority:[\s\S]*$/g, "");
81
+ return sanitized.trim();
63
82
  }
64
83
 
65
84
  function formatMessage(msg: Message): string | undefined {
@@ -84,7 +103,7 @@ function formatMessage(msg: Message): string | undefined {
84
103
  case "toolResult": {
85
104
  const text = formatContentBlocks(msg.content);
86
105
  const label = msg.isError ? "Tool error" : "Tool result";
87
- return `${label} (${msg.toolName}, call ${msg.toolCallId}): ${text}`;
106
+ return `${label} (${getCursorReplayPromptLabel(msg.toolName)}, call ${msg.toolCallId}): ${text}`;
88
107
  }
89
108
  }
90
109
  }
@@ -152,15 +171,17 @@ export function buildCursorPrompt(context: Context, options: CursorPromptOptions
152
171
  const sectionsBeforeMessages: string[] = [
153
172
  [
154
173
  "Cursor SDK tool boundary:",
155
- "Only tools exposed by the Cursor SDK in this run are callable. The pi system prompt and transcript are context only; they do not grant access to pi tools or tool names mentioned there.",
156
- "If the user asks you to search, fetch, browse, or research the web, use an actual Cursor SDK web/search/browser/MCP tool call. If no such Cursor SDK tool is available, say that web search is not configured for this Cursor SDK run.",
157
- "Do not plan to use or claim to have used pi-only tools such as WebSearch or WebFetch unless the Cursor SDK actually exposes and executes that tool in this run.",
158
- "Image payload boundary: only images attached to the latest user message are available as image bytes. Earlier images appear only as [image omitted from transcript] placeholders; ask the user to reattach or describe a prior image if the latest request depends on it.",
174
+ "You can call only tools actually exposed by Cursor SDK in this run. Pi tool names, replay tool names, and transcript tool names are context only, not callable capabilities.",
175
+ "If asked to list or exercise available tools, list and exercise Cursor SDK tools only; do not claim access to pi-side tools from the system prompt unless Cursor exposes an equivalent tool that runs.",
176
+ "Use pi__cursor_ask_question for material choices if exposed.",
177
+ "Web: use Cursor web/search/browser/MCP or say web search is not configured; do not claim WebSearch/WebFetch unless Cursor executes them.",
178
+ "Replay: pi may display recorded Cursor tool activity as pi-style cards, but replay is display-only and not a capability to invoke.",
179
+ "Images: only latest user images are sent; ask to reattach or describe prior images.",
159
180
  ].join("\n"),
160
181
  ];
161
182
 
162
183
  if (context.systemPrompt) {
163
- sectionsBeforeMessages.push(`System instructions from pi:\n${context.systemPrompt}`);
184
+ sectionsBeforeMessages.push(`System instructions from pi:\n${sanitizeSystemPromptForCursor(context.systemPrompt)}`);
164
185
  }
165
186
 
166
187
  const messageSections = context.messages
@@ -171,8 +192,8 @@ export function buildCursorPrompt(context: Context, options: CursorPromptOptions
171
192
  .filter((section): section is { index: number; text: string } => section !== undefined);
172
193
  const sectionsAfterMessages = [
173
194
  [
174
- "Answer the latest user request above using your capabilities. Do not assume access to pi tools.",
175
- "If the user asks for web research, do not claim to have searched the web unless a Cursor SDK web/search/browser/MCP tool was actually used.",
195
+ "Answer the latest user request above using Cursor SDK capabilities only. Do not list, promise, or call pi-only tools from the system prompt as if they were available.",
196
+ "If web research is requested, do not claim it unless a Cursor web/search/browser/MCP tool ran.",
176
197
  ].join("\n"),
177
198
  ];
178
199
  const images = extractLatestImages(context.messages);
@@ -188,6 +209,8 @@ export function buildCursorPrompt(context: Context, options: CursorPromptOptions
188
209
  getLatestUserMessageIndex(context.messages),
189
210
  budgetOptions,
190
211
  );
212
+ const text = parts.join(SECTION_SEPARATOR);
213
+
191
214
 
192
- return { text: parts.join(SECTION_SEPARATOR), images };
215
+ return { text, images };
193
216
  }