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 +30 -0
- package/README.md +55 -13
- package/docs/cursor-model-ux-spec.md +17 -3
- package/docs/cursor-native-tool-replay.md +88 -0
- package/docs/cursor-native-tool-visual-audit.md +183 -0
- package/package.json +5 -2
- package/src/context.ts +34 -11
- package/src/cursor-mcp-timeout-override.ts +111 -0
- package/src/cursor-native-tool-display.ts +397 -46
- package/src/cursor-pi-tool-bridge.ts +637 -0
- package/src/cursor-provider.ts +477 -81
- package/src/cursor-question-tool.ts +247 -0
- package/src/cursor-session-cwd.ts +33 -0
- package/src/cursor-tool-names.ts +67 -0
- package/src/cursor-tool-transcript.ts +730 -61
- package/src/index.ts +7 -0
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
|
|
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
|
-
- **
|
|
212
|
-
- **
|
|
213
|
-
- **
|
|
214
|
-
- **Cursor setting sources
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
156
|
-
"If
|
|
157
|
-
"
|
|
158
|
-
"
|
|
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
|
|
175
|
-
"If
|
|
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
|
|
215
|
+
return { text, images };
|
|
193
216
|
}
|