pi-cursor-sdk 0.5.2 → 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 (114) hide show
  1. package/CHANGELOG.md +66 -1
  2. package/README.md +62 -795
  3. package/dist/context.js +13 -7
  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-custom-subagent-definitions.js +1 -1
  10. package/dist/cursor-fallback-models.generated.js +665 -1
  11. package/dist/cursor-live-run-coordinator.js +1 -0
  12. package/dist/cursor-local-agent-ownership.js +17 -0
  13. package/dist/cursor-mcp-timeout-override.js +7 -18
  14. package/dist/cursor-native-tool-display-replay.js +5 -5
  15. package/dist/cursor-native-tools.js +11 -0
  16. package/dist/cursor-pi-tool-bridge-diagnostics.js +4 -0
  17. package/dist/cursor-pi-tool-bridge-env.js +25 -2
  18. package/dist/cursor-pi-tool-bridge-run.js +10 -3
  19. package/dist/cursor-pi-tool-bridge-server.js +98 -48
  20. package/dist/cursor-pi-tool-bridge-snapshot.js +1 -1
  21. package/dist/cursor-pi-tool-bridge.js +4 -12
  22. package/dist/cursor-provider-errors.js +2 -8
  23. package/dist/cursor-provider-live-run-drain.js +8 -3
  24. package/dist/cursor-provider-overflow.js +7 -21
  25. package/dist/cursor-provider-turn-prepare.js +8 -1
  26. package/dist/cursor-provider-turn-runner.js +17 -6
  27. package/dist/cursor-provider-turn-summary.js +1 -1
  28. package/dist/cursor-provider.js +1 -4
  29. package/dist/cursor-runtime-state.js +3 -3
  30. package/dist/cursor-sdk-event-debug.js +2 -2
  31. package/dist/cursor-sdk-output-filter.js +1 -1
  32. package/dist/cursor-sdk-process-error-guard.js +1 -2
  33. package/dist/cursor-session-agent-cleanup.js +10 -1
  34. package/dist/cursor-session-agent-lifecycle.js +2 -2
  35. package/dist/cursor-session-agent-lineage.js +11 -7
  36. package/dist/cursor-session-agent-resume.js +15 -9
  37. package/dist/cursor-session-agent.js +28 -15
  38. package/dist/cursor-session-scope.js +28 -21
  39. package/dist/cursor-session-settings.js +3 -3
  40. package/dist/cursor-session-store.js +2 -2
  41. package/dist/cursor-state.js +26 -29
  42. package/dist/cursor-tool-lifecycle.js +0 -3
  43. package/dist/cursor-tool-manifest.js +3 -1
  44. package/dist/cursor-tool-presentation-registry.js +2 -4
  45. package/dist/cursor-tool-visibility.js +0 -3
  46. package/dist/cursor-transcript-tool-specs.js +1 -2
  47. package/dist/index.js +21 -0
  48. package/dist/model-discovery.js +8 -10
  49. package/dist/model-list-cache.js +4 -9
  50. package/docs/cursor-live-smoke-checklist.md +4 -4
  51. package/docs/cursor-model-ux-spec.md +36 -19
  52. package/docs/cursor-native-tool-replay.md +8 -5
  53. package/docs/cursor-native-tool-visual-audit.md +1 -1
  54. package/docs/cursor-testing-lessons.md +39 -9
  55. package/docs/cursor-tool-surfaces.md +48 -4
  56. package/docs/development.md +93 -0
  57. package/docs/platform-smoke-implementation.md +3 -2
  58. package/docs/reference.md +806 -0
  59. package/package.json +13 -11
  60. package/scripts/platform-smoke/artifact-secrets.mjs +13 -4
  61. package/scripts/platform-smoke/local-resume-runner.mjs +0 -1
  62. package/scripts/platform-smoke/target-runtime.mjs +4 -2
  63. package/scripts/platform-smoke/targets.mjs +0 -1
  64. package/shared/cursor-sdk-output-filter.d.mts +2 -0
  65. package/shared/cursor-sdk-output-filter.mjs +92 -28
  66. package/shared/cursor-sensitive-text.d.mts +1 -0
  67. package/shared/cursor-sensitive-text.mjs +7 -0
  68. package/src/context.ts +18 -11
  69. package/src/cursor-agents-context.ts +6 -6
  70. package/src/cursor-api-key.ts +1 -1
  71. package/src/cursor-bridge-contract.ts +1 -15
  72. package/src/cursor-cloud-local-state.ts +0 -2
  73. package/src/cursor-config.ts +1 -24
  74. package/src/cursor-custom-subagent-definitions.ts +1 -1
  75. package/src/cursor-fallback-models.generated.ts +665 -1
  76. package/src/cursor-live-run-coordinator.ts +5 -0
  77. package/src/cursor-local-agent-ownership.ts +18 -0
  78. package/src/cursor-mcp-timeout-override.ts +7 -31
  79. package/src/cursor-native-tool-display-replay.ts +4 -6
  80. package/src/cursor-native-tools.ts +14 -0
  81. package/src/cursor-pi-tool-bridge-diagnostics.ts +17 -0
  82. package/src/cursor-pi-tool-bridge-env.ts +31 -2
  83. package/src/cursor-pi-tool-bridge-run.ts +10 -3
  84. package/src/cursor-pi-tool-bridge-server.ts +89 -45
  85. package/src/cursor-pi-tool-bridge-snapshot.ts +0 -1
  86. package/src/cursor-pi-tool-bridge.ts +2 -18
  87. package/src/cursor-provider-errors.ts +2 -10
  88. package/src/cursor-provider-live-run-drain.ts +8 -2
  89. package/src/cursor-provider-overflow.ts +6 -24
  90. package/src/cursor-provider-turn-prepare.ts +10 -1
  91. package/src/cursor-provider-turn-runner.ts +17 -6
  92. package/src/cursor-provider-turn-summary.ts +1 -1
  93. package/src/cursor-provider.ts +0 -5
  94. package/src/cursor-runtime-state.ts +3 -3
  95. package/src/cursor-sdk-event-debug.ts +2 -2
  96. package/src/cursor-sdk-output-filter.ts +2 -1
  97. package/src/cursor-sdk-process-error-guard.ts +1 -3
  98. package/src/cursor-session-agent-cleanup.ts +6 -1
  99. package/src/cursor-session-agent-lifecycle.ts +2 -2
  100. package/src/cursor-session-agent-lineage.ts +10 -5
  101. package/src/cursor-session-agent-resume.ts +14 -7
  102. package/src/cursor-session-agent.ts +31 -15
  103. package/src/cursor-session-scope.ts +34 -20
  104. package/src/cursor-session-settings.ts +3 -3
  105. package/src/cursor-session-store.ts +2 -2
  106. package/src/cursor-state.ts +34 -41
  107. package/src/cursor-tool-lifecycle.ts +0 -4
  108. package/src/cursor-tool-manifest.ts +4 -1
  109. package/src/cursor-tool-presentation-registry.ts +3 -5
  110. package/src/cursor-tool-visibility.ts +0 -4
  111. package/src/cursor-transcript-tool-specs.ts +0 -3
  112. package/src/index.ts +18 -0
  113. package/src/model-discovery.ts +7 -9
  114. package/src/model-list-cache.ts +4 -10
