pi-cursor-sdk 0.5.3 → 0.5.4

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 (94) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/README.md +62 -816
  3. package/dist/context.js +11 -5
  4. package/dist/cursor-agents-context.js +6 -6
  5. package/dist/cursor-api-key.js +1 -1
  6. package/dist/cursor-bridge-contract.js +1 -13
  7. package/dist/cursor-cloud-local-state.js +0 -1
  8. package/dist/cursor-config.js +1 -17
  9. package/dist/cursor-fallback-models.generated.js +264 -1
  10. package/dist/cursor-live-run-coordinator.js +1 -0
  11. package/dist/cursor-mcp-timeout-override.js +6 -17
  12. package/dist/cursor-native-tool-display-replay.js +5 -5
  13. package/dist/cursor-native-tools.js +11 -0
  14. package/dist/cursor-pi-tool-bridge-env.js +25 -2
  15. package/dist/cursor-pi-tool-bridge-server.js +98 -48
  16. package/dist/cursor-pi-tool-bridge-snapshot.js +1 -1
  17. package/dist/cursor-pi-tool-bridge.js +4 -12
  18. package/dist/cursor-provider-errors.js +0 -6
  19. package/dist/cursor-provider-live-run-drain.js +8 -3
  20. package/dist/cursor-provider-overflow.js +7 -21
  21. package/dist/cursor-provider-turn-prepare.js +8 -1
  22. package/dist/cursor-provider-turn-runner.js +17 -6
  23. package/dist/cursor-provider.js +1 -4
  24. package/dist/cursor-runtime-state.js +3 -3
  25. package/dist/cursor-sdk-event-debug.js +2 -2
  26. package/dist/cursor-sdk-output-filter.js +1 -1
  27. package/dist/cursor-sdk-process-error-guard.js +1 -2
  28. package/dist/cursor-session-agent.js +6 -8
  29. package/dist/cursor-session-scope.js +1 -7
  30. package/dist/cursor-session-store.js +1 -1
  31. package/dist/cursor-state.js +9 -23
  32. package/dist/cursor-tool-lifecycle.js +0 -3
  33. package/dist/cursor-tool-manifest.js +3 -1
  34. package/dist/cursor-tool-presentation-registry.js +2 -4
  35. package/dist/cursor-tool-visibility.js +0 -3
  36. package/dist/cursor-transcript-tool-specs.js +1 -2
  37. package/dist/index.js +21 -0
  38. package/dist/model-discovery.js +0 -1
  39. package/dist/model-list-cache.js +4 -9
  40. package/docs/cursor-live-smoke-checklist.md +3 -3
  41. package/docs/cursor-model-ux-spec.md +16 -8
  42. package/docs/cursor-native-tool-replay.md +2 -2
  43. package/docs/cursor-native-tool-visual-audit.md +1 -1
  44. package/docs/cursor-testing-lessons.md +15 -3
  45. package/docs/cursor-tool-surfaces.md +32 -3
  46. package/docs/development.md +93 -0
  47. package/docs/platform-smoke-implementation.md +3 -2
  48. package/docs/reference.md +806 -0
  49. package/package.json +6 -4
  50. package/scripts/platform-smoke/artifact-secrets.mjs +13 -4
  51. package/scripts/platform-smoke/local-resume-runner.mjs +0 -1
  52. package/scripts/platform-smoke/target-runtime.mjs +4 -2
  53. package/scripts/platform-smoke/targets.mjs +0 -1
  54. package/shared/cursor-sdk-output-filter.d.mts +2 -0
  55. package/shared/cursor-sdk-output-filter.mjs +92 -28
  56. package/shared/cursor-sensitive-text.d.mts +1 -0
  57. package/shared/cursor-sensitive-text.mjs +7 -0
  58. package/src/context.ts +16 -9
  59. package/src/cursor-agents-context.ts +6 -6
  60. package/src/cursor-api-key.ts +1 -1
  61. package/src/cursor-bridge-contract.ts +1 -15
  62. package/src/cursor-cloud-local-state.ts +0 -2
  63. package/src/cursor-config.ts +1 -24
  64. package/src/cursor-fallback-models.generated.ts +264 -1
  65. package/src/cursor-live-run-coordinator.ts +5 -0
  66. package/src/cursor-mcp-timeout-override.ts +6 -30
  67. package/src/cursor-native-tool-display-replay.ts +4 -6
  68. package/src/cursor-native-tools.ts +14 -0
  69. package/src/cursor-pi-tool-bridge-env.ts +31 -2
  70. package/src/cursor-pi-tool-bridge-server.ts +89 -45
  71. package/src/cursor-pi-tool-bridge-snapshot.ts +0 -1
  72. package/src/cursor-pi-tool-bridge.ts +2 -18
  73. package/src/cursor-provider-errors.ts +0 -8
  74. package/src/cursor-provider-live-run-drain.ts +8 -2
  75. package/src/cursor-provider-overflow.ts +6 -24
  76. package/src/cursor-provider-turn-prepare.ts +10 -1
  77. package/src/cursor-provider-turn-runner.ts +17 -6
  78. package/src/cursor-provider.ts +0 -5
  79. package/src/cursor-runtime-state.ts +3 -3
  80. package/src/cursor-sdk-event-debug.ts +2 -2
  81. package/src/cursor-sdk-output-filter.ts +2 -1
  82. package/src/cursor-sdk-process-error-guard.ts +1 -3
  83. package/src/cursor-session-agent.ts +8 -8
  84. package/src/cursor-session-scope.ts +1 -9
  85. package/src/cursor-session-store.ts +1 -1
  86. package/src/cursor-state.ts +9 -36
  87. package/src/cursor-tool-lifecycle.ts +0 -4
  88. package/src/cursor-tool-manifest.ts +4 -1
  89. package/src/cursor-tool-presentation-registry.ts +3 -5
  90. package/src/cursor-tool-visibility.ts +0 -4
  91. package/src/cursor-transcript-tool-specs.ts +0 -3
  92. package/src/index.ts +18 -0
  93. package/src/model-discovery.ts +0 -1
  94. package/src/model-list-cache.ts +4 -10
