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.
- package/CHANGELOG.md +66 -1
- package/README.md +62 -795
- package/dist/context.js +13 -7
- package/dist/cursor-agents-context.js +6 -6
- package/dist/cursor-api-key.js +1 -1
- package/dist/cursor-bridge-contract.js +1 -13
- package/dist/cursor-cloud-local-state.js +0 -1
- package/dist/cursor-config.js +1 -17
- package/dist/cursor-custom-subagent-definitions.js +1 -1
- package/dist/cursor-fallback-models.generated.js +665 -1
- package/dist/cursor-live-run-coordinator.js +1 -0
- package/dist/cursor-local-agent-ownership.js +17 -0
- package/dist/cursor-mcp-timeout-override.js +7 -18
- package/dist/cursor-native-tool-display-replay.js +5 -5
- package/dist/cursor-native-tools.js +11 -0
- package/dist/cursor-pi-tool-bridge-diagnostics.js +4 -0
- package/dist/cursor-pi-tool-bridge-env.js +25 -2
- package/dist/cursor-pi-tool-bridge-run.js +10 -3
- package/dist/cursor-pi-tool-bridge-server.js +98 -48
- package/dist/cursor-pi-tool-bridge-snapshot.js +1 -1
- package/dist/cursor-pi-tool-bridge.js +4 -12
- package/dist/cursor-provider-errors.js +2 -8
- package/dist/cursor-provider-live-run-drain.js +8 -3
- package/dist/cursor-provider-overflow.js +7 -21
- package/dist/cursor-provider-turn-prepare.js +8 -1
- package/dist/cursor-provider-turn-runner.js +17 -6
- package/dist/cursor-provider-turn-summary.js +1 -1
- package/dist/cursor-provider.js +1 -4
- package/dist/cursor-runtime-state.js +3 -3
- package/dist/cursor-sdk-event-debug.js +2 -2
- package/dist/cursor-sdk-output-filter.js +1 -1
- package/dist/cursor-sdk-process-error-guard.js +1 -2
- package/dist/cursor-session-agent-cleanup.js +10 -1
- package/dist/cursor-session-agent-lifecycle.js +2 -2
- package/dist/cursor-session-agent-lineage.js +11 -7
- package/dist/cursor-session-agent-resume.js +15 -9
- package/dist/cursor-session-agent.js +28 -15
- package/dist/cursor-session-scope.js +28 -21
- package/dist/cursor-session-settings.js +3 -3
- package/dist/cursor-session-store.js +2 -2
- package/dist/cursor-state.js +26 -29
- package/dist/cursor-tool-lifecycle.js +0 -3
- package/dist/cursor-tool-manifest.js +3 -1
- package/dist/cursor-tool-presentation-registry.js +2 -4
- package/dist/cursor-tool-visibility.js +0 -3
- package/dist/cursor-transcript-tool-specs.js +1 -2
- package/dist/index.js +21 -0
- package/dist/model-discovery.js +8 -10
- package/dist/model-list-cache.js +4 -9
- package/docs/cursor-live-smoke-checklist.md +4 -4
- package/docs/cursor-model-ux-spec.md +36 -19
- package/docs/cursor-native-tool-replay.md +8 -5
- package/docs/cursor-native-tool-visual-audit.md +1 -1
- package/docs/cursor-testing-lessons.md +39 -9
- package/docs/cursor-tool-surfaces.md +48 -4
- package/docs/development.md +93 -0
- package/docs/platform-smoke-implementation.md +3 -2
- package/docs/reference.md +806 -0
- package/package.json +13 -11
- package/scripts/platform-smoke/artifact-secrets.mjs +13 -4
- package/scripts/platform-smoke/local-resume-runner.mjs +0 -1
- package/scripts/platform-smoke/target-runtime.mjs +4 -2
- package/scripts/platform-smoke/targets.mjs +0 -1
- package/shared/cursor-sdk-output-filter.d.mts +2 -0
- package/shared/cursor-sdk-output-filter.mjs +92 -28
- package/shared/cursor-sensitive-text.d.mts +1 -0
- package/shared/cursor-sensitive-text.mjs +7 -0
- package/src/context.ts +18 -11
- package/src/cursor-agents-context.ts +6 -6
- package/src/cursor-api-key.ts +1 -1
- package/src/cursor-bridge-contract.ts +1 -15
- package/src/cursor-cloud-local-state.ts +0 -2
- package/src/cursor-config.ts +1 -24
- package/src/cursor-custom-subagent-definitions.ts +1 -1
- package/src/cursor-fallback-models.generated.ts +665 -1
- package/src/cursor-live-run-coordinator.ts +5 -0
- package/src/cursor-local-agent-ownership.ts +18 -0
- package/src/cursor-mcp-timeout-override.ts +7 -31
- package/src/cursor-native-tool-display-replay.ts +4 -6
- package/src/cursor-native-tools.ts +14 -0
- package/src/cursor-pi-tool-bridge-diagnostics.ts +17 -0
- package/src/cursor-pi-tool-bridge-env.ts +31 -2
- package/src/cursor-pi-tool-bridge-run.ts +10 -3
- package/src/cursor-pi-tool-bridge-server.ts +89 -45
- package/src/cursor-pi-tool-bridge-snapshot.ts +0 -1
- package/src/cursor-pi-tool-bridge.ts +2 -18
- package/src/cursor-provider-errors.ts +2 -10
- package/src/cursor-provider-live-run-drain.ts +8 -2
- package/src/cursor-provider-overflow.ts +6 -24
- package/src/cursor-provider-turn-prepare.ts +10 -1
- package/src/cursor-provider-turn-runner.ts +17 -6
- package/src/cursor-provider-turn-summary.ts +1 -1
- package/src/cursor-provider.ts +0 -5
- package/src/cursor-runtime-state.ts +3 -3
- package/src/cursor-sdk-event-debug.ts +2 -2
- package/src/cursor-sdk-output-filter.ts +2 -1
- package/src/cursor-sdk-process-error-guard.ts +1 -3
- package/src/cursor-session-agent-cleanup.ts +6 -1
- package/src/cursor-session-agent-lifecycle.ts +2 -2
- package/src/cursor-session-agent-lineage.ts +10 -5
- package/src/cursor-session-agent-resume.ts +14 -7
- package/src/cursor-session-agent.ts +31 -15
- package/src/cursor-session-scope.ts +34 -20
- package/src/cursor-session-settings.ts +3 -3
- package/src/cursor-session-store.ts +2 -2
- package/src/cursor-state.ts +34 -41
- package/src/cursor-tool-lifecycle.ts +0 -4
- package/src/cursor-tool-manifest.ts +4 -1
- package/src/cursor-tool-presentation-registry.ts +3 -5
- package/src/cursor-tool-visibility.ts +0 -4
- package/src/cursor-transcript-tool-specs.ts +0 -3
- package/src/index.ts +18 -0
- package/src/model-discovery.ts +7 -9
- 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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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 {
|
|
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 ?
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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, "");
|
package/src/cursor-state.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
82
|
+
function isCursorAgentMode(value: unknown): value is AgentModeOption {
|
|
93
83
|
return value === "agent" || value === "plan";
|
|
94
84
|
}
|
|
95
85
|
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
214
|
+
type CursorAgentModeResolution =
|
|
229
215
|
| { kind: "valid"; mode: AgentModeOption }
|
|
230
216
|
| { kind: "invalid"; raw: string; message: string };
|
|
231
217
|
|
|
232
|
-
|
|
218
|
+
function getStoredCursorAgentMode(scopeKey?: string): AgentModeOption {
|
|
233
219
|
return getCursorSessionSettings(scopeKey).mode ?? DEFAULT_CURSOR_AGENT_MODE;
|
|
234
220
|
}
|
|
235
221
|
|
|
236
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
package/src/model-discovery.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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 (
|
|
323
|
+
if (effortId) {
|
|
323
324
|
if (metadata.parameterIds.thinking) setParam(params, "thinking", "true");
|
|
324
|
-
setParam(params,
|
|
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.
|
|
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
|
};
|
package/src/model-list-cache.ts
CHANGED
|
@@ -29,11 +29,11 @@ function getCachePath(): string {
|
|
|
29
29
|
return join(getAgentDir(), MODEL_LIST_CACHE_FILE);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
function isModelCacheDisabled(): boolean {
|
|
33
33
|
return parseEnvBoolean(process.env[DISABLE_ENV_VAR], false);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
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
|
|
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
|
-
};
|