pi-cursor-sdk 0.5.3 → 0.5.5

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 (141) hide show
  1. package/CHANGELOG.md +61 -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 +424 -1
  10. package/dist/cursor-live-run-coordinator.js +1 -0
  11. package/dist/cursor-local-run-terminal-error.js +57 -0
  12. package/dist/cursor-mcp-timeout-override.js +6 -17
  13. package/dist/cursor-native-tool-display-replay.js +5 -5
  14. package/dist/cursor-native-tools.js +11 -0
  15. package/dist/cursor-pi-tool-bridge-env.js +25 -2
  16. package/dist/cursor-pi-tool-bridge-server.js +98 -48
  17. package/dist/cursor-pi-tool-bridge-snapshot.js +1 -1
  18. package/dist/cursor-pi-tool-bridge.js +4 -12
  19. package/dist/cursor-provider-errors.js +0 -6
  20. package/dist/cursor-provider-live-run-drain.js +8 -3
  21. package/dist/cursor-provider-overflow.js +7 -21
  22. package/dist/cursor-provider-turn-finalize.js +2 -0
  23. package/dist/cursor-provider-turn-prepare.js +8 -1
  24. package/dist/cursor-provider-turn-runner.js +17 -6
  25. package/dist/cursor-provider-turn-send.js +3 -0
  26. package/dist/cursor-provider.js +1 -4
  27. package/dist/cursor-runtime-state.js +3 -3
  28. package/dist/cursor-sdk-event-debug.js +2 -2
  29. package/dist/cursor-sdk-output-filter.js +1 -1
  30. package/dist/cursor-sdk-process-error-guard.js +1 -2
  31. package/dist/cursor-session-agent.js +6 -8
  32. package/dist/cursor-session-scope.js +1 -7
  33. package/dist/cursor-session-store.js +3 -2
  34. package/dist/cursor-state.js +9 -23
  35. package/dist/cursor-tool-lifecycle.js +0 -3
  36. package/dist/cursor-tool-manifest.js +3 -1
  37. package/dist/cursor-tool-presentation-registry.js +2 -4
  38. package/dist/cursor-tool-visibility.js +0 -3
  39. package/dist/cursor-transcript-tool-specs.js +1 -2
  40. package/dist/index.js +21 -0
  41. package/dist/model-discovery.js +0 -1
  42. package/dist/model-list-cache.js +4 -9
  43. package/docs/cursor-live-smoke-checklist.md +4 -4
  44. package/docs/cursor-model-ux-spec.md +18 -8
  45. package/docs/cursor-native-tool-replay.md +2 -2
  46. package/docs/cursor-native-tool-visual-audit.md +1 -1
  47. package/docs/cursor-testing-lessons.md +22 -4
  48. package/docs/cursor-tool-surfaces.md +32 -3
  49. package/docs/development.md +101 -0
  50. package/docs/platform-smoke-implementation.md +3 -2
  51. package/docs/reference.md +810 -0
  52. package/node_modules/@modelcontextprotocol/core/node_modules/zod/package.json +1 -1
  53. package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
  54. package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
  55. package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
  56. package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/core/compile.ts +9 -0
  57. package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/core/schemas.ts +35 -1
  58. package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/core/util.ts +13 -0
  59. package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/core/versions.ts +1 -1
  60. package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/compile.cjs +8 -0
  61. package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/compile.js +8 -0
  62. package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/schemas.cjs +30 -0
  63. package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/schemas.d.cts +3 -1
  64. package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/schemas.d.ts +3 -1
  65. package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/schemas.js +30 -0
  66. package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/util.cjs +15 -0
  67. package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/util.d.cts +2 -0
  68. package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/util.d.ts +2 -0
  69. package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/util.js +14 -0
  70. package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/versions.cjs +1 -1
  71. package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/versions.js +1 -1
  72. package/node_modules/@modelcontextprotocol/server/node_modules/zod/package.json +1 -1
  73. package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
  74. package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
  75. package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
  76. package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/core/compile.ts +9 -0
  77. package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/core/schemas.ts +35 -1
  78. package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/core/util.ts +13 -0
  79. package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/core/versions.ts +1 -1
  80. package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/compile.cjs +8 -0
  81. package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/compile.js +8 -0
  82. package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/schemas.cjs +30 -0
  83. package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/schemas.d.cts +3 -1
  84. package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/schemas.d.ts +3 -1
  85. package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/schemas.js +30 -0
  86. package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/util.cjs +15 -0
  87. package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/util.d.cts +2 -0
  88. package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/util.d.ts +2 -0
  89. package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/util.js +14 -0
  90. package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/versions.cjs +1 -1
  91. package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/versions.js +1 -1
  92. package/package.json +14 -6
  93. package/scripts/platform-smoke/artifact-secrets.mjs +13 -4
  94. package/scripts/platform-smoke/local-resume-runner.mjs +0 -1
  95. package/scripts/platform-smoke/target-runtime.mjs +4 -2
  96. package/scripts/platform-smoke/targets.mjs +0 -1
  97. package/scripts/steering-rpc-smoke.mjs +1 -1
  98. package/shared/cursor-sdk-output-filter.d.mts +2 -0
  99. package/shared/cursor-sdk-output-filter.mjs +92 -28
  100. package/shared/cursor-sensitive-text.d.mts +1 -0
  101. package/shared/cursor-sensitive-text.mjs +7 -0
  102. package/src/context.ts +16 -9
  103. package/src/cursor-agents-context.ts +6 -6
  104. package/src/cursor-api-key.ts +1 -1
  105. package/src/cursor-bridge-contract.ts +1 -15
  106. package/src/cursor-cloud-local-state.ts +0 -2
  107. package/src/cursor-config.ts +1 -24
  108. package/src/cursor-fallback-models.generated.ts +424 -1
  109. package/src/cursor-live-run-coordinator.ts +5 -0
  110. package/src/cursor-local-run-terminal-error.ts +58 -0
  111. package/src/cursor-mcp-timeout-override.ts +6 -30
  112. package/src/cursor-native-tool-display-replay.ts +4 -6
  113. package/src/cursor-native-tools.ts +14 -0
  114. package/src/cursor-pi-tool-bridge-env.ts +31 -2
  115. package/src/cursor-pi-tool-bridge-server.ts +89 -45
  116. package/src/cursor-pi-tool-bridge-snapshot.ts +0 -1
  117. package/src/cursor-pi-tool-bridge.ts +2 -18
  118. package/src/cursor-provider-errors.ts +0 -8
  119. package/src/cursor-provider-live-run-drain.ts +8 -2
  120. package/src/cursor-provider-overflow.ts +6 -24
  121. package/src/cursor-provider-turn-finalize.ts +2 -0
  122. package/src/cursor-provider-turn-prepare.ts +10 -1
  123. package/src/cursor-provider-turn-runner.ts +17 -6
  124. package/src/cursor-provider-turn-send.ts +2 -0
  125. package/src/cursor-provider.ts +0 -5
  126. package/src/cursor-runtime-state.ts +3 -3
  127. package/src/cursor-sdk-event-debug.ts +2 -2
  128. package/src/cursor-sdk-output-filter.ts +2 -1
  129. package/src/cursor-sdk-process-error-guard.ts +1 -3
  130. package/src/cursor-session-agent.ts +8 -8
  131. package/src/cursor-session-scope.ts +1 -9
  132. package/src/cursor-session-store.ts +3 -2
  133. package/src/cursor-state.ts +9 -36
  134. package/src/cursor-tool-lifecycle.ts +0 -4
  135. package/src/cursor-tool-manifest.ts +4 -1
  136. package/src/cursor-tool-presentation-registry.ts +3 -5
  137. package/src/cursor-tool-visibility.ts +0 -4
  138. package/src/cursor-transcript-tool-specs.ts +0 -3
  139. package/src/index.ts +18 -0
  140. package/src/model-discovery.ts +0 -1
  141. package/src/model-list-cache.ts +4 -10
