@emmaneugene/pi-cursor-sdk 0.4.2 → 0.5.0

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +19 -39
  3. package/dist/bundled-context-windows.js +6 -13
  4. package/dist/context.js +0 -3
  5. package/dist/cursor-config.js +0 -3
  6. package/dist/cursor-live-run-coordinator.js +11 -0
  7. package/dist/cursor-pi-tool-bridge-run.js +3 -0
  8. package/dist/cursor-provider-turn-prepare.js +0 -1
  9. package/dist/cursor-state.js +3 -16
  10. package/dist/index.js +0 -2
  11. package/dist/model-discovery.js +11 -47
  12. package/docs/cursor-dogfood-checklist.md +1 -1
  13. package/docs/cursor-live-smoke-checklist.md +1 -2
  14. package/docs/cursor-model-ux-spec.md +80 -151
  15. package/docs/cursor-native-tool-replay.md +2 -3
  16. package/docs/cursor-native-tool-visual-audit.md +1 -1
  17. package/docs/cursor-testing-lessons.md +1 -1
  18. package/docs/cursor-tool-surfaces.md +3 -6
  19. package/docs/platform-smoke-implementation.md +1 -1
  20. package/docs/platform-smoke.md +1 -1
  21. package/package.json +1 -1
  22. package/scripts/isolated-cursor-smoke.sh +1 -1
  23. package/scripts/lib/local-resume-smoke-harness.mjs +2 -1
  24. package/scripts/local-resume-smoke.mjs +1 -1
  25. package/scripts/platform-smoke/card-detect.mjs +1 -1
  26. package/scripts/refresh-cursor-model-snapshots.mjs +3 -3
  27. package/shared/cursor-model-selection-identities.d.mts +1 -4
  28. package/shared/cursor-model-selection-identities.mjs +21 -74
  29. package/src/bundled-context-windows.ts +6 -13
  30. package/src/context.ts +1 -5
  31. package/src/cursor-config.ts +9 -13
  32. package/src/cursor-live-run-coordinator.ts +11 -0
  33. package/src/cursor-pi-tool-bridge-run.ts +4 -0
  34. package/src/cursor-pi-tool-bridge-types.ts +1 -0
  35. package/src/cursor-provider-turn-prepare.ts +0 -1
  36. package/src/cursor-state.ts +3 -22
  37. package/src/index.ts +0 -3
  38. package/src/model-discovery.ts +16 -62
  39. package/dist/cursor-question-tool.js +0 -194
  40. package/node_modules/cross-spawn/node_modules/which/CHANGELOG.md +0 -166
  41. package/src/cursor-question-tool.ts +0 -267
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 - 2026-09-13
4
+
5
+ Breaking: Cursor model aliases, context-specific model IDs, `:fast` / `:slow` virtual models, and the dedicated `cursor_ask_question` tool are removed.
6
+
7
+ ### Changed
8
+
9
+ - Register exactly one pi model for each item returned by `Cursor.models.list()`. The model picker now uses canonical Cursor IDs only, while the catalog item's default variant supplies its context and fast parameters.
10
+ - Keep fast mode as runtime state through `/cursor-fast`, saved defaults, `--cursor-fast`, and `--cursor-no-fast` instead of registering extra model rows.
11
+ - Remove the dedicated `cursor_ask_question` tool and `PI_CURSOR_ASK_QUESTION`. Cursor models continue to receive pi's richer `AskUserQuestion` tool through the existing tool bridge.
12
+
13
+ ## 0.4.3 - 2026-09-11
14
+
15
+ ### Fixed
16
+
17
+ - Keep live Cursor runs open while bridged pi tools await results. Long `cursor_ask_question` waits and other bridge calls now use the bridge CallTool deadline instead of being cancelled by the five-minute inactive-run cleanup.
18
+
3
19
  ## 0.4.2 - 2026-09-07
4
20
 
5
21
  ### Fixed
package/README.md CHANGED
@@ -10,7 +10,7 @@ Use this extension if you primarily use Cursor models inside pi and want Cursor'
10
10
 
11
11
  Use `pi-cursor-sdk` when you primarily want to use Cursor models **inside pi**.
12
12
 
13
- This extension runs Cursor models through the local `@cursor/sdk` agent runtime and keeps Cursor's agent loop intact. pi integrates around that loop: model discovery, model selection, context-window variants, thinking controls where Cursor exposes them, fast/slow aliases, Cursor mode, session handling, native replay cards, and the optional pi tool bridge.
13
+ This extension runs Cursor models through the local `@cursor/sdk` agent runtime and keeps Cursor's agent loop intact. pi integrates around that loop: model discovery, model selection, thinking controls where Cursor exposes them, fast mode, Cursor mode, session handling, native replay cards, and the optional pi tool bridge.
14
14
 
15
15
  OpenAI-compatible Cursor proxies are useful when you want a generic `/v1/chat/completions` or `/v1/responses` endpoint for many clients such as curl, the OpenAI SDK, OpenCode, or other tools. That compatibility comes from translating Cursor behavior into OpenAI-shaped requests, responses, and tool calls.
16
16
 
@@ -181,28 +181,21 @@ Choose Cursor models interactively with `/model`, or pass a model on the command
181
181
 
182
182
  ```bash
183
183
  pi --model cursor/grok-4.6
184
- pi --model cursor/gpt-5.5@1m
185
- pi --model cursor/gpt-5.5@272k
186
- pi --model cursor/claude-opus-4-8@300k
184
+ pi --model cursor/gpt-5.5
185
+ pi --model cursor/claude-opus-4-8
187
186
  ```
188
187
 
189
- How to read model IDs:
188
+ Each item returned by `Cursor.models.list()` becomes one pi model with the canonical Cursor ID. Cursor aliases, alternative context values, and fast/slow states do not create additional model rows. The selected catalog item's default variant supplies its context and fast parameters.
190
189
 