@@ -12,13 +12,15 @@ 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
 
19
19
  export interface CursorTurnScope {
20
20
  readonly cwd: string;
21
21
  readonly scopeKey: string;
22
+ /** Durable identity only; never use it to own live runtime resources. */
23
+ readonly persistentScopeKey?: string;
22
24
  readonly sessionFile: string | undefined;
23
25
  readonly sessionId: string | undefined;
24
26
  readonly sessionName: string | undefined;
@@ -33,6 +35,7 @@ const state = {
33
35
  sessionName: undefined as string | undefined,
34
36
  projectTrusted: false,
35
37
  sessionGeneration: 0,
38
+ runtimeOwner: undefined as number | undefined,
36
39
  };
37
40
  type ScopeRecord = typeof state;
38
41
  const scopes = new Map<object, ScopeRecord>();
@@ -40,20 +43,34 @@ const scopeChangeHandlers = new Map<object, CursorSessionScopeChangeHandler>();
40
43
  const scopeGenerations = new Map<string, number>([[ANONYMOUS_SESSION_SCOPE_KEY, 0]]);
41
44
  const projectTrustResolutionCwds = new Set<string>();
42
45
  let nextSessionGeneration = 1;
46
+ let nextRuntimeOwner = 1;
47
+ let runtimeOwners = new WeakMap<object, number>();
48
+
49
+ function persistentScopeKeyFor(record: Pick<ScopeRecord, "sessionFile" | "sessionId">): string {
50
+ return record.sessionFile ?? (record.sessionId ? `${EPHEMERAL_SESSION_SCOPE_PREFIX}${record.sessionId}` : ANONYMOUS_SESSION_SCOPE_KEY);
51
+ }
43
52
 
44
53
  function scopeKeyFor(record: ScopeRecord): string {
45
- if (record.sessionFile) return record.sessionFile;
46
- if (record.sessionId) return `${EPHEMERAL_SESSION_SCOPE_PREFIX}${record.sessionId}`;
47
- return ANONYMOUS_SESSION_SCOPE_KEY;
54
+ const persistent = persistentScopeKeyFor(record);
55
+ return record.runtimeOwner === undefined ? persistent : `${persistent}\0runtime:${record.runtimeOwner}`;
48
56
  }
49
57
 
50
58
  export function cursorSessionScopeKeyForManager(manager: {
51
59
  getSessionFile?(): string | undefined | null;
52
60
  getSessionId?(): string | undefined;
53
61
  }): string {
54
- return manager.getSessionFile?.() ?? (manager.getSessionId?.()
55
- ? `${EPHEMERAL_SESSION_SCOPE_PREFIX}${manager.getSessionId()}`
56
- : ANONYMOUS_SESSION_SCOPE_KEY);
62
+ return persistentScopeKeyFor({
63
+ sessionFile: manager.getSessionFile?.() ?? undefined, sessionId: manager.getSessionId?.(),
64
+ });
65
+ }
66
+
67
+ export function cursorRuntimeSessionScopeKeyForManager(manager: {
68
+ getSessionFile?(): string | undefined | null;
69
+ getSessionId?(): string | undefined;
70
+ }): string {
71
+ const persistent = cursorSessionScopeKeyForManager(manager);
72
+ const owner = runtimeOwners.get(manager);
73
+ return owner === undefined ? persistent : `${persistent}\0runtime:${owner}`;
57
74
  }
58
75
 
59
76
  export function getCursorSessionScopeSnapshot(pi?: object): CursorTurnScope {
@@ -64,6 +81,7 @@ export function getCursorSessionScopeSnapshot(pi?: object): CursorTurnScope {
64
81
  return Object.freeze({
65
82
  cwd: record.sessionCwd,
66
83
  scopeKey,
84
+ persistentScopeKey: persistentScopeKeyFor(record),
67
85
  sessionFile: record.sessionFile,
68
86
  sessionId: record.sessionId,
69
87
  sessionName: record.sessionName,
@@ -76,11 +94,7 @@ export function hasLiveCursorSessions(): boolean {
76
94
  return scopes.size > 0;
77
95
  }
78
96
 
79
- export function getCursorSessionFile(): string | undefined {
80
- return state.sessionFile;
81
- }
82
-
83
- /** Stable pool key, with process-local anonymous fallback before session_start. */
97
+ /** Runtime ownership key; persisted identities are captured separately. */
84
98
  export function getCursorSessionScopeKey(): string {
85
99
  return scopeKeyFor(state);
86
100
  }
@@ -89,18 +103,10 @@ export function getCursorSessionScopeGeneration(scopeKey: string = getCursorSess
89
103
  return scopeGenerations.get(scopeKey) ?? 0;
90
104
  }
91
105
 
92
- export function getCursorSessionCwd(): string {
93
- return state.sessionCwd;
94
- }
95
-
96
106
  export function getCursorSessionProjectTrusted(pi?: object): boolean {
97
107
  return pi ? scopes.get(pi)?.projectTrusted === true : state.projectTrusted;
98
108
  }
99
109
 
100
- export function getCursorSessionName(): string | undefined {
101
- return state.sessionName;
102
- }
103
-
104
110
  function normalizeCursorSessionName(name: string | undefined): string | undefined {
105
111
  if (name === undefined) return undefined;
106
112
  return truncateCursorDisplayLine(name, MAX_CURSOR_SESSION_NAME_LENGTH) || undefined;
@@ -112,6 +118,7 @@ function setCursorSessionScope(
112
118
  sessionId?: string,
113
119
  projectTrusted = false,
114
120
  sessionName?: string,
121
+ runtimeOwner?: number,
115
122
  ): void {
116
123
  state.sessionCwd = cwd;
117
124
  state.sessionFile = sessionFile;
@@ -119,6 +126,7 @@ function setCursorSessionScope(
119
126
  state.sessionName = normalizeCursorSessionName(sessionName);
120
127
  state.projectTrusted = projectTrusted;
121
128
  state.sessionGeneration = nextSessionGeneration++;
129
+ state.runtimeOwner = runtimeOwner;
122
130
  scopeGenerations.set(scopeKeyFor(state), state.sessionGeneration);
123
131
  }
124
132
 
@@ -134,6 +142,7 @@ function resetCursorSessionScope(): void {
134
142
  Object.assign(state, {
135
143
  sessionCwd: process.cwd(), sessionFile: undefined, sessionId: undefined,
136
144
  sessionName: undefined, projectTrusted: false, sessionGeneration: 0,
145
+ runtimeOwner: undefined,
137
146
  });
138
147
  nextSessionGeneration = 1;
139
148
  scopeGenerations.clear();
@@ -141,6 +150,8 @@ function resetCursorSessionScope(): void {
141
150
  projectTrustResolutionCwds.clear();
142
151
  scopes.clear();
143
152
  scopeChangeHandlers.clear();
153
+ runtimeOwners = new WeakMap();
154
+ nextRuntimeOwner = 1;
144
155
  }
145
156
 
146
157
  export function onCursorSessionScopeKeyChange(pi: object, handler: CursorSessionScopeChangeHandler): void {
@@ -152,6 +163,7 @@ export function registerCursorSessionScope(pi: CursorSessionScopeExtensionApi):
152
163
  const record: ScopeRecord = {
153
164
  sessionCwd: process.cwd(), sessionFile: undefined, sessionId: undefined,
154
165
  sessionName: undefined, projectTrusted: false, sessionGeneration: 0,
166
+ runtimeOwner: undefined,
155
167
  };
156
168
  scopes.set(pi, record);
157
169
  const trustedCwds = new Set<string>();
@@ -162,6 +174,7 @@ export function registerCursorSessionScope(pi: CursorSessionScopeExtensionApi):
162
174
  pi.on("session_start", async (_event, ctx) => {
163
175
  const previousScopeKey = scopeKeyFor(record);
164
176
  const wasBound = record.sessionGeneration > 0;
177
+ if (!runtimeOwners.has(ctx.sessionManager)) runtimeOwners.set(ctx.sessionManager, nextRuntimeOwner++);
165
178
  setCursorSessionScope(
166
179
  ctx.cwd,
167
180
  ctx.sessionManager?.getSessionFile?.() ?? undefined,
@@ -169,6 +182,7 @@ export function registerCursorSessionScope(pi: CursorSessionScopeExtensionApi):
169
182
  ctx.isProjectTrusted?.() === true
170
183
  && (trustedCwds.has(resolve(ctx.cwd)) || projectTrustResolutionCwds.has(resolve(ctx.cwd)) || isCliProjectTrustApproved()),
171
184
  ctx.sessionManager?.getSessionName?.() ?? undefined,
185
+ runtimeOwners.get(ctx.sessionManager),
172
186
  );
173
187
  Object.assign(record, state);
174
188
  scopes.set(pi, record);
@@ -1,7 +1,7 @@
1
1
  import type { AgentModeOption } from "@cursor/sdk";
2
2
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  import type { CursorExplicitSdkConfig, CursorRuntime } from "./cursor-config.js";
4
- import { cursorSessionScopeKeyForManager, getCursorSessionProjectTrusted } from "./cursor-session-scope.js";
4
+ import { cursorRuntimeSessionScopeKeyForManager, getCursorSessionProjectTrusted } from "./cursor-session-scope.js";
5
5
 
6
6
  export type CursorCliModeState =
7
7
  | { kind: "unset" }
@@ -45,7 +45,7 @@ export function getCursorSessionSettings(scopeKey?: string) {
45
45
 
46
46
  export function cursorSettingsScopeForContext(ctx: Partial<Pick<ExtensionContext, "sessionManager">>): string | undefined {
47
47
  const manager = ctx.sessionManager;
48
- return manager ? cursorSessionScopeKeyForManager(manager) : undefined;
48
+ return manager ? cursorRuntimeSessionScopeKeyForManager(manager) : undefined;
49
49
  }
50
50
 
51
51
  export function registerCursorSessionSettings(pi: Pick<ExtensionAPI, "on">): void {
@@ -53,7 +53,7 @@ export function registerCursorSessionSettings(pi: Pick<ExtensionAPI, "on">): voi
53
53
  let cli = createCliSettings();
54
54
  pi.on("session_start", (_event, ctx) => {
55
55
  if (scopeKey) settings.delete(scopeKey);
56
- scopeKey = cursorSessionScopeKeyForManager(ctx.sessionManager);
56
+ scopeKey = cursorRuntimeSessionScopeKeyForManager(ctx.sessionManager);
57
57
  legacySettings = getCursorSessionSettings(scopeKey);
58
58
  // Reload creates a new ExtensionAPI but retains the same public manager.
59
59
  // Keep the run's one-shot CLI consumption without retaining dead managers.
@@ -55,7 +55,7 @@ const activeWorkspaceRoots = new Map<string, { root: Promise<string>; users: num
55
55
  // ponytail: leases protect only extension-owned stores in this process. Other SDK
56
56
  // users/processes must stop before upgrade; a cross-process lock belongs in the SDK.
57
57
 
58
- export function hashCursorSessionStoreScope(scopeKey: string): string {
58
+ function hashCursorSessionStoreScope(scopeKey: string): string {
59
59
  return createHash("sha256")
60
60
  .update("pi-cursor-sdk-session-store\0")
61
61
  .update(scopeKey)
@@ -194,7 +194,7 @@ function workspaceHashes(cwd: string): { current: string; legacy: string | undef
194
194
  }
195
195
 
196
196
  function assertSafeWorkspaceLayout(cwd: string): void {
197
- // ponytail: SDK 1.0.36 has no public read-only root resolver; use one when exposed.
197
+ // ponytail: SDK 1.0.37 has no public read-only root resolver; use one when exposed.
198
198
  // This pure layout is contract-verified against its factory and public getter;
199
199
  // guard it before that getter can rename MD5 history through an owned link.
200
200
  const slug = cwd.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
@@ -1,7 +1,7 @@
1
1
  import type { AgentModeOption } from "@cursor/sdk";
2
2
  import type { ExtensionAPI, ExtensionContext, SessionEntry } from "@earendil-works/pi-coding-agent";
3
3
  import {
4
- buildCursorToolManifestText,
4
+ CURSOR_HOST_TOOL_MANIFEST_SUMMARY,
5
5
  CURSOR_TOOL_MANIFEST_ENV,
6
6
  resolveCursorToolManifestEnabled,
7
7
  } from "./cursor-tool-manifest.js";
@@ -17,8 +17,10 @@ import {
17
17
  import {
18
18
  buildCursorPiToolBridgeSnapshot,
19
19
  CURSOR_PI_TOOL_BRIDGE_ENV,
20
+ resolveCursorPiToolBridgeBuiltinsEnabled,
20
21
  resolveCursorPiToolBridgeEnabled,
21
22
  } from "./cursor-pi-tool-bridge-snapshot.js";
23
+ import { CURSOR_PI_TOOL_BRIDGE_DEBUG_ENV } from "./cursor-pi-tool-bridge-diagnostics.js";
22
24
  import {
23
25
  CURSOR_SETTING_SOURCES_ENV,
24
26
  DEFAULT_CURSOR_SETTING_SOURCES,
@@ -42,11 +44,7 @@ import {
42
44
  updateCursorSdkConfig,
43
45
  } from "./cursor-config.js";
44
46
  import {
45
- consumeCursorLocalForceOverride,
46
- CURSOR_RUNTIME_ENTRY_TYPE,
47
47
  formatCursorStatus,
48
- getCursorCliConfig,
49
- getCursorSessionConfig,
50
48
  registerCursorCloudRuntimeControls,
51
49
  resetCursorRuntimeStateForTests,
52
50
  resolveCursorStatusRuntime,
@@ -55,19 +53,11 @@ import {
55
53
  type CursorRuntimeStateExtensionApi,
56
54
  } from "./cursor-runtime-state.js";
57
55
 
58
- export {
59
- consumeCursorLocalForceOverride,
60
- getCursorCliConfig,
61
- getCursorSessionConfig,
62
- } from "./cursor-runtime-state.js";
63
-
64
56
  export const CURSOR_FOOTER_ENV = "PI_CURSOR_FOOTER";
65
57
 
66
58
  const FAST_ENTRY_TYPE = "cursor-fast-state";
67
59
  const MODE_ENTRY_TYPE = "cursor-mode-state";
68
60
 
69
- export type CursorAgentMode = AgentModeOption;
70
-
71
61
  const DEFAULT_CURSOR_AGENT_MODE: AgentModeOption = "agent";
72
62
 
73
63
  interface CursorFastEntryData {
@@ -89,11 +79,11 @@ const authoritativeGlobalFastPreferenceIds = new Set<string>();
89
79
  let globalFastPreferences = new Map<string, boolean>();
90
80
  const invalidCursorModeNotifiedSessionScopeKeys = new Set<string>();
91
81
 
92
- export function isCursorAgentMode(value: unknown): value is AgentModeOption {
82
+ function isCursorAgentMode(value: unknown): value is AgentModeOption {
93
83
  return value === "agent" || value === "plan";
94
84
  }
95
85
 
96
- export function parseCursorAgentMode(raw: unknown): AgentModeOption | undefined {
86
+ function parseCursorAgentMode(raw: unknown): AgentModeOption | undefined {
97
87
  if (typeof raw !== "string") return undefined;
98
88
  const mode = raw.trim();
99
89
  return isCursorAgentMode(mode) ? mode : undefined;
@@ -113,17 +103,13 @@ function isCursorModeEntryData(value: unknown): value is CursorModeEntryData {
113
103
  return isCursorAgentMode(asRecord(value)?.mode);
114
104
  }
115
105
 
116
- function getConfigPath(): string {
117
- return getCursorSdkUserConfigPath();
118
- }
119
-
120
106
  function loadGlobalFastPreferences(): Map<string, boolean> {
121
107
  return cursorFastDefaultsFromConfig(loadCursorSdkUserConfig());
122
108
  }
123
109
 
124
110
  function saveGlobalFastPreference(modelId: string, fast: boolean): void {
125
111
  updateCursorSdkConfig(
126
- getConfigPath(),
112
+ getCursorSdkUserConfigPath(),
127
113
  (current) => {
128
114
  const fastDefaults = { ...asRecord(current.fastDefaults), [modelId]: fast };
129
115
  return {
@@ -139,7 +125,7 @@ function saveGlobalFastPreference(modelId: string, fast: boolean): void {
139
125
 
140
126
  function saveGlobalCursorHttp1Enabled(enabled: boolean): void {
141
127
  updateCursorSdkConfig(
142
- getConfigPath(),
128
+ getCursorSdkUserConfigPath(),
143
129
  (current) => mergeCursorSdkConfigForUpdate(current, {
144
130
  local: { useHttp1ForAgent: enabled },
145
131
  }),
@@ -225,15 +211,15 @@ function formatInvalidCursorMode(raw: string): string {
225
211
  return `Invalid --cursor-mode "${raw}". Use "agent" or "plan".`;
226
212
  }
227
213
 
228
- export type CursorAgentModeResolution =
214
+ type CursorAgentModeResolution =
229
215
  | { kind: "valid"; mode: AgentModeOption }
230
216
  | { kind: "invalid"; raw: string; message: string };
231
217
 
232
- export function getStoredCursorAgentMode(scopeKey?: string): AgentModeOption {
218
+ function getStoredCursorAgentMode(scopeKey?: string): AgentModeOption {
233
219
  return getCursorSessionSettings(scopeKey).mode ?? DEFAULT_CURSOR_AGENT_MODE;
234
220
  }
235
221
 
236
- export function resolveCursorAgentMode(scopeKey?: string): CursorAgentModeResolution {
222
+ function resolveCursorAgentMode(scopeKey?: string): CursorAgentModeResolution {
237
223
  const cliCursorModeState = getCursorSessionSettings(scopeKey).cli.mode;
238
224
  switch (cliCursorModeState.kind) {
239
225
  case "valid":
@@ -381,14 +367,14 @@ function formatEffectiveCursorSettingSourcesLabel(raw: string | undefined = proc
381
367
  return `${rawLabel} (effective: ${effectiveLabel})`;
382
368
  }
383
369
 
384
- export function formatCursorToolsDebugReport(
370
+ function formatCursorToolsDebugReport(
385
371
  pi: Pick<ExtensionAPI, "getActiveTools" | "getAllTools">,
386
- env: Record<string, string | undefined> = process.env,
387
372
  ): string {
373
+ const env = process.env;
388
374
  const bridgeEnabled = resolveCursorPiToolBridgeEnabled(env);
389
375
  const manifestEnabled = resolveCursorToolManifestEnabled(env);
390
376
  const lines = [
391
- "Cursor tool surfaces (current session):",
377
+ "Cursor tool surfaces (current registry snapshot; not a live run):",
392
378
  `${CURSOR_PI_TOOL_BRIDGE_ENV}: ${bridgeEnabled ? "enabled" : "disabled"}`,
393
379
  `${CURSOR_TOOL_MANIFEST_ENV}: ${manifestEnabled ? "enabled" : "disabled"}`,
394
380
  `${CURSOR_SETTING_SOURCES_ENV}: ${formatEffectiveCursorSettingSourcesLabel(env[CURSOR_SETTING_SOURCES_ENV])}`,
@@ -397,13 +383,31 @@ export function formatCursorToolsDebugReport(
397
383
  let bridgeSnapshot;
398
384
  if (bridgeEnabled) {
399
385
  try {
400
- bridgeSnapshot = buildCursorPiToolBridgeSnapshot(pi);
386
+ bridgeSnapshot = buildCursorPiToolBridgeSnapshot(pi, {
387
+ exposeOverlappingBuiltins: resolveCursorPiToolBridgeBuiltinsEnabled(env),
388
+ });
401
389
  } catch {
402
390
  lines.push("Pi bridge snapshot: unavailable (extension tool APIs required).");
403
391
  }
404
392
  }
405
393
 
406
- lines.push(buildCursorToolManifestText({ bridgeSnapshot, piBridgeEnabled: bridgeEnabled }));
394
+ lines.push(
395
+ `Cursor host/MCP: ${CURSOR_HOST_TOOL_MANIFEST_SUMMARY}; configured MCP depends on Cursor settings.`,
396
+ "Pi tool toggles affect pi tools/bridge exposure only; they do not disable Cursor host/configured MCP tools.",
397
+ );
398
+ if (!bridgeEnabled) {
399
+ lines.push("Pi bridge: disabled (PI_CURSOR_PI_TOOL_BRIDGE=0).");
400
+ } else if (bridgeSnapshot) {
401
+ const names = bridgeSnapshot.tools.map((tool) => tool.mcpToolName).sort();
402
+ lines.push(`Pi bridge exposure eligibility: ${names.length ? names.join(", ") : "no eligible pi__* tools"}.`);
403
+ }
404
+ lines.push(
405
+ "This snapshot does not prove a run's endpoint attachment, MCP initialization, catalog fetch, or tool calls.",
406
+ `For per-run observations, enable ${CURSOR_PI_TOOL_BRIDGE_DEBUG_ENV}=1 before the turn.`,
407
+ "run_created/tools_exposed record exposure only; mcp_initialized records a validated notifications/initialized receipt; mcp_tools_list records catalog-handler output (toolCount), not client/model consumption or ongoing connection health.",
408
+ "request_queued/resolved/rejected record actual bridge call handling; absent receipts do not diagnose why a client/model made no request.",
409
+ "Not callable: cursor-replay-* IDs, pi history names, transcript labels.",
410
+ );
407
411
  return lines.join("\n");
408
412
  }
409
413
 
@@ -496,7 +500,7 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
496
500
  });
497
501
 
498
502
  pi.registerCommand("cursor-tools", {
499
- description: "Show live Cursor tool surfaces for this session (maintainer debug)",
503
+ description: "Show current-registry Cursor tool exposure eligibility (maintainer debug)",
500
504
  handler: async (_args, ctx) => {
501
505
  emitCursorToolsDebugReport(pi, ctx);
502
506
  },
@@ -669,16 +673,5 @@ function resetCursorModeStateForTests(): void {
669
673
  }
670
674
 
671
675
  export const __testUtils = {
672
- FAST_ENTRY_TYPE,
673
- MODE_ENTRY_TYPE,
674
- CURSOR_HTTP1_ENTRY_TYPE,
675
- RUNTIME_ENTRY_TYPE: CURSOR_RUNTIME_ENTRY_TYPE,
676
- DEFAULT_CURSOR_AGENT_MODE,
677
- getConfigPath,
678
- loadGlobalFastPreferences,
679
- get sessionFastPreferences() { return getCursorSessionSettings().fast; },
680
- getSessionCursorAgentMode: () => getCursorSessionSettings().mode,
681
- getCliCursorAgentMode: () => { const mode = getCursorSessionSettings().cli.mode; return mode.kind === "valid" ? mode.mode : undefined; },
682
- getCliCursorModeState: () => getCursorSessionSettings().cli.mode,
683
676
  resetCursorModeStateForTests,
684
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);
@@ -115,9 +115,9 @@ function mapComparableLevel(
115
115
 
116
116
  function getThinkingLevelMap(item: ModelListItem): ThinkingLevelMap | undefined {
117
117
  const reasoningParameter = getParameter(item, "reasoning");
118
- const effortParameter = getParameter(item, "effort");
118
+ const effortParameter = getParameter(item, "effort") ?? getParameter(item, "reasoning_effort");
119
119
  const thinkingParameter = getParameter(item, "thinking");
120
- const valueParameter = effortParameter ?? reasoningParameter ?? thinkingParameter ?? getParameter(item, "reasoning_effort");
120
+ const valueParameter = effortParameter ?? reasoningParameter ?? thinkingParameter;
121
121
  if (!valueParameter) return undefined;
122
122
 
123
123
  if (valueParameter.id === "thinking" && hasBooleanValues(valueParameter)) {
@@ -306,11 +306,12 @@ function applyThinkingLevel(
306
306
  ): void {
307
307
  const mapped = metadata.thinkingLevelMap?.[level];
308
308
  if (mapped === undefined || mapped === null) return;
309
+ const effortId = metadata.parameterIds.effort ? "effort" : metadata.parameterIds.reasoning_effort ? "reasoning_effort" : undefined;
309
310
 
310
311
  if (level === "off") {
311
312
  if (metadata.parameterIds.thinking && mapped === "false") {
312
313
  setParam(params, "thinking", mapped);
313
- deleteParam(params, "effort");
314
+ if (effortId) deleteParam(params, effortId);
314
315
  return;
315
316
  }
316
317
  if (metadata.parameterIds.reasoning) {
@@ -319,9 +320,9 @@ function applyThinkingLevel(
319
320
  return;
320
321
  }
321
322
 
322
- if (metadata.parameterIds.effort) {
323
+ if (effortId) {
323
324
  if (metadata.parameterIds.thinking) setParam(params, "thinking", "true");
324
- setParam(params, "effort", mapped);
325
+ setParam(params, effortId, mapped);
325
326
  return;
326
327
  }
327
328
 
@@ -332,8 +333,6 @@ function applyThinkingLevel(
332
333
 
333
334
  if (metadata.parameterIds.thinking) {
334
335
  setParam(params, "thinking", mapped);
335
- } else if (metadata.parameterIds.reasoning_effort) {
336
- setParam(params, "reasoning_effort", mapped);
337
336
  }
338
337
  }
339
338
 
@@ -418,7 +417,7 @@ export async function discoverModels(options: DiscoverModelsOptions = {}): Promi
418
417
  try {
419
418
  const { Cursor } = await loadCursorSdk();
420
419
  options.signal?.throwIfAborted();
421
- // SDK 1.0.36 CursorRequestOptions has no caller signal. A late response
420
+ // SDK 1.0.37 CursorRequestOptions has no caller signal. A late response
422
421
  // still cannot publish outside the native generation that requested it.
423
422
  const models = await Cursor.models.list({ apiKey });
424
423
  options.signal?.throwIfAborted();
@@ -452,6 +451,5 @@ export async function discoverModels(options: DiscoverModelsOptions = {}): Promi
452
451
  }
453
452
 
454
453
  export const __testUtils = {
455
- parseContextWindow,
456
454
  registerModelItems,
457
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
- };