pi-cursor-sdk 0.1.55 → 0.1.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.1.56 - 2026-07-04
4
+
5
+ ### Changed
6
+
7
+ - Upgrade the pinned Cursor SDK runtime dependency to exact `@cursor/sdk@1.0.23` after auditing the 1.0.22 → 1.0.23 package/type/docs delta.
8
+ - Bump the local pi validation baseline (`@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, `@earendil-works/pi-tui`) to `0.80.3` after reviewing the current installed Pi 0.80.3 changelog.
9
+
10
+ ### Fixed
11
+
12
+ - Preserve Cursor SDK `RunError` terminal failure details and codes when formatting failed run diagnostics.
13
+
14
+ ### Validation
15
+
16
+ - `npm run typecheck`, `npm test`, `npm run check:platform-smoke`, `npm pack --dry-run`, and `npm run smoke:platform:all` pass locally.
17
+ - Thermo-nuclear subagent release review signed off with no behavior, TUI/visual, package-baseline, or maintainability blockers.
4
18
 
5
19
  ## 0.1.55 - 2026-07-02
6
20
 
package/README.md CHANGED
@@ -51,10 +51,10 @@ If pi started without a key, run `/cursor-refresh-models` after `/login` to refr
51
51
  ## Requirements
52
52
 
53
53
  - Node.js 22.19+
54
- - pi 0.80.2 or newer recommended; pi core peer metadata is intentionally unpinned so newer pi releases are not blocked
54
+ - pi 0.80.3 or newer recommended; pi core peer metadata is intentionally unpinned so newer pi releases are not blocked
55
55
  - a Cursor SDK API key saved through `/login`, available as `CURSOR_API_KEY`, or passed with pi's `--api-key`
56
56
 
57
- No global `@cursor/sdk` install is required. This package depends on exact `@cursor/sdk@1.0.22`, so normal package installation brings in the SDK version this extension was built and tested against. Cursor SDK 1.0.22 declares its Node ConnectRPC transport dependency directly, so npm installs place `@connectrpc/connect-node` where the SDK can resolve it. The extension intentionally does not bundle `@cursor/sdk` or its platform packages, because packing from one maintainer OS can otherwise ship the wrong optional SDK binary for another OS. Cursor SDK 1.0.22 keeps the older `sqlite3 -> node-gyp@8` dependency chain out of the runtime tree, so deprecated install warnings for `inflight`, `rimraf`, `glob@7`, `npmlog`, `gauge`, `are-we-there-yet`, and `tar@6` from that chain are not expected. This package follows pi package guidance by declaring pi core package peers with `"*"` ranges, so users who update pi before this extension is republished are not blocked by peer metadata. The current recommended and validated pi baseline is 0.80.2 plus Cursor SDK 1.0.22; older pi compatibility paths are best-effort and older Cursor SDK compatibility paths are not maintained.
57
+ No global `@cursor/sdk` install is required. This package depends on exact `@cursor/sdk@1.0.23`, so normal package installation brings in the SDK version this extension was built and tested against. Cursor SDK 1.0.23 declares its Node ConnectRPC transport dependency directly, so npm installs place `@connectrpc/connect-node` where the SDK can resolve it. The extension intentionally does not bundle `@cursor/sdk` or its platform packages, because packing from one maintainer OS can otherwise ship the wrong optional SDK binary for another OS. Cursor SDK 1.0.23 keeps the older `sqlite3 -> node-gyp@8` dependency chain out of the runtime tree, so deprecated install warnings for `inflight`, `rimraf`, `glob@7`, `npmlog`, `gauge`, `are-we-there-yet`, and `tar@6` from that chain are not expected. This package follows pi package guidance by declaring pi core package peers with `"*"` ranges, so users who update pi before this extension is republished are not blocked by peer metadata. The current recommended and validated pi baseline is 0.80.3 plus Cursor SDK 1.0.23; older pi compatibility paths are best-effort and older Cursor SDK compatibility paths are not maintained.
58
58
 
59
59
  ## Install
60
60
 
@@ -67,8 +67,8 @@ The replay scan flags only error `toolResult` / error assistant messages with `T
67
67
 
68
68
  Pass criteria:
69
69
 
70
- - `pi --version` reports pi 0.80.2 for this cutover baseline.
71
- - `npm ls` shows `@cursor/sdk@1.0.22` and local `@earendil-works/*@0.80.2` packages.
70
+ - `pi --version` reports pi 0.80.3 for this cutover baseline.
71
+ - `npm ls` shows `@cursor/sdk@1.0.23` and local `@earendil-works/*@0.80.3` packages.
72
72
  - `cursor/composer-2-5` appears in the model list.
73
73
  - No Cursor key or auth token is printed.
74
74
  - If neither `~/.pi/agent/auth.json` cursor auth nor `CURSOR_API_KEY` is available, stop and report the live smoke as blocked.
@@ -125,7 +125,7 @@ Observe with `tmux capture-pane -pt "$SESSION"` or attach manually.
125
125
  Pass criteria:
126
126
 
127
127
  - Footer shows `(cursor) composer-2-5`. With `--cursor-no-fast`, Cursor fast mode is off and the Cursor extension status should show `cursor-fast:off`; ignore unrelated status text from other extensions.
128
- - The run uses pi 0.80.2 `--session-id` successfully.
128
+ - The run uses pi 0.80.3 `--session-id` successfully.
129
129
  - Assistant answer appears correctly.
130
130
  - `/session` shows one user and one assistant message for the simple run.
131
131
  - Persisted JSONL has one assistant message. If the screen appears duplicated, inspect JSONL before deciding whether it is a rendering bug.
@@ -133,7 +133,7 @@ Pass criteria:
133
133
 
134
134
  ## 4. Focused visual card/color rendering check
135
135
 
136
- This is the canonical inner-loop visual debug path for Cursor provider/runtime changes. It requires offscreen TUI visual inspection, not only JSONL or code review. Use pi 0.80.2, `@cursor/sdk@1.0.22`, a fresh temporary session dir, Cursor SDK `plan` mode, native replay enabled, and the checked-in visual runner. The runner resolves `pi` by directly walking the parent `PATH`, uses `process.execPath` for Node, and prepends that Node directory for both prereq checks and tmux launches so `#!/usr/bin/env node` shims use the validated Node. The default matrix is native replay only: native replay registration is forced on, settings sources are `none`, the pi bridge is off, overlapping built-in pi tools are not exposed, and inherited Cursor SDK event-debug artifact env is cleared. With `--event-debug`, debug capture writes to a deterministic directory under `VISUAL_DIR`.
136
+ This is the canonical inner-loop visual debug path for Cursor provider/runtime changes. It requires offscreen TUI visual inspection, not only JSONL or code review. Use pi 0.80.3, `@cursor/sdk@1.0.23`, a fresh temporary session dir, Cursor SDK `plan` mode, native replay enabled, and the checked-in visual runner. The runner resolves `pi` by directly walking the parent `PATH`, uses `process.execPath` for Node, and prepends that Node directory for both prereq checks and tmux launches so `#!/usr/bin/env node` shims use the validated Node. The default matrix is native replay only: native replay registration is forced on, settings sources are `none`, the pi bridge is off, overlapping built-in pi tools are not exposed, and inherited Cursor SDK event-debug artifact env is cleared. With `--event-debug`, debug capture writes to a deterministic directory under `VISUAL_DIR`.
137
137
 
138
138
  ```bash
139
139
  VISUAL_DIR="$(mktemp -d /tmp/pi-cursor-sdk-1016-visual.XXXXXX)"
@@ -15,7 +15,7 @@ Current implementation notes:
15
15
  - Cursor status uses one coordinated `ctx.ui.setStatus("cursor", ...)` value for fast and non-default plan mode; the default pi footer remains intact.
16
16
  - Installed `@cursor/sdk` user messages accept images, and Cursor models are treated as image-capable; registered input metadata is `text` plus `image`.
17
17
  - 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.
18
- - Exact `@cursor/sdk@1.0.22` is a package dependency of this extension; users should not need a global SDK install. pi 0.80.2 is the current recommended validation baseline, while published pi core peer dependencies use `"*"` ranges per current pi package guidance. Newer pi versions are allowed to attempt loading this extension before a matching extension release exists; compatibility is best-effort until validated.
18
+ - Exact `@cursor/sdk@1.0.23` is a package dependency of this extension; users should not need a global SDK install. pi 0.80.3 is the current recommended validation baseline, while published pi core peer dependencies use `"*"` ranges per current pi package guidance. Newer pi versions are allowed to attempt loading this extension before a matching extension release exists; compatibility is best-effort until validated.
19
19
  - 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.
20
20
  - 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 bootstrap stdout/stderr/console noise (including late first-send workspace loading such as hook compatibility warnings) so it does not pollute pi's TUI.
21
21
  - On `cursor/*` models, pi-cursor-sdk removes only pi-generated `<project_instructions>` blocks that overlap the effective Cursor `settingSources`: `user` for `~/.pi/agent/AGENTS.md`; `project` for discovered repo/parent `AGENTS.md` and `CLAUDE.md` (verified Cursor behavior: local agents load project `AGENTS.md` and `CLAUDE.md`). `~/.pi/agent/CLAUDE.md` is not removed (Cursor user layer uses `~/.claude/CLAUDE.md`). Blocks are removed by exact pi serialization match from structured `contextFiles` via the `before_agent_start` hook, not in `buildCursorPrompt` sanitization. Suppression is skipped with `-nc`, `PI_CURSOR_SETTING_SOURCES=none`, narrowed sources such as `plugins` that omit the matching layer, or `PI_CURSOR_PRESERVE_PI_AGENTS_MD=1`. Switching away from a Cursor model restores pi's full context block on the next user message.
@@ -26,17 +26,17 @@ Current implementation notes:
26
26
  - Prompt text is the primary provider/bridge contract. Bootstrap prompts carry a short boundary block plus the callable-surface manifest by default (`PI_CURSOR_TOOL_MANIFEST=1`). MCP `listTools` descriptions use a one-line pointer to the bootstrap prompt instead of repeating the full contract (`buildCursorPiBridgeMcpToolDescription()`). Cursor must call the exposed `pi__*` MCP name, not the real pi tool name shown in pi history or transcripts. Pi emits and executes the real pi tool name. Maintainer debug: `/cursor-tools` prints bridge/manifest enablement, effective `PI_CURSOR_SETTING_SOURCES`, and the current callable-surface snapshot.
27
27
  - 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. When pi has visible Agent Skills loaded, the provider rewrites the skill catalog for Cursor and registers `cursor_activate_skill` as `pi__cursor_activate_skill`; pi executes it through the normal tool path so Cursor can load the full `SKILL.md` and skill resource list for the current pi-loaded skill source of truth. `PI_CURSOR_PI_TOOL_BRIDGE=0` disables the local bridge, including question and skill activation bridging. Cloud Cursor agents remain out of scope for the bridge.
28
28
  - The bridge queues MCP calls, emits provider `toolcall_*` events, waits for matching pi `toolResult` messages by `toolCallId`, resolves the result back into the same live Cursor SDK run without creating a new `Agent`, and never calls tool `execute()` handlers directly. The same-run resume invariant holds unless the run was disposed, aborted, or cancelled.
29
- - Cursor SDK MCP tool calls use a guarded timeout override because installed `@cursor/sdk` 1.0.22 still has a 60-second MCP request default with no public per-server timeout option. The extension extends the verified Cursor SDK MCP `callTool` timeout path to 3600 seconds by default and shortens the verified first-send MCP initialize/listTools timeout paths to 10 seconds by default so unavailable configured MCP servers do not block the first reply for a full minute; unknown MCP protocol timeout stacks keep the SDK default. Users can override tool-call timeouts with `PI_CURSOR_MCP_TOOL_TIMEOUT_MS` or `PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS`, and initialize/listTools timeouts with `PI_CURSOR_MCP_CONNECT_TIMEOUT_MS` or `PI_CURSOR_MCP_CONNECT_TIMEOUT_SECONDS`.
29
+ - Cursor SDK MCP tool calls use a guarded timeout override because installed `@cursor/sdk` 1.0.23 still has a 60-second MCP request default with no public per-server timeout option. The extension extends the verified Cursor SDK MCP `callTool` timeout path to 3600 seconds by default and shortens the verified first-send MCP initialize/listTools timeout paths to 10 seconds by default so unavailable configured MCP servers do not block the first reply for a full minute; unknown MCP protocol timeout stacks keep the SDK default. Users can override tool-call timeouts with `PI_CURSOR_MCP_TOOL_TIMEOUT_MS` or `PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS`, and initialize/listTools timeouts with `PI_CURSOR_MCP_CONNECT_TIMEOUT_MS` or `PI_CURSOR_MCP_CONNECT_TIMEOUT_SECONDS`.
30
30
  - Bridge diagnostics are opt-in only: `PI_CURSOR_PI_TOOL_BRIDGE_DEBUG=1` writes typed, allowlisted, scrubbed single-line JSONL records to `process.stderr` with prefix `[pi-cursor-sdk:bridge]`. Diagnostics are scrubbed operational logs, not anonymous telemetry. They intentionally include tool names, safe correlation IDs, run lifecycle, exposed pi↔MCP name pairs, queued requests, result resolution, rejection, cancellation, and pending counts. Correlation IDs are generated independently from the tokenized endpoint path, and Cursor MCP call IDs are hashed before serialization. Diagnostics must not include endpoint paths/URLs/path components/tokens, API keys, bearer tokens, cookies, session credentials, raw args/results, stdout/stderr payloads, file contents, Cursor settings output, or local private session paths in tracked docs, and they must not call pi UI status, notification, or footer APIs. If tool names themselves are unacceptable for a release target, bridge debug diagnostics are not safe for shared logs under the current contract.
31
31
  - This repo does not provide a generic desktop-automation, browser-driver, or CDP recipe. Provider docs should describe pi-cursor-sdk's Cursor provider/bridge contract only.
32
- - Cursor internal tool activity is recorded from SDK events and scrubbed. Maintainer reference for all 16 `@cursor/sdk@1.0.22` `ToolType` values, runtime alias normalization, and intentional mapping/fallback rules: [Cursor native tool replay — SDK ToolType replay matrix](./cursor-native-tool-replay.md#sdk-tooltype-replay-matrix) (official SDK docs: https://cursor.com/docs/sdk/typescript). In TUI sessions and structured JSON/RPC modes, supported completed `read`, `bash`, `grep`, `find`, `ls`, `edit`, `write`, diagnostics, delete, todo/plan, task, image generation, MCP, semantic search, and screen recording activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so users and JSON/RPC consumers can see native-looking cards/events 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/subagent, image, and MCP activity use neutral Cursor activity cards with pi's default success/error shell. Cursor SDK `task` activity is labeled **Cursor subagent** by default because it represents Cursor-spawned child-agent work; the card summary includes description plus subagent kind/model/short ID when Cursor reports them, and `PI_CURSOR_TASK_PRESENTATION=task` restores the older **Cursor task** wording for comparison. This is visibility over Cursor SDK task events, not a native pi subagent session: pi shows start/final output plus any `conversationSteps` tool-call summaries Cursor returns, but cannot show a live nested read/shell/MCP trail when the SDK only returns final subagent text. Neutral Cursor activity calls include `activityTitle` and, when available, `activitySummary` so partial/collapsed cards preserve identity such as `Cursor plan`, `Cursor todos`, `Cursor subagent`, `Cursor MCP`, or `Cursor edit`. For long-running or externally meaningful Cursor tools (`task`, `shell`, `mcp`, `generateImage`, `recordScreen`, `semSearch`, web search/fetch, plan/todo), the provider may surface one low-noise deferred in-progress thinking line such as `Cursor MCP: external_search` from bounded, scrubbed SDK args; fast local tools (`read`, `grep`, `glob`, and similar) skip lifecycle lines when completion follows immediately, and pi bridge MCP calls are excluded because pi already shows real pi tool execution ([lifecycle visibility](./cursor-native-tool-replay.md#low-noise-tool-lifecycle-visibility)). 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 mode/task/todo/plan activity 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 surfaced with bounded user-visible labels/traces (neutral activity cards when native replay routing allows, otherwise the same inactive or transcript trace fallbacks used for completed replay) instead of being silently discarded when the run failed/aborted, produced no assistant text, or involved external/side-effectful tools; incomplete fast local discovery starts (`read`, `grep`, `glob`, `ls`) remain maintainer-debug-only after successful text-producing runs so stale SDK start events do not create red post-answer cards. Explicit failures remain visible when Cursor reports them through completed tool calls or step results. Pi bridge MCP starts remain excluded from duplicate incomplete Cursor cards because pi already shows real pi tool execution. `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 also shown as bounded live progress while one shell call is active and used as display-only fallback for empty successful shell completions, while overlapping shell calls drop ambiguous deltas instead of guessing. Print mode keeps bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when no live-run turn split is active.
32
+ - Cursor internal tool activity is recorded from SDK events and scrubbed. Maintainer reference for `@cursor/sdk@1.0.23` `ToolType` values, runtime alias normalization, and intentional mapping/fallback rules: [Cursor native tool replay — SDK ToolType replay matrix](./cursor-native-tool-replay.md#sdk-tooltype-replay-matrix) (official SDK docs: https://cursor.com/docs/sdk/typescript). In TUI sessions and structured JSON/RPC modes, supported completed `read`, `bash`, `grep`, `find`, `ls`, `edit`, `write`, diagnostics, delete, todo/plan, task, image generation, MCP, semantic search, and screen recording activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so users and JSON/RPC consumers can see native-looking cards/events 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/subagent, image, and MCP activity use neutral Cursor activity cards with pi's default success/error shell. Cursor SDK `task` activity is labeled **Cursor subagent** by default because it represents Cursor-spawned child-agent work; the card summary includes description plus subagent kind/model/short ID when Cursor reports them, and `PI_CURSOR_TASK_PRESENTATION=task` restores the older **Cursor task** wording for comparison. This is visibility over Cursor SDK task events, not a native pi subagent session: pi shows start/final output plus any `conversationSteps` tool-call summaries Cursor returns, but cannot show a live nested read/shell/MCP trail when the SDK only returns final subagent text. Neutral Cursor activity calls include `activityTitle` and, when available, `activitySummary` so partial/collapsed cards preserve identity such as `Cursor plan`, `Cursor todos`, `Cursor subagent`, `Cursor MCP`, or `Cursor edit`. For long-running or externally meaningful Cursor tools (`task`, `shell`, `mcp`, `generateImage`, `recordScreen`, `semSearch`, web search/fetch, plan/todo), the provider may surface one low-noise deferred in-progress thinking line such as `Cursor MCP: external_search` from bounded, scrubbed SDK args; fast local tools (`read`, `grep`, `glob`, and similar) skip lifecycle lines when completion follows immediately, and pi bridge MCP calls are excluded because pi already shows real pi tool execution ([lifecycle visibility](./cursor-native-tool-replay.md#low-noise-tool-lifecycle-visibility)). 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 mode/task/todo/plan activity 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 surfaced with bounded user-visible labels/traces (neutral activity cards when native replay routing allows, otherwise the same inactive or transcript trace fallbacks used for completed replay) instead of being silently discarded when the run failed/aborted, produced no assistant text, or involved external/side-effectful tools; incomplete fast local discovery starts (`read`, `grep`, `glob`, `ls`) remain maintainer-debug-only after successful text-producing runs so stale SDK start events do not create red post-answer cards. Explicit failures remain visible when Cursor reports them through completed tool calls or step results. Pi bridge MCP starts remain excluded from duplicate incomplete Cursor cards because pi already shows real pi tool execution. `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 also shown as bounded live progress while one shell call is active and used as display-only fallback for empty successful shell completions, while overlapping shell calls drop ambiguous deltas instead of guessing. Print mode keeps bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when no live-run turn split is active.
33
33
  - Cursor native replay uses one neutral replay tool name, `cursor`, plus native-compatible card names when renderer-compatible (`read`, `bash`, `grep`, `find`, `ls`, `edit`, `write`). Neutral replay identity lives in `activityTitle`, `activitySummary`, and typed replay details, not in extra registered tool names. Bridge MCP names such as `pi__sem_reindex` are MCP-only; pi session output uses real pi tool names.
34
34
  - Cursor SDK usage events are used when the SDK reports them before the corresponding pi turn is emitted. For each SDK-attributed assistant turn, `usage.input`, `usage.output`, `usage.cacheRead`, and `usage.cacheWrite` come from the latest per-turn SDK usage; `usage.totalTokens = latestTurn.inputTokens + latestTurn.outputTokens` so cache reads do not inflate context occupancy. Cumulative `RunResult.usage` is never used for per-message occupancy. If the SDK reports no usage in time, the provider falls back to local `input/output` activity estimates while setting `usage.totalTokens` to the current replayable context estimate so footer/compaction context does not collapse after split tool turns; after a split live-run turn times out waiting for SDK usage, later SDK usage for that live run is ignored rather than risk applying stale usage to the wrong pi turn. Cursor SDK cost is unavailable, so cost remains absent/zero. `src/cursor-usage-accounting.ts` owns this policy.
35
35
  - Audit observation, 2026-05-19, superseded by the 2026-05-21 replay pass and #68 incomplete visibility, then narrowed by the 2026-05-26 fast-local suppression: 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 external/side-effectful started calls are surfaced at run completion through the same native replay routing as completed tools (activity cards when allowed, otherwise inactive/transcript traces), while fast local discovery starts are debug-only after a successful text-producing run. Cursor-reported completed/step errors remain visible.
36
36
  - 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.
37
37
  - Cursor provider/runtime releases must pass the [Platform Smoke Gate](./platform-smoke.md): `npm run smoke:platform:doctor && npm run smoke:platform:all`. Use [Cursor Live Smoke Checklist](./cursor-live-smoke-checklist.md) only for focused inner-loop/debug runs with real `pi --approve -e . --cursor-no-fast --model cursor/composer-2-5` invocations, manual observation, temporary session dirs, diagnostics scans, and persisted JSONL inspection. See [Cursor testing lessons](./cursor-testing-lessons.md) for auth.json seeding, isolated smoke harnesses, and replay JSONL scans. Assume every runtime surface is in scope.
38
38
  - 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.
39
- - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.22 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.
39
+ - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.23 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.
40
40
  - The installed `@cursor/sdk` exposes 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`. Cursor-only fast preferences are keyed by the selected SDK model ID/alias, with read fallback for older preferences keyed by 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.
41
41
  - Session-scoped Cursor SDK agent pooling reuses one live `@cursor/sdk` agent across compatible follow-up turns within the same pi session scope. `planCursorSessionSend()` in `src/cursor-session-send-policy.ts` decides whether the next turn sends a full bootstrap prompt or an incremental follow-up, whether the SDK agent must be recreated, and why. `computeCursorContextFingerprint()` and `shouldBootstrapCursorContext()` remain the context-only bootstrap signal. The pool recreates the agent when context diverges, when branch or compaction summaries appear after `/tree` navigation or compaction, after 20 completed incremental sends, when the API key identity changes, after send errors, on `session_shutdown`, and when `session_before_tree` / `session_tree` invalidate the active branch. Incremental sends omit the full Cursor SDK tool boundary block because the session agent retains prior bootstrap context, but every send ends with a short tool tail guard placed after the latest user request (including an explicit shell `cd` hint).
42
42
  - Pi steering/follow-up delivery can arrive while a split live Cursor SDK run is still active. The provider resolves pending live runs by scanning trailing `toolResult` messages while skipping trailing `user` messages, tracks the active live run per session scope, and resumes the in-flight run instead of calling `Agent.send()` again. When the context ends with steering user text after tool results, the provider releases the prior live run and chains an incremental `Agent.send()` for the latest user message in the same provider turn; if the prior run emits more text or tool requests after steering arrives, that stale activity is cancelled instead of surfacing another old-run tool turn and losing the new user input. A pre-send guard waits for or resumes any still-active scoped live run before starting a fresh send so `@cursor/sdk` `AgentBusyError` (`already has active run`) does not surface to pi users. Pooled session agents mark busy as soon as live/direct `run.wait()` tracking starts (`trackRunCompletion` on the session lease), and `acquireSessionCursorAgent()` awaits that busy state before returning a lease so send planning, transcript offsets, and later `Agent.send()` do not race the prior turn's SDK run completion (for example pi auto-compaction summarization). `session_before_compact` calls `prepareCursorSessionForCompaction()` to release scoped live-run drain state and reset the pooled agent before summarization streams. Tracked completions and send commits are scoped to the pooled agent `instanceId` so disposal/replacement drops stale tracking and ignores late commits from disposed agents.
@@ -62,13 +62,13 @@ When Cursor reports completed tool activity, the extension can display recorded
62
62
 
63
63
  Cursor `glob` activity is displayed through native `find` cards.
64
64
 
65
- For the full `@cursor/sdk@1.0.22` `ToolType` set, disposition matrix, and runtime alias normalization, see [SDK ToolType replay matrix](#sdk-tooltype-replay-matrix) below. Official SDK reference: https://cursor.com/docs/sdk/typescript
65
+ For the full `@cursor/sdk@1.0.23` `ToolType` set, disposition matrix, and runtime alias normalization, see [SDK ToolType replay matrix](#sdk-tooltype-replay-matrix) below. Official SDK reference: https://cursor.com/docs/sdk/typescript
66
66
 
67
67
  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/subagent, image, MCP, semantic search, screen recording, and web search/fetch activity use neutral Cursor activity cards with pi's default success/error tool shell. Cursor SDK `task` activity is labeled **Cursor subagent** by default because it represents Cursor-spawned child-agent work; `PI_CURSOR_TASK_PRESENTATION=task` keeps the older **Cursor task** wording for comparison. MCP completions whose `toolName` is `WebSearch` / `web_search` / `WebFetch` / similar are labeled **Cursor web search** or **Cursor web fetch** instead of generic **Cursor MCP**. Neutral Cursor activity cards carry display metadata such as `activityTitle` and `activitySummary`, so partial/collapsed cards can say `Cursor plan`, `Cursor todos`, `Cursor subagent`, `Cursor MCP`, `Cursor semantic search`, `Cursor screen recording`, `Cursor web search`, `Cursor web fetch`, 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. Most collapsed replay cards include bounded previews for diffs and text details so small edits, todos, task output, and MCP results are visible without expanding; web search/fetch activity stays summary-only while collapsed because those cards often arrive after final text and can otherwise bury the answer. Ctrl+O expansion shows the recorded details. 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.
68
68
 
69
69
  ## SDK ToolType replay matrix
70
70
 
71
- Source of truth for SDK tool names: `@cursor/sdk@1.0.22` conversation `ToolType` values and https://cursor.com/docs/sdk/typescript
71
+ Source of truth for SDK tool names: `@cursor/sdk@1.0.23` conversation `ToolType` values and https://cursor.com/docs/sdk/typescript
72
72
 
73
73
  Implementation owners: `src/cursor-tool-presentation-registry.ts` (canonical names, labels, visibility, replay policy, bridge exclusions for internal replay wrappers, alias normalization, and display-spec key completeness), `src/cursor-transcript-tool-specs.ts` (registry-keyed display implementations for transcript formatting and pi display builders), `src/cursor-native-tool-display-replay.ts` (replay card rendering derived from registry replay metadata), and `src/cursor-web-tool-activity.ts` (MCP/web alias remapping before display lookup).
74
74
 
@@ -6,7 +6,7 @@ This workflow is the canonical repo path for verifying Cursor SDK tool replay th
6
6
 
7
7
  Use it before accepting replay-card commits or PRs, and for every Cursor provider/runtime release where TUI card/color behavior could regress. Text logs and JSONL are necessary, but they are not enough when the claim is visual parity: always keep PNGs for the exact prompt, and keep before/after PNGs when reviewing a rendering change.
8
8
 
9
- Current validation baseline: pi 0.80.2, exact `@cursor/sdk@1.0.22`, local validation packages `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` at 0.80.2. Published pi core peer dependencies use `"*"` ranges per current pi package guidance, so newer pi installs can try the extension before a matching validation release exists.
9
+ Current validation baseline: pi 0.80.3, exact `@cursor/sdk@1.0.23`, local validation packages `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` at 0.80.3. Published pi core peer dependencies use `"*"` ranges per current pi package guidance, so newer pi installs can try the extension before a matching validation release exists.
10
10
 
11
11
  ## Cursor SDK 1.0.17 / pi 0.79.0 cutover visual record
12
12
 
@@ -243,7 +243,7 @@ The script writes timestamped artifacts under `--out` (default `/tmp/pi-cursor-s
243
243
 
244
244
  Stdout prints artifact paths and summary counts only. Raw payloads stay on disk and may contain local paths, project text, tool args/results, or secrets — do not commit or share them.
245
245
 
246
- Hard repo rule: Cursor SDK behavior claims must come from the installed `@cursor/sdk` package and/or https://cursor.com/docs/sdk/typescript, not from memory or ad-hoc probes alone. Current cutover validation targets exact `@cursor/sdk@1.0.22` and pi 0.80.2 local packages.
246
+ Hard repo rule: Cursor SDK behavior claims must come from the installed `@cursor/sdk` package and/or https://cursor.com/docs/sdk/typescript, not from memory or ad-hoc probes alone. Current cutover validation targets exact `@cursor/sdk@1.0.23` and pi 0.80.3 local packages.
247
247
 
248
248
  ## Pi provider SDK event capture
249
249
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cursor-sdk",
3
- "version": "0.1.55",
3
+ "version": "0.1.56",
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",
@@ -109,7 +109,7 @@
109
109
  "smoke:platform:all": "npm run smoke:platform:doctor && node scripts/platform-smoke.mjs run --target macos,ubuntu,windows-native"
110
110
  },
111
111
  "dependencies": {
112
- "@cursor/sdk": "1.0.22",
112
+ "@cursor/sdk": "1.0.23",
113
113
  "@modelcontextprotocol/sdk": "^1.29.0"
114
114
  },
115
115
  "peerDependencies": {
@@ -119,9 +119,9 @@
119
119
  "typebox": "*"
120
120
  },
121
121
  "devDependencies": {
122
- "@earendil-works/pi-ai": "0.80.2",
123
- "@earendil-works/pi-coding-agent": "0.80.2",
124
- "@earendil-works/pi-tui": "0.80.2",
122
+ "@earendil-works/pi-ai": "0.80.3",
123
+ "@earendil-works/pi-coding-agent": "0.80.3",
124
+ "@earendil-works/pi-tui": "0.80.3",
125
125
  "@xterm/xterm": "^6.0.0",
126
126
  "node-pty": "^1.1.0",
127
127
  "playwright": "^1.60.0",
@@ -1,4 +1,4 @@
1
- import type { RunResult } from "@cursor/sdk";
1
+ import type { RunError, RunResult } from "@cursor/sdk";
2
2
  import { asRecord } from "./cursor-record-utils.js";
3
3
  import { scrubSensitiveText } from "./cursor-sensitive-text.js";
4
4
 
@@ -15,7 +15,7 @@ const NETWORK_CURSOR_SDK_ERROR_MESSAGE =
15
15
  // Keep this phrase aligned with pi's agent-level retry classifier (`provider.?returned.?error`).
16
16
  const RETRYABLE_CURSOR_RUN_FAILURE_PREFIX = "Provider returned error: Cursor SDK run failed";
17
17
 
18
- export type CursorSdkRunFailureSource = Pick<RunResult, "id" | "requestId" | "status" | "durationMs" | "model" | "result">;
18
+ export type CursorSdkRunFailureSource = Pick<RunResult, "id" | "requestId" | "status" | "durationMs" | "model" | "result" | "error">;
19
19
 
20
20
  function isGenericErrorMessage(message: string): boolean {
21
21
  const normalized = message.trim().toLowerCase();
@@ -208,17 +208,43 @@ function shortRunId(runId: string): string {
208
208
  return `${trimmed.slice(0, 8)}…`;
209
209
  }
210
210
 
211
- export function formatCursorSdkRunFailureDetail(result: CursorSdkRunFailureSource, runResult?: string): string {
211
+ function runErrorCode(error: RunError | undefined): string | undefined {
212
+ return error?.code?.trim() || undefined;
213
+ }
214
+
215
+ function nonGenericRunErrorMessage(error: RunError | undefined): string | undefined {
216
+ const message = error?.message?.trim();
217
+ return message && !isKnownGenericRunFailureText(message) ? message : undefined;
218
+ }
219
+
220
+ function withRunErrorCode(message: string, code: string | undefined): string {
221
+ return code ? `${message} (code: ${code})` : message;
222
+ }
223
+
224
+ export function formatCursorSdkRunFailureDetail(
225
+ result: CursorSdkRunFailureSource,
226
+ runResult?: string,
227
+ runError?: RunError,
228
+ ): string {
229
+ const errorCode = runErrorCode(result.error) ?? runErrorCode(runError);
230
+ const fromWaitError = nonGenericRunErrorMessage(result.error);
231
+ if (fromWaitError) return withRunErrorCode(fromWaitError, errorCode);
232
+
212
233
  const fromWait = result.result?.trim();
213
234
  if (fromWait && !isKnownGenericRunFailureText(fromWait)) {
214
- return fromWait;
235
+ return withRunErrorCode(fromWait, errorCode);
215
236
  }
237
+
238
+ const fromRunError = nonGenericRunErrorMessage(runError);
239
+ if (fromRunError) return withRunErrorCode(fromRunError, errorCode);
240
+
216
241
  const fromRun = runResult?.trim();
217
242
  if (fromRun && !isKnownGenericRunFailureText(fromRun)) {
218
- return fromRun;
243
+ return withRunErrorCode(fromRun, errorCode);
219
244
  }
220
245
 
221
246
  const parts = [RETRYABLE_CURSOR_RUN_FAILURE_PREFIX];
247
+ if (errorCode) parts.push(`code ${errorCode}`);
222
248
  if (result.model?.id) parts.push(`model ${result.model.id}`);
223
249
  parts.push(`run ${shortRunId(result.id)}`);
224
250
  if (result.requestId) parts.push(`request ${shortRunId(result.requestId)}`);
@@ -93,6 +93,7 @@ export class CursorRunFinalizer {
93
93
  modelId,
94
94
  signal: runnerParams.options?.signal,
95
95
  runResultFallback: run.result,
96
+ runErrorFallback: run.error,
96
97
  resolvedApiKey: this.params.resolvedApiKey(),
97
98
  optionsApiKey: runnerParams.options?.apiKey,
98
99
  sdkEventDebug,
@@ -1,4 +1,4 @@
1
- import type { RunResult } from "@cursor/sdk";
1
+ import type { RunError, RunResult } from "@cursor/sdk";
2
2
  import { selectCursorFinalText } from "./cursor-run-final-text.js";
3
3
  import {
4
4
  formatCursorSdkAbortMessage,
@@ -42,6 +42,7 @@ export interface ResolveCursorRunOutcomeParams {
42
42
  planTextCandidate?: string;
43
43
  selectFinalTextOptions?: { allowPartialPrefix?: boolean };
44
44
  runResultFallback?: string;
45
+ runErrorFallback?: RunError;
45
46
  resolvedApiKey?: string;
46
47
  optionsApiKey?: string;
47
48
  }
@@ -91,7 +92,11 @@ export function resolveCursorRunOutcome(params: ResolveCursorRunOutcomeParams):
91
92
  }
92
93
 
93
94
  if (waitResult.status === "error") {
94
- const failureDetail = formatCursorSdkRunFailureDetail(waitResult, params.runResultFallback);
95
+ const failureDetail = formatCursorSdkRunFailureDetail(
96
+ waitResult,
97
+ params.runResultFallback,
98
+ params.runErrorFallback,
99
+ );
95
100
  return {
96
101
  kind: "error",
97
102
  waitResult,
@@ -1,4 +1,4 @@
1
- import type { SDKAgent } from "@cursor/sdk";
1
+ import type { RunError, SDKAgent } from "@cursor/sdk";
2
2
  import { loadCursorTranscriptWebToolCallsAfterOffset } from "./cursor-agent-message-web-tools.js";
3
3
  import { getCheckpointContextWindow, saveCachedContextWindow } from "./context-window-cache.js";
4
4
  import type { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
@@ -30,6 +30,7 @@ export interface BuildCursorRunOutcomeParams {
30
30
  prepared: CursorProviderTurnPrepareResult;
31
31
  signal?: AbortSignal;
32
32
  runResultFallback?: string;
33
+ runErrorFallback?: RunError;
33
34
  resolvedApiKey?: string;
34
35
  optionsApiKey?: string;
35
36
  }
@@ -46,6 +47,7 @@ export function buildCursorRunOutcomeFromWait(params: BuildCursorRunOutcomeParam
46
47
  planTextCandidate: turnCoordinator.planTextCandidate,
47
48
  selectFinalTextOptions: liveRun ? undefined : { allowPartialPrefix: true },
48
49
  runResultFallback: params.runResultFallback,
50
+ runErrorFallback: params.runErrorFallback,
49
51
  resolvedApiKey: params.resolvedApiKey,
50
52
  optionsApiKey: params.optionsApiKey,
51
53
  });
@@ -83,6 +85,7 @@ export interface AwaitFinalizeCursorRunOutcomeParams {
83
85
  modelId: string;
84
86
  signal?: AbortSignal;
85
87
  runResultFallback?: string;
88
+ runErrorFallback?: RunError;
86
89
  resolvedApiKey?: string;
87
90
  optionsApiKey?: string;
88
91
  sdkEventDebug?: CursorSdkEventDebugSink;
@@ -101,6 +104,7 @@ export async function awaitFinalizeCursorRunOutcome(params: AwaitFinalizeCursorR
101
104
  prepared: params.prepared,
102
105
  signal: params.signal,
103
106
  runResultFallback: params.runResultFallback,
107
+ runErrorFallback: params.runErrorFallback,
104
108
  resolvedApiKey: params.resolvedApiKey,
105
109
  optionsApiKey: params.optionsApiKey,
106
110
  });
@@ -104,6 +104,7 @@ export class CursorProviderTurnRunner {
104
104
  modelId: model.id,
105
105
  signal: options?.signal,
106
106
  runResultFallback: send.run.result,
107
+ runErrorFallback: send.run.error,
107
108
  resolvedApiKey: this.resolvedApiKey,
108
109
  optionsApiKey: options?.apiKey,
109
110
  sdkEventDebug: this.sdkEventDebug,
@@ -4,7 +4,7 @@ import type { CursorPiToolBridgeSnapshot } from "./cursor-pi-tool-bridge-types.j
4
4
  export const CURSOR_TOOL_MANIFEST_ENV = "PI_CURSOR_TOOL_MANIFEST";
5
5
 
6
6
  /**
7
- * Representative @cursor/sdk@1.0.22 local-agent ToolType values; actual exposure can vary by run.
7
+ * Representative @cursor/sdk@1.0.23 local-agent ToolType values; actual exposure can vary by run.
8
8
  * See docs/cursor-native-tool-replay.md#sdk-tooltype-replay-matrix.
9
9
  */
10
10
  export const CURSOR_HOST_TOOL_MANIFEST_SUMMARY =