191
- - `cursor/...` is the Cursor provider registered by this extension
192
- - `@1m`, `@272k`, and `@300k` are context-window variants
193
- - `:medium`, `:high`, `:xhigh`, and `:max` are pi thinking-level suffixes for models where the Cursor SDK exposes the corresponding pi-controllable thinking parameter
194
- - unambiguous latest-style Cursor aliases returned by `Cursor.models.list()` are registered too, using the same context suffixes when the target model has context variants; aliases shared by multiple base models or colliding with a base model ID are skipped because their SDK resolution and displayed metadata can diverge
195
-
196
- Examples with pi thinking controls:
190
+ Pi applies supported thinking levels at send time without creating more model rows:
197
191
 
198
192
  ```bash
199
- pi --model cursor/gpt-5.5@1m:medium
200
- pi --model cursor/gpt-5.5@272k:xhigh
201
- pi --model cursor/claude-opus-4-7@1m:max
202
- pi --model cursor/gpt-5.5@1m --thinking medium
193
+ pi --model cursor/gpt-5.5:medium
194
+ pi --model cursor/claude-opus-4-7:max
195
+ pi --model cursor/gpt-5.5 --thinking medium
203
196
  ```
204
197
 
205
- Cursor `context` becomes a pi-visible model variant because it changes pi's native `contextWindow`. For models that expose Cursor's boolean `fast` parameter, the extension also registers virtual `:fast` and `:slow` model aliases such as `cursor/grok-4.6:slow` and `cursor/gpt-5.5@1m:fast`. Those aliases are selection-only controls for subagents and workflow-spawned agents: they send the same Cursor SDK model ID plus an explicit `fast=true` or `fast=false` param, and they take precedence over saved `/cursor-fast` session/global defaults. Cursor SDK conversation mode remains extension state, not model identity. Alias model IDs use their selected SDK ID for Cursor-only state such as fast defaults, with read fallback for older defaults keyed by the underlying Cursor base model.
198
+ The model's static pi `contextWindow` follows cached checkpoint evidence for its canonical ID or default context, then falls back to the default context reported by Cursor. Cursor SDK conversation mode and fast mode remain extension state, not model identity.
206
199
 
207
200
  ## Thinking support
208
201
 
@@ -226,9 +219,9 @@ Some Cursor SDK models do not expose a `reasoning`, `effort`, or `thinking` para
226
219
 
227
220
  ## Fast mode
228
221
 
229
- Use `/cursor-fast` to persistently toggle fast mode for the selected unsuffixed Cursor model when the model supports Cursor's `fast` parameter.
222
+ Use `/cursor-fast` to persistently toggle fast mode for the selected Cursor model when the model supports Cursor's `fast` parameter.
230
223
 
231
- Fast preferences are remembered per selected Cursor SDK model ID or alias and stored:
224
+ Fast preferences are remembered per canonical Cursor model ID and stored:
232
225
 
233
226
  - in the current session with `pi.appendEntry()`
234
227
  - globally in `~/.pi/agent/cursor-sdk.json`
@@ -236,20 +229,11 @@ Fast preferences are remembered per selected Cursor SDK model ID or alias and st
236
229
  For one run, force fast on or off without changing saved defaults:
237
230
 
238
231
  ```bash
239
- pi --model cursor/gpt-5.5@1m --cursor-fast -p "Say ok only"
232
+ pi --model cursor/gpt-5.5 --cursor-fast -p "Say ok only"
240
233
  pi --model cursor/grok-4.6 --cursor-no-fast -p "Say ok only"
241
234
  ```
242
235
 
243
- For per-agent control, select the virtual model alias instead of mutating the shared saved default:
244
-
245
- ```bash
246
- pi --model cursor/grok-4.6:slow -p "Say ok only"
247
- pi --model cursor/gpt-5.5@1m:fast -p "Say ok only"
248
- ```
249
-
250
- The `:fast` and `:slow` aliases are available only for Cursor models whose catalog exposes a `fast` parameter. They override saved `/cursor-fast` session/global defaults while leaving `--cursor-fast` and `--cursor-no-fast` as explicit process-level force flags. `/cursor-fast` does not persist a new default while a virtual fast/slow alias is selected; switch to the unsuffixed model first.
251
-
252
- Composer 2 and Composer 2.5 can default to fast. Use `--cursor-no-fast` or a `:slow` virtual alias for a one-shot no-fast Composer run. In print mode (`-p`), `--cursor-no-fast` is silent and does not write `~/.pi/agent/cursor-sdk.json`.
236
+ Composer 2 and Composer 2.5 can default to fast. Use `--cursor-no-fast` for a one-shot no-fast Composer run. In print mode (`-p`), `--cursor-no-fast` is silent and does not write `~/.pi/agent/cursor-sdk.json`.
253
237
 
254
238
  In interactive mode, the footer shows Cursor status only while a Cursor model is active. Fast-capable models show fast state explicitly, and fast and plan mode share one Cursor status value so they do not overwrite each other:
255
239
 
@@ -267,7 +251,7 @@ cursor · fast:on · http1
267
251
 
268
252
  ## Cursor SDK mode
269
253
 
270
- Cursor SDK conversation mode is Cursor-only extension state. It is not a pi model variant, not pi thinking/reasoning, not a `:fast`/`:slow` virtual fast alias, and not pi's separate read-only plan-mode extension.
254
+ Cursor SDK conversation mode is Cursor-only extension state. It is not a pi model variant, not pi thinking/reasoning, and not pi's separate read-only plan-mode extension.
271
255
 
272
256
  Default mode is `agent`. Start a one-shot run in a specific mode:
273
257
 
