pi-cursor-sdk 0.4.0 → 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 (125) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +65 -13
  3. package/dist/context-window-cache.js +0 -6
  4. package/dist/context.js +8 -0
  5. package/dist/cursor-cloud-lifecycle.js +122 -98
  6. package/dist/cursor-durable-fs.js +97 -3
  7. package/dist/cursor-fallback-models.generated.js +288 -1
  8. package/dist/cursor-fallback-warning.js +2 -2
  9. package/dist/cursor-http1.js +8 -8
  10. package/dist/cursor-live-run-coordinator.js +9 -13
  11. package/dist/cursor-native-replay-routing.js +1 -1
  12. package/dist/cursor-native-tool-display-registration.js +46 -22
  13. package/dist/cursor-native-tool-display-state.js +57 -22
  14. package/dist/cursor-native-tool-display-tools.js +5 -6
  15. package/dist/cursor-pi-tool-bridge-constants.js +23 -2
  16. package/dist/cursor-pi-tool-bridge-run.js +7 -3
  17. package/dist/cursor-pi-tool-bridge-snapshot.js +1 -1
  18. package/dist/cursor-pi-tool-bridge.js +31 -9
  19. package/dist/cursor-provider-binding.js +58 -0
  20. package/dist/cursor-provider-errors.js +28 -7
  21. package/dist/cursor-provider-lazy.js +23 -11
  22. package/dist/cursor-provider-live-run-drain.js +15 -12
  23. package/dist/cursor-provider-run-finalizer.js +19 -8
  24. package/dist/cursor-provider-turn-coordinator.js +1 -0
  25. package/dist/cursor-provider-turn-display-router.js +4 -0
  26. package/dist/cursor-provider-turn-emit.js +1 -0
  27. package/dist/cursor-provider-turn-finalize.js +29 -20
  28. package/dist/cursor-provider-turn-prepare.js +24 -11
  29. package/dist/cursor-provider-turn-runner.js +37 -11
  30. package/dist/cursor-provider-turn-send.js +22 -9
  31. package/dist/cursor-provider-turn-summary.js +89 -0
  32. package/dist/cursor-provider.js +9 -3
  33. package/dist/cursor-request-provenance.js +42 -0
  34. package/dist/cursor-ripgrep-path.js +14 -3
  35. package/dist/cursor-runtime-state.js +50 -43
  36. package/dist/cursor-sdk-billed-usage.js +54 -75
  37. package/dist/cursor-sdk-event-debug-session.js +5 -6
  38. package/dist/cursor-sdk-event-debug.js +9 -6
  39. package/dist/cursor-sdk-process-error-guard.js +8 -7
  40. package/dist/cursor-sdk-runtime.js +13 -0
  41. package/dist/cursor-session-agent-cleanup.js +2 -2
  42. package/dist/cursor-session-agent-lifecycle.js +18 -9
  43. package/dist/cursor-session-agent-lineage.js +28 -12
  44. package/dist/cursor-session-agent-resume.js +51 -35
  45. package/dist/cursor-session-agent.js +9 -9
  46. package/dist/cursor-session-compaction-prep.js +2 -7
  47. package/dist/cursor-session-scope.js +71 -39
  48. package/dist/cursor-session-settings.js +61 -0
  49. package/dist/cursor-skill-tool.js +32 -49
  50. package/dist/cursor-state.js +72 -62
  51. package/dist/cursor-usage-accounting.js +13 -70
  52. package/dist/cursor-usage-command.js +89 -0
  53. package/dist/cursor-usage-ledger.js +512 -0
  54. package/dist/index.js +11 -19
  55. package/dist/model-discovery.js +11 -19
  56. package/docs/cursor-dogfood-checklist.md +3 -3
  57. package/docs/cursor-live-smoke-checklist.md +15 -15
  58. package/docs/cursor-model-ux-spec.md +59 -18
  59. package/docs/cursor-native-tool-replay.md +5 -3
  60. package/docs/cursor-native-tool-visual-audit.md +8 -8
  61. package/docs/cursor-testing-lessons.md +47 -23
  62. package/docs/cursor-tool-surfaces.md +1 -1
  63. package/docs/platform-smoke-implementation.md +10 -6
  64. package/docs/platform-smoke.md +35 -46
  65. package/package.json +6 -6
  66. package/scripts/cloud-runtime-smoke.mjs +4 -2
  67. package/scripts/local-resume-smoke.mjs +1 -1
  68. package/scripts/platform-smoke/card-detect.mjs +2 -8
  69. package/scripts/platform-smoke/targets.mjs +8 -15
  70. package/scripts/platform-smoke/visual-evidence.mjs +7 -2
  71. package/scripts/tmux-live-smoke.sh +57 -90
  72. package/src/context-window-cache.ts +0 -6
  73. package/src/context.ts +8 -0
  74. package/src/cursor-cloud-lifecycle.ts +124 -98
  75. package/src/cursor-durable-fs.ts +65 -3
  76. package/src/cursor-fallback-models.generated.ts +288 -1
  77. package/src/cursor-fallback-warning.ts +2 -2
  78. package/src/cursor-http1.ts +8 -8
  79. package/src/cursor-live-run-coordinator.ts +12 -13
  80. package/src/cursor-native-replay-routing.ts +2 -1
  81. package/src/cursor-native-tool-display-registration.ts +48 -24
  82. package/src/cursor-native-tool-display-state.ts +65 -21
  83. package/src/cursor-native-tool-display-tools.ts +7 -4
  84. package/src/cursor-pi-tool-bridge-constants.ts +27 -2
  85. package/src/cursor-pi-tool-bridge-run.ts +11 -3
  86. package/src/cursor-pi-tool-bridge-snapshot.ts +1 -1
  87. package/src/cursor-pi-tool-bridge.ts +26 -9
  88. package/src/cursor-provider-binding.ts +62 -0
  89. package/src/cursor-provider-errors.ts +27 -7
  90. package/src/cursor-provider-lazy.ts +21 -15
  91. package/src/cursor-provider-live-run-drain.ts +20 -13
  92. package/src/cursor-provider-run-finalizer.ts +24 -15
  93. package/src/cursor-provider-turn-coordinator.ts +2 -0
  94. package/src/cursor-provider-turn-display-router.ts +5 -0
  95. package/src/cursor-provider-turn-emit.ts +3 -1
  96. package/src/cursor-provider-turn-finalize.ts +38 -23
  97. package/src/cursor-provider-turn-prepare.ts +23 -14
  98. package/src/cursor-provider-turn-runner.ts +40 -13
  99. package/src/cursor-provider-turn-send.ts +23 -15
  100. package/src/cursor-provider-turn-summary.ts +94 -0
  101. package/src/cursor-provider-turn-types.ts +37 -4
  102. package/src/cursor-provider.ts +25 -3
  103. package/src/cursor-request-provenance.ts +64 -0
  104. package/src/cursor-ripgrep-path.ts +15 -5
  105. package/src/cursor-runtime-state.ts +53 -48
  106. package/src/cursor-sdk-billed-usage.ts +54 -102
  107. package/src/cursor-sdk-event-debug-session.ts +5 -5
  108. package/src/cursor-sdk-event-debug.ts +10 -6
  109. package/src/cursor-sdk-process-error-guard.ts +7 -7
  110. package/src/cursor-sdk-runtime.ts +14 -0
  111. package/src/cursor-session-agent-cleanup.ts +2 -2
  112. package/src/cursor-session-agent-lifecycle.ts +14 -9
  113. package/src/cursor-session-agent-lineage.ts +26 -12
  114. package/src/cursor-session-agent-resume.ts +49 -36
  115. package/src/cursor-session-agent.ts +13 -10
  116. package/src/cursor-session-compaction-prep.ts +2 -7
  117. package/src/cursor-session-scope.ts +87 -39
  118. package/src/cursor-session-settings.ts +72 -0
  119. package/src/cursor-skill-tool.ts +37 -61
  120. package/src/cursor-state.ts +74 -67
  121. package/src/cursor-usage-accounting.ts +17 -82
  122. package/src/cursor-usage-command.ts +76 -0
  123. package/src/cursor-usage-ledger.ts +500 -0
  124. package/src/index.ts +14 -23
  125. package/src/model-discovery.ts +14 -22
@@ -11,14 +11,17 @@ Current implementation notes:
11
11
  - Provider contexts support official Pi 0.87.1/latest and current `fitchmultz/pi` main, including transcript-only hosts. `cursor-pi-context.ts` directly imports Pi 0.87.1's required public replay helpers. Current instructions and tools are replayed natively, system deltas are fingerprinted, and system messages are excluded from Cursor conversation formatting and trailing-result scans. Fresh cloud context preserves current instructions while excluding history. Native replay uses the request tool snapshot (including empty); the pi bridge retains its separate registry-owned surface. XML prompt sections are sanitized/deduplicated alongside the stock format.
12
12
 
13
13
  - Cursor context variants use `base@context` pi model IDs.
14
- - Cursor `reasoning`, `effort`, and boolean `thinking` parameters are driven by pi native thinking when the Cursor SDK exposes those controls.
14
+ - Cursor `reasoning`, `effort`, `reasoning_effort`, and boolean `thinking` parameters are driven by pi native thinking when the Cursor SDK exposes those controls.
15
15
  - Cursor `fast` is extension state by default; models that expose `fast` also get selection-only `:fast` / `:slow` virtual aliases for per-agent overrides.
16
16
  - Cursor SDK `mode` (`agent` or `plan`) is extension session state, not model identity, pi thinking, Cursor `fast`, or pi's separate plan-mode extension.