@@ -1,5 +1,6 @@
1
+ import { assertCursorNativeToolsRuntime, resolveCursorNativeToolsEnabled } from "./cursor-native-tools.js";
1
2
  import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
2
- import { drainExistingCursorLiveRunBeforeSend } from "./cursor-provider-live-run-drain.js";
3
+ import { cursorLiveRuns, drainExistingCursorLiveRunBeforeSend } from "./cursor-provider-live-run-drain.js";
3
4
  import { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
4
5
  import type { CursorRuntime } from "./cursor-config.js";
5
6
  import { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
@@ -68,6 +69,13 @@ export class CursorProviderTurnRunner {
68
69
  this.throwIfAborted();
69
70
  const { scope } = this.params;
70
71
  const cwd = scope.cwd;
72
+
73
+ // Resolved once here, before any drain await, so the drain decision and the
74
+ // prepare dispatch below always act on the same config snapshot.
75
+ const resolvedConfig = resolveCursorProviderTurnConfig(cwd, scope.projectTrusted, scope.scopeKey);
76
+ this.runtimeTarget = resolvedConfig.runtime.value;
77
+ const nativeToolsEnabled = resolveCursorNativeToolsEnabled();
78
+ assertCursorNativeToolsRuntime(this.runtimeTarget, nativeToolsEnabled);
71
79
  this.sdkEventDebug = CursorSdkEventDebugSink.maybeCreate({
72
80
  cwd,
73
81
  modelId: model.id,
@@ -76,13 +84,9 @@ export class CursorProviderTurnRunner {
76
84
  });
77
85
  sdkEventDebugRef.current = this.sdkEventDebug;
78
86
  this.sdkEventDebug?.recordContextSnapshot(context);
79
- // Resolved once here, before any drain await, so the drain decision and the
80
- // prepare dispatch below always act on the same config snapshot.
81
- const resolvedConfig = resolveCursorProviderTurnConfig(cwd, scope.projectTrusted, scope.scopeKey);
82
- this.runtimeTarget = resolvedConfig.runtime.value;
83
87
  if (resolvedConfig.runtime.value === "local" && this.params.request.purpose === "normal") {
84
88
  if (
85
- (await drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, options?.signal, this.sdkEventDebug, scope.scopeKey, this.params.request.occupancyFloor)) ===
89
+ (await drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, options?.signal, this.sdkEventDebug, scope.scopeKey, this.params.request.occupancyFloor, nativeToolsEnabled)) ===
86
90
  "stream_ended"
87
91
  ) {
88
92
  return;
@@ -98,6 +102,7 @@ export class CursorProviderTurnRunner {
98
102
  sdkEventDebug: this.sdkEventDebug,
99
103
  throwIfAborted: () => this.throwIfAborted(),
100
104
  resolvedConfig,
105
+ nativeToolsEnabled,
101
106
  });
102
107
 
103
108
  const usage = await this.params.usageRecorder.start({
@@ -173,6 +178,12 @@ export class CursorProviderTurnRunner {
173
178
  displayOnlyTraceBlock: finalized.displayOnlyTraceBlock,
174
179
  });
175
180
  } catch (error) {
181
+ // This body owns the session turn lease; a queued request aborted before
182
+ // entry must not release another request's run in handleOuterCatch().
183
+ if (!prepared && (error instanceof CursorLiveRunAbortError || options?.signal?.aborted)) {
184
+ const pending = cursorLiveRuns.getActiveForScope(this.params.scope.scopeKey);
185
+ if (pending && !pending.disposed) await cursorLiveRuns.release(pending);
186
+ }
176
187
  await runFinalizer.applyTerminalEvent({ kind: "error", prepared: started ?? prepared, error });
177
188
  } finally {
178
189
  await runFinalizer.cleanup(prepared, sendResult, liveCompletion);
@@ -4,6 +4,7 @@ import { createCursorCloudLifecyclePersistenceError } from "./cursor-cloud-lifec
4
4
  import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
5
5
  import { consumeCursorLocalForceOverride } from "./cursor-runtime-state.js";
6
6
  import { recordCursorSessionAgentLineage } from "./cursor-session-agent-lineage.js";
7
+ import { watchCursorLocalRunTerminalError } from "./cursor-local-run-terminal-error.js";
7
8
  import type { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
8
9
  import type {
9
10
  CursorProviderTurnRunnerParams,
@@ -123,6 +124,7 @@ export async function sendCursorProviderTurn(sendParams: SendCursorProviderTurnP
123
124
  recordCursorSessionAgentLineage(agent.agentId, params.scope.scopeKey);
124
125
  }
125
126
  const run = await runPromise;
127
+ if (prepared.runtimeTarget === "local") watchCursorLocalRunTerminalError(prepared.store, run);
126
128
  sdkRun = run;
127
129
  runtime.sdkRun = run;
128
130
  try {
@@ -9,8 +9,6 @@ import {
9
9
  } from "@earendil-works/pi-ai";
10
10
  import {
11
11
  cursorLiveRuns,
12
- DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS,
13
- getPendingCursorLiveRun,
14
12
  hasTrailingUserMessagesAfterToolResults,
15
13
  releaseAllPendingCursorLiveRunsForTests,
16
14
  resetCursorNativeReplayIdleDisposeMs,
@@ -114,10 +112,7 @@ export function streamCursor(
114
112
  }
115
113
 
116
114
  export const __testUtils = {
117
- DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS,
118
115
  pendingCursorNativeRunCount: cursorLiveRuns.count,
119
- getPendingCursorLiveRun,
120
- getActiveCursorLiveRunForScope: cursorLiveRuns.getActiveForScope,
121
116
  hasTrailingUserMessagesAfterToolResults,
122
117
  setCursorNativeReplayIdleDisposeMs,
123
118
  resetCursorNativeReplayIdleDisposeMs,
@@ -39,9 +39,9 @@ import { getResolvedSessionCursorHttp1Enabled } from "./cursor-http1.js";
39
39
  import { getCursorSessionScopeSnapshot } from "./cursor-session-scope.js";
40
40
  import { cursorSettingsScopeForContext, getCursorSessionSettings } from "./cursor-session-settings.js";
41
41
 
42
- export const CURSOR_RUNTIME_ENTRY_TYPE = "cursor-runtime-state";
42
+ const CURSOR_RUNTIME_ENTRY_TYPE = "cursor-runtime-state";
43
43
 
44
- export const CURSOR_CLOUD_ACK_DISCLOSURE = [
44
+ const CURSOR_CLOUD_ACK_DISCLOSURE = [
45
45
  "Cursor Cloud executes this work remotely.",
46
46
  "Fresh context is used by default; prior Pi context is included only with explicit bootstrap opt-in.",
47
47
  "Pi-local tools and the Pi bridge are unavailable, and Pi environment variables are not forwarded.",
@@ -145,7 +145,7 @@ export function resolveCursorStatusRuntime(ctx: CursorRuntimeContext): CursorRun
145
145
  }
146
146
  }
147
147
 
148
- export function formatResolvedCursorRuntime(runtime: CursorResolvedSetting<CursorRuntime>): string {
148
+ function formatResolvedCursorRuntime(runtime: CursorResolvedSetting<CursorRuntime>): string {
149
149
  const cap = runtime.cappedBy;
150
150
  return cap
151
151
  ? `${runtime.value} (source: ${runtime.source} safety cap over ${cap.cappedSource} ${cap.cappedValue})`
@@ -171,7 +171,7 @@ export type DiscardedIncompleteStartedToolCallReason =
171
171
  | "sdk-failure"
172
172
  | "run-drain";
173
173
 
174
- export function hashCursorSdkCallId(callId: string): string {
174
+ function hashCursorSdkCallId(callId: string): string {
175
175
  return createHash("sha256").update(callId).digest("hex").slice(0, 8);
176
176
  }
177
177
 
@@ -182,7 +182,7 @@ export interface DiscardedIncompleteStartedToolCallRecord {
182
182
  reason: DiscardedIncompleteStartedToolCallReason;
183
183
  }
184
184
 
185
- export function serializeDiscardedIncompleteStartedToolCall(record: {
185
+ function serializeDiscardedIncompleteStartedToolCall(record: {
186
186
  toolName: string;
187
187
  callId: string;
188
188
  reason?: DiscardedIncompleteStartedToolCallReason;
@@ -1,5 +1,6 @@
1
1
  export {
2
- CURSOR_SDK_STARTUP_NOISE_PATTERNS,
2
+ __testUtils,
3
+ installCursorSdkProcessOutputFilter,
3
4
  withCursorSdkOutputNoticeHandler,
4
5
  installCursorSdkOutputFilter,
5
6
  isCursorSdkOutputSuppressed,
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import { classifyCursorConnectError, isCursorSdkAbortConnectError, isCursorSdkConnectionStalledError } from "./cursor-provider-errors.js";
2
+ import { classifyCursorConnectError, isCursorSdkConnectionStalledError } from "./cursor-provider-errors.js";
3
3
 
4
4
  interface CursorSdkProcessErrorGuardToken {
5
5
  suppressAbortErrors: boolean;
@@ -147,8 +147,6 @@ export const __testUtils = {
147
147
  },
148
148
  };
149
149
 
150
- export { isCursorSdkAbortConnectError };
151
-
152
150
  export function installCursorSdkProcessErrorGuard(): CursorSdkProcessErrorGuard {
153
151
  const token: CursorSdkProcessErrorGuardToken = { suppressAbortErrors: false };
154
152
  activeProviderTurns.add(token);
@@ -140,6 +140,8 @@ interface SessionCursorAgentCreateParams {
140
140
  settingSources?: SettingSource[];
141
141
  localSafety?: CursorLocalSafetyOptions;
142
142
  agents?: AgentOptions["agents"];
143
+ /** Captured by the turn owner before asynchronous acquisition. */
144
+ nativeToolsEnabled?: boolean;
143
145
  useHttp1ForAgent?: boolean;
144
146
  onBridgeToolRequest?: (request: CursorPiBridgeToolRequest) => void;
145
147
  debugRecorder?: CursorSdkEventDebugRecorder;
@@ -153,7 +155,7 @@ interface SessionCursorAgentCreateParams {
153
155
  const sessionAgentsByScope = new Map<string, SessionCursorAgentPoolEntry>();
154
156
  const invalidatedScopeKeys = new Set<string>();
155
157
  const deadTransportScopeKeys = new Set<string>();
156
- let deadTransportAgentDisposeTimeoutMs = 3000;
158
+ const DEAD_TRANSPORT_AGENT_DISPOSE_TIMEOUT_MS = 3000;
157
159
  const terminalDisposedScopeGenerations = new Map<string, number>();
158
160
  const scopeCreationGenerations = new Map<string, number>();
159
161
  const EMPTY_POOL_STATE: SessionCursorAgentPoolState = { status: "empty" };
@@ -254,6 +256,8 @@ function buildSessionAgentPoolKey(scopeKey: string, params: SessionCursorAgentCr
254
256
  buildApiKeyPoolKeyFingerprint(params.apiKey),
255
257
  buildBridgePoolKeySuffix("bridge" in params ? params.bridge : getRegisteredCursorPiToolBridge()),
256
258
  ...buildCustomSubagentPoolKeySuffix(params.agents),
259
+ // Omit for the default so historical durable keys remain byte-for-byte stable.
260
+ ...(params.nativeToolsEnabled === false ? ["tools:mcp-only"] : []),
257
261
  ].join("\0");
258
262
  }
259
263
 
@@ -273,7 +277,7 @@ async function disposePoolEntry(entry: SessionCursorAgentPoolEntry, options?: {
273
277
  await (options?.deadTransport
274
278
  ? Promise.race([
275
279
  disposal,
276
- new Promise<void>((resolve) => setTimeout(resolve, deadTransportAgentDisposeTimeoutMs).unref?.()),
280
+ new Promise<void>((resolve) => setTimeout(resolve, DEAD_TRANSPORT_AGENT_DISPOSE_TIMEOUT_MS).unref?.()),
277
281
  ])
278
282
  : disposal);
279
283
  } catch {
@@ -520,6 +524,7 @@ async function createSessionAgentEntry(
520
524
  localSafety: params.localSafety,
521
525
  store: sessionStore!.store,
522
526
  }),
527
+ ...(params.nativeToolsEnabled === false ? { tools: ["mcp"] } : {}),
523
528
  ...(bridgeRun?.mcpServers ? { mcpServers: bridgeRun.mcpServers } : {}),
524
529
  ...(params.agents && Object.keys(params.agents).length ? { agents: params.agents } : {}),
525
530
  });
@@ -584,7 +589,7 @@ export {
584
589
  } from "./cursor-session-send-policy.js";
585
590
 
586
591
  export function invalidateSessionAgent(
587
- scopeKey: string = getCursorSessionScopeKey(),
592
+ scopeKey: string,
588
593
  options?: { deadTransport?: boolean },
589
594
  ): void {
590
595
  invalidatedScopeKeys.add(scopeKey);
@@ -725,11 +730,6 @@ export const __testUtils = {
725
730
  resetSessionCursorAgent,
726
731
  refreshSessionCursorAgentConfig,
727
732
  disposeAllSessionCursorAgents,
728
- setDeadTransportAgentDisposeTimeoutMs(ms: number): number {
729
- const previous = deadTransportAgentDisposeTimeoutMs;
730
- deadTransportAgentDisposeTimeoutMs = ms;
731
- return previous;
732
- },
733
733
  SessionCursorAgentCreationSupersededError,
734
734
  SessionCursorAgentScopeClosedError,
735
735
  };
@@ -12,7 +12,7 @@ interface CursorSessionScopeExtensionApi {
12
12
 
13
13
  const ANONYMOUS_SESSION_SCOPE_KEY = "__anonymous__";
14
14
  const EPHEMERAL_SESSION_SCOPE_PREFIX = "__ephemeral__:";
15
- export const MAX_CURSOR_SESSION_NAME_LENGTH = 100;
15
+ const MAX_CURSOR_SESSION_NAME_LENGTH = 100;
16
16
 
17
17
  type CursorSessionScopeChangeHandler = (previousScopeKey: string) => Promise<void> | void;
18
18
 
@@ -103,18 +103,10 @@ export function getCursorSessionScopeGeneration(scopeKey: string = getCursorSess
103
103
  return scopeGenerations.get(scopeKey) ?? 0;
104
104
  }
105
105
 
106
- export function getCursorSessionCwd(): string {
107
- return state.sessionCwd;
108
- }
109
-
110
106
  export function getCursorSessionProjectTrusted(pi?: object): boolean {
111
107
  return pi ? scopes.get(pi)?.projectTrusted === true : state.projectTrusted;
112
108
  }
113
109
 
114
- export function getCursorSessionName(): string | undefined {
115
- return state.sessionName;
116
- }
117
-
118
110
  function normalizeCursorSessionName(name: string | undefined): string | undefined {
119
111
  if (name === undefined) return undefined;
120
112
  return truncateCursorDisplayLine(name, MAX_CURSOR_SESSION_NAME_LENGTH) || undefined;
@@ -6,6 +6,7 @@ import { dirname, isAbsolute, join, relative, resolve, sep, toNamespacedPath } f
6
6
  import type { LocalAgentStore } from "@cursor/sdk";
7
7
  import { loadCursorSdk } from "./cursor-sdk-runtime.js";
8
8
  import { guardCursorCustomStorePath, normalizeCursorCustomStoreRoot, prepareCursorCustomStoreRoot } from "./cursor-custom-store-path.js";
9
+ import { observeCursorLocalRunTerminalErrors } from "./cursor-local-run-terminal-error.js";
9
10
 
10
11
  export interface CursorSessionStoreIdentity {
11
12
  readonly version: 1;
@@ -55,7 +56,7 @@ const activeWorkspaceRoots = new Map<string, { root: Promise<string>; users: num
55
56
  // ponytail: leases protect only extension-owned stores in this process. Other SDK
56
57
  // users/processes must stop before upgrade; a cross-process lock belongs in the SDK.
57
58
 
58
- export function hashCursorSessionStoreScope(scopeKey: string): string {
59
+ function hashCursorSessionStoreScope(scopeKey: string): string {
59
60
  return createHash("sha256")
60
61
  .update("pi-cursor-sdk-session-store\0")
61
62
  .update(scopeKey)
@@ -299,7 +300,7 @@ async function openOwnedCursorSessionStore(
299
300
  const openedStore = store;
300
301
  return {
301
302
  identity: openedIdentity,
302
- store: openedStore,
303
+ store: observeCursorLocalRunTerminalErrors(openedStore),
303
304
  dispose: async () => {
304
305
  try {
305
306
  await openedStore.dispose();
@@ -44,11 +44,7 @@ import {
44
44
  updateCursorSdkConfig,
45
45
  } from "./cursor-config.js";
46
46
  import {
47
- consumeCursorLocalForceOverride,
48
- CURSOR_RUNTIME_ENTRY_TYPE,
49
47
  formatCursorStatus,
50
- getCursorCliConfig,
51
- getCursorSessionConfig,
52
48
  registerCursorCloudRuntimeControls,
53
49
  resetCursorRuntimeStateForTests,
54
50
  resolveCursorStatusRuntime,
@@ -57,19 +53,11 @@ import {
57
53
  type CursorRuntimeStateExtensionApi,
58
54
  } from "./cursor-runtime-state.js";
59
55
 
60
- export {
61
- consumeCursorLocalForceOverride,
62
- getCursorCliConfig,
63
- getCursorSessionConfig,
64
- } from "./cursor-runtime-state.js";
65
-
66
56
  export const CURSOR_FOOTER_ENV = "PI_CURSOR_FOOTER";
67
57
 
68
58
  const FAST_ENTRY_TYPE = "cursor-fast-state";
69
59
  const MODE_ENTRY_TYPE = "cursor-mode-state";
70
60
 
71
- export type CursorAgentMode = AgentModeOption;
72
-
73
61
  const DEFAULT_CURSOR_AGENT_MODE: AgentModeOption = "agent";
74
62
 
75
63
  interface CursorFastEntryData {
@@ -91,11 +79,11 @@ const authoritativeGlobalFastPreferenceIds = new Set<string>();
91
79
  let globalFastPreferences = new Map<string, boolean>();
92
80
  const invalidCursorModeNotifiedSessionScopeKeys = new Set<string>();
93
81
 
94
- export function isCursorAgentMode(value: unknown): value is AgentModeOption {
82
+ function isCursorAgentMode(value: unknown): value is AgentModeOption {
95
83
  return value === "agent" || value === "plan";
96
84
  }
97
85
 
98
- export function parseCursorAgentMode(raw: unknown): AgentModeOption | undefined {
86
+ function parseCursorAgentMode(raw: unknown): AgentModeOption | undefined {
99
87
  if (typeof raw !== "string") return undefined;
100
88
  const mode = raw.trim();
101
89
  return isCursorAgentMode(mode) ? mode : undefined;
@@ -115,17 +103,13 @@ function isCursorModeEntryData(value: unknown): value is CursorModeEntryData {
115
103
  return isCursorAgentMode(asRecord(value)?.mode);
116
104
  }
117
105
 
118
- function getConfigPath(): string {
119
- return getCursorSdkUserConfigPath();
120
- }
121
-
122
106
  function loadGlobalFastPreferences(): Map<string, boolean> {
123
107
  return cursorFastDefaultsFromConfig(loadCursorSdkUserConfig());
124
108
  }
125
109
 
126
110
  function saveGlobalFastPreference(modelId: string, fast: boolean): void {
127
111
  updateCursorSdkConfig(
128
- getConfigPath(),
112
+ getCursorSdkUserConfigPath(),
129
113
  (current) => {
130
114
  const fastDefaults = { ...asRecord(current.fastDefaults), [modelId]: fast };
131
115
  return {
@@ -141,7 +125,7 @@ function saveGlobalFastPreference(modelId: string, fast: boolean): void {
141
125
 
142
126
  function saveGlobalCursorHttp1Enabled(enabled: boolean): void {
143
127
  updateCursorSdkConfig(
144
- getConfigPath(),
128
+ getCursorSdkUserConfigPath(),
145
129
  (current) => mergeCursorSdkConfigForUpdate(current, {
146
130
  local: { useHttp1ForAgent: enabled },
147
131
  }),
@@ -227,15 +211,15 @@ function formatInvalidCursorMode(raw: string): string {
227
211
  return `Invalid --cursor-mode "${raw}". Use "agent" or "plan".`;
228
212
  }
229
213
 
230
- export type CursorAgentModeResolution =
214
+ type CursorAgentModeResolution =
231
215
  | { kind: "valid"; mode: AgentModeOption }
232
216
  | { kind: "invalid"; raw: string; message: string };
233
217
 
234
- export function getStoredCursorAgentMode(scopeKey?: string): AgentModeOption {
218
+ function getStoredCursorAgentMode(scopeKey?: string): AgentModeOption {
235
219
  return getCursorSessionSettings(scopeKey).mode ?? DEFAULT_CURSOR_AGENT_MODE;
236
220
  }
237
221
 
238
- export function resolveCursorAgentMode(scopeKey?: string): CursorAgentModeResolution {
222
+ function resolveCursorAgentMode(scopeKey?: string): CursorAgentModeResolution {
239
223
  const cliCursorModeState = getCursorSessionSettings(scopeKey).cli.mode;
240
224
  switch (cliCursorModeState.kind) {
241
225
  case "valid":
@@ -383,10 +367,10 @@ function formatEffectiveCursorSettingSourcesLabel(raw: string | undefined = proc
383
367
  return `${rawLabel} (effective: ${effectiveLabel})`;
384
368
  }
385
369
 
386
- export function formatCursorToolsDebugReport(
370
+ function formatCursorToolsDebugReport(
387
371
  pi: Pick<ExtensionAPI, "getActiveTools" | "getAllTools">,
388
- env: Record<string, string | undefined> = process.env,
389
372
  ): string {
373
+ const env = process.env;
390
374
  const bridgeEnabled = resolveCursorPiToolBridgeEnabled(env);
391
375
  const manifestEnabled = resolveCursorToolManifestEnabled(env);
392
376
  const lines = [
@@ -689,16 +673,5 @@ function resetCursorModeStateForTests(): void {
689
673
  }
690
674
 
691
675
  export const __testUtils = {
692
- FAST_ENTRY_TYPE,
693
- MODE_ENTRY_TYPE,
694
- CURSOR_HTTP1_ENTRY_TYPE,
695
- RUNTIME_ENTRY_TYPE: CURSOR_RUNTIME_ENTRY_TYPE,
696
- DEFAULT_CURSOR_AGENT_MODE,
697
- getConfigPath,
698
- loadGlobalFastPreferences,
699
- get sessionFastPreferences() { return getCursorSessionSettings().fast; },
700
- getSessionCursorAgentMode: () => getCursorSessionSettings().mode,
701
- getCliCursorAgentMode: () => { const mode = getCursorSessionSettings().cli.mode; return mode.kind === "valid" ? mode.mode : undefined; },
702
- getCliCursorModeState: () => getCursorSessionSettings().cli.mode,
703
676
  resetCursorModeStateForTests,
704
677
  };
@@ -86,10 +86,6 @@ function buildCursorToolLifecycleLabelFromVisibility(
86
86
  }
87
87
  }
88
88
 
89
- export function buildCursorToolLifecycleLabel(toolCall: unknown, apiKey?: string): string | undefined {
90
- return buildCursorToolLifecycleLabelFromVisibility(classifyCursorToolVisibility(toolCall), apiKey);
91
- }
92
-
93
89
  export function formatCursorToolLifecycleProgressText(toolCall: unknown, apiKey?: string): string | undefined {
94
90
  const visibility = classifyCursorToolVisibility(toolCall);
95
91
  const label = buildCursorToolLifecycleLabelFromVisibility(visibility, apiKey);
@@ -18,6 +18,7 @@ export function resolveCursorToolManifestEnabled(
18
18
 
19
19
  export function buildCursorToolManifestText(options: {
20
20
  bridgeSnapshot?: CursorPiToolBridgeSnapshot;
21
+ nativeToolsEnabled?: boolean;
21
22
  /** When false, bridge is off via PI_CURSOR_PI_TOOL_BRIDGE=0 (not merely empty). */
22
23
  piBridgeEnabled?: boolean;
23
24
  includePiBridgeGuidance?: boolean;
@@ -26,7 +27,9 @@ export function buildCursorToolManifestText(options: {
26
27
  const includePiBridgeGuidance = options.includePiBridgeGuidance !== false;
27
28
  const lines = [
28
29
  "Callable tool surfaces this run:",
29
- `- Cursor host/MCP: ${CURSOR_HOST_TOOL_MANIFEST_SUMMARY}; configured MCP depends on Cursor settings.`,
30
+ options.nativeToolsEnabled === false
31
+ ? "- Cursor native tools (including task/subagents): disabled (PI_CURSOR_NATIVE_TOOLS=0). MCP remains available; configured MCP depends on Cursor settings."
32
+ : `- Cursor host/MCP: ${CURSOR_HOST_TOOL_MANIFEST_SUMMARY}; configured MCP depends on Cursor settings.`,
30
33
  "- Pi tool toggles affect pi tools/bridge exposure only; they do not disable Cursor host/configured MCP tools.",
31
34
  ];
32
35
  const bridgeTools = includePiBridgeGuidance ? options.bridgeSnapshot?.tools ?? [] : [];
@@ -40,7 +40,7 @@ import {
40
40
  buildWebSearchReplaySummaryArgs,
41
41
  type CursorReplayActivityBuildContext,
42
42
  } from "./cursor-replay-activity-builders.js";
43
- import { CURSOR_REPLAY_SOURCE_TOOL_NAMES, type CursorReplaySourceToolName } from "./cursor-replay-source-names.js";
43
+ import type { CursorReplaySourceToolName } from "./cursor-replay-source-names.js";
44
44
  import type {
45
45
  CursorReplayActivityDetailFields,
46
46
  CursorReplayGenerateImageDetailFields,
@@ -112,7 +112,7 @@ const WEB_SEARCH_NAME_PATTERN =
112
112
  const WEB_FETCH_NAME_PATTERN =
113
113
  /^(?:web[-_ ]?fetch|fetch[-_ ]?web|webfetch|browser[-_ ]?fetch|fetch[-_ ]?url)$/i;
114
114
 
115
- export const CURSOR_TOOL_PRESENTATION_SPECS = [
115
+ const CURSOR_TOOL_PRESENTATION_SPECS = [
116
116
  {
117
117
  normalizedName: "read",
118
118
  nameAliases: ["read_file"],
@@ -345,9 +345,7 @@ const WEB_KIND_BY_PATTERN = CURSOR_TOOL_PRESENTATION_SPECS.flatMap((spec) => {
345
345
  return webNamePatterns.map((pattern) => ({ pattern, webKind }));
346
346
  });
347
347
 
348
- export const CURSOR_KNOWN_NORMALIZED_TOOL_NAMES: readonly CursorNormalizedToolName[] = CURSOR_REPLAY_SOURCE_TOOL_NAMES;
349
-
350
- export function getCursorToolPresentationSpec(
348
+ function getCursorToolPresentationSpec(
351
349
  name: string,
352
350
  ): CursorToolPresentationSpec | undefined {
353
351
  const trimmed = name.trim();
@@ -41,7 +41,3 @@ export function classifyCursorToolVisibility(toolCall: unknown): CursorToolVisib
41
41
  fastLocalDiscovery: config?.fastLocalDiscovery ?? false,
42
42
  };
43
43
  }
44
-
45
- export function isFastLocalDiscoveryTool(toolCall: unknown): boolean {
46
- return classifyCursorToolVisibility(toolCall).fastLocalDiscovery;
47
- }
@@ -1,5 +1,4 @@
1
1
  import {
2
- CURSOR_KNOWN_NORMALIZED_TOOL_NAMES,
3
2
  CURSOR_REPLAY_ACTIVITY_TOOL_NAME,
4
3
  getCursorReplayCallSummary,
5
4
  getCursorToolActivityTitle,
@@ -417,8 +416,6 @@ const TOOL_DISPLAY_IMPLEMENTATIONS: Record<CursorNormalizedToolName, ToolDisplay
417
416
  },
418
417
  };
419
418
 
420
- export const CURSOR_TOOL_DISPLAY_SPEC_KEYS = CURSOR_KNOWN_NORMALIZED_TOOL_NAMES;
421
-
422
419
  function getToolDisplaySpec(name: string): ToolDisplaySpec | undefined {
423
420
  if (Object.hasOwn(TOOL_DISPLAY_IMPLEMENTATIONS, name)) return TOOL_DISPLAY_IMPLEMENTATIONS[name as CursorNormalizedToolName];
424
421
  return undefined;
package/src/index.ts CHANGED
@@ -15,6 +15,8 @@ import { registerCursorFallbackIssueWarning } from "./cursor-fallback-warning.js
15
15
  import { registerCursorAgentsContextDedup } from "./cursor-agents-context-registration.js";
16
16
  import { registerCursorOverflowNormalization } from "./cursor-provider-overflow.js";
17
17
  import { registerCursorSdkSessionProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
18
+ import { installCursorSdkProcessOutputFilter } from "./cursor-sdk-output-filter.js";
19
+ import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
18
20
  import { prepareCursorSessionForCompaction } from "./cursor-session-compaction-prep.js";
19
21
  import { registerCursorUsageLedger } from "./cursor-usage-ledger.js";
20
22
  import { registerCursorUsageCommand } from "./cursor-usage-command.js";
@@ -37,10 +39,26 @@ type CursorExtensionApi =
37
39
  & Parameters<typeof registerCursorUsageLedger>[0];
38
40
 
39
41
  export default async function (pi: CursorExtensionApi) {
42
+ installCursorSdkProcessOutputFilter();
40
43
  // Session cwd must register before other session_start listeners that depend on it.
41
44
  registerCursorSessionScope(pi);
42
45
  registerCursorUsageLedger(pi);
43
46
  registerCursorUsageCommand(pi);
47
+ pi.registerCommand("cursor-abort", {
48
+ description: "Abort this session's pending local Cursor run without undoing completed work",
49
+ handler: async (_args, ctx) => {
50
+ const run = cursorLiveRuns.getActiveForScope(getCursorSessionScopeSnapshot(pi).scopeKey);
51
+ if (run && !run.disposed) {
52
+ if (!ctx.isIdle() && ctx.model?.provider === "cursor") ctx.abort();
53
+ await cursorLiveRuns.release(run);
54
+ }
55
+ const message = run
56
+ ? "Pending local Cursor run released. Retry under the current policy; already-executed work was not undone."
57
+ : "No pending local Cursor run in this session.";
58
+ if (ctx.hasUI) ctx.ui.notify(message, "info");
59
+ else process.stderr.write(`${message}\n`);
60
+ },
61
+ });
44
62
  let fallbackIssue: CursorModelFallbackIssue | undefined;
45
63
  const registerCursorProvider = registerCursorProviderBinding(pi);
46
64
  const setFallbackWarning = registerCursorFallbackIssueWarning(pi);
@@ -451,6 +451,5 @@ export async function discoverModels(options: DiscoverModelsOptions = {}): Promi
451
451
  }
452
452
 
453
453
  export const __testUtils = {
454
- parseContextWindow,
455
454
  registerModelItems,
456
455
  };
@@ -29,11 +29,11 @@ function getCachePath(): string {
29
29
  return join(getAgentDir(), MODEL_LIST_CACHE_FILE);
30
30
  }
31
31
 
32
- export function isModelCacheDisabled(): boolean {
32
+ function isModelCacheDisabled(): boolean {
33
33
  return parseEnvBoolean(process.env[DISABLE_ENV_VAR], false);
34
34
  }
35
35
 
36
- export function getModelCacheTtlMs(): number {
36
+ function getModelCacheTtlMs(): number {
37
37
  const raw = process.env[TTL_ENV_VAR];
38
38
  if (raw === undefined) return DEFAULT_TTL_MS;
39
39
  const parsed = Number.parseInt(raw, 10);
@@ -133,7 +133,8 @@ function readCacheFile(): ModelListCacheFile | undefined {
133
133
 
134
134
  // Return cached models only when caching is enabled, the key matches, and the
135
135
  // entry is within the TTL. Used on the hot startup path to skip the network.
136
- export function loadFreshCachedModels(keyFingerprint: string, now: number = Date.now()): ModelListItem[] | undefined {
136
+ export function loadFreshCachedModels(keyFingerprint: string): ModelListItem[] | undefined {
137
+ const now = Date.now();
137
138
  if (isModelCacheDisabled()) return undefined;
138
139
  const ttlMs = getModelCacheTtlMs();
139
140
  if (ttlMs <= 0) return undefined;
@@ -180,10 +181,3 @@ export function clearModelListCache(): void {
180
181
  // Cache cleanup is best-effort; it must not break native logout.
181
182
  }
182
183
  }
183
-
184
- export const __testUtils = {
185
- getCachePath,
186
- DEFAULT_TTL_MS,
187
- DISABLE_ENV_VAR,
188
- TTL_ENV_VAR,
189
- };