@@ -369,16 +353,13 @@ Bridge capabilities are snapshotted from `pi.getActiveTools()` and `pi.getAllToo
369
353
 
370
354
  Pi subagents can select Cursor models under both Cursor and non-Cursor parents. Each nested Cursor child registers a child-local provider and bridge and uses an isolated SDK agent scope, so it does not replace or wait on the parent's active Cursor agent.
371
355
 
372
- Overlapping built-in pi tools (`read`, `bash`, `write`, `edit`, `grep`, `find`, `ls`) are hidden by default because Cursor local agents already have native equivalents. Extension/custom tools and non-overlapping active tools present in pi's active tool registry normally remain exposed. 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. When pi has visible Agent Skills loaded, the extension rewrites pi's skill catalog for Cursor and exposes `cursor_activate_skill` as `pi__cursor_activate_skill`; Cursor should call that bridge tool with a listed skill name to load the full `SKILL.md` and bundled resource list before applying the skill. If the bridge is disabled, the catalog remains available and instructs Cursor to fall back to reading the listed `SKILL.md` path directly.
356
+ Overlapping built-in pi tools (`read`, `bash`, `write`, `edit`, `grep`, `find`, `ls`) are hidden by default because Cursor local agents already have native equivalents. Extension/custom tools and non-overlapping active tools present in pi's active tool registry normally remain exposed. When pi has visible Agent Skills loaded, the extension rewrites pi's skill catalog for Cursor and exposes `cursor_activate_skill` as `pi__cursor_activate_skill`; Cursor should call that bridge tool with a listed skill name to load the full `SKILL.md` and bundled resource list before applying the skill. If the bridge is disabled, the catalog remains available and instructs Cursor to fall back to reading the listed `SKILL.md` path directly.
373
357
 
374
358
  Cursor-native tool replay is separate from the bridge. Replay cards are display-only recorded Cursor SDK activity. They never re-run Cursor-side commands, reapply Cursor edits, call MCP servers, or mutate pi state. See [Cursor native tool replay](docs/cursor-native-tool-replay.md).
375
359
 
376
360
  Bridge controls:
377
361
 
378
362
  ```bash
379
- # Keep the pi bridge but disable Cursor's interactive question tool.
380
- PI_CURSOR_ASK_QUESTION=0 pi --model cursor/grok-4.6
381
-
382
363
  # Roll back to Cursor SDK tools/settings/MCP only; do not expose active pi tools through the bridge.
383
364
  PI_CURSOR_PI_TOOL_BRIDGE=0 pi --model cursor/grok-4.6
384
365
 
@@ -410,7 +391,7 @@ PI_CURSOR_PI_TOOL_BRIDGE_DEBUG=1 pi --model cursor/grok-4.6
410
391
 
411
392
  On bootstrap sends, a compact **callable tool surfaces** block is injected into the Cursor prompt by default. It reminds the model that Cursor host/configured MCP tools are controlled by Cursor, while pi tool toggles only affect pi tools/bridge exposure; when bridge tools are exposed, it lists the current `pi__*` names. Disable with `PI_CURSOR_TOOL_MANIFEST=0`.
412
393
 
413
- `PI_CURSOR_ASK_QUESTION=0` disables only `cursor_ask_question`, leaving the rest of the pi bridge available; it is enabled by default. `PI_CURSOR_PI_TOOL_BRIDGE=0` is the supported rollback flag and disables the bridge entirely. Both flags treat `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. The installed Cursor SDK uses a 60-second MCP protocol default with no public per-server timeout option. pi-cursor-sdk overrides that seam in two directions by default: MCP `callTool` requests are extended to 3600 seconds for long-running local MCP tools (including the pi bridge and configured Cursor MCP servers), and known MCP initialize/listTools requests on first send are shortened to 10 seconds so unavailable configured MCP servers fail fast instead of blocking for a full minute. Unknown Cursor SDK MCP protocol timeout stacks keep the SDK default instead of being shortened. Override tool-call timeouts with `PI_CURSOR_MCP_TOOL_TIMEOUT_MS` or `PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS`, and first-send initialize/listTools timeouts with `PI_CURSOR_MCP_CONNECT_TIMEOUT_MS` or `PI_CURSOR_MCP_CONNECT_TIMEOUT_SECONDS`. Bridged calls also have a local fail-closed deadline that defaults to the effective MCP tool timeout; lower it with `PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS` when a lost pi result should fail sooner. On expiry, the bridge rejects and removes the pending call and aborts active pi execution when available. The bridge's `listTools` handler returns its snapshot synchronously, so a Cursor UI label such as `GetMcpTools` does not by itself identify a `listTools` deadlock; the durable bridge waiter is `CallTool` awaiting its matching pi result.
394
+ `PI_CURSOR_PI_TOOL_BRIDGE=0` is the supported rollback flag and disables the bridge entirely. Both flags treat `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. The installed Cursor SDK uses a 60-second MCP protocol default with no public per-server timeout option. pi-cursor-sdk overrides that seam in two directions by default: MCP `callTool` requests are extended to 3600 seconds for long-running local MCP tools (including the pi bridge and configured Cursor MCP servers), and known MCP initialize/listTools requests on first send are shortened to 10 seconds so unavailable configured MCP servers fail fast instead of blocking for a full minute. Unknown Cursor SDK MCP protocol timeout stacks keep the SDK default instead of being shortened. Override tool-call timeouts with `PI_CURSOR_MCP_TOOL_TIMEOUT_MS` or `PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS`, and first-send initialize/listTools timeouts with `PI_CURSOR_MCP_CONNECT_TIMEOUT_MS` or `PI_CURSOR_MCP_CONNECT_TIMEOUT_SECONDS`. Bridged calls also have a local fail-closed deadline that defaults to the effective MCP tool timeout; lower it with `PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS` when a lost pi result should fail sooner. On expiry, the bridge rejects and removes the pending call and aborts active pi execution when available. The bridge's `listTools` handler returns its snapshot synchronously, so a Cursor UI label such as `GetMcpTools` does not by itself identify a `listTools` deadlock; the durable bridge waiter is `CallTool` awaiting its matching pi result.
414
395
 