17
17
  - Cursor status uses one coordinated `ctx.ui.setStatus("cursor", ...)` value for fast, non-default plan mode, and the local-only `http1` transport marker; the default pi footer remains intact.
18
18
  - Installed `@cursor/sdk` user messages accept images, and Cursor models are treated as image-capable; registered input metadata is `text` plus `image`.
19
19
  - 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.
20
- - Exact `@cursor/sdk@1.0.32` is an unbundled package dependency; users do not need a global SDK install. Package 0.4.0 requires Node 24+ and official Pi 0.87.1+, while optional published Pi and TypeBox peer dependencies use `"*"` ranges per Pi guidance. The currently validated local Pi packages are 0.87.1.
21
- - After each finished SDK run, the provider calls `agent.getUsage()` (no `runId` on local agents; `runId` only for cloud `run-*` IDs) and maps billed spend into pi `usage` spend fields. Occupancy `totalTokens` uses only in-window local turn-ended occupancy below the latest compaction `tokensBefore`; billed rows never become occupancy, so footer/auto-compact stay aligned with the post-compact Cursor prompt.
20
+ - Shared SDK initialization resolves the native platform package relative to `@cursor/sdk` before import and sets `CURSOR_TREE_SITTER_VENDOR_DIR` only when unset and both vendored parser entrypoints exist. Explicit overrides remain SDK-owned. This repairs sibling-install native lookup, not compiled Bun or embedded-host dependency graphs.
21
+ - The selected vendor path is process-wide and inherited by shell commands and child Pi processes. A child using another install/worktree preserves the inherited value as an explicit override, even when stale. Unset it in the child's environment to rediscover that install's parser, or provide its absolute vendor path; see [native parser troubleshooting](../README.md#native-shell-parsing-or-module-loading-fails).
22
+ - Provider and discovery errors retain bounded, scrubbed name/code/message/cause fields without serializing arbitrary error objects. Module-loading and ambiguous session-authentication failures do not imply a rejected API key. Explicit key rejection retains actionable setup guidance; idle expiry and recovery policy are unchanged.
23
+ - Exact `@cursor/sdk@1.0.32` is an unbundled package dependency; users do not need a global SDK install. Package 0.5.0 requires Node 24+ and official Pi 0.87.1+, while optional published Pi and TypeBox peer dependencies use `"*"` ranges per Pi guidance. The exact development Pi cohort is 0.99.1, with host TypeBox 1.3.27.
24
+ - Native usage contains coherent current-context components only. The origin-owned ledger records every raw turn, original wait/handle snapshots, terminal success/error/abort/abandon, and bounded public whole-agent `getUsage()` observations separately. Cumulative bills never enter native usage. Request floors use actual native source chronology and projection equivalence, not text or clocks; see [Context and accounting boundaries](#context-and-accounting-boundaries).
22
25
  - Startup discovery does not duplicate Pi CLI parsing: it uses stored `~/.pi/agent/auth.json` API-key auth from `/login`, then `CURSOR_API_KEY`, otherwise it registers the bundled fallback catalog. Provider turns keep Pi's resolved `options.apiKey`. `/cursor-refresh-models` and `/cursor-cloud` mutations resolve provider `cursor` through the command context's Pi ModelRegistry, then normalize placeholders through `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults and the user-level local HTTP transport preference.
23
26
  - Cursor Cloud repository overrides accept only HTTPS repository URLs without userinfo, query parameters, or fragments. Invalid values fail during preflight before `Agent.create()`, messages never echo the supplied URL, and shared provider/maintainer scrubbing removes URL/SCP-style userinfo defensively.
24
27
  - Cursor Cloud requires a persisted pi session. Immediately after remote `Agent.create()` returns, before debug work or abort checks, the provider appends a branch-local pi lifecycle entry, fsyncs the existing Pi session JSONL anchor through a read-write descriptor, and then fsyncs a newline-framed sidecar keyed by the stable pi session ID (POSIX mode `0600`; Windows inherits the user session directory ACL) in the session directory. Journal creation is exclusive, and existing append/read opens reject symlinks and require matching regular-file descriptor/path identity before using the descriptor. Existing session files use the exact lifecycle entry ID as anchor; fileless first turns use an orphan marker that a same-session-ID restart durably claims onto exactly one matching or replacement branch, surviving the timestamped path change and later JSONL creation without granting sibling access; returned run IDs are fsynced before abort/wait handling, readers skip malformed/truncated lines independently, and branch-only history events are reduced after deduplicated sidecar history, and tombstones are tracked before records exist. A durable sidecar result remains authoritative if its optional Pi mirror append fails; if the sidecar result itself fails, the prior durable intent remains pending and blocks retry rather than claiming success. `--no-session` and durable-ledger failures fail closed before send, while post-send ledger failure requests bounded cancellation. `/cursor-cloud` always validates the embedded session ID, accepts only null anchors while truly fileless, and reconciles each orphan to one branch before listing or mutation. Successive record events merge run/branch metadata monotonically even when mirrored sources arrive out of order. Archive/delete require resolved Cursor auth, fsync a durable intent before the SDK call, and fsync a durable success result afterward; unresolved intent blocks repeat mutation and requires dashboard inspection.
@@ -34,21 +37,22 @@ Current implementation notes:
34
37
  - Cursor SDK MCP tool calls use a guarded timeout override because installed `@cursor/sdk` 1.0.32 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`. Bridged `CallTool` waits also have a local fail-closed deadline that defaults to and cannot exceed the effective MCP tool timeout; `PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS` can lower it, expiry or MCP cancellation aborts active pi execution when available, and expired bridge events are dropped before pi tool emission.
35
38
  - Cursor SDK local safety controls are off by default. `--cursor-auto-review` / `PI_CURSOR_AUTO_REVIEW` and `--cursor-sandbox` / `PI_CURSOR_SANDBOX` pass only explicit enabled values into `Agent.create({ local })`; user or trusted project config can set `local.autoReview` and `local.sandboxOptions.enabled`; project config is active only when Pi's project-trust flow reached the extension and approved the project or the run used explicit `--approve`, and project saves require the same immutable trust provenance rather than creating Pi trust resources automatically. Pi loads `pi install -l` project-local extensions after the trust event, so those installs require `--approve` on every run that reads or writes `.pi/cursor-sdk.json`. Explicit runtime, fast-default, and HTTP transport saves preserve unrecognized config fields, reject malformed or non-object JSON without rewriting it, and use one lock-protected read-modify-write path; fast saves mutate only the selected model key. Because Pi can mutate its in-memory session branch before a journal append throws, a completed global save is authoritative and the command reports the partial journal failure instead of attempting an ambiguous rollback; the new global value stays authoritative over stale branch entries until a later successful save or session restart.
36
39
  - Local HTTP/1.1/SSE compatibility is strictly opt-in through `PI_CURSOR_HTTP_1_1`, `/cursor-http on|off|toggle`, or user `cursor-sdk.json` `local.useHttp1ForAgent`. Precedence is session, environment, user, then the built-in unset default; project config is excluded. Unset makes no `Cursor.configure()` call. Explicit values configure the installed SDK before local `Agent.create()`, extension-owned explicit state is cleared with the SDK's documented `null` reset when returning to unset and during session shutdown before module reload, and default/HTTP2/HTTP1 choices split pooled local agents. Pi's supported CLI/TUI/print/RPC lifecycle has one active session runtime per process; concurrent independent `AgentSession` embedding in one process is outside this transport toggle's contract because the installed SDK setting and executor cache are module-global. The footer adds `http1` only for enabled local runtime; cloud creation and status remain untouched.
37
- - 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.
40
+ - 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. Provider-facing bridge tool-call IDs use the run UUID without separators in the `cursor-pi-bridge-<32 hex>-t<counter>` shape and are capped at 64 characters; bridge run IDs and tool-call IDs remain distinct.
38
41
  - 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.
39
42
  - Cursor internal tool activity is recorded from SDK events and scrubbed. Maintainer reference for `@cursor/sdk@1.0.32` `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.
40
43
  - 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.
41
- - Local Cursor SDK usage events are used when the SDK reports them before the corresponding pi turn is emitted and the reported counts fit the selected pi model window. For each safe local SDK-attributed assistant turn, `usage.input`, `usage.output`, `usage.cacheRead`, and `usage.cacheWrite` come from the latest per-turn raw `turn-ended.usage` (not SDK `toTokenUsage`): observed local runtime keeps `inputTokens` as the full prompt with cache fields as a partition, even though published SDK `TokenUsage.totalTokens` sums all four fields. Pi maps that raw local shape to disjoint components (`input = inputTokens - cacheReadTokens - cacheWriteTokens`) and `usage.totalTokens = inputTokens + outputTokens` for occupancy/compaction only when that total is below the latest compaction `tokensBefore`; otherwise occupancy uses the post-compaction estimate floor. Cloud billed `getUsage()` rows map to spend fields only and never become occupancy. Approximate fallback never reports less occupancy than the last compatible same-model in-window assistant measurement in context. Cumulative `RunResult.usage` is never used for per-message occupancy. If the local SDK reports no usage in time, or reports full-agent-context-sized usage outside the selected model window, 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.
44
+ - In-time safe LOCAL raw `turn-ended.usage` maps full-prompt input into disjoint native components (`input = inputTokens - cacheReadTokens - cacheWriteTokens`), retaining caches and output; their sum equals `totalTokens = inputTokens + outputTokens`. Fresh usage supersedes historical floors. Fallback components describe the same estimated current context as total. Late SDK data never overwrites an emitted pi message, but raw events remain ledger facts independent of replay/display gating. `RunResult.usage`, Cloud raw data, and billed snapshots never become per-message occupancy. `src/cursor-usage-accounting.ts` owns native mapping; `src/cursor-usage-ledger.ts` owns durable telemetry.
45
+ - Discovery registers zero cost rates. `models.json` configured per-million rates and tiers use native `calculateCost(model, partial.usage)` on each emitted message's coherent context components. Fallback has no inferred cache split, and split-message estimates are not internal request pricing. Public billed usage already has disjoint uncached input/cache/output categories; it is not normalized like raw LOCAL full-prompt input. Optional `cost.rawCostCents` and `cost.chargedCents` remain separate, revisable floating-point-cent observations, never substituted for configured native costs.
42
46
  - 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.
43
47
  - 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.
44
- - Cursor provider/runtime releases must pass the local [Platform Smoke Gate](./platform-smoke.md): `npm run smoke:platform:all`. Cloud-runtime changes must also pass `npm run smoke:cloud`. 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/grok-4.6` 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.
48
+ - Verification is offline/faux first, including cross-platform CI and native contracts. Reuse exact-input retained evidence. Only changed behavior that needs new real-service proof warrants the smallest meaningful live check on one representative environment; docs/metadata-only changes need no paid runs. No full paid campaign replay, matrix-only host coverage, or automatic paid retries. `npm run smoke:platform:all` is an optional comprehensive matrix, not an unconditional commit/release gate. No paid Cursor Cloud testing for generic PRs/releases; only explicitly Cursor Cloud-focused PRs/issues may select a necessary focused Cloud check, retaining run/evidence and cleanup contracts. Automated Cursor PR reviews, offline Cloud contracts, and Cloud product capabilities remain unchanged. See [Platform Smoke](./platform-smoke.md), [Cursor Live Smoke Checklist](./cursor-live-smoke-checklist.md), and [Cursor testing lessons](./cursor-testing-lessons.md).
45
49
  - 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.
46
50
  - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.32 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.
47
51
  - 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.
48
52
  - Local restart resume treats user entries already present at `session_start` or selected by tree navigation as crash-ambiguous: an older SDK handle cannot span them because the prior process may already have submitted that prompt. A user entry appended after startup in the current process may span the last completed handle for the normal next send.
49
53
  - Persisted pi sessions use a session-scoped Cursor SDK SQLite store at `<getDefaultSdkStateRoot(cwd)>/pi-sessions/<session-hash>/`; create/resume, transcript reads, checkpoint lookup, and exact-ID cleanup all receive that same store. Fileless acquisitions use unique OS-temporary stores that are removed on graceful disposal; invalidation starts a fresh agent instead of reopening a disposed temporary store. Resume entries version the store identity. Legacy entries still resume against the SDK default workspace store, then move to the per-session store after fallback or agent replacement. Removing a persisted pi session does not automatically remove its store directory; only a verified session-derived `pi-sessions/<session-hash>` root may be removed after no pi process uses it. The shared SDK default workspace root recorded by legacy entries must never be removed as session cleanup. Cloud agents are unchanged.
50
54
  - Session-scoped Cursor SDK agent pooling reuses one live `@cursor/sdk` agent across compatible follow-up turns within the same pi session scope. Independently, each distinct local agent whose `Agent.send()` is initiated is best-effort recorded once per native pi session as a non-resumable `cursor-sdk-agent-lineage` custom entry (including failed/cancelled sends and when local resume is disabled). Cloned/forked sessions record lineage under their own pi session ID; donor entries do not suppress the new session. `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 newly appended model-visible input cannot fit the single-user incremental prompt (for example, a visible `!` shell result before the next question), 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. Pi's `!!` shell output remains excluded from model context, so an ordinary follow-up after `!!` stays incremental. 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). True incremental sends also omit invariant Pi system instructions; system-prompt changes are part of the context fingerprint and force a full context-divergence bootstrap that includes the updated system section.
51
- - 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.
55
+ - Pi steering/follow-up delivery can arrive while a split live Cursor SDK run is still active. The provider resolves only the captured session scope's indexed live run against trailing `toolResult` messages while skipping trailing `user` messages, ignores inherited foreign-run IDs without scanning sibling runs, and resumes its own 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.
52
56
 
53
57
  ## Goal
54
58
 
@@ -57,7 +61,7 @@ Make Cursor models feel native in pi by leaning on pi's existing model, thinking
57
61
  Main outcomes:
58
62
 
59
63
  - `pi --list-models` shows pi-native Cursor models with accurate `contextWindow`, pi-controllable thinking metadata, and conservative defaults where the Cursor SDK does not expose limits or capabilities.
60
- - `shift+tab` is pi's native thinking control and drives Cursor `reasoning` or `effort`.
64
+ - `shift+tab` is pi's native thinking control and drives Cursor `reasoning`, `effort`, `reasoning_effort`, or `thinking`.
61
65
  - Cursor context options are represented as pi-visible model variants when they change native model metadata.
62
66
  - Cursor-only state (`fast` and Cursor SDK `mode`) is controlled by extension flags/commands and shown through native status text only when non-default.
63
67
  - The default pi footer remains intact.
@@ -151,6 +155,7 @@ Use native pi abstractions wherever possible:
151
155
  | Cursor `context` | pi-visible model variant because it changes `contextWindow` |
152
156
  | Cursor `reasoning` | pi native thinking via `thinkingLevelMap` |
153
157
  | Cursor `effort` | pi native thinking via `thinkingLevelMap` |
158
+ | Cursor `reasoning_effort` | pi native thinking via `thinkingLevelMap` |
154
159
  | Cursor `thinking=false` | pi native `off` |
155
160
  | Cursor `fast` | extension state plus `:fast` / `:slow` virtual aliases for per-agent overrides |
156
161
  | Cursor SDK `mode` | extension session state; `agent` by default, `plan` via SDK-native mode |
@@ -172,7 +177,7 @@ Rules:
172
177
  - Register one pi model for each Cursor base model and each unambiguous SDK alias when there is no Cursor `context` parameter.
173
178
  - Register one pi model per Cursor `context` value for each Cursor base model and each unambiguous SDK alias when the model exposes a `context` parameter.
174
179
  - Skip SDK aliases that collide with another base model ID or are shared by multiple base models; those aliases can resolve differently from the pi row metadata.
175
- - Do not encode `reasoning`, `effort`, `thinking`, or Cursor SDK `mode` into pi model IDs. For models with a Cursor `fast` parameter, also register selection-only `:fast` and `:slow` virtual model aliases that do not change pi-native metadata.
180
+ - Do not encode `reasoning`, `effort`, `reasoning_effort`, `thinking`, or Cursor SDK `mode` into pi model IDs. For models with a Cursor `fast` parameter, also register selection-only `:fast` and `:slow` virtual model aliases that do not change pi-native metadata.
176
181
  - Prefer stable, readable `@<context>` suffixes that do not conflict with pi's final `:<thinking>` suffix parser.
177
182
  - Sort Cursor models by base ID, then context value in Cursor SDK order before calling `pi.registerProvider()`. Registration order matters for `/model` display and model cycling; `--list-models` sorts output separately.
178
183
 
@@ -220,12 +225,12 @@ Each registered model must set:
220
225
 
221
226
  - `id`: context-qualified pi model ID when needed. For SDK aliases, this uses the alias as the pi-visible ID and the alias is sent back to Cursor as `ModelSelection.id`.
222
227
  - `name`: human-readable Cursor display name plus context when useful.
223
- - `reasoning`: `true` only if a Cursor `reasoning`, `effort`, or `thinking` parameter can map to pi thinking. This controls pi's thinking UI and `pi --list-models` `thinking` column; it must not be used to claim whether the Cursor model can think internally. Cursor SDK models are thinking-capable even when this is `false`.
228
+ - `reasoning`: `true` only if a Cursor `reasoning`, `effort`, `reasoning_effort`, or `thinking` parameter can map to pi thinking. This controls pi's thinking UI and `pi --list-models` `thinking` column; it must not be used to claim whether the Cursor model can think internally. Cursor SDK models are thinking-capable even when this is `false`.
224
229
  - `thinkingLevelMap`: model-specific pi-to-Cursor mapping for pi UI, clamping, persistence, and footer display.
225
230
  - `contextWindow`: parsed from context variant, else conservative fallback.
226
231
  - `maxTokens`: conservative explicit value until Cursor SDK exposes output limits.
227
232
  - `input`: supported input types. The installed Cursor SDK accepts `SDKUserMessage.images`, and Cursor models are expected to support image input, so advertise `["text", "image"]`.
228
- - `cost`: zeroed unless reliable Cursor costs are available.
233
+ - `cost`: zero rates at registration; pi applies user-configured `models.json` overrides. SDK billed cents are separate from these rates.
229
234
 
230
235
  The extension stores runtime metadata in an internal map keyed by registered pi model ID. That map records the Cursor base catalog model ID, the Cursor selection model ID (base ID or alias), selected context param, default params, and discovered capabilities. `ProviderModelConfig` has no dedicated metadata field, so do not rely on hidden custom fields for this state.
231
236
 
@@ -240,6 +245,7 @@ Infer behavior from discovered params:
240
245
  | `context` with values | register pi-visible context variants |
241
246
  | `reasoning` | populate `thinkingLevelMap` |
242
247
  | `effort` | populate `thinkingLevelMap` |
248
+ | `reasoning_effort` | populate `thinkingLevelMap` |
243
249
  | `thinking` with `true/false` | map `false` to pi `off`; map `true` to the enabled pi level chosen for boolean-only thinking |
244
250
  | `fast` with `true/false` | enable fast extension setting |
245
251
 
@@ -269,6 +275,7 @@ Cursor extension controls:
269
275
  | Set local HTTP transport | `/cursor-http on\|off\|toggle` | local Cursor runtime |
270
276
  | Refresh filesystem Cursor config | `/cursor-refresh-config` | Cursor model selected and an SDK agent may exist |
271
277
  | Show tool surfaces (maintainer) | `/cursor-tools` | Cursor model selected |
278
+ | View, refresh, or export accounting | `/cursor-usage [refresh\|export <new-file.json>\|help]` | current branch has recorded Cursor facts |
272
279
 
273
280
  Do not register a shortcut for `shift+tab`. Pi reserves the native thinking keybinding, and the extension should only influence it through model metadata.
274
281
 
@@ -279,7 +286,7 @@ Do not add a context-cycle shortcut in the first pass. Context is a pi model var
279
286
  Important distinction:
280
287
 
281
288
  - **Cursor thinking support** applies to all Cursor SDK models. The extension should assume Cursor models can think and may emit thinking deltas.
282
- - **Pi-controllable thinking** means Cursor exposes a `reasoning`, `effort`, or `thinking` parameter that the extension can set from pi's native thinking level. These models register `reasoning: true` and show `thinking=yes` in `pi --list-models`.
289
+ - **Pi-controllable thinking** means Cursor exposes a `reasoning`, `effort`, `reasoning_effort`, or `thinking` parameter that the extension can set from pi's native thinking level. These models register `reasoning: true` and show `thinking=yes` in `pi --list-models`.
283
290
  - **Cursor SDK thinking-control gap** means the model can still think, but the SDK does not expose a user-controllable thinking parameter for that model. These models register `reasoning: false` and show `thinking=no` in `pi --list-models` because pi cannot control a level for them. The extension still surfaces Cursor `thinking-delta` and summary events through pi's native thinking rendering when they are emitted.
284
291
 
285
292
  Do not mark a model `reasoning: true` only because it can think. That would make pi show controls such as `--thinking`, `:medium`, and shift+tab even though the extension cannot translate them into Cursor SDK params.
@@ -307,6 +314,7 @@ Mapping rules:
307
314
  Important details:
308
315
 
309
316
  - Use `null` for unsupported pi levels so pi hides/skips/clamps them natively.
317
+ - Gemini 3.8 Flash maps `low`, `medium`, and `high` to the native `reasoning_effort` parameter. Its other pi levels are unsupported; direct provider selections of those levels retain the SDK's `high` default.
310
318
  - Include `xhigh` and `max` only when Cursor exposes real values for them.
311
319
  - Keep `xhigh` and `max` distinct. Cursor exposes both on some models, while `extra-high` remains an `xhigh` alias.
312
320
  - If Cursor exposes `reasoning=none`, map pi `off` to `none`.
@@ -424,7 +432,7 @@ Rules:
424
432
  - `/cursor-mode` with no args reports current mode and usage.
425
433
  - Invalid CLI values fail non-UI runs and notify interactive users before the provider rejects the run.
426
434
  - New SDK agents are seeded with `Agent.create({ mode })`.
427
- - Every SDK send passes the effective mode through `agent.send(..., { mode })` so `/cursor-mode` and `--cursor-mode` remain the source of truth.
435
+ - Every conversation send passes the effective mode through `agent.send(..., { mode })`; LOCAL compaction/tree summaries explicitly use text-only `agent` mode.
428
436
  - Mode is not part of the session-agent pool key because Cursor SDK supports SDK-native per-send mode switches.
429
437
  - Cursor plan/todo/task/mode activity remains display-only Cursor activity unless pi itself exposes a native state path. Replay cards do not mutate pi plan/todo state or active tools.
430
438
 
@@ -472,6 +480,22 @@ not:
472
480
  (cursor) gpt-5.5 • 1M • medium • fast
473
481
  ```
474
482
 
483
+ ## Context and accounting boundaries
484
+
485
+ The default Pi footer and `/stats` use native coherent current-context usage and configured-price estimates, not complete Cursor invoice/session totals. Official supported extensions expose custom entries, not a native invoice-usage append API. Do not manufacture tool messages, mutate private session state, disable compaction, or inflate windows to hide billing-driven overflow.
486
+
487
+ `cursor-request-provenance.ts` captures the public native projection at `before_provider_headers`. An ordinary request may use the last visible compatible same-model in-window measurement only when the converted canonical projection is value-equivalent to the outgoing request and its raw source follows the latest compaction or context edit. Projected order, timestamps, quoted summary wrappers, and `tokensBefore` comparisons are not authority. Transformed requests and compaction/tree summaries receive no historical floor.
488
+
489
+ Compaction and tree purpose follows the owning operation's public `AbortSignal`, including sequential summary calls and native retries. LOCAL summaries use fresh non-pooled agents with `tools: []`, `local.settingSources: []`, no MCP/bridge/manifest/replay, `agent` mode, no normal resume/lineage writes, and an ephemeral store. Native instructions are preserved without coding-tool guards or conversation prompt budgets. Disposal is awaited before terminal emission so successful native operations return after temporary-store removal; failure/cancellation also disposes the agent/store. Cloud cannot use the same tool restriction, and bug reports lack a public purpose hook; neither is silently classified from text or empty Pi tool declarations.
490
+
491
+ `cursor-usage-ledger.ts` keeps distinct original raw/reported snapshots, qualified LOCAL corrected turns, terminal facts, run/request IDs, and validated public billed snapshots. The recorded LOCAL SDK 1.0.32 cache case is raw full input 4232 / cache read 4096 / output 3: corrected input 136 and total 4235, while original additive SDK wait/handle total 8331 is retained only as reported telemetry. Public billed categories already sum to 4235; subtracting cache again is wrong. Cloud raw normalization and invalid LOCAL partitions retain their original numeric fields with explicit unqualified/invalid status. Aggregate billed totals are not limited by one response's output cap or context window.
492
+
493
+ Before a send, a unique public native branch claim anchors a fsynced intent in a validated sidecar keyed by stable native session ID. Frames contain only IDs, lineage, model selection/pricing, counts, statuses, and observation times—not prompts, tool content, or credentials. Billed frames store aggregate usage/cost plus new/changed/removed UUID rows; unchanged historical rows are not recopied into every journal/session observation. Optional custom-entry mirrors cannot establish or advance recognition; the durable journal and unique native claim do. Late facts remain in the original journal, and mirrors are omitted after owner/session/branch changes. Same-ID recovery in the original directory exposes lost-claim records as unclaimed facts, distinct from valid claims on other branches of that native session. Cross-directory forks use exact inherited provenance and retain mirror facts with explicit gaps when a referenced journal is unavailable; no sibling scans or writes. Fileless sessions are ephemeral.
494
+
495
+ Snapshot append chronology owns revisions within each origin, never wall clocks. Reconstructed whole-agent snapshots retain changed/lower rows, optional costs, and aggregate-only remainders without adding overlapping raw/wait/handle/billed sources. Missing revision history is explicit: agent and per-origin `latestAggregate` preserve absolute observed usage/cost independently of run reconstruction, including after an unavailable attempt. Incomplete histories expose neither `wholeAgent` nor aggregate-only remainders; the view labels the remainder unknown because run history is incomplete, not absent SDK reporting. Multiple-origin agent totals stay ambiguous while per-origin aggregates remain visible. Unknown inherited history is not re-billed. LOCAL client run IDs do not join billing UUIDs, so whole-agent consumption is not attributed to a particular client turn or priced with its current model. Multiple origins have no qualified global revision order and remain explicitly ambiguous. All successful billing observations are pending settlement, not final invoices; an unavailable public endpoint stays unavailable without private fallback.
496
+
497
+ `/cursor-usage` renders the canonical current-branch view, including shared lineage, unavailable/pending data, journal gaps/torn tails, other-branch facts and genuinely unclaimed records. `refresh` takes bounded public snapshots for up to 32 agents (five seconds each), owned by the invoking branch, without model sends or polling. `export <new-file.json>` creates an exclusive private JSON export; non-UI output goes to stderr to preserve stdout/RPC framing. `help`, `-h`, and `--help` explain use and recovery. Journals have a 16 MiB per-origin ceiling and fail before spend when intent cannot persist; mid-run persistence failure leaves accounting incomplete without changing response success and may warn once while the origin is current. Validated prefixes of torn-tail journals remain readable/exportable while further appends fail closed. Export and retain old history, then fork into a new native session or start a new session; never truncate/delete a journal to continue an old SDK lineage. Losing every native reference after moving the origin directory is an explicit discovery limit, not permission to scan unrelated sessions.
498
+
475
499
  ## State And Persistence
476
500
 
477
501
  Match pi's native mental model:
@@ -528,6 +552,20 @@ Use:
528
552
  3. global fast defaults for the selected SDK model ID or alias, falling back to older base-model keys,
529
553
  4. else Cursor default variant params.
530
554
 
555
+ ### Multi-session embedding
556
+
557
+ Each ExtensionAPI registers its own provider closure. Its native header receipt captures immutable scope/projection provenance and the usage origin before deferred work; stream entry consumes that receipt and captures the owning bridge, replay state, and cloud recorder. Default SDK sessions create independent ModelRuntime instances on supported official Pi 0.87.1+ and the maintained fork. Concurrent sessions, children launched inside a parent's tool before `turn_end`, and direct manual/automatic compaction, tree summaries, and bug-report summaries retain their own pool/store, persisted lineage/resume entries, journals, and debug metadata. Ownership does not depend on turn/agent event windows. Inherited tool-result IDs cannot select a sibling's live run. Replay wrappers require both their owning display state and the normalized recorded tool name before consuming a result; a foreign-owner or wrong-tool ID is rejected without consuming the original card or executing native work.
558
+
559
+ Pi's SDK supplies a session-owned `before_provider_headers` callback for ordinary and auxiliary streams. The extension records ownership against the real headers object without modifying it; Pi forwards that same object to the registered provider. Stream entry consumes and checks this one-shot request receipt before touching SDK/storage state, then verifies actual request projection equivalence when resolving a fallback floor. Other extensions' header mutations are preserved. No turn windows, lifecycle scans, sibling graph, or cross-session queue are needed; the existing per-scope send queue remains unchanged.
560
+
561
+ An explicitly shared ModelRuntime still has only one Cursor registration. A request can run only when its receipt belongs to that registration's active session. Mismatched or missing receipts fail before SDK/storage work:
562
+
563
+ > Cursor provider ownership is unavailable: this request does not belong to the active Cursor registration. Bind this session's extensions and refresh Cursor models/reload it; use independent ModelRuntime instances for sibling sessions.
564
+
565
+ Pre-bind and shutdown closures also fail closed. Refreshing A while an unstarted shared sibling exists cannot let that sibling enter A's registration, including bug-report streams without lifecycle events. Catalog refresh keeps its binding's original closure, including when auth resolution races a sibling registration. Bare calls to a registered provider without the native headers receipt, including extension calls to `ctx.modelRegistry.streamSimple`, or reuse of a consumed receipt, fail closed. Bind/refresh/reload cannot supply a receipt to that bare API: use the owning AgentSession's native stream path or an independent child AgentSession. Direct `streamCursor` callers must also supply captured ownership, request provenance, and a usage recorder; there is no unbound global fallback. Default independent ModelRuntime instances remain the embedding recommendation.
566
+
567
+ Branch mode, runtime/cloud acknowledgement, fast and HTTP preferences are session-scoped. CLI snapshots follow their owning ExtensionRuntime, including independent sibling flags and one-shot local-force consumption across rebinding/reload. Environment overrides and user defaults remain process-wide. Cursor SDK HTTP/1.1 transport configuration remains process-global; different branch preferences are not independent simultaneous SDK configurations. Shutdown clears the extension-owned transport override only after the last binding leaves, and a never-started child cannot reset or terminally dispose a parent's pool. Cloud commands capture the original public manager, session file/id, branch anchor, and lifecycle authority before auth/SDK awaits, then reject changed ownership or authority before writing an intent or starting the SDK mutation. Authenticated orphan reconciliation uses only that unchanged captured authority. After SDK start, actual completion is persisted in the original durable journal with its post-intent anchor even across branch changes, reload, or shutdown; the optional Pi mirror is omitted if the original context/branch is no longer current. General provider recording retains live before/after branch-anchor checks, and no-follow/fsync guards remain in force.
568
+
531
569
  ## CLI / Print Mode
532
570
 
533
571
  Guaranteed first-pass support:
@@ -584,7 +622,7 @@ These examples document the capability shapes the extension handles, not an exha
584
622
  | unique latest alias for any shape | aliases | same pi rows as the base model shape, using the alias as `ModelSelection.id` |
585
623
  | shared generic alias across multiple base models | aliases | skipped to avoid misleading pi rows |
586
624
 
587
- If Cursor later adds `fast`, `context`, `reasoning`, `effort`, or aliases to a model, the extension picks up unambiguous capability changes dynamically.
625
+ If Cursor later adds `fast`, `context`, `reasoning`, `effort`, `reasoning_effort`, or aliases to a model, the extension picks up unambiguous capability changes dynamically.
588
626
 
589
627
  ## Detailed Examples
590
628
 
@@ -719,9 +757,11 @@ Fast toggle maps to the Cursor `fast` parameter. `--cursor-no-fast` and `:slow`
719
757
 
720
758
  `shift+tab` maps the available low, medium, high, and xhigh levels to Cursor `effort`; levels without a catalog value do not invent one.
721
759
 
722
- ## Validation Plan
760
+ ## Validation evidence criteria
761
+
762
+ The sections below are selectable evidence criteria, not a mandatory runtime/print/visual campaign. Start with offline/faux checks and reuse valid retained proof for unchanged tested inputs. Select criteria that protect the changed behavior; only a remaining need for real-service proof warrants the smallest meaningful existing live check on one representative environment. The print commands are examples, not five required paid turns. Docs/metadata-only changes need no paid testing. Do not automatically retry paid runs or add hosts merely for matrix coverage.
723
763
 
724
- Before calling done:
764
+ Preserve every selected check's assertions and truthful scope. Visual claims need matching rendered PNG and persisted JSONL evidence; session/resume/lifecycle claims need persisted session/debug evidence, not assistant text alone. Retain process, agent, repository and artifact cleanup requirements for selected runs. Cloud product capabilities remain supported, but paid Cloud testing is permitted only for a PR or issue explicitly focused on Cursor Cloud.
725
765
 
726
766
  1. Unit tests:
727
767
  - context-variant model IDs
@@ -731,6 +771,7 @@ Before calling done:
731
771
  - Cursor SDK mode session/CLI state and status behavior
732
772
  - `reasoning` mapping
733
773
  - `effort` mapping
774
+ - `reasoning_effort` mapping and unchanged SDK defaults for unsupported levels
734
775
  - boolean `thinking` maps to pi `off` / enabled levels
735
776
  - pi `xhigh` preference order: `xhigh`, then `extra-high`
736
777
  - pi `max` maps only to Cursor `max`
@@ -745,7 +786,7 @@ Before calling done:
745
786
  - verify default pi footer remains unchanged
746
787
  - verify Cursor status appears only for Cursor models
747
788
  - verify Cursor fast-capable local models show `cursor:local · fast:on` or `cursor:local · fast:off`
748
- - required `cursor-http1-live` platform lane proves an enabled local HTTP/1.1/SSE turn completes and shows `http1`; unit/default live lanes prove local-disabled and cloud statuses omit it
789
+ - the selected `cursor-http1-live` platform lane proves an enabled local HTTP/1.1/SSE turn completes and shows `http1`; offline status tests cover local-disabled and cloud status omission. Select this live lane only when changed behavior needs new real-service proof.
749
790
  - verify Cursor `plan` status appears only in non-default mode and combines with status as `cursor:local · fast:n/a · plan`, `cursor:local · fast:on · plan`, `cursor:local · fast:off · plan`, or `cursor:cloud · fast:n/a · plan`
750
791
  - verify non-cursor footer/status unchanged
751
792
  - verify `shift+tab` uses pi native thinking
@@ -769,4 +810,4 @@ Before calling done:
769
810
  - confirm `PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS` can only lower the bridge deadline; expiry and cancellation clear pending state, abort active pi execution, suppress stale events/empty drain turns, and superseded registration handlers do not block replacement runs
770
811
  - confirm `PI_CURSOR_MCP_CONNECT_TIMEOUT_MS` and `PI_CURSOR_MCP_CONNECT_TIMEOUT_SECONDS` override the Cursor SDK MCP initialize/listTools timeout seam while unknown protocol timeout stacks keep the SDK default
771
812
  - confirm `PI_CURSOR_PI_TOOL_BRIDGE_DEBUG=1` emits typed, allowlisted, scrubbed JSONL to `process.stderr` with prefix `[pi-cursor-sdk:bridge]`, omits endpoint URLs/path components/tokens, and unset/false leaves output unchanged
772
- - run the visual audit workflow when replay card visuals or bridge card visuals change; JSONL should show real pi tool names for bridged calls and no duplicate MCP replay for bridge calls
813
+ - for changed replay/bridge card visuals, use offline rendering checks and valid exact-input retained visual proof first. Only if new real-TUI proof is still necessary, select the smallest meaningful visual audit on one representative environment; retain rendered PNG, persisted JSONL and cleanup evidence. JSONL should show real pi tool names for bridged calls and no duplicate MCP replay for bridge calls.
@@ -13,11 +13,11 @@ This document is about replay. Replay is not execution and is not the local pi b
13
13
 
14
14
  | Surface | Names Cursor can call | Names pi shows | IDs | Execution behavior |
15
15
  | --- | --- | --- | --- | --- |
16
- | Local pi MCP bridge | Live MCP names such as `pi__sem_reindex`, only when exposed in the current run | Real pi tool names such as `sem_reindex` | Bridge run and tool IDs begin with `cursor-pi-bridge-*` | Real pi execution through normal pi `toolCall` / `toolResult` flow |
16
+ | Local pi MCP bridge | Live MCP names such as `pi__sem_reindex`, only when exposed in the current run | Real pi tool names such as `sem_reindex` | Run IDs begin with `cursor-pi-bridge-run-*`; tool-call IDs use `cursor-pi-bridge-<32 hex>-t<counter>` and are capped at 64 characters | Real pi execution through normal pi `toolCall` / `toolResult` flow |
17
17
  | Cursor native tool replay | None; replay names are not callable tools | Native-compatible card names or neutral Cursor activity labels | Replay IDs begin with `cursor-replay-*` | Display-only recorded Cursor results; no re-run, file mutation, MCP call, or pi state mutation |
18
18
  | Cursor-native host tools/settings/plugins/MCP | Cursor SDK local-agent tool names, as provided by Cursor | Only replay cards or transcript summaries when reported by the SDK | Cursor SDK-owned IDs | Neither pi bridge nor replay execution; owned by the Cursor SDK local agent path |
19
19
 
20
- Replay labels, replay cards, and transcript tool names are display-only/context-only. Bridge MCP names are also not pi tool names: Cursor must call the exposed `pi__*` MCP name, while pi history and cards use the real pi tool name.
20
+ Replay labels, replay cards, and transcript tool names are display-only/context-only. Bridge MCP names are also not pi tool names: Cursor must call the exposed `pi__*` MCP name, while pi history and cards use the real pi tool name. Older session state may contain UUID-separated bridge tool-call IDs; the runtime continues to recognize those legacy IDs when blocking stale calls.
21
21
 
22
22
  Cursor SDK `plan` mode (`--cursor-mode plan` or `/cursor-mode plan`) can make Cursor produce plan-oriented text and plan/todo activity. Replay still treats Cursor `createPlan`, `updateTodos`, task/mode, and related workflow activity as display-only Cursor activity. It does not switch pi into plan mode, mutate pi todos, or change pi active tools.
23
23
 
@@ -186,7 +186,7 @@ Lifecycle rules:
186
186
 
187
187
  As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results, then continuing with live post-tool Cursor thinking/text, later Cursor tool batches, or Cursor's final answer as the next assistant turn. For plan-mode runs, neutral Cursor plan/todo cards can therefore appear before the final Cursor plan text.
188
188
 
189
- Bridged pi tool calls follow the same visible pi `toolUse` turn shape, but they are real pi tool executions rather than replayed Cursor results. Local usage accounting uses per-turn raw Cursor SDK `turn-ended.usage` when available before the corresponding pi turn is emitted, including cache read/write fields; observed local full-prompt/partition semantics map into disjoint pi input/cache components and set `usage.totalTokens = inputTokens + outputTokens` for occupancy (distinct from published SDK `toTokenUsage` additive totals). If the local SDK reports no usage in time, the provider falls back to local `input/output` activity estimates with `usage.totalTokens` set to the current replayable context estimate, then ignores later usage for that live run rather than risk applying stale usage to the wrong pi turn. Cloud raw usage remains display-only and uses approximate pi message accounting until its field semantics are independently captured.
189
+ Bridged pi tool calls follow the same visible pi `toolUse` turn shape, but they are real pi tool executions rather than replayed Cursor results. Local usage accounting uses per-turn raw Cursor SDK `turn-ended.usage` when available before the corresponding pi turn is emitted, including cache read/write fields; observed local full-prompt/partition semantics map into disjoint pi input/cache components and set `usage.totalTokens = inputTokens + outputTokens` for occupancy (distinct from published SDK `toTokenUsage` additive totals). If the local SDK reports no safely attributable usage in time, the provider estimates the current context; its disjoint input/cache/output components sum to `usage.totalTokens`. Later raw usage is retained by the separate ledger, not applied retroactively to emitted pi messages. Public billed facts never enter native occupancy. Cloud raw telemetry is retained without assuming LOCAL normalization, while native Cloud messages use approximate context accounting. See [context, compaction, and Cursor usage](../README.md#context-compaction-and-cursor-usage).
190
190
 
191
191
  For shell replay, completed `stdout` / `stderr` remain the primary source. While exactly one shell call is active, the provider also emits a bounded scrubbed preview of the first few `shell-output-delta` stdout/stderr chunks so long-running commands show visible progress before completion. If a successful completed shell result is empty, the replay card uses unambiguous buffered delta data as display-only fallback data. Overlapping shell calls make delta attribution ambiguous, so those fallback/progress deltas are dropped rather than guessed. `(no output)` is kept only when no completed output or safe delta fallback is available.
192
192
 
@@ -202,6 +202,8 @@ Bridge MCP names are also not pi tool names. Cursor may see names such as `pi__s
202
202
 
203
203
  Native replay wrappers are registered only for tool names not already owned by another extension. If another extension already owns a wrapper name needed for replay, pi-cursor-sdk skips only the conflicting wrapper and uses the scrubbed Cursor activity transcript for that tool instead.
204
204
 
205
+ Registration checks the current tool owner on every model sync, not once per process. Pi rebuilds its tool registry after `/new`, `/resume`, reloads, and session switches. Cursor reinstalls wrappers over builtins, but if the new session loads another extension's tool, Cursor stops replaying through that name and leaves the other extension untouched. Replay becomes available again when the conflict disappears. A conflicting name is reported to the user only once. Any `cursor-replay-*` call that reaches a Cursor wrapper with no recorded display fails closed instead of running the real tool.
206
+
205
207
  Disable native replay registration entirely:
206
208
 
207
209
  ```bash
@@ -1,16 +1,16 @@
1
1
  # Cursor Native Tool Visual Audit Workflow
2
2
 
3
- > **Platform Smoke (new):** The required cross-platform release gate includes a deterministic visual card matrix across all targets. See [docs/platform-smoke.md](./platform-smoke.md) for the required cards, assertion contract, and platform-matrix budget.
3
+ > **Cost-conscious verification:** Start offline/faux and reuse exact-input retained evidence. For changed visual behavior needing new live proof, select the smallest meaningful check on one representative environment. The comprehensive [platform matrix](./platform-smoke.md) is optional; no full paid campaign replay, matrix-only host coverage, or automatic paid retries. No paid Cloud testing for generic PRs/releases; only explicitly Cursor Cloud-focused PRs/issues may select necessary focused Cloud proof. Automated Cursor PR reviews continue unchanged. Selected visual checks keep their PNG, JSONL, and cleanup contracts.
4
4
 
5
5
  This workflow is the canonical repo path for verifying Cursor SDK tool replay the way a human sees it in pi's interactive TUI, without stealing macOS focus.
6
6
 
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.
7
+ Use it when changed replay-card behavior needs new visual proof not covered by offline rendering contracts or valid retained evidence. Text logs and JSONL are not enough for visual-parity claims: keep PNGs for the exact prompt, and before/after PNGs for a rendering comparison. Reuse a valid retained baseline instead of paying to recreate it.
8
8
 
9
- Current validation baseline: Node 24+, exact `@cursor/sdk@1.0.32`, official Pi 0.87.1/latest and current `fitchmultz/pi` main. Local validation packages `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` are exact 0.87.1. Optional published Pi and TypeBox peer dependencies use `"*"` ranges per Pi guidance.
9
+ Current validation baseline: Node 24+, exact `@cursor/sdk@1.0.32`, official Pi 0.87.1/latest and current `fitchmultz/pi` main. Development Pi packages `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` are exact 0.99.1, with host TypeBox 1.3.27. Optional published Pi and TypeBox peer dependencies use `"*"` ranges per Pi guidance.
10
10
 
11
11
  ## Cursor SDK 1.0.17 / pi 0.79.0 cutover visual record
12
12
 
13
- Record the required cutover validation here or in the final release handoff. The default matrix is native replay only: the runner forces native replay registration on, forces Cursor setting sources off, disables the pi bridge, disables overlapping built-in pi tool exposure, and clears inherited Cursor SDK event-debug artifact env. With `--event-debug`, debug capture writes to a deterministic directory under the visual output directory. Do not commit raw ANSI logs, screenshots, terminal recordings, debug artifacts, or `.debug/visual-smoke` scratch files.
13
+ This section retains historical cutover requirements, not a new mandatory paid campaign. For current changes, select only necessary changed categories and reuse valid retained proof. The default matrix is native replay only: the runner forces native replay registration on, forces Cursor setting sources off, disables the pi bridge, disables overlapping built-in pi tool exposure, and clears inherited Cursor SDK event-debug artifact env. With `--event-debug`, debug capture writes to a deterministic directory under the visual output directory. Do not commit raw ANSI logs, screenshots, terminal recordings, debug artifacts, or `.debug/visual-smoke` scratch files.
14
14
 
15
15
  | Field | Required value / evidence |
16
16
  | --- | --- |
@@ -36,7 +36,7 @@ Required prompt matrix for this cutover:
36
36
  | `read-missing` | `Use only your file read tool to read .debug/visual-smoke/does-not-exist.txt. Then explain the result. Do not use shell, grep, glob, find, ls, edit, or write.` | `toolCall.name=read`, `toolResult.toolName=read`, `isError=true` | True failure is visible, bounded, and distinct from neutral Cursor activity |
37
37
  | `workflow-activity` | `Stay in Cursor plan mode. If Cursor exposes plan, todo, task, or mode activity for this request, use that capability to outline a tiny unit test without editing files. Otherwise answer with a concise numbered plan. Do not use shell or file mutation tools.` | Optional: completed `cursor` activity whose details/source identify `createPlan`, `updateTodos`, `task`, or mode activity. If absent, record this category as not exercised. | Optional: neutral Cursor workflow activity is neutral, not red, and does not mutate pi plan/todo state. If absent, do not claim this visual category passed. |
38
38
 
39
- Do not mark a category passed because the prompt was sent. A category passes only when the PNG shows the expected card and the JSONL shows the expected completed `toolCall` / `toolResult` pair. If Cursor chooses a different tool, rerun with a tighter prompt or record that the category was not exercised.
39
+ Do not mark a category passed because the prompt was sent. A category passes only when the PNG shows the expected card and the JSONL shows the expected completed `toolCall` / `toolResult` pair. If Cursor chooses a different tool, record the category as not exercised and diagnose offline; do not automatically rerun.
40
40
 
41
41
  ## When to use this
42
42
 
@@ -63,7 +63,7 @@ The canonical workflow is now offscreen and browser-rendered:
63
63
  5. Save PNG screenshots with `agent_browser` when the harness is available, or Playwright directly when running outside that harness.
64
64
  6. Inspect the session JSONL for exact persisted `toolCall` / `toolResult` data.
65
65
 
66
- This is the best default focused visual-debug path because it exercises the real pi TUI, captures card class/color/label/order/truncation issues before users see them, avoids desktop focus stealing, and leaves reviewable artifacts. Use visible Terminal/Ghostty screenshots only for terminal-specific or pixel-level bugs that cannot be judged through browser-rendered ANSI. The cross-platform release gate remains [Platform Smoke](./platform-smoke.md).
66
+ This is the best default focused visual-debug path because it exercises the real pi TUI, captures card class/color/label/order/truncation issues before users see them, avoids desktop focus stealing, and leaves reviewable artifacts. Use visible Terminal/Ghostty screenshots only for terminal-specific or pixel-level bugs that cannot be judged through browser-rendered ANSI. See [Platform Smoke](./platform-smoke.md) for focused suite selection and the optional comprehensive matrix.
67
67
 
68
68
  ## Tool stack
69
69
 
@@ -120,7 +120,7 @@ The runner writes the `.png` through Playwright by default. In the pi agent harn
120
120
 
121
121
  ## Before/after comparison
122
122
 
123
- Use a clean worktree for the baseline and the active worktree for the candidate change:
123
+ Reuse an exact-input retained baseline when available. Only if a necessary comparison has no valid retained baseline, use a clean worktree for it and the active worktree for the candidate change:
124
124
 
125
125
  ```bash
126
126
  BASE=/tmp/pi-cursor-visual-review
@@ -137,7 +137,7 @@ git worktree add --detach "$BEFORE_WT" "$BASE_COMMIT"
137
137
  ln -s "$AFTER_WT/node_modules" "$BEFORE_WT/node_modules"
138
138
  ```
139
139
 
140
- Then run the same prompt against both extension dirs:
140
+ Run only the missing side of the comparison; the following commands illustrate each side, not an unconditional two-call requirement:
141
141
 
142
142
  ```bash
143
143
  npm run smoke:visual -- \
@@ -1,12 +1,12 @@
1
1
  # Cursor Testing Lessons
2
2
 
3
- > **Platform Smoke:** The required local cross-platform release gate is `npm run smoke:platform:all`; cloud-runtime changes additionally require `npm run smoke:cloud`. See [the platform smoke runbook](./platform-smoke.md). For portable guidance, see the [implementation reference](./platform-smoke-implementation.md#portability-to-other-pi-extensions) and the repo-local `docs/pi-extension-platform-testing.md` from a Crabbox checkout. The live smoke checklist remains useful for inner-loop development but is not the release gate.
3
+ > **Cost-conscious verification:** Offline/faux checks first; reuse exact-input retained evidence. Only changed behavior needing new real-service proof warrants the smallest meaningful live check on one representative environment. Docs/metadata-only changes need no paid runs. No full paid campaign replay, matrix-only host coverage, or automatic paid retries. No paid Cloud testing for generic PRs/releases; only explicitly Cursor Cloud-focused PRs/issues may select a necessary focused Cloud check. Automated Cursor PR reviews continue unchanged. `npm run smoke:platform:all` is optional comprehensive coverage. See [the platform smoke runbook](./platform-smoke.md) and its [implementation reference](./platform-smoke-implementation.md#portability-to-other-pi-extensions).
4
4
 
5
5
  ## Purpose
6
6
 
7
7
  This document records maintainer testing lessons for `pi-cursor-sdk`. It complements unit tests and the [Cursor live smoke checklist](./cursor-live-smoke-checklist.md). Use it when adding regression coverage, debugging false-green releases, or building isolated smoke harnesses.
8
8
 
9
- For a **minimal one-session dogfood pass** (baseline env, one native + one bridge call, JSONL ID patterns, bootstrap manifest, edit diff card), use the [Cursor dogfood checklist](./cursor-dogfood-checklist.md) as inner-loop evidence before running the platform smoke gate.
9
+ For a **minimal one-session dogfood pass** (baseline env, one native + one bridge call, JSONL ID patterns, bootstrap manifest, edit diff card), use the [Cursor dogfood checklist](./cursor-dogfood-checklist.md) only when its selected changed behavior needs new live proof; do not follow it with a full paid matrix by default.
10
10
 
11
11
  ## Core lesson: integration-shaped bugs beat unit mocks
12
12
 
@@ -50,9 +50,9 @@ If resync runs but `context.tools` is still stale (e.g. only `read` listed), the
50
50
 
51
51
  ## Stock/transcript provider contract
52
52
 
53
- `test/cursor-provider-pi-context.test.ts` drives real `ModelRuntime` / `ModelRegistry` requests into `streamCursor`, with only Cursor SDK execution mocked. Run it against each supported host with all Pi peer imports pinned to that host (including nested native imports); a top-level package version alone is not resolution evidence. Cover official Pi 0.87.1, official latest, and current `fitchmultz/pi` main. The currently validated local `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` packages are 0.87.1.
53
+ `test/cursor-provider-pi-context.test.ts` drives real `ModelRuntime` / `ModelRegistry` requests into `streamCursor`, with only Cursor SDK execution mocked. Run it against each supported host with all Pi peer imports pinned to that host (including nested native imports); a top-level package version alone is not resolution evidence. Cover official Pi 0.87.1, official latest, and current `fitchmultz/pi` main. The exact development Pi cohort is 0.99.1, with host TypeBox 1.3.27.
54
54
 
55
- The test covers bootstrap/incremental prompts, empty-vs-absent request tools, native replay/drain, cloud fresh/bootstrap selection, and actual host prompt serialization for context files and skills. It is offline contract evidence, not a replacement for the required live platform/cloud release gates.
55
+ The test covers bootstrap/incremental prompts, empty-vs-absent request tools, native replay/drain, cloud fresh/bootstrap selection, and actual host prompt serialization for context files and skills. `test/native-cursor-flow.test.mjs` additionally exercises the compiled extension through the real loader and registered Cursor provider, substituting only the external SDK transport/storage. It verifies actual bridge tool execution, replay without file access, persisted usage/lineage, tree, compaction, queued steering, abort and reload/disposal. Explicit `tools` is now an allowlist, not merely an initial active set; use `defaultTools` for a native fixture that allows replay wrappers to activate. It is offline contract evidence. Add live proof only for changed behavior that still needs real-service evidence under the cost policy above; do not rerun unchanged live lanes or infer full live coverage from offline tests.
56
56
 
57
57
  ## Auth: use `auth.json`, not only env
58
58
 
@@ -132,6 +132,7 @@ Every live check should use its own `--session-dir` under the isolated tree. Do
132
132
  | Inherited shell env | mise/profile hooks hung or polluted runs | Use `env -i ... MISE_DISABLE=1` for isolated pi calls |
133
133
  | No per-check timeout | One stuck prompt blocked entire harness | Wrap each live check with timeout/watchdog |
134
134
  | stdout-only assertions | Missed replay failures persisted only in JSONL | Scan JSONL for `Tool grep/cursor/find/ls not found` |
135
+ | Unspecified “visible” marker source | Conversation recall became an empty-workspace search | Specify conversation history only, with no tools or file inspection; compare actual send payload and SDK events before blaming history loss |
135
136
  | Naive JSONL substring scan | Successful `read` of docs mentioning replay errors looked like failures | `validate-smoke-jsonl.mjs` only flags error `toolResult` / error assistant messages |
136
137
  | Plan strip only on first turn | Under-tested multi-turn resync | Shim strips on every `turn_start`; stress multi-turn separately |
137
138
  | Assuming env auth equals pi auth | False "blocked" or false "pass" in CI-like shells | Check `auth.json` provider keys explicitly when needed |
@@ -169,23 +170,31 @@ Successful tool results are ignored even when file contents mention those string
169
170
 
170
171
  Session summaries can hide per-message usage bugs. When investigating token or compaction regressions, inspect assistant message `usage` rows directly:
171
172
 
172
- - `usage.input`, `usage.output`, `usage.cacheRead`, and `usage.cacheWrite` are additive spend-style counters for the assistant turn.
173
- - `usage.totalTokens` is pi context occupancy for that turn, not a value to sum across all assistant messages.
174
- - Distinguish published SDK `TokenUsage` from observed raw local `turn-ended.usage`: installed `@cursor/sdk` `toTokenUsage` sets `totalTokens = input+output+cacheRead+cacheWrite`, but captured local raw `turn-ended.usage` keeps `inputTokens` as the full prompt while cache fields partition it (see `test/fixtures/cursor-sdk-turn-ended-usage-1.0.23.json` / issue #196). Map raw turn-ended samples to pi as uncached `input = inputTokens - cacheReadTokens - cacheWriteTokens` and `totalTokens = inputTokens + outputTokens`.
173
+ - Native `usage.input`, `output`, `cacheRead`, and `cacheWrite` must sum to `totalTokens` and describe the same current context. Native overflow reads prompt components, not only total; keeping a small total beside cumulative billed input/cache still triggers false compaction.
174
+ - Native configured-price estimates are per emitted message, not complete Cursor invoice totals. Recorded raw/reported/billed sources overlap and must not be summed.
175
+ - Distinguish observed LOCAL full-prompt `turn-ended.usage` from public billed `AgentUsage`: normalize raw input by subtracting its cache partition once. Billed input is already uncached. The captured SDK 1.0.32 case has raw 4232 input / 4096 cache read / 3 output, corrected 136 uncached / 4096 cache / 3 output = 4235; original additive wait/handle total 8331 remains reported telemetry only. Public getter transport coverage verifies the documented disjoint billed contract; do not invent successful live endpoint access when it was unavailable.
175
176
  - No single assistant message should persist SDK/full-agent-context-sized usage outside the selected model window.
176
177
  - Real bad-session evidence should be reduced to a sanitized fixture, like `test/fixtures/cursor-run-usage-compaction-poison.jsonl`, instead of committing raw session JSONL.
177
178
 
178
- The compaction poison fixture mirrors the observed failure shape: one assistant message with `RunResult`-sized input/cache-read counts near 1M immediately before compaction. Regression coverage should prove that such usage falls back to bounded pi estimates before it reaches `AssistantMessage.usage`.
179
+ The compaction poison fixture mirrors the observed failure shape: one assistant message with `RunResult`-sized input/cache-read counts near 1M immediately before compaction. Regression coverage proves that such usage falls back to bounded pi estimates before reaching `AssistantMessage.usage`.
180
+
181
+ Use native registered-provider tests for the full scheduler boundary. `test/native-cursor-flow.test.mjs` covers large cumulative bills with no false compaction, genuine pressure still auto-compacting, and manual compaction persisting after reopen. Its floor controls exercise actual compaction/context-edit source chronology, future/equal clocks, quoted wrappers and request-local transformations without mocking Pi's scheduler, receipt or projection. `test/cursor-provider-binding.test.ts` additionally verifies actual native header/signal identity through sequential compaction summaries, retry and tree navigation.
182
+
183
+ For LOCAL summary isolation, inspect actual SDK create/send options and persisted lineage, not assistant text alone: tools/settings empty, no bridge/MCP/manifest, mode `agent`, fresh IDs, no ordinary resume writes, agent/store disposed. Materialize the fixture's temporary directory and assert it is removed **when the native operation returns**; merely observing `dispose()` or eventual removal misses a terminal-emission/cleanup race. SDK fixtures must honor `tools: []` instead of replaying historical marker-driven tool calls despite the restriction.
184
+
185
+ `test/cursor-usage-ledger.test.ts` uses real public SessionManager custom entries and bounded filesystem journals. It protects restart, optional mirror failure, backward/equal snapshot clocks, revised/aggregate-only/large bills, linear compact-revision storage, unavailable-to-late observations, abandon without a billing fetch, late abort, owner/sibling switches, inherited cross-directory gaps, lost native claims, pricing tiers, torn-tail prefix export/native-fork recovery and malformed/symlink files. Export is a canonical view of facts and uncertainty, not an invented client-run billing join. A failed durable append must not advance recognized accounting.
186
+
187
+ For supported official hosts without committing a repair, use `node scripts/ci-compatibility.mjs --working-tree --official-only --versions 0.87.1,0.99.1,1.0.2`. This hashes an isolated source snapshot, builds/packs it, tests package loading and runs real native session contracts. Keep matching host-peer resolution and exact source evidence; offline SDK transport substitution proves integration, not live service availability. See [context/accounting boundaries](./cursor-model-ux-spec.md#context-and-accounting-boundaries) for the operational ledger ceiling and public-API limits.
179
188
 
180
189
  ### False-positive edge case (2026-05-23)
181
190
 
182
191
  Plan-strip live smoke can make Cursor `read` testing docs that *document* replay failure strings. A naive whole-record JSON scan reported four failures from one successful `read` toolResult (`isError: false`).
183
192
 
184
- When changing replay scan logic:
193
+ For changed replay scan logic, use offline checks and valid exact-input retained evidence first. These are selectable evidence criteria, not a mandatory paid checklist:
185
194
 
186
- 1. Update `scripts/validate-smoke-jsonl.mjs`
187
- 2. Add/adjust cases in `test/validate-smoke-jsonl.test.ts` (error toolResult must still fail; successful read of doc text must pass)
188
- 3. Re-run `npm run smoke:isolated` on a packed temp install before release
195
+ 1. Update `scripts/validate-smoke-jsonl.mjs` for the intended scanner behavior.
196
+ 2. Add/adjust cases in `test/validate-smoke-jsonl.test.ts` (error toolResult must still fail; successful read of doc text must pass).
197
+ 3. Use offline packed-install checks (`SKIP_LIVE=1 npm run smoke:isolated`) or valid exact-input retained proof for packaging/replay evidence. Only if the changed behavior still needs real-service proof, select the smallest meaningful existing live check on one representative environment; do not automatically run the full paid `npm run smoke:isolated` sequence. Keep the selected check's replay assertions, persisted JSONL, visual proof when relevant, and cleanup intact.
189
198
 
190
199
  ## Plan-mode regression scenario
191
200
 
@@ -209,7 +218,7 @@ Pass criteria:
209
218
 
210
219
  ## Local validation ladder
211
220
 
212
- Run local checks first, then the local platform smoke gate before claiming release-ready for provider/runtime changes. Add `npm run smoke:cloud` for cloud-runtime changes:
221
+ Run offline checks first, then reuse retained proof whose tested inputs are unchanged. If changed behavior still needs real-service proof, choose one existing meaningful check on a representative environment. Do not run every command below as a paid ladder; docs/metadata-only changes need zero paid runs.
213
222
 
214
223
  TypeScript 7 owns builds and type checks. `@typescript/typescript6` is dev-only for the AST architecture test because TypeScript 7 has no stable compiler API. Vitest 5 defaults `clearMocks` to `true`.
215
224
 
@@ -217,12 +226,25 @@ TypeScript 7 owns builds and type checks. `@typescript/typescript6` is dev-only
217
226
  npm test
218
227
  npm run typecheck
219
228
  npm pack --dry-run
220
- SKIP_LIVE=1 npm run smoke:isolated
221
- npm run smoke:isolated # inner-loop helper; requires auth.json or CURSOR_API_KEY
222
- npm run smoke:live # inner-loop partial tmux checklist subset
223
- npm run smoke:platform:doctor
229
+ SKIP_LIVE=1 npm run smoke:isolated # offline helper
230
+ ```
231
+
232
+ Only when changed behavior requires new local service proof, select the relevant existing suite (example: restart) and one representative target:
233
+
234
+ ```bash
235
+ node scripts/platform-smoke.mjs run --target macos --suite cursor-local-resume-restart
236
+ ```
237
+
238
+ Optional comprehensive coverage, not a default ship step:
239
+
240
+ ```bash
224
241
  npm run smoke:platform:all
225
- npm run smoke:cloud # required for cloud-runtime changes
242
+ ```
243
+
244
+ Only for a PR/issue explicitly focused on Cursor Cloud, and only if this coverage is necessary (the multi-lane matrix is not mandatory):
245
+
246
+ ```bash
247
+ npm run smoke:cloud
226
248
  ```
227
249
 
228
250
  After changing `scripts/validate-smoke-jsonl.mjs` or replay scan expectations, also run:
@@ -231,16 +253,18 @@ After changing `scripts/validate-smoke-jsonl.mjs` or replay scan expectations, a
231
253
  npm test -- test/validate-smoke-jsonl.test.ts
232
254
  ```
233
255
 
234
- Then use the [Cursor live smoke checklist](./cursor-live-smoke-checklist.md) only for focused inner-loop surfaces the scripts do not cover (bridge MCP, abort/cancel, full TUI observation, packaging review, cleanup) before rerunning the local platform smoke gate and, for cloud-runtime changes, `npm run smoke:cloud`.
256
+ Use the [Cursor live smoke checklist](./cursor-live-smoke-checklist.md) as a menu for necessary changed-behavior proof the selected script does not cover, not a full paid campaign. Keep distinct behavior assertions, persisted evidence, visual inspection when relevant, and cleanup intact. Diagnose failures offline; no automatic paid retries.
235
257
 
236
258
  ## What belongs in CI vs platform/manual smoke
237
259
 
238
260
  - **CI / default `npm test`:** mocked provider tests, extension lifecycle tests, JSONL validator tests, script syntax/help checks. No live Cursor calls.
239
- - **Local platform release gate:** `npm run smoke:platform:all` (runs doctor first). Requires real Cursor auth and cross-platform Crabbox setup.
240
- - **Cloud runtime release gate:** `npm run smoke:cloud` for PRs that touch actual cloud runtime execution.
241
- - **Focused manual smoke:** `npm run smoke:isolated`, `npm run smoke:live`, and selected live-checklist sections for inner-loop debugging of behavior mocks cannot reproduce.
261
+ - **Focused local live proof:** a necessary existing single-suite/single-target check for changed behavior only. Reuse valid retained proof instead of repeating unchanged lanes.
262
+ - **Comprehensive local matrix:** `npm run smoke:platform:all` (doctor first), optional rather than an unconditional commit/release gate.
263
+ - **Cloud testing exception:** no paid Cloud testing for generic PRs/releases. Only PRs/issues explicitly focused on Cursor Cloud may select a necessary focused Cloud check. Cloud file touch alone does not qualify; the multi-lane `npm run smoke:cloud` is not mandatory. Run/evidence and agent/repository cleanup contracts remain intact.
264
+ - **Automated Cursor PR reviews:** continue unchanged; do not disable them or introduce extra push/review churn for testing.
265
+ - **Manual helpers:** `smoke:isolated`, `smoke:live`, and selected checklist sections are available, but run only the smallest necessary check.
242
266
 
243
- If platform smoke auth or target setup is unavailable, report the release as **blocked**, not skipped-ready.
267
+ If a selected necessary live check lacks auth or setup, report that specific proof gap, not a pass. Optional unselected matrix lanes do not block landing.
244
268
 
245
269
  ## Cursor SDK event capture probe
246
270
 
@@ -79,7 +79,7 @@ To reproduce a **minimal** surface (pi-cursor-sdk + Cursor host only), use exten
79
79
  | ID prefix | Meaning |
80
80
  | --- | --- |
81
81
  | `cursor-replay-*` | Display-only replay of Cursor SDK activity |
82
- | `cursor-pi-bridge-run-*` | Live pi execution via bridge |
82
+ | `cursor-pi-bridge-run-*` (run) / `cursor-pi-bridge-<32 hex>-t<counter>` (tool) | Live pi execution via bridge |
83
83
 
84
84
  Example mistake: treating `cursor-replay-…` as a tool to invoke. Replay never re-runs work.
85
85