@@ -13,8 +13,7 @@ import { cursorSettingsScopeForContext, getCursorSessionSettings, registerCursor
13
13
  import { refreshSessionCursorAgentConfig } from "./cursor-session-agent.js";
14
14
  import { getCursorModelMetadata } from "./model-discovery.js";
15
15
  import { cursorFastDefaultsFromConfig, getCursorSdkUserConfigPath, loadCursorSdkUserConfig, mergeCursorSdkConfigForUpdate, resolveCursorFastDefault, resolveCursorLocalStoreRoot, updateCursorSdkConfig, } from "./cursor-config.js";
16
- import { CURSOR_RUNTIME_ENTRY_TYPE, formatCursorStatus, registerCursorCloudRuntimeControls, resetCursorRuntimeStateForTests, resolveCursorStatusRuntime, restoreCursorCliState, restoreSessionCursorRuntimeState, } from "./cursor-runtime-state.js";
17
- export { consumeCursorLocalForceOverride, getCursorCliConfig, getCursorSessionConfig, } from "./cursor-runtime-state.js";
16
+ import { formatCursorStatus, registerCursorCloudRuntimeControls, resetCursorRuntimeStateForTests, resolveCursorStatusRuntime, restoreCursorCliState, restoreSessionCursorRuntimeState, } from "./cursor-runtime-state.js";
18
17
  export const CURSOR_FOOTER_ENV = "PI_CURSOR_FOOTER";
19
18
  const FAST_ENTRY_TYPE = "cursor-fast-state";
20
19
  const MODE_ENTRY_TYPE = "cursor-mode-state";
@@ -22,10 +21,10 @@ const DEFAULT_CURSOR_AGENT_MODE = "agent";
22
21
  const authoritativeGlobalFastPreferenceIds = new Set();
23
22
  let globalFastPreferences = new Map();
24
23
  const invalidCursorModeNotifiedSessionScopeKeys = new Set();
25
- export function isCursorAgentMode(value) {
24
+ function isCursorAgentMode(value) {
26
25
  return value === "agent" || value === "plan";
27
26
  }
28
- export function parseCursorAgentMode(raw) {
27
+ function parseCursorAgentMode(raw) {
29
28
  if (typeof raw !== "string")
30
29
  return undefined;
31
30
  const mode = raw.trim();
@@ -43,14 +42,11 @@ function getCursorFastEntryModelId(data) {
43
42
  function isCursorModeEntryData(value) {
44
43
  return isCursorAgentMode(asRecord(value)?.mode);
45
44
  }
46
- function getConfigPath() {
47
- return getCursorSdkUserConfigPath();
48
- }
49
45
  function loadGlobalFastPreferences() {
50
46
  return cursorFastDefaultsFromConfig(loadCursorSdkUserConfig());
51
47
  }
52
48
  function saveGlobalFastPreference(modelId, fast) {
53
- updateCursorSdkConfig(getConfigPath(), (current) => {
49
+ updateCursorSdkConfig(getCursorSdkUserConfigPath(), (current) => {
54
50
  const fastDefaults = { ...asRecord(current.fastDefaults), [modelId]: fast };
55
51
  return {
56
52
  ...current,
@@ -59,7 +55,7 @@ function saveGlobalFastPreference(modelId, fast) {
59
55
  }, { newFileMode: 0o600 });
60
56
  }
61
57
  function saveGlobalCursorHttp1Enabled(enabled) {
62
- updateCursorSdkConfig(getConfigPath(), (current) => mergeCursorSdkConfigForUpdate(current, {
58
+ updateCursorSdkConfig(getCursorSdkUserConfigPath(), (current) => mergeCursorSdkConfigForUpdate(current, {
63
59
  local: { useHttp1ForAgent: enabled },
64
60
  }), { newFileMode: 0o600 });
65
61
  }
@@ -134,10 +130,10 @@ function getEffectiveFast(modelId, scopeKey) {
134
130
  function formatInvalidCursorMode(raw) {
135
131
  return `Invalid --cursor-mode "${raw}". Use "agent" or "plan".`;
136
132
  }
137
- export function getStoredCursorAgentMode(scopeKey) {
133
+ function getStoredCursorAgentMode(scopeKey) {
138
134
  return getCursorSessionSettings(scopeKey).mode ?? DEFAULT_CURSOR_AGENT_MODE;
139
135
  }
140
- export function resolveCursorAgentMode(scopeKey) {
136
+ function resolveCursorAgentMode(scopeKey) {
141
137
  const cliCursorModeState = getCursorSessionSettings(scopeKey).cli.mode;
142
138
  switch (cliCursorModeState.kind) {
143
139
  case "valid":
@@ -271,7 +267,8 @@ function formatEffectiveCursorSettingSourcesLabel(raw = process.env[CURSOR_SETTI
271
267
  const rawLabel = raw?.trim() ? raw.trim() : `(unset → ${DEFAULT_CURSOR_SETTING_SOURCES.join(",")})`;
272
268
  return `${rawLabel} (effective: ${effectiveLabel})`;
273
269
  }
274
- export function formatCursorToolsDebugReport(pi, env = process.env) {
270
+ function formatCursorToolsDebugReport(pi) {
271
+ const env = process.env;
275
272
  const bridgeEnabled = resolveCursorPiToolBridgeEnabled(env);
276
273
  const manifestEnabled = resolveCursorToolManifestEnabled(env);
277
274
  const lines = [
@@ -528,16 +525,5 @@ function resetCursorModeStateForTests() {
528
525
  authoritativeGlobalFastPreferenceIds.clear();
529
526
  }
530
527
  export const __testUtils = {
531
- FAST_ENTRY_TYPE,
532
- MODE_ENTRY_TYPE,
533
- CURSOR_HTTP1_ENTRY_TYPE,
534
- RUNTIME_ENTRY_TYPE: CURSOR_RUNTIME_ENTRY_TYPE,
535
- DEFAULT_CURSOR_AGENT_MODE,
536
- getConfigPath,
537
- loadGlobalFastPreferences,
538
- get sessionFastPreferences() { return getCursorSessionSettings().fast; },
539
- getSessionCursorAgentMode: () => getCursorSessionSettings().mode,
540
- getCliCursorAgentMode: () => { const mode = getCursorSessionSettings().cli.mode; return mode.kind === "valid" ? mode.mode : undefined; },
541
- getCliCursorModeState: () => getCursorSessionSettings().cli.mode,
542
528
  resetCursorModeStateForTests,
543
529
  };
@@ -82,9 +82,6 @@ function buildCursorToolLifecycleLabelFromVisibility(visibility, apiKey) {
82
82
  return undefined;
83
83
  }
84
84
  }
85
- export function buildCursorToolLifecycleLabel(toolCall, apiKey) {
86
- return buildCursorToolLifecycleLabelFromVisibility(classifyCursorToolVisibility(toolCall), apiKey);
87
- }
88
85
  export function formatCursorToolLifecycleProgressText(toolCall, apiKey) {
89
86
  const visibility = classifyCursorToolVisibility(toolCall);
90
87
  const label = buildCursorToolLifecycleLabelFromVisibility(visibility, apiKey);
@@ -13,7 +13,9 @@ export function buildCursorToolManifestText(options = {}) {
13
13
  const includePiBridgeGuidance = options.includePiBridgeGuidance !== false;
14
14
  const lines = [
15
15
  "Callable tool surfaces this run:",
16
- `- Cursor host/MCP: ${CURSOR_HOST_TOOL_MANIFEST_SUMMARY}; configured MCP depends on Cursor settings.`,
16
+ options.nativeToolsEnabled === false
17
+ ? "- Cursor native tools (including task/subagents): disabled (PI_CURSOR_NATIVE_TOOLS=0). MCP remains available; configured MCP depends on Cursor settings."
18
+ : `- Cursor host/MCP: ${CURSOR_HOST_TOOL_MANIFEST_SUMMARY}; configured MCP depends on Cursor settings.`,
17
19
  "- Pi tool toggles affect pi tools/bridge exposure only; they do not disable Cursor host/configured MCP tools.",
18
20
  ];
19
21
  const bridgeTools = includePiBridgeGuidance ? options.bridgeSnapshot?.tools ?? [] : [];
@@ -7,13 +7,12 @@ import { formatReplaySemSearchQuery, readCursorReplaySummaryString, summarizeRep
7
7
  import { truncateCursorDisplayLine } from "./cursor-display-text.js";
8
8
  import { getCursorTaskActivityTitle } from "./cursor-task-presentation.js";
9
9
  import { buildCreatePlanReplaySummaryArgs, buildDeleteReplayDetailFields, buildDeleteReplaySummaryArgs, buildGenerateImageReplayDetailFields, buildGenerateImageReplaySummaryArgs, buildMcpReplaySummaryArgs, buildReadLintsReplaySummaryArgs, buildRecordScreenReplaySummaryArgs, buildSemSearchReplaySummaryArgs, buildTaskReplaySummaryArgs, buildTodoReplaySummaryArgs, buildWebFetchReplaySummaryArgs, buildWebSearchReplaySummaryArgs, } from "./cursor-replay-activity-builders.js";
10
- import { CURSOR_REPLAY_SOURCE_TOOL_NAMES } from "./cursor-replay-source-names.js";
11
10
  export const CURSOR_REPLAY_ACTIVITY_TOOL_NAME = "cursor";
12
11
  const EMPTY_REPLAY_DETAIL_FIELDS = () => ({});
13
12
  const COLLAPSED_REPLAY_DETAIL_FIELDS = () => ({ collapseDetailsByDefault: true });
14
13
  const WEB_SEARCH_NAME_PATTERN = /^(?:web[-_ ]?search|search[-_ ]?web|websearch|browser[-_ ]?search)$/i;
15
14
  const WEB_FETCH_NAME_PATTERN = /^(?:web[-_ ]?fetch|fetch[-_ ]?web|webfetch|browser[-_ ]?fetch|fetch[-_ ]?url)$/i;
16
- export const CURSOR_TOOL_PRESENTATION_SPECS = [
15
+ const CURSOR_TOOL_PRESENTATION_SPECS = [
17
16
  {
18
17
  normalizedName: "read",
19
18
  nameAliases: ["read_file"],
@@ -218,8 +217,7 @@ const WEB_KIND_BY_PATTERN = CURSOR_TOOL_PRESENTATION_SPECS.flatMap((spec) => {
218
217
  return [];
219
218
  return webNamePatterns.map((pattern) => ({ pattern, webKind }));
220
219
  });
221
- export const CURSOR_KNOWN_NORMALIZED_TOOL_NAMES = CURSOR_REPLAY_SOURCE_TOOL_NAMES;
222
- export function getCursorToolPresentationSpec(name) {
220
+ function getCursorToolPresentationSpec(name) {
223
221
  const trimmed = name.trim();
224
222
  if (!trimmed)
225
223
  return undefined;
@@ -23,6 +23,3 @@ export function classifyCursorToolVisibility(toolCall) {
23
23
  fastLocalDiscovery: config?.fastLocalDiscovery ?? false,
24
24
  };
25
25
  }
26
- export function isFastLocalDiscoveryTool(toolCall) {
27
- return classifyCursorToolVisibility(toolCall).fastLocalDiscovery;
28
- }
@@ -1,4 +1,4 @@
1
- import { CURSOR_KNOWN_NORMALIZED_TOOL_NAMES, CURSOR_REPLAY_ACTIVITY_TOOL_NAME, getCursorReplayCallSummary, getCursorToolActivityTitle, getCursorToolActivityReplaySpec, getCursorToolGenerateImageReplaySpec, } from "./cursor-tool-presentation-registry.js";
1
+ import { CURSOR_REPLAY_ACTIVITY_TOOL_NAME, getCursorReplayCallSummary, getCursorToolActivityTitle, getCursorToolActivityReplaySpec, getCursorToolGenerateImageReplaySpec, } from "./cursor-tool-presentation-registry.js";
2
2
  import { resolveCursorEditDiff } from "./cursor-edit-diff.js";
3
3
  import { assembleCursorReplayActivityDetails, assembleCursorReplayGenerateImageDetails, CURSOR_REPLAY_UNREGISTERED_ACTIVITY_TOOL_NAME, } from "./cursor-replay-tool-details.js";
4
4
  import { asRecord, getNumber, getString } from "./cursor-record-utils.js";
@@ -264,7 +264,6 @@ const TOOL_DISPLAY_IMPLEMENTATIONS = {
264
264
  buildPiToolDisplay: (context) => buildActivityReplayDisplay("webFetch", context),
265
265
  },
266
266
  };
267
- export const CURSOR_TOOL_DISPLAY_SPEC_KEYS = CURSOR_KNOWN_NORMALIZED_TOOL_NAMES;
268
267
  function getToolDisplaySpec(name) {
269
268
  if (Object.hasOwn(TOOL_DISPLAY_IMPLEMENTATIONS, name))
270
269
  return TOOL_DISPLAY_IMPLEMENTATIONS[name];
package/dist/index.js CHANGED
@@ -14,14 +14,35 @@ import { registerCursorFallbackIssueWarning } from "./cursor-fallback-warning.js
14
14
  import { registerCursorAgentsContextDedup } from "./cursor-agents-context-registration.js";
15
15
  import { registerCursorOverflowNormalization } from "./cursor-provider-overflow.js";
16
16
  import { registerCursorSdkSessionProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
17
+ import { installCursorSdkProcessOutputFilter } from "./cursor-sdk-output-filter.js";
18
+ import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
17
19
  import { prepareCursorSessionForCompaction } from "./cursor-session-compaction-prep.js";
18
20
  import { registerCursorUsageLedger } from "./cursor-usage-ledger.js";
19
21
  import { registerCursorUsageCommand } from "./cursor-usage-command.js";
20
22
  export default async function (pi) {
23
+ installCursorSdkProcessOutputFilter();
21
24
  // Session cwd must register before other session_start listeners that depend on it.
22
25
  registerCursorSessionScope(pi);
23
26
  registerCursorUsageLedger(pi);
24
27
  registerCursorUsageCommand(pi);
28
+ pi.registerCommand("cursor-abort", {
29
+ description: "Abort this session's pending local Cursor run without undoing completed work",
30
+ handler: async (_args, ctx) => {
31
+ const run = cursorLiveRuns.getActiveForScope(getCursorSessionScopeSnapshot(pi).scopeKey);
32
+ if (run && !run.disposed) {
33
+ if (!ctx.isIdle() && ctx.model?.provider === "cursor")
34
+ ctx.abort();
35
+ await cursorLiveRuns.release(run);
36
+ }
37
+ const message = run
38
+ ? "Pending local Cursor run released. Retry under the current policy; already-executed work was not undone."
39
+ : "No pending local Cursor run in this session.";
40
+ if (ctx.hasUI)
41
+ ctx.ui.notify(message, "info");
42
+ else
43
+ process.stderr.write(`${message}\n`);
44
+ },
45
+ });
25
46
  let fallbackIssue;
26
47
  const registerCursorProvider = registerCursorProviderBinding(pi);
27
48
  const setFallbackWarning = registerCursorFallbackIssueWarning(pi);
@@ -324,6 +324,5 @@ export async function discoverModels(options = {}) {
324
324
  }
325
325
  }
326
326
  export const __testUtils = {
327
- parseContextWindow,
328
327
  registerModelItems,
329
328
  };
@@ -13,10 +13,10 @@ const TTL_ENV_VAR = "PI_CURSOR_SDK_MODEL_CACHE_TTL_MS";
13
13
  function getCachePath() {
14
14
  return join(getAgentDir(), MODEL_LIST_CACHE_FILE);
15
15
  }
16
- export function isModelCacheDisabled() {
16
+ function isModelCacheDisabled() {
17
17
  return parseEnvBoolean(process.env[DISABLE_ENV_VAR], false);
18
18
  }
19
- export function getModelCacheTtlMs() {
19
+ function getModelCacheTtlMs() {
20
20
  const raw = process.env[TTL_ENV_VAR];
21
21
  if (raw === undefined)
22
22
  return DEFAULT_TTL_MS;
@@ -105,7 +105,8 @@ function readCacheFile() {
105
105
  }
106
106
  // Return cached models only when caching is enabled, the key matches, and the
107
107
  // entry is within the TTL. Used on the hot startup path to skip the network.
108
- export function loadFreshCachedModels(keyFingerprint, now = Date.now()) {
108
+ export function loadFreshCachedModels(keyFingerprint) {
109
+ const now = Date.now();
109
110
  if (isModelCacheDisabled())
110
111
  return undefined;
111
112
  const ttlMs = getModelCacheTtlMs();
@@ -159,9 +160,3 @@ export function clearModelListCache() {
159
160
  // Cache cleanup is best-effort; it must not break native logout.
160
161
  }
161
162
  }
162
- export const __testUtils = {
163
- getCachePath,
164
- DEFAULT_TTL_MS,
165
- DISABLE_ENV_VAR,
166
- TTL_ENV_VAR,
167
- };
@@ -71,7 +71,7 @@ Pass criteria:
71
71
 
72
72
  - `node --version` reports Node 24+.
73
73
  - `pi --version` reports official Pi 0.87.1 or the selected official-latest/current-`fitchmultz/pi` compatibility target.
74
- - `npm ls` shows exact `@cursor/sdk@1.0.37`; development `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` 1.0.4 with matching lockfile-resolved transitives, nested host TypeBox 1.3.27 and root validation TypeBox 1.3.36; and the bundled bridge runtime closure `@modelcontextprotocol/server@2.3.1`, `@modelcontextprotocol/hono@2.0.2`, `hono@4.13.13`, and `@hono/node-server@2.1.3`. These locked development snapshots are not qualification targets; resolve latest stable official Pi and current `fitchmultz/pi` main once per workflow run.
74
+ - `npm ls` shows exact `@cursor/sdk@1.0.37`; development `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` 1.1.0 with matching lockfile-resolved Pi cohort, nested host TypeBox 1.3.27 and root validation TypeBox 1.3.36; and the bundled bridge runtime closure `@modelcontextprotocol/server@2.3.1`, `@modelcontextprotocol/hono@2.0.2`, `hono@4.13.13`, and `@hono/node-server@2.1.3`. These locked development snapshots are not qualification targets; resolve latest stable official Pi and current `fitchmultz/pi` main once per workflow run.
75
75
  - `cursor/grok-4.6` appears in the model list.
76
76
  - No Cursor key or auth token is printed.
77
77
  - If neither `~/.pi/agent/auth.json` cursor auth nor `CURSOR_API_KEY` is available, stop and report the live smoke as blocked.
@@ -354,7 +354,7 @@ Script-enforced pass criteria:
354
354
  Additional manual usage checks for provider/accounting changes:
355
355
 
356
356
  - Each assistant message's disjoint input/cache/output components should sum to `totalTokens` and describe one coherent current context, not cumulative billed spend.
357
- - In split runs, earlier tool results can remain in later current-context estimates. Fresh, attributable LOCAL SDK usage takes precedence; late raw and billed facts remain in the separate ledger without rewriting emitted messages. See [context, compaction, and Cursor usage](../README.md#context-compaction-and-cursor-usage).
357
+ - In split runs, earlier tool results can remain in later current-context estimates. Fresh, attributable LOCAL SDK usage takes precedence; late raw and billed facts remain in the separate ledger without rewriting emitted messages. See [context, compaction, and Cursor usage](reference.md#context-compaction-and-cursor-usage).
358
358
 
359
359
  ## 11. Standard local gates
360
360
 
@@ -391,7 +391,7 @@ Use this list to choose meaningful changed-behavior coverage, not to trigger a f
391
391
  - Native replay cards beyond read, especially shell/edit/write cards, when those renderers change.
392
392
  - Bridge question UI when `cursor_ask_question` changes.
393
393
  - MCP timeout override behavior (3600s `callTool` default, 10s initialize/listTools default, and SDK-default unknown protocol stacks) when timeout code changes.
394
- - SDK `semSearch` / `recordScreen` activity replay when those formatters change. There is no reliable local prompt that forces Cursor to call these built-in SDK tools on demand; regression is covered by `test/cursor-tool-transcript.test.ts`. Opportunistically confirm neutral `Cursor semantic search` / `Cursor screen recording` cards if a live run surfaces them.
394
+ - SDK `semSearch` / `recordScreen` activity replay when those formatters change. There is no reliable local prompt that forces Cursor to call these built-in SDK tools on demand; regression is covered by `test/cursor-tool-transcript-mcp-web.test.ts`. Opportunistically confirm neutral `Cursor semantic search` / `Cursor screen recording` cards if a live run surfaces them.
395
395
  - Ambient Cursor setting-source behavior when startup filtering or local Cursor settings handling changes.
396
396
  - Model discovery aliases/context variants when model-discovery code or Cursor SDK versions change.
397
397
 
@@ -22,20 +22,25 @@ Current implementation notes:
22
22
  - Native request receipts retain canonical source-role/order scalars from public `SessionManager.buildSessionProjection()` and `convertToLlm()`, not prompt text. Only exact equivalence with the actual provider request enables native input selection. Hidden `display:false` notices remain model-visible background context, never genuine user submissions. Pending native input travels together in bootstrap/incremental prompts and remains required under transcript budgeting. Custom-only continuation has no new user request or attachment; historical bootstrap requests are explicitly history. Cloud fresh prompts select the same pending input group without previous requests; bootstrap handoff preserves history with the same native-origin selection. Origin means the persisted native source, not a role-only rewrite yielding identical LLM content. If request-local transforms change payload/order (including blocked-image conversion), origin is unavailable: preserve conservative last-converted-user/reset behavior rather than guessing. This is not universal final-preconversion provenance.
23
23
  - Image payload forwarding for request-equivalent native inputs sends images only from the latest pending native user message, even with trailing custom notices. Other/unmatched contexts retain last-converted-user selection. 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.
24
24
  - 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. Static SDK and bridge imports additionally repair the compiled-Bun extension graph; the packed extension was qualified in official compiled Pi 1.0.3 on macOS arm64 with isolated empty credentials and a fake key. Embedded Windows missing-export behavior remains unqualified.
25
- - 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).
25
+ - 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](reference.md#native-shell-parsing-or-module-loading-fails).
26
26
  - 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; no automatic reload or authentication recovery is performed.
27
- - Exact `@cursor/sdk@1.0.37` is an unbundled package dependency; users do not need a global SDK install. Package 0.5.3 requires Node 24+ and official Pi 0.87.1+, while optional published Pi and TypeBox peer dependencies use `"*"` ranges per Pi guidance. Direct development Pi dependencies and their lockfile-resolved transitives are 1.0.4; nested host TypeBox remains 1.3.27; the extension's root TypeBox validation baseline is 1.3.36.
27
+ - Exact `@cursor/sdk@1.0.37` is an unbundled package dependency; users do not need a global SDK install. This package requires Node 24+ and official Pi 0.87.1+, while optional published Pi and TypeBox peer dependencies use `"*"` ranges per Pi guidance. Direct development Pi dependencies and their lockfile-resolved Pi cohort are 1.1.0 reproducible snapshots, not qualification targets or user runtime upgrades; nested host TypeBox remains 1.3.27; the extension's root TypeBox validation baseline is 1.3.36.
28
28
  - 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).
29
29
  - Cursor owns one public native Provider with native API-key login and a small resolver for stored/provider-env, runtime, composed config, and ambient credentials. Startup and auth mutations are cache-only, retaining known cached/fallback identities. `PI_CURSOR_HIDE_MODELS_WHEN_LOGGED_OUT=1` hides only unauthenticated picker/list availability, not CLI selection or login. Discovery receives explicit effective native auth and never guesses a default auth store. `/cursor-refresh-models` uses native registry refresh/publication. Provider turns keep Pi's resolved `options.apiKey`; non-secret Cursor-only settings remain separate.
30
30
  - 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.
31
31
  - 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.
32
- - Local agents pass `settingSources: ["all"]` by default so Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities are available. Users can narrow loading with a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`, or disable ambient setting sources with `PI_CURSOR_SETTING_SOURCES=none`. `/cursor-refresh-config` calls the current pooled SDK agent's `agent.reload()` to refresh filesystem Cursor config without recreating the agent. The shared process sink filters complete, verified timestamp/INFO inventory and completion frames plus exact supported hook-compatibility notices, including pre-bound SDK console output during first send and unresolved live SDK completion across Pi bridge/replay turns. The same lease restores on completion/cancellation cleanup, not when a Pi tool-use stream ends. A new write invocation starts a frame after unrelated partial output has forwarded, so an unterminated native Pi footer/prompt does not admit the following SDK diagnostic. stdout/stderr framing is independent; outside the retained creation-only mute around `Agent.create()`, unrecognized output retains its original bytes, callbacks and delegated backpressure. Only possible diagnostic prefixes buffer (up to 16 KiB); undecided tails flush on final restoration and oversized candidates pass through. Outside-root local-plugin symlink rejection and the exact native-parser-unavailable warning escape the creation mute and route through the request's async owner to a bounded native notification (stderr in non-UI modes) and a `pi-cursor-sdk:capability-notice` non-model session entry, at most once per kind per request. Notices contain fixed recovery guidance, not raw paths or arbitrary SDK metadata. Unowned maintainer scripts retain these meaningful diagnostics. Outside the creation mute, real plugin/hook/config/parser/ripgrep failures stay visible; the plugin confinement guard, settings sources and native parser setup are unchanged. Text alone cannot distinguish an unrelated emitter printing an identical complete SDK frame; strict attribution requires a future public SDK logger/sink API.
32
+ - Local agents pass `settingSources: ["all"]` by default so Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities are available. Users can narrow loading with a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`, or disable ambient setting sources with `PI_CURSOR_SETTING_SOURCES=none`. `/cursor-refresh-config` calls the current pooled SDK agent's `agent.reload()` to refresh filesystem Cursor config without recreating the agent.
33
+ - The shared process sink filters complete, verified timestamp/INFO inventory and completion frames plus exact hook-compatibility notices. Unsupported and unknown Claude Code events match only the SDK's two complete warning templates, with a variable quoted event name; new event names do not require an allowlist update, and unrelated `[hooks]` warnings remain visible. Extension initialization installs one process-lifetime lease before session/turn work. It remains active across idle time, detached rules/skills loads, filesystem watchers, config refresh, teardown, the asynchronous interval during `/reload`, and failed reloads. This is deliberate: SDK service disposal does not await every pending load, and Pi's old shutdown/new startup events cannot safely hand off a lease when reload fails or removes Cursor. The narrowly classified sink therefore remains installed until Pi exits, even if Cursor is removed from the active extension set. Reloaded module copies share the sink, refcounts and both async attribution scopes through one versioned process singleton; repeated initialization and concurrent sessions do not stack permanent leases. Existing provider/live-run/script leases remain temporary and nested, so direct provider/script use without extension initialization still restores on its own cleanup. The sink also catches pre-bound SDK console methods.
34
+ - A new write invocation starts a frame after unrelated partial output has forwarded, so an unterminated native Pi footer/prompt does not admit the following SDK diagnostic. stdout/stderr framing is independent; outside the retained creation-only mute around `Agent.create()`, unrecognized output retains its original bytes, callbacks and delegated backpressure. The creation mute expires when its operation returns/settles, so detached async work cannot inherit an indefinite mute for unrelated warnings. Only possible diagnostic prefixes buffer (up to 16 KiB); undecided tails flush on final restoration (process exit for an initialized extension) and oversized candidates pass through. Final restoration only removes stream methods still owned by this filter; later third-party wrappers remain installed and retained filter methods become transparent delegates.
35
+ - Outside-root local-plugin symlink rejection and the exact native-parser-unavailable warning escape the creation mute and route through the request's async owner to a bounded native notification (stderr in non-UI modes) and a `pi-cursor-sdk:capability-notice` non-model session entry, at most once per kind per request. Notices contain fixed recovery guidance, not raw paths or arbitrary SDK metadata. Unowned maintainer scripts retain these meaningful diagnostics. Outside the creation mute, real plugin/hook/config/parser/ripgrep failures stay visible; the plugin confinement guard, settings sources and native parser setup are unchanged. Text alone cannot distinguish an unrelated emitter printing an identical complete SDK frame; strict attribution requires a future public SDK logger/sink API.
33
36
  - On `cursor/*` models, pi-cursor-sdk removes only pi-generated `<project_instructions>` blocks that overlap the effective Cursor `settingSources`: `user` for `~/.pi/agent/AGENTS.md`; `project` for discovered repo/parent `AGENTS.md` and `CLAUDE.md` (verified Cursor behavior: local agents load project `AGENTS.md` and `CLAUDE.md`). `~/.pi/agent/CLAUDE.md` is not removed (Cursor user layer uses `~/.claude/CLAUDE.md`). Blocks are removed by exact pi serialization match from structured `contextFiles` via the `before_agent_start` hook, not in `buildCursorPrompt` sanitization. Suppression is skipped with `-nc`, `PI_CURSOR_SETTING_SOURCES=none`, narrowed sources such as `plugins` that omit the matching layer, or `PI_CURSOR_PRESERVE_PI_AGENTS_MD=1`. Switching away from a Cursor model restores pi's full context block on the next user message.
34
37
  - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model.
35
38
  - Cursor-side thinking remains visible through pi's native thinking rendering when the Cursor SDK emits thinking or summary deltas.
36
- - Local Cursor agents get two tool surfaces. First, Cursor keeps the Cursor SDK local-agent tool surface plus configured Cursor settings, plugins, and Cursor MCP servers. Second, pi-cursor-sdk exposes active pi tools through a default-on, tokenized loopback stable MCP v2 bridge when bridgeable tools exist. The bundled bridge runtime closure is `@modelcontextprotocol/server@2.3.1`, `@modelcontextprotocol/hono@2.0.2`, `hono@4.13.13`, and `@hono/node-server@2.1.3`; it binds loopback and validates Hono `Host` and `Origin` headers.
39
+ - Local Cursor agents get two tool surfaces. First, Cursor keeps the Cursor SDK local-agent tool surface plus configured Cursor settings, plugins, and Cursor MCP servers. Second, pi-cursor-sdk exposes active pi tools through a default-on, tokenized loopback stable MCP v2 bridge when bridgeable tools exist. The bundled bridge runtime closure is `@modelcontextprotocol/server@2.3.1`, `@modelcontextprotocol/hono@2.0.2`, `hono@4.13.13`, and `@hono/node-server@2.1.3`; it binds loopback and validates Hono `Host` and `Origin` headers. The default owns IPv4 only; opt-in `localhost` owns both explicit loopbacks on one port before advertising a URL, fails closed without IPv6, and freezes the advertised host/listener policy for that shared server lifetime. Failed run startup releases its run state and partial listeners without changing sibling routes.
40
+ - Ordinary LOCAL turns preserve the SDK default tools unless `PI_CURSOR_NATIVE_TOOLS=0` (canonical boolean parsing) opts into public `AgentOptions.tools: ["mcp"]`. This excludes all other main tools, especially native task/subagents whose toolsets are independently curated. Capture before drain/acquisition; forward identically to create/resume and append a compatibility suffix only for opt-out, preserving default durable bytes. Cloud fails before SDK/lifecycle side effects with explicit native disable; isolated LOCAL summaries retain `tools: []`. Bootstrap/plan/tail guidance and manifest must not advertise unavailable host tools under opt-out. Pi builtins still require explicit `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1`. MCP-only retains ambient configured Cursor MCP unless setting sources are disabled; it is not Pi-only, a sandbox, or an OS security boundary. Permitted Pi/MCP tools can execute arbitrary work.
41
+ - Pi `tool_call` hooks govern actual bridged Pi execution, not native Cursor execution already completed before replay. Hiding replay/registration, disabling the bridge, or narrowing Pi tools is not native enforcement; do not fabricate pre-execution callbacks. Before any drain consumption/result delivery, compare the pending run's captured native policy under its drain lease; reject opposite-policy continuation without implicit cancellation and with restore-or-`/cursor-abort` guidance. `/cursor-abort` is a registered explicit local command using the current bound runtime scope and existing live-run release owner; it sends no model turn and never releases a sibling scope or Cloud agent. Only when that owned run exists and the current Cursor Pi turn is active does the command also invoke public `ctx.abort()` before release; no-owned-run and non-Cursor Pi turns are not aborted. Idle native Pi abort has no active signal/event after a settled mismatch, so it cannot be advertised as recovery for the old Cursor waiter. On explicit active provider abort before preparation, turn-owned cleanup releases only the pending live run from that request's captured runtime scope; ordinary errors and requests aborted before acquiring the turn lease do not release it. Already-executed work is not undone; no retroactive promise is made.
37
42
  - `buildCursorPiToolBridgeSnapshot()` is the runtime capability source for pi bridge tools. It snapshots `pi.getActiveTools()` and `pi.getAllTools()`, carries pi 0.77+ per-tool `promptGuidelines` into bridge MCP descriptions, filters internal replay names, hides overlapping built-in pi tools (`read`, `bash`, `write`, `edit`, `grep`, `find`, `ls`) unless `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1`, and creates collision-safe MCP names such as `pi__sem_reindex`. Cursor discovers the current run's exposed bridge tools through MCP `listTools`. Bootstrap prompts include a compact callable-surface manifest from `buildCursorToolManifestText()` by default (`PI_CURSOR_TOOL_MANIFEST=1`); disable with `PI_CURSOR_TOOL_MANIFEST=0`. There is no per-turn visible tool list, status manifest, or footer manifest. User-facing summary: [Cursor tool surfaces in pi](./cursor-tool-surfaces.md).
38
- - Prompt text is the primary provider/bridge contract. Bootstrap prompts carry a short boundary block plus the callable-surface manifest by default (`PI_CURSOR_TOOL_MANIFEST=1`). MCP `listTools` descriptions use a one-line pointer to the bootstrap prompt instead of repeating the full contract (`buildCursorPiBridgeMcpToolDescription()`). Cursor must call the exposed `pi__*` MCP name, not the real pi tool name shown in pi history or transcripts. When exposed, `pi__mcp` takes preference over Cursor-configured MCP for MCP work and `pi__subagent` takes preference over Cursor-native subagents for delegation; the Cursor-native surfaces remain fallbacks when the matching pi bridge tool is absent or unavailable. Pi emits and executes the real pi tool name. Maintainer debug: `/cursor-tools` prints bridge/manifest enablement, effective `PI_CURSOR_SETTING_SOURCES`, and current-registry exposure eligibility with builtin filtering, not a live-run surface or health check. The shared prompt tail retains GitHub issue/PR comment guidance on both bootstrap and incremental sends; this is model guidance, not an execution permission guard.
43
+ - Prompt text is the primary provider/bridge contract. Bootstrap prompts carry a short boundary block plus the callable-surface manifest by default (`PI_CURSOR_TOOL_MANIFEST=1`). MCP `listTools` descriptions use a one-line pointer to the bootstrap prompt instead of repeating the full contract (`buildCursorPiBridgeMcpToolDescription()`). Cursor must call the exposed `pi__*` MCP name, not the real pi tool name shown in pi history or transcripts. Exposed `pi__*` tools take preference for their described capabilities, including delegation or MCP work, using the current schemas and prompt guidelines rather than historical tool aliases; Cursor-configured MCP and Cursor-native subagents remain fallbacks when no matching exposed Pi capability is available. Pi emits and executes the real pi tool name. Maintainer debug: `/cursor-tools` prints bridge/manifest enablement, effective `PI_CURSOR_SETTING_SOURCES`, and current-registry exposure eligibility with builtin filtering, not a live-run surface or health check. The shared prompt tail retains GitHub issue/PR comment guidance on both bootstrap and incremental sends; this is model guidance, not an execution permission guard.
39
44
  - The provider also registers `cursor_ask_question` for Cursor models when the bridge is on and `PI_CURSOR_ASK_QUESTION=1`. While the tool awaits pi UI input it emits package event `pi-cursor-sdk:ask-question:blocked` with `{ active: true }` and clears `{ active: false }` in `finally`; the tool runs with `executionMode: "sequential"` so parallel sibling calls cannot overlap dialogs. Cursor sees it as `pi__cursor_ask_question`, and pi executes it through the normal tool path. The control is off by default so Cursor states an assumption instead of prompting. In non-UI modes it reports that UI is unavailable so Cursor can state a default assumption instead. When pi has visible Agent Skills loaded, the provider rewrites the skill catalog for Cursor and registers `cursor_activate_skill` as `pi__cursor_activate_skill`; pi executes it through the normal tool path so Cursor can load the full `SKILL.md` and skill resource list for the current pi-loaded skill source of truth. `PI_CURSOR_PI_TOOL_BRIDGE=0` disables the local bridge, including question and skill activation bridging. Cloud Cursor agents remain out of scope for the bridge.
40
45
  - The bridge queues MCP calls, emits provider `toolcall_*` events, waits for matching pi `toolResult` messages by `toolCallId`, resolves the result back into the same live Cursor SDK run without creating a new `Agent`, and never calls tool `execute()` handlers directly. The same-run resume invariant holds unless the run was disposed, aborted, or cancelled.
41
46
  - Pending pi bridge calls keep their owning local Cursor live run active, including queued/emitted calls and pi execution awaiting human input. The last matching settlement restarts the full idle interval (five minutes by default) after the provider drain lease ends, including when cleanup was armed before the lease. Lease/queue/pending state is rechecked at expiry, and the per-run observer is detached on release/replacement. A pending call in another session does not delay cleanup. Explicit abort/shutdown/reset/compaction and the fail-closed MCP/bridge deadline (one hour by default) remain effective; display-only native replay abandonment is unchanged.
@@ -98,7 +103,7 @@ Cursor SDK is the source of truth for Cursor model IDs and Cursor-supported para
98
103
 
99
104
  Startup registers a known cached/fallback baseline without catalog/network calls. The SDK remains in the static extension graph for compiled-Bun loading; cache-only does not promise zero SDK module evaluation. Native cache-only refresh follows real login/logout/rotation/runtime-key mutations; the genuine extension context supplies composed config auth once bound. There is no polling, replayed auth lifecycle event, or independent CLI parser.
100
105
 
101
- Known host limitation: official Pi 1.0.4 has a demonstrated startup refresh-admission race that can temporarily leave fallback models and an incorrect missing-key warning despite a matching authenticated cache. The shared-owner repair is merged in `fitchmultz/pi` via [#190](https://github.com/fitchmultz/pi/pull/190); this is not an official-host fix. See [#337](https://github.com/fitchmultz/pi-cursor-sdk/pull/337). It is separate from Cursor service authentication failures and the compiled-loader repair.
106
+ Known host limitation: official Pi 1.0.4 and 1.1.0 have a demonstrated startup refresh-admission/readiness race that can temporarily leave fallback models and an incorrect missing-key warning despite a matching authenticated cache. The shared-owner repair is merged in `fitchmultz/pi` via [#190](https://github.com/fitchmultz/pi/pull/190); this is not an official-host fix. See [#337](https://github.com/fitchmultz/pi-cursor-sdk/pull/337) and the [native auth/catalog fixture boundary](./cursor-testing-lessons.md#native-authcatalog-fixture-boundary). It is separate from Cursor service authentication failures and the compiled-loader repair.
102
107
 
103
108
  Effective auth follows native precedence: runtime override, stored API-key credential (key then provider-scoped `env.CURSOR_API_KEY` then ambient), composed `models.json` key when no stored credential exists, then ambient `CURSOR_API_KEY`. Placeholder normalization does not shadow composed config. An unsupported stored credential type remains unconfigured. Native logout removes both stored credentials and the runtime override; surviving env/config auth prevents false orphan-cache deletion. Same-process environment/config changes are picked up by native registry refresh, not a watcher.
104
109
 
@@ -154,7 +159,9 @@ CURSOR_API_KEY="your-key" npm run refresh:cursor-snapshots -- --write \
154
159
 
155
160
  Both modes call `Cursor.models.list({ apiKey })` and use the same sanitizer and stable sort. `--check` byte-compares `src/cursor-fallback-models.generated.ts` without writing; `--write` refreshes it and updates `src/bundled-context-windows.ts` only when `--context-windows` is provided. Context-window inputs are limited to current selectable model IDs; redundant default `:fast`/`:slow` aliases collapse to one key, conflicting equivalent selections fail generation, and stale or ambiguous aliases are omitted. Generated provenance and command output record the installed `@cursor/sdk` version and model count. The script prints model IDs/counts only and scrubs known auth material from SDK errors; it must not print or store API keys. Review generated diffs before committing because Cursor can change aliases, defaults, and parameter meanings.
156
161
 
157
- The authenticated SDK 1.0.37 catalog-only refresh on 2026-10-07 returned 46 models, adding Claude Haiku 5.5 (`claude-haiku-5-5`) while preserving all 45 model records from the SDK 1.0.36 capture on 2026-10-06. The generated header records SDK 1.0.37 and the 46-model count. Haiku 5.5 exposes 20 variants across `thinking=false|true`, `context=300k|1m`, and `reasoning_effort=low|medium|high|xhigh|max`; its default is `thinking=true`, `context=1m`, `reasoning_effort=high`. The 33 bundled checkpoint-derived entries were unchanged; this refresh started no Agent/model turn or Cloud run and does not qualify effective checkpoint limits.
162
+ The authenticated SDK 1.0.37 catalog-only refresh on 2026-10-07 returned 46 models, adding Claude Haiku 5.5 (`claude-haiku-5-5`) while preserving all 45 model records from the SDK 1.0.36 capture on 2026-10-06. That refresh's generated header recorded SDK 1.0.37 and the 46-model count. Haiku 5.5 exposes 20 variants across `thinking=false|true`, `context=300k|1m`, and `reasoning_effort=low|medium|high|xhigh|max`; its default is `thinking=true`, `context=1m`, `reasoning_effort=high`. The 33 bundled checkpoint-derived entries were unchanged; this refresh started no Agent/model turn or Cloud run and does not qualify effective checkpoint limits.
163
+
164
+ The current snapshot is the authenticated SDK 1.0.37 catalog-only refresh on 2026-10-22: 48 models, adding Gemini 4 Argon (`gemini-4-argon`, low/medium/high reasoning with medium default) and Muse Wonder (`muse-wonder-1.0`, 300k/1m contexts and minimal through max reasoning, default 1m/xhigh) while preserving the prior 46 records. Existing generic context/thinking selection handles both; neither adds fast controls or aliases. Preserve SDK variant labels verbatim. The 33 checkpoint-derived entries remain unchanged; this refresh started no Agent/model turn or Cloud run and establishes no new checkpoint limit.
158
165
 
159
166
  Dated evidence for Cursor's assistant-visible, model-specific system text and reconstructed tool guidance lives in [Cursor System Prompts and Tool Guidance — 2026-08-02](https://github.com/fitchmultz/pi-cursor-sdk/blob/main/docs/evidence/cursor-system-prompts-2026-08-02/README.md). Keep that evidence separate from pi-cursor-sdk's own bootstrap prompt: Cursor persists its base system message in the local SDK checkpoint, while this extension sends Pi context and bridge instructions as user content.
160
167
 
@@ -287,6 +294,7 @@ Cursor extension controls:
287
294
  | Set SDK mode | `/cursor-mode agent\|plan` | Cursor model selected |
288
295
  | Set local HTTP transport | `/cursor-http on\|off\|toggle` | local Cursor runtime |
289
296
  | Refresh filesystem Cursor config | `/cursor-refresh-config` | Cursor model selected and an SDK agent may exist |
297
+ | Explicitly abort pending local Cursor work | `/cursor-abort` | current session owns a pending local Cursor run |
290
298
  | Show tool surfaces (maintainer) | `/cursor-tools` | Cursor model selected |
291
299
  | View, refresh, or export accounting | `/cursor-usage [refresh\|export <new-file.json>\|help]` | current branch has recorded Cursor facts |
292
300
 
@@ -424,7 +432,7 @@ The full resolved selection is submitted for both runtimes. **SDK 1.0.37 LOCAL c
424
432
 
425
433
  Nonempty definitions add a SHA256 pool-key suffix over name-sorted fixed-shape content with sorted model params. Definition/property/param order does not affect identity; text/model/param/add/remove changes do. No-feature keys stay byte-identical. Each turn captures one definition set, including forced rebootstrap; next-turn config edits repool and exact persisted-key matching rejects stale resumes. Existing persistence stores the key digest only, not definition prompts.
426
434
 
427
- LOCAL compact/tree summary constructors remain fresh, tool-free, definition-free and ephemeral, without conversation resume/lineage. Bug-report streams have no public summary-purpose hook and retain ordinary capabilities. Bridge snapshots/manifests/routing are unchanged and `pi__subagent` stays preferred when available. User configuration and limits: [SDK-native custom agents](../README.md#sdk-native-custom-agents).
435
+ LOCAL compact/tree summary constructors remain fresh, tool-free, definition-free and ephemeral, without conversation resume/lineage. Bug-report streams have no public summary-purpose hook and retain ordinary capabilities. Bridge snapshots/manifests/routing are unchanged; an exposed Pi delegation capability stays preferred under its actual current name, schema and prompt guidelines. User configuration and limits: [SDK-native custom agents](reference.md#sdk-native-custom-agents).
428
436
 
429
437
  ## Fast Behavior
430
438
 
@@ -7,7 +7,7 @@ pi-cursor-sdk has two separate pi-facing paths plus Cursor's own local-agent too
7
7
  1. **Local pi MCP bridge:** default-on for local Cursor agents. It exposes the current pi session's bridgeable active tools to Cursor through a tokenized `127.0.0.1` MCP endpoint, excluding internal Cursor replay activity names and, by default, overlapping built-in pi tools (`read`, `bash`, `write`, `edit`, `grep`, `find`, `ls`). When Cursor calls one of those MCP tools, pi executes the real pi tool through the normal pi tool path.
8
8
  2. **Cursor native tool replay:** display-only. It renders completed Cursor SDK tool activity as pi-native-looking cards using recorded Cursor results.
9
9
 
10
- This document is about replay. Replay is not execution and is not the local pi bridge.
10
+ This document is about replay. Replay is not execution and is not the local pi bridge. Pi `tool_call` hooks cannot govern native work that already ran before replay; blocking or rewriting its card cannot prevent or transform the original command. Hiding replay or disabling the bridge is not enforcement. For the opt-in public SDK MCP-only local surface, its ambient-MCP limits, and why it is not a sandbox, see [MCP-only local turns](./cursor-tool-surfaces.md#opt-in-mcp-only-local-turns).
11
11
 
12
12
  ## Live bridge vs replay
13
13
 
@@ -197,7 +197,7 @@ Lifecycle rules:
197
197
 
198
198
  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.
199
199
 
200
- 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).
200
+ 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](reference.md#context-compaction-and-cursor-usage).
201
201
 
202
202
  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.
203
203
 
@@ -6,7 +6,7 @@ This workflow is the canonical repo path for verifying Cursor SDK tool replay th
6
6
 
7
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.37`, 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 locked 1.0.4 snapshots, with host TypeBox 1.3.27. These locked development snapshots are not qualification targets; resolve latest stable official Pi and current `fitchmultz/pi` main once per workflow run. Optional published Pi and TypeBox peer dependencies use `"*"` ranges per Pi guidance.
9
+ Current validation baseline: Node 24+, exact `@cursor/sdk@1.0.37`, 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 locked 1.1.0 snapshots, with host TypeBox 1.3.27. These locked development snapshots are not qualification targets; resolve latest stable official Pi and current `fitchmultz/pi` main once per workflow run. 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
 
@@ -8,6 +8,12 @@ This document records maintainer testing lessons for `pi-cursor-sdk`. It complem
8
8
 
9
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
+ ## Durable store identity versus SDK pathname
12
+
13
+ Persisted `storeIdentity.stateRoot` is a durable identity, while the SQLite-open boundary passes `toNamespacedPath(stateRoot)` to the SDK. On Windows, the SDK pathname can therefore have the `\\?\` prefix without changing the stored identity. Native resume tests must compare the SDK value exactly with `toNamespacedPath(handle.storeIdentity.stateRoot)` and verify both existing directories with `realpath`; do not strip arbitrary prefixes or weaken the stable-handle/live-sibling assertions.
14
+
15
+ For native harness diagnostics, set `PI_CURSOR_TEST_EVIDENCE_DIR` before starting the test and use a fresh owned temp directory. Evidence-configured `concurrentFixture` runs retain their original fixture roots and write `fixture-root-*.json` metadata before execution; copied JSONL does not authorize deleting the original JSONL or its ancestors. The targeted macOS/Windows native ownership CI step runs the whole ownership test file, avoiding a name filter that could silently match no cases, and places both original roots and copied evidence under the existing `native-host` artifact directory. Runs without evidence configuration keep their existing teardown behavior.
16
+
11
17
  ## Core lesson: integration-shaped bugs beat unit mocks
12
18
 
13
19
  The native replay `Tool grep not found` failure was integration-shaped, not unit-shaped:
@@ -19,11 +25,11 @@ The native replay `Tool grep not found` failure was integration-shaped, not unit
19
25
 
20
26
  Passing hundreds of unit tests did not prove that chain was safe. Regression coverage now includes:
21
27
 
22
- - `test/index.test.ts` — `before_agent_start` and `turn_start` resync after plan-style strip
28
+ - `test/index-registration.test.ts` — `before_agent_start` and `turn_start` resync after plan-style strip
23
29
  - `test/cursor-native-replay-stress.test.ts` — plan strip → resync → grep replay; inactive-tool trace fallbacks
24
30
  - `test/cursor-provider-replay-live-run.test.ts` — inactive replay tools emit trace instead of broken `toolUse`
25
31
  - `test/cursor-native-replay-trace.test.ts` — shared inactive replay trace formatting
26
- - `test/cursor-native-replay-routing.test.ts` — `resolveNativeReplayDisposition` and `partitionNativeToolsByActiveContext`
32
+ - `test/cursor-native-replay-routing.test.ts` — disabled-replay disposition, absent-snapshot compatibility, and mixed active/inactive tool partitioning (other disposition behavior is exercised at the provider boundary above)
27
33
  - `test/validate-smoke-jsonl.test.ts` — replay scan semantics (real errors vs doc mentions in successful reads)
28
34
 
29
35
  When changing provider/runtime behavior, ask whether the bug spans **pi extension lifecycle**, **active tool state**, **provider streaming**, and **persisted JSONL**. If yes, add an integration-style unit test or live smoke coverage for that chain.
@@ -50,10 +56,16 @@ If resync runs but `context.tools` is still stale (e.g. only `read` listed), the
50
56
 
51
57
  ## Stock/transcript provider contract
52
58
 
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 direct development Pi packages are locked 1.0.4 snapshots, not qualification targets; resolve latest stable official Pi and current `fitchmultz/pi` main once per workflow run. Their actual nested resolution is recorded with each check, including host TypeBox 1.3.27.
59
+ `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 direct development Pi packages are locked 1.1.0 snapshots, not qualification targets; resolve latest stable official Pi and current `fitchmultz/pi` main once per workflow run. Their actual nested resolution is recorded with each check, including host TypeBox 1.3.27.
54
60
 
55
61
  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, pending-work idle/abort/shutdown/deadline ownership, replay without file access, persisted usage/lineage, tree, compaction, queued steering, abort and reload/disposal. `test/native-cursor-prompt.test.mjs` owns genuine native input, hidden notices, required-budget input, current attachments, custom-only continuation, equivalent conversion, payload/order-changing rejection, and GitHub comment soft guidance across bootstrap/incremental/custom-only sends. `test/native-cursor-ownership.test.mjs` owns concurrent-session contracts, including independently reopened managers for the same file, pending bridge survival across sibling reload/tree/shutdown, durable-key stability and atomic live-handle exclusion. These owners use `test/helpers/native-cursor-harness.mjs` and run in the explicit `check:compat` list and manual official-host compatibility route; the native-platform Vitest job is not execution evidence for these Node test owners. Explicit `tools` is now an allowlist, not merely an initial active set; use `defaultTools` for a native fixture that allows replay wrappers to activate. `test/native-provider.test.mjs` additionally verifies canonical `SessionManager.appendContextEdit()` plus `refreshContext()`, unchanged raw history, and conversation filtering that preserves system instructions, the current tool declaration, and explicit tool removal (the focused native slice from #271). 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
62
 
63
+ ### Native auth/catalog fixture boundary
64
+
65
+ `test/cursor-native-auth-catalog.test.ts` uses public `ModelRuntime.create({ modelsPath: null })` for cases that do not exercise configuration files. Credentials and the Cursor catalog cache remain real; config-precedence and config-edit cases still use a temporary `models.json`. Seed startup credentials and the matching cache before creating the runtime.
66
+
67
+ Official Pi 1.1.0 retains the [file-backed startup refresh-admission/readiness limitation](./cursor-model-ux-spec.md#source-of-truth): registration starts an unawaited refresh, and an asynchronous config load can supersede the awaited session refresh before its catalog publishes. This can return the bundled fallback despite an authenticated matching cache, including when auth/cache were seeded before runtime creation. The in-memory fixture isolates Cursor auth/publication assertions from that shared host defect; it does **not** qualify or repair official file-backed startup. The shared-owner repair in `fitchmultz/pi` [#190](https://github.com/fitchmultz/pi/pull/190) admits refresh ownership before config I/O and awaits replacement publication. File-backed startup proof must use that repaired host and retain its exact revision.
68
+
57
69
  ## Installed SDK vs native host evidence
58
70
 
59
71
  The native harness substitutes external Cursor SDK transport/storage. It qualifies Pi ownership, scheduling and persisted-session behavior, not real SDK request-time linking. `test/cursor-sdk-lazy-import.test.ts` separately drives public installed `Agent.create()` → `send()` → `run.wait()` with `JsonlLocalAgentStore`, an empty credential environment/private HOME, and pre-import Node network/process guards. The positive case reaches exactly one blocked key-exchange fetch; the negative case denies an SDK module only after creation and fails before network. This protects first-send linking beyond a successful static import. The guards are public Node API interception, not an OS sandbox, and neither case qualifies authenticated gRPC, compiled Pi or the original Windows embedded hosts.
@@ -4,9 +4,11 @@ pi-cursor-sdk runs Cursor models through the local `@cursor/sdk` agent runtime b
4
4
 
5
5
  ## The three surfaces
6
6
 
7
+ **Version boundary:** the MCP-only opt-out (`PI_CURSOR_NATIVE_TOOLS`) and `/cursor-abort` require **0.5.4 or later**, including [#372](https://github.com/fitchmultz/pi-cursor-sdk/pull/372). Source checkouts preparing 0.5.4 remain candidates until exact npm/GitHub publication records exist; verify the installed artifact separately. Published **0.5.3 ignores the flag and has no such command**. Do not treat these examples as enforcement on an older installed package, or infer feature availability from a development version label alone.
8
+
7
9
  | Surface | Who owns it | Callable by Cursor? | What pi shows |
8
10
  | --- | --- | --- | --- |
9
- | **Cursor SDK host tools** | Cursor local agent | Yes | Native replay cards (`read`, `bash`, …) or neutral Cursor activity. Representative ToolType list: [SDK ToolType replay matrix](./cursor-native-tool-replay.md#sdk-tooltype-replay-matrix). |
11
+ | **Cursor SDK host tools** | Cursor local agent | Yes by default; disabled with `PI_CURSOR_NATIVE_TOOLS=0` | Native replay cards (`read`, `bash`, …) or neutral Cursor activity. Representative ToolType list: [SDK ToolType replay matrix](./cursor-native-tool-replay.md#sdk-tooltype-replay-matrix). |
10
12
  | **Configured Cursor MCP** | Cursor settings / `~/.cursor/mcp.json` | Yes (when loaded) | Neutral **Cursor MCP** activity cards on replay |
11
13
  | **Pi bridge (`pi__*`)** | pi-cursor-sdk loopback MCP | Yes, when exposed | Real pi tool names (`cursor_ask_question`, `cursor_activate_skill`, extension tools, …) |
12
14
 
@@ -14,6 +16,27 @@ Pi CLI tool toggles apply at the pi tool-registry boundary. `--no-tools`, `--too
14
16
 
15
17
  **Not callable:** `cursor-replay-*` IDs in JSONL, pi history tool names used only for display, and transcript labels. Cursor must call exposed `pi__*` MCP names for bridged pi tools, not the pi card name.
16
18
 
19
+ ## Opt-in MCP-only local turns
20
+
21
+ This section requires the 0.5.4+ implementation and publication/installation check described above; it is not available in published npm 0.5.3.
22
+
23
+ `PI_CURSOR_NATIVE_TOOLS=0` restricts **ordinary LOCAL** agents through the public SDK `AgentOptions.tools: ["mcp"]`. All other main-agent tools are omitted, including native `task`/subagents (whose own curated toolsets do not inherit a main-loop restriction). Unset, blank, unrecognized, or enabled values preserve the SDK's default toolset. The canonical boolean parser accepts `0`, `false`, `off`, `none`, `no`, and `disabled` as off, and `1`, `true`, `on`, `yes`, and `enabled` as on, case-insensitively with surrounding whitespace ignored.
24
+
25
+ To send file/shell work through Pi's normal execution and `tool_call` hook boundary instead of native Cursor tools:
26
+
27
+ ```bash
28
+ # Requires 0.5.4+ containing #372; verify publication/installation (0.5.3 ignores the flag).
29
+ PI_CURSOR_NATIVE_TOOLS=0 PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 PI_CURSOR_SETTING_SOURCES=none pi --model cursor/grok-4.6
30
+ ```
31
+
32
+ The builtin-exposure flag is still required; disabling native tools does **not** silently expose Pi builtins. Pi's active tool registry and hooks still decide which bridged tools are available or blocked. Disabling the bridge as well leaves no Pi tools callable, but does not by itself disable configured Cursor MCP.
33
+
34
+ **MCP-only is not Pi-only or a sandbox.** The SDK `mcp` capability group retains configured Cursor MCP and the Pi bridge. Ambient configured MCP remains unless setting sources are narrowed or disabled (`PI_CURSOR_SETTING_SOURCES=none`). Non-Pi MCP calls do not pass through Pi's `tool_call` hooks. A permitted Pi or MCP tool can itself run a shell, access files/network, or delegate arbitrary work; this is no OS/process security boundary.
35
+
36
+ **Native replay cannot enforce a pre-execution policy.** Native Cursor work runs before its recorded result is replayed into Pi. Pi `tool_call` hooks may block a bridged Pi call before its real `execute()`, but blocking or rewriting native replay cannot prevent or transform the original Cursor command. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0`, `PI_CURSOR_REGISTER_NATIVE_TOOLS=0`, Pi tool toggles, and `PI_CURSOR_PI_TOOL_BRIDGE=0` are not native execution controls. Use the SDK tool-surface opt-out rather than fabricated pre-execution callbacks.
37
+
38
+ The policy is captured before drain/acquisition and forwarded identically to create and resume (SDK tool restrictions are not persisted by Cursor). Opt-out agents have a distinct pooled/durable compatibility identity; defaults retain historical identity bytes. Changing policy replaces the agent at acquisition instead of reusing/resuming one with the opposite policy. Before draining a pending run, the provider checks its captured policy under the drain lease and rejects an opposite-policy continuation without delivering Pi results back to that SDK run. Ordinary mismatch does not cancel the old run: restore the prior policy to finish it, or invoke `/cursor-abort` to release the exact pending local run owned by this session before retrying. This command sends no model turn. If its owned run has an active Cursor Pi turn, it also invokes that turn's public abort before release; it does not abort a non-Cursor Pi turn or release sibling sessions or Cloud agents. Pi's normal abort only signals an active Pi turn; once the mismatch error has fully settled, idle Pi abort cannot reach that older Cursor waiter. Explicit active provider abort still releases its exact scoped pending run even when the signal was aborted before preparation. No recovery path undoes already-executed work. Fresh text-only LOCAL compaction/tree summary agents continue using `tools: []`, independently of this flag. Cloud with explicit native tools disabled fails before SDK creation or lifecycle side effects rather than silently ignoring the control.
39
+
17
40
  ## Discoverability
18
41
 
19
42
  - **MCP `listTools`** (and pi's MCP catalog when present) lists **MCP servers only** — for example `pi_tools` with `pi__cursor_ask_question`. It does **not** enumerate Cursor SDK host tools such as `Read` or `Shell`.
@@ -41,7 +64,7 @@ For an advertised-but-unused `pi__subagent`, first identify the active provider/
41
64
  Default behavior:
42
65
 
43
66
  - Cursor host tools handle files, shell, grep, and edits.
44
- - When exposed, `pi__mcp` is preferred for MCP work and `pi__subagent` is preferred for delegation. Cursor-configured MCP and Cursor-native subagents are fallbacks when the matching pi tool is not exposed or is unavailable.
67
+ - Prefer exposed `pi__*` tools for the capabilities described by their current schemas and prompt guidelines, including delegation or MCP work. Use Cursor-configured MCP or Cursor-native subagents only when no matching exposed Pi tool is available; do not assume a particular delegation tool name is registered.
45
68
  - The pi bridge exposes **active pi tools** as `pi__*` MCP names when `PI_CURSOR_PI_TOOL_BRIDGE` is enabled (default on).
46
69
  - Overlapping pi builtins (`read`, `bash`, `write`, `edit`, `grep`, `find`, `ls`) are **hidden** from the bridge unless `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1`.
47
70
 
@@ -60,10 +83,15 @@ PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 pi --model cursor/grok-4.6
60
83
  # Fail a stranded bridge call sooner than the effective MCP tool timeout
61
84
  PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS=120000 pi --model cursor/grok-4.6
62
85
 
86
+ # Announce the bridge URL as localhost and listen on a port or range (for example for a Cursor team MCP allowlist such as http://localhost:800*/mcp).
87
+ PI_CURSOR_PI_TOOL_BRIDGE_HOST=localhost PI_CURSOR_PI_TOOL_BRIDGE_PORT=8000-8009 pi --model cursor/grok-4.6
88
+
63
89
  # Disable bootstrap tool manifest
64
90
  PI_CURSOR_TOOL_MANIFEST=0 pi --model cursor/grok-4.6
65
91
  ```
66
92
 
93
+ For `localhost`, the bridge owns both `127.0.0.1` and `::1` on the selected port before advertising its URL; a conflict on either family skips the port, and missing IPv6 fails closed. The default remains IPv4-only. Host/port settings are captured for the shared server lifetime, not changed under active runs; dispose all its runs before changing them.
94
+
67
95
  ## Runtime and transport policy
68
96
 
69
97
  Current defaults:
@@ -82,6 +110,7 @@ Disabling or removing an MCP server **only in pi** does not remove Cursor ambien
82
110
  | --- | --- |
83
111
  | `pi --no-tools` | Disables pi built-in/extension/custom tools and therefore removes pi bridge exposure; Cursor SDK host tools still remain callable. |
84
112
  | `pi --tools ...` / `pi --exclude-tools ...` | Narrows pi's active tool registry and therefore the pi bridge snapshot; Cursor SDK host tools and configured Cursor MCP are unchanged. |
113
+ | `PI_CURSOR_NATIVE_TOOLS=0` | Restricts ordinary LOCAL main-agent tools to MCP only, including disabling native task/subagents; Cloud fails closed. Default unchanged. |
85
114
  | `PI_CURSOR_SETTING_SOURCES=all` (default) | Loads user/project Cursor MCP, plugins, rules (`~/.cursor/mcp.json`, etc.) |
86
115
  | `PI_CURSOR_SETTING_SOURCES=none` | Disables ambient Cursor setting sources for local agents |
87
116
  | `PI_CURSOR_SETTING_SOURCES=project,plugins` | Narrows which layers load |
@@ -100,6 +129,6 @@ Example mistake: treating `cursor-replay-…` as a tool to invoke. Replay never
100
129
 
101
130
  ## Related docs
102
131
 
103
- - [README — Cursor provider tool contract](../README.md#cursor-provider-tool-contract)
132
+ - [Reference — Cursor provider tool contract](reference.md#cursor-provider-tool-contract)
104
133
  - [Cursor native tool replay](./cursor-native-tool-replay.md)
105
134
  - [Cursor model UX spec](./cursor-model-ux-spec.md)