415
396
  `PI_CURSOR_HTTP_1_1=true` maps to the Cursor SDK `Cursor.configure({ local: { useHttp1ForAgent: true } })` compatibility mode for corporate VPN/proxy environments where HTTP/2 streams fail. In interactive sessions, `/cursor-http on`, `/cursor-http off`, and `/cursor-http toggle` set the branch-scoped session preference and save the user default as `local.useHttp1ForAgent` in `~/.pi/agent/cursor-sdk.json`; `/cursor-http` with no argument reports the effective state. Precedence is session command/history, explicit `PI_CURSOR_HTTP_1_1`, user config, then the built-in unset default; project config is ignored for this user-level compatibility choice. Unset performs no SDK configuration, preserving the existing default path. Session shutdown clears extension-owned SDK transport state before module reload. Changing the effective setting splits the local agent pool so an agent created under another transport is not reused. When enabled, the Cursor footer shows `http1` (for example `cursor · fast:on · http1`). This affects Cursor SDK local-agent backend streams, which are all provider streams. It does not configure HTTP proxies, TLS certificates, or HTTP/3.
416
397
 
@@ -423,7 +404,7 @@ For Cursor provider/runtime changes, the current fork release evidence bar is:
423
404
  - `npm test`
424
405
  - `npm run typecheck`
425
406
  - `npm pack --dry-run`
426
- - one live print-mode Cursor run with `cursor/grok-4.6:slow`
407
+ - one live print-mode Cursor run with `cursor/grok-4.6`
427
408
  - `npm run smoke:visual -- --label release-check --prompt 'Read ./package.json and reply with its package name.'`
428
409
 
429
410
  The visual smoke captures an offscreen PTY, renders it through browser/xterm, and saves PNG screenshots with Playwright or `agent_browser`. Its default matrix is native replay only: native replay registration is forced on, Cursor setting sources are disabled, the pi bridge is off, overlapping built-in pi tools are not exposed, and inherited Cursor SDK event-debug artifact env is cleared. The visible TUI/output, rendered screenshots, scrubbed diagnostics, and persisted JSONL must agree. See [Cursor live smoke checklist](docs/cursor-live-smoke-checklist.md) and [Cursor testing lessons](docs/cursor-testing-lessons.md).
@@ -442,7 +423,7 @@ See [Cursor testing lessons](docs/cursor-testing-lessons.md#cursor-sdk-event-cap
442
423
 
443
424
  If startup has no stored `/login` key or `CURSOR_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. Pi CLI `--api-key` remains available to provider turns but is not parsed independently during startup discovery.
444
425
 
445
- The fallback snapshot includes Grok 4.6, Composer 2.5 (`composer-2.5` and `composer-2-5`), Composer 2, Cursor's GPT-5.6 Luna/Sol/Terra models, Claude, Gemini, Grok 4.5, Kimi, and other model IDs exposed by the reviewed `Cursor.models.list()` output. Recommended local/smoke runs use `cursor/grok-4.6`. Pi's separate `openai-codex` catalog is owned by Pi itself; Pi 0.84.0 includes native `gpt-5.6-luna`, `gpt-5.6-sol`, and `gpt-5.6-terra` support. The exact checked-in Cursor snapshot lives in `src/cursor-fallback-models.generated.ts`. A dated maintainer capture documents the assistant-visible [Cursor system prompts and tool guidance](https://github.com/emmaneugene/pi-cursor-sdk/blob/main/docs/evidence/cursor-system-prompts-2026-08-02/README.md) for Grok 4.5, Opus 5, Fable 5, and the GPT-5.6 Sol/Terra/Luna family.
426
+ The fallback snapshot includes Grok 4.6, Composer 2.5, Composer 2, Cursor's GPT-5.6 Luna/Sol/Terra models, Claude, Gemini, Grok 4.5, Kimi, and other model IDs exposed by the reviewed `Cursor.models.list()` output. Recommended local/smoke runs use `cursor/grok-4.6`. Pi's separate `openai-codex` catalog is owned by Pi itself; Pi 0.84.0 includes native `gpt-5.6-luna`, `gpt-5.6-sol`, and `gpt-5.6-terra` support. The exact checked-in Cursor snapshot lives in `src/cursor-fallback-models.generated.ts`. A dated maintainer capture documents the assistant-visible [Cursor system prompts and tool guidance](https://github.com/emmaneugene/pi-cursor-sdk/blob/main/docs/evidence/cursor-system-prompts-2026-08-02/README.md) for Grok 4.5, Opus 5, Fable 5, and the GPT-5.6 Sol/Terra/Luna family.
446
427
 
447
428
  Actual Cursor runs still need a key from `/login`, `CURSOR_API_KEY`, or `--api-key`. If you add auth after startup, run `/cursor-refresh-models` to refresh the full live Cursor model catalog without restarting pi.
448
429
 
@@ -454,7 +435,7 @@ Actual Cursor runs still need a key from `/login`, `CURSOR_API_KEY`, or `--api-k
454
435
  - **Final assistant text is the last non-empty text part.** Composer responses can produce one assistant message with early progress `text`, thinking/tool metadata, and a later final `text` report. Consumers that need a final answer should scan assistant message content from the end and use the last non-empty `text` part, not the first. Cursor `thinking` deltas are shown as thinking traces when the SDK emits them; those traces can include draft answers or copied exact-output targets and are intentionally not collapsed by this extension.
455
436
  - **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 bootstrap logs (settings, skills, hook-load compatibility warnings, and similar) are suppressed so they do not pollute the TUI.
456
437
  - **AGENTS.md / CLAUDE.md are not duplicated on Cursor models when Cursor loads the same rules.** Pi discovers global and project context files (`AGENTS.md`, `CLAUDE.md`, and case variants) unless you start with `-nc`. On `cursor/*` models the extension removes only `<project_instructions>` blocks that overlap Cursor `settingSources` via the `before_agent_start` hook: `user` for `~/.pi/agent/AGENTS.md`, `project` for repo/parent `AGENTS.md` and `CLAUDE.md` (verified Cursor behavior: local agents load project `AGENTS.md` and `CLAUDE.md` alongside Cursor rules). `~/.pi/agent/CLAUDE.md` is not stripped (Cursor user rules use `~/.claude/CLAUDE.md`, not pi's agent dir). With `PI_CURSOR_SETTING_SOURCES=none` or `plugins`-only, pi context is left intact. Set `PI_CURSOR_PRESERVE_PI_AGENTS_MD=1` to keep duplicate injection.
457
- - **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.
438
+ - **Max Mode is not a manual pi variant.** Cursor's SDK may enable Max Mode automatically for models that require it. This extension uses each model's default Cursor context and cached checkpoint evidence for pi context accounting.
458
439
  - **Output token limits are conservative.** Cursor SDK model metadata does not currently expose output token limits directly.
459
440
  - **Local token usage uses Cursor SDK data when safely attributable.** For local turns with in-time SDK usage, pi records the latest per-turn raw `turn-ended` `inputTokens`, `outputTokens`, `cacheReadTokens`, and `cacheWriteTokens`; that raw local shape keeps full-prompt `inputTokens` with cache as a partition (published SDK `toTokenUsage` totals differ), so pi maps disjoint components (`input = inputTokens - cacheRead - cacheWrite`, plus cache fields) and sets `totalTokens = inputTokens + outputTokens` for occupancy/compaction. If the local SDK reports no usage in time, the extension falls back to local `input/output` activity estimates while setting `totalTokens` to the current replayable context estimate so the footer/compaction percentage does not collapse after split tool turns. Later usage for that live run is ignored rather than risk applying stale usage to the wrong pi turn. Cursor SDK cost is not exposed, so pi cost remains zero/absent.
460
441
 
@@ -490,7 +471,6 @@ All `PI_CURSOR_*` overrides in one list. They are otherwise documented where eac
490
471
  |---|---|---|
491
472
  | `PI_CURSOR_PI_TOOL_BRIDGE` | On | Master switch for exposing active pi tools to local Cursor agents through the loopback MCP bridge. `0` rolls back to Cursor SDK tools/settings/MCP only. |
492
473
  | `PI_CURSOR_EXPOSE_BUILTIN_TOOLS` | Off | Also exposes overlapping pi built-ins (`read`, `bash`, `write`, `edit`, `grep`, `find`, `ls`) that Cursor already implements natively. |
493
- | `PI_CURSOR_ASK_QUESTION` | On | Registers `cursor_ask_question` (surfaced as `pi__cursor_ask_question`) so Cursor can ask the user instead of guessing. Still requires a Cursor model with the bridge enabled. |
494
474
  | `PI_CURSOR_TOOL_MANIFEST` | On | Injects the compact callable-surface guidance block on bootstrap sends. |
495
475
  | `PI_CURSOR_MCP_TOOL_TIMEOUT_MS` / `PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS` | `3600000` (1h) | Overrides SDK MCP `callTool` timeout for bridged pi tools and configured Cursor MCP servers. Clamped to 60s–~24.8d. |
496
476
  | `PI_CURSOR_MCP_CONNECT_TIMEOUT_MS` / `PI_CURSOR_MCP_CONNECT_TIMEOUT_SECONDS` | `10000` (10s) | Overrides known MCP initialize/listTools timeouts on first send so dead servers fail fast. Clamped to 1s–60s. Unknown MCP protocol stacks keep the SDK default. |
@@ -1,18 +1,16 @@
1
1
  // Generated from Cursor SDK checkpoint tokenDetails.maxTokens on 2026-08-18.
2
2
  // Refresh with: npm run refresh:cursor-snapshots -- --write --context-windows ~/.pi/agent/cursor-sdk-context-windows.json
3
- // Keys are current selectable model IDs. Equivalent default :fast/:slow selections
4
- // collapse to one key; stale and ambiguous aliases are omitted. Values are observed
5
- // or conservative default/non-Max-mode limits and may override a catalog context
6
- // label when the completed SDK checkpoint reports a different effective limit.
3
+ // Keys are canonical Cursor catalog model IDs. Evidence for a model's default
4
+ // context is normalized to that ID; aliases, forced-speed variants, non-default
5
+ // contexts, and stale IDs are omitted.
7
6
  export const BUNDLED_CONTEXT_WINDOWS = {
8
- "default": 200000,
9
7
  "auto-smart": 200000,
10
- "claude-fable-5@300k": 300000,
11
8
  "claude-haiku-4-5": 200000,
12
9
  "claude-opus-4-5": 200000,
13
- "claude-opus-4-8@1m": 300000,
10
+ "claude-opus-4-8": 300000,
14
11
  "composer-2": 200000,
15
12
  "composer-2.5": 200000,
13
+ "default": 200000,
16
14
  "gemini-2.5-flash": 200000,
17
15
  "gemini-3-flash": 200000,
18
16
  "gemini-3.1-pro": 200000,
@@ -20,20 +18,15 @@ export const BUNDLED_CONTEXT_WINDOWS = {
20
18
  "gemini-3.6-flash": 200000,
21
19
  "gemini-3.7-flash": 200000,
22
20
  "glm-5.2": 200000,
23
- "gpt-5-5@272k": 272000,
24
21
  "gpt-5-mini": 272000,
25
22
  "gpt-5.1": 272000,
26
23
  "gpt-5.2": 272000,
27
24
  "gpt-5.3-codex": 272000,
28
25
  "gpt-5.4-mini": 272000,
29
26
  "gpt-5.4-nano": 272000,
30
- "gpt-5.5@272k": 272000,
31
- "gpt-5.6@1m": 272000,
27
+ "gpt-5.6-sol": 272000,
32
28
  "grok-4.5": 256000,
33
- "grok-4.5:slow": 256000,
34
29
  "grok-4.6": 256000,
35
30
  "kimi-k2.7-code": 200000,
36
31
  "kimi-k3": 200000,
37
- "opus-4.8@1m": 300000,
38
- "opus-4.8@300k": 300000,
39
32
  };
package/dist/context.js CHANGED
@@ -29,7 +29,6 @@ export function getCursorToolTailGuardText(options = {}) {
29
29
  }
30
30
  function getCursorToolBoundaryText(options = {}) {
31
31
  const includePiBridgeGuidance = options.includePiBridgeGuidance !== false;
32
- const includePiAskQuestionGuidance = includePiBridgeGuidance && options.includePiAskQuestionGuidance !== false;
33
32
  const lines = [
34
33
  "Cursor SDK tool boundary:",
35
34
  "Call only Cursor SDK/MCP tools exposed in this run; pi history names, replay labels, and transcript names are not callable.",
@@ -37,7 +36,6 @@ function getCursorToolBoundaryText(options = {}) {
37
36
  ? "For exposed pi bridge tools, call pi__* MCP names, not pi card/history names."
38
37
  : undefined,
39
38
  "Do not claim pi-side or WebSearch/WebFetch tools unless Cursor ran an equivalent tool.",
40
- includePiAskQuestionGuidance ? "Use pi__cursor_ask_question for material choices if exposed." : undefined,
41
39
  getCursorPlanModeToolGuidanceText(options.agentMode, { includePiBridgeGuidance }),
42
40
  "Images: only latest user images are sent; ask to reattach prior images.",
43
41
  ].filter((line) => line !== undefined);
@@ -311,7 +309,6 @@ export function buildCursorPrompt(context, options = {}) {
311
309
  agentMode: options.agentMode,
312
310
  hasToolManifest: Boolean(options.toolManifest),
313
311
  includePiBridgeGuidance: options.includePiBridgeGuidance,
314
- includePiAskQuestionGuidance: options.includePiAskQuestionGuidance,
315
312
  })];
316
313
  if (options.toolManifest) {
317
314
  sectionsBeforeMessages.push(options.toolManifest);
@@ -16,7 +16,6 @@ const TRUST_LEVELS = {
16
16
  project: "trusted-project",
17
17
  user: "user",
18
18
  session: "session",
19
- "model-alias": "model-catalog",
20
19
  builtin: "builtin",
21
20
  };
22
21
  function validateExplicitValue(raw, name, isValid, validValues) {
@@ -340,8 +339,6 @@ export function resolveCursorFastDefault(options) {
340
339
  return resolved("cli", false);
341
340
  if (options.cliForceFast)
342
341
  return resolved("cli", true);
343
- if (options.aliasOverride !== undefined)
344
- return resolved("model-alias", options.aliasOverride);
345
342
  if (options.sessionValue !== undefined)
346
343
  return resolved("session", options.sessionValue);
347
344
  if (options.userValue !== undefined)
@@ -11,6 +11,11 @@ function isPendingBridgeToolRequest(run, request) {
11
11
  const bridgeRun = [run.bridgeRun, run.sessionBridgeRun].find((candidate) => candidate?.id === request.runId);
12
12
  return bridgeRun?.hasPendingPiToolCallId(request.piToolCallId) === true;
13
13
  }
14
+ function hasPendingBridgeCalls(run) {
15
+ if (run.bridgeRun?.hasPendingCalls())
16
+ return true;
17
+ return run.sessionBridgeRun !== run.bridgeRun && run.sessionBridgeRun?.hasPendingCalls() === true;
18
+ }
14
19
  async function cancelCursorLiveSdkRun(run) {
15
20
  if (!run.sdkRun)
16
21
  return;
@@ -350,6 +355,12 @@ export function createCursorLiveRunCoordinator(deps) {
350
355
  return;
351
356
  state.idleDisposeRequested = false;
352
357
  state.idleDisposeTimer = setTimeout(() => {
358
+ if (hasPendingBridgeCalls(run)) {
359
+ // Bridge calls have their own deadline. Reuse the idle interval to avoid a hot
360
+ // polling loop if the SDK does not emit a terminal event after that deadline.
361
+ coordinator.requestIdleDispose(run);
362
+ return;
363
+ }
353
364
  void coordinator.release(run).catch(() => {
354
365
  // Idle dispose must not leave release failures as unhandled rejections.
355
366
  });
@@ -126,6 +126,9 @@ export class CursorPiToolBridgeRunImpl {
126
126
  async resolveToolResultsFromContext(context) {
127
127
  await this.resolveToolResults(context.messages.map(asToolResultMessage).filter((message) => message !== undefined));
128
128
  }
129
+ hasPendingCalls() {
130
+ return this.pendingCount() > 0;
131
+ }
129
132
  hasPendingPiToolCallId(piToolCallId) {
130
133
  return this.pendingByPiToolCallId.has(piToolCallId);
131
134
  }
@@ -93,7 +93,6 @@ async function prepareCursorLocalProviderTurn(prepareParams) {
93
93
  ...getCursorPromptOptions(model),
94
94
  agentMode,
95
95
  includePiBridgeGuidance,
96
- includePiAskQuestionGuidance: bridgeToolNames.has("pi__cursor_ask_question"),
97
96
  };
98
97
  if (plan.mode !== "bootstrap" || !resolveCursorToolManifestEnabled()) {
99
98
  return promptOptions;
@@ -102,15 +102,8 @@ function restoreSessionCursorHttp1(branch) {
102
102
  }
103
103
  }
104
104
  }
105
- function getFastPreferenceModelId(metadata) {
106
- return metadata.selectionModelId || metadata.baseModelId;
107
- }
108
- function getVirtualFastBaseModelId(modelId) {
109
- return modelId.replace(/:(?:fast|slow)$/, "");
110
- }
111
105
  function getMapFastPreference(map, metadata) {
112
- const preferenceModelId = getFastPreferenceModelId(metadata);
113
- return map.get(preferenceModelId) ?? (preferenceModelId !== metadata.baseModelId ? map.get(metadata.baseModelId) : undefined);
106
+ return map.get(metadata.piModelId);
114
107
  }
115
108
  function getEffectiveFast(modelId) {
116
109
  const metadata = getCursorModelMetadata(modelId);
@@ -119,8 +112,7 @@ function getEffectiveFast(modelId) {
119
112
  return resolveCursorFastDefault({
120
113
  cliForceNoFast,
121
114
  cliForceFast,
122
- aliasOverride: metadata.fastOverride,
123
- sessionValue: authoritativeGlobalFastPreferenceIds.has(getFastPreferenceModelId(metadata))
115
+ sessionValue: authoritativeGlobalFastPreferenceIds.has(metadata.piModelId)
124
116
  ? undefined
125
117
  : getMapFastPreference(sessionFastPreferences, metadata),
126
118
  userValue: getMapFastPreference(globalFastPreferences, metadata),
@@ -338,12 +330,7 @@ export function registerCursorRuntimeControls(pi) {
338
330
  ctx.ui.notify("Cursor fast is forced by --cursor-fast", "info");
339
331
  return;
340
332
  }
341
- if (metadata.fastOverride !== undefined) {
342
- const state = metadata.fastOverride ? "enabled" : "disabled";
343
- ctx.ui.notify(`Cursor fast is fixed ${state} by selected model ${metadata.piModelId}; choose ${getVirtualFastBaseModelId(metadata.piModelId)} to use /cursor-fast preferences`, "info");
344
- return;
345
- }
346
- const preferenceModelId = getFastPreferenceModelId(metadata);
333
+ const preferenceModelId = metadata.piModelId;
347
334
  const current = getEffectiveFast(metadata.piModelId) ?? false;
348
335
  const next = !current;
349
336
  let appendError;
package/dist/index.js CHANGED
@@ -3,7 +3,6 @@ import { discoverModels } from "./model-discovery.js";
3
3
  import { registerCursorRuntimeControls } from "./cursor-state.js";
4
4
  import { registerCursorNativeToolDisplay } from "./cursor-native-tool-display-registration.js";
5
5
  import { registerCursorPiToolBridge, registerNestedCursorPiToolBridge } from "./cursor-pi-tool-bridge.js";
6
- import { registerCursorQuestionTool } from "./cursor-question-tool.js";
7
6
  import { registerCursorSkillTool } from "./cursor-skill-tool.js";
8
7
  import { registerCursorSessionScope } from "./cursor-session-scope.js";
9
8
  import { registerCursorSessionAgentLifecycle } from "./cursor-session-agent-lifecycle.js";
@@ -91,7 +90,6 @@ export default async function (pi) {
91
90
  });
92
91
  registerCursorRuntimeControls(pi);
93
92
  registerCursorNativeToolDisplay(pi);
94
- registerCursorQuestionTool(pi);
95
93
  registerCursorSkillTool(pi);
96
94
  registerCursorPiToolBridge(pi);
97
95
  registerCursorAgentsContextDedup(pi);
@@ -88,58 +88,26 @@ function getDefaultParams(item) {
88
88
  const defaultVariant = item.variants.find((variant) => variant.isDefault) ?? item.variants[0];
89
89
  return cloneParams(defaultVariant?.params ?? []);
90
90
  }
91
- function replaceParam(params, id, value) {
92
- let replaced = false;
93
- const next = params.map((param) => {
94
- if (param.id !== id)
95
- return { ...param };
96
- replaced = true;
97
- return { id, value };
98
- });
99
- if (!replaced)
100
- next.push({ id, value });
101
- return next;
102
- }
103
91
  function getParamValue(params, id) {
104
92
  return params.find((param) => param.id === id)?.value;
105
93
  }
106
- function getModelName(item, context, alias, fastOverride) {
107
- const displayName = item.displayName || item.id;
108
- const qualifiers = [];
109
- if (alias)
110
- qualifiers.push(alias);
111
- if (fastOverride === true)
112
- qualifiers.push("fast");
113
- if (fastOverride === false)
114
- qualifiers.push("slow");
115
- const baseName = qualifiers.length > 0 ? `${displayName} (${qualifiers.join(", ")})` : displayName;
116
- return context ? `${baseName} @ ${context}` : baseName;
117
- }
118
- function getContextWindow(contextWindowCache, selectionKeys, context, baseModelId) {
119
- for (const key of new Set(selectionKeys)) {
120
- const contextWindow = contextWindowCache.get(key);
121
- if (contextWindow !== undefined)
122
- return contextWindow;
123
- }
124
- return ((context ? parseContextWindow(context) : undefined) ??
125
- (baseModelId ? contextWindowCache.get(baseModelId) : undefined) ??
94
+ function getContextWindow(contextWindowCache, modelId, defaultContext, contextWindowKey) {
95
+ return (contextWindowCache.get(modelId) ??
96
+ contextWindowCache.get(contextWindowKey) ??
97
+ (defaultContext ? parseContextWindow(defaultContext) : undefined) ??
126
98
  contextWindowCache.get("default") ??
127
99
  FALLBACK_CONTEXT_WINDOW);
128
100
  }
129
- function toMetadata(item, piModelId, selectionModelId, defaultParams, context, contextWindowCache, contextWindowKeys, fastOverride) {
101
+ function toMetadata(item, defaultParams, defaultContext, contextWindowCache, contextWindowKey) {
130
102
  const thinkingLevelMap = getThinkingLevelMap(item);
131
103
  const fastValue = getParamValue(defaultParams, "fast")?.toLowerCase();
132
104
  return {
133
- piModelId,
134
- baseModelId: item.id,
135
- selectionModelId,
105
+ piModelId: item.id,
136
106
  displayName: item.displayName || item.id,
137
107
  defaultParams: cloneParams(defaultParams),
138
- ...(context ? { context } : {}),
139
- contextWindow: getContextWindow(contextWindowCache, contextWindowKeys, context, item.id),
108
+ contextWindow: getContextWindow(contextWindowCache, item.id, defaultContext, contextWindowKey),
140
109
  supportsFast: getParameter(item, "fast") !== undefined,
141
110
  defaultFast: fastValue === "true",
142
- ...(fastOverride !== undefined ? { fastOverride } : {}),
143
111
  supportsReasoning: thinkingLevelMap !== undefined,
144
112
  ...(thinkingLevelMap ? { thinkingLevelMap } : {}),
145
113
  parameterIds: {
@@ -166,14 +134,10 @@ function toModelConfig(metadata, name) {
166
134
  function registerModelItems(items) {
167
135
  metadataByPiModelId.clear();
168
136
  const contextWindowCache = loadContextWindowCache();
169
- return getCursorModelSelectionIdentities(items).map(({ model: item, selectionModelId, context, fastOverride, piModelId, contextWindowKey, baseContextWindowKey }) => {
170
- const defaultParams = getDefaultParams(item);
171
- const contextParams = context ? replaceParam(defaultParams, "context", context) : defaultParams;
172
- const params = fastOverride === undefined ? contextParams : replaceParam(contextParams, "fast", fastOverride ? "true" : "false");
173
- const metadata = toMetadata(item, piModelId, selectionModelId, params, context, contextWindowCache, [piModelId, contextWindowKey, baseContextWindowKey], fastOverride);
137
+ return getCursorModelSelectionIdentities(items).map(({ model: item, piModelId, defaultContext, contextWindowKey }) => {
138
+ const metadata = toMetadata(item, getDefaultParams(item), defaultContext, contextWindowCache, contextWindowKey);
174
139
  metadataByPiModelId.set(piModelId, metadata);
175
- const alias = selectionModelId === item.id ? undefined : selectionModelId;
176
- return toModelConfig(metadata, getModelName(item, context, alias, fastOverride));
140
+ return toModelConfig(metadata, metadata.displayName);
177
141
  });
178
142
  }
179
143
  export function getCursorModelMetadata(modelId) {
@@ -239,7 +203,7 @@ export function buildCursorModelSelection(modelId, thinkingLevel, fastEnabled) {
239
203
  if (metadata.supportsFast && fastEnabled !== undefined) {
240
204
  setParam(params, "fast", fastEnabled ? "true" : "false");
241
205
  }
242
- return params.length > 0 ? { id: metadata.selectionModelId, params } : { id: metadata.selectionModelId };
206
+ return params.length > 0 ? { id: metadata.piModelId, params } : { id: metadata.piModelId };
243
207
  }
244
208
  function sanitizeDiscoveryError(error, apiKey) {
245
209
  const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
@@ -15,7 +15,7 @@ Short maintainer checklist for **minimal-surface** validation after prompt, brid
15
15
  ## One-turn exercise
16
16
 
17
17
  1. **Native Cursor host tool** — one `read` or `shell` call (Cursor SDK host tools; not listed in MCP `listTools`).
18
- 2. **Pi bridge** (if enabled) — one bridged call via exposed `pi__*` MCP name, e.g. `pi__cursor_ask_question` when active.
18
+ 2. **Pi bridge** (if enabled) — one bridged call via exposed `pi__*` MCP name, e.g. `pi__cursor_activate_skill` when active.
19
19
  3. **Configured MCP** (optional) — only when you intentionally load Cursor MCP via settings; skip for minimal baseline.
20
20
 
21
21
  `pi --no-tools` is a pi-registry toggle, not a Cursor SDK host-tool kill switch. In dogfood, expect it to remove pi bridge exposure while Cursor host tools can still run.
@@ -387,10 +387,9 @@ Everything in this section is in scope when using this checklist for Cursor prov
387
387
 
388
388
  - Long-running bridged tool abort/cancel cleanup.
389
389
  - Native replay cards beyond read, especially shell/edit/write cards, when those renderers change.
390
- - Bridge question UI when `cursor_ask_question` changes.
391
390
  - MCP timeout override behavior (3600s `callTool` default, 10s initialize/listTools default, and SDK-default unknown protocol stacks) when timeout code changes.
392
391
  - SDK `semSearch` / `recordScreen` activity replay when those formatters change. There is no reliable local prompt that forces Cursor to call these built-in SDK tools on demand; regression is covered by `test/cursor-tool-transcript.test.ts`. Opportunistically confirm neutral `Cursor semantic search` / `Cursor screen recording` cards if a live run surfaces them.
393
392
  - Ambient Cursor setting-source behavior when startup filtering or local Cursor settings handling changes.
394
- - Model discovery aliases/context variants when model-discovery code or Cursor SDK versions change.
393
+ - Canonical model registration and default parameters when model-discovery code or Cursor SDK versions change.
395
394
 
396
395
  If any changed surface has no adequate focused live check, add that coverage before release instead of assuming mocks cover reality.