pi-cursor-sdk 0.1.19 → 0.1.21
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 +52 -0
- package/README.md +72 -11
- package/docs/cursor-dogfood-checklist.md +57 -0
- package/docs/cursor-live-smoke-checklist.md +116 -10
- package/docs/cursor-model-ux-spec.md +60 -19
- package/docs/cursor-native-tool-replay.md +21 -11
- package/docs/cursor-native-tool-visual-audit.md +104 -59
- package/docs/cursor-testing-lessons.md +10 -5
- package/docs/cursor-tool-surfaces.md +69 -0
- package/package.json +37 -11
- package/scripts/debug-provider-events.d.mts +59 -0
- package/scripts/debug-provider-events.mjs +70 -175
- package/scripts/debug-sdk-events.d.mts +90 -0
- package/scripts/debug-sdk-events.mjs +36 -98
- package/scripts/fixtures/plan-strip-shim/index.ts +12 -0
- package/scripts/isolated-cursor-smoke.sh +264 -102
- package/scripts/lib/cursor-child-process.d.mts +10 -0
- package/scripts/lib/cursor-child-process.mjs +50 -0
- package/scripts/lib/cursor-cli-args.d.mts +63 -0
- package/scripts/lib/cursor-cli-args.mjs +129 -0
- package/scripts/lib/cursor-script-fail.d.mts +1 -0
- package/scripts/lib/cursor-script-fail.mjs +13 -0
- package/scripts/lib/cursor-sdk-output-filter.d.mts +5 -0
- package/scripts/lib/cursor-smoke-env.d.mts +38 -0
- package/scripts/lib/cursor-smoke-env.mjs +81 -0
- package/scripts/lib/cursor-smoke-shell.sh +174 -0
- package/scripts/lib/cursor-visual-render.d.mts +15 -0
- package/scripts/lib/cursor-visual-render.mjs +131 -0
- package/scripts/probe-mcp-coldstart.mjs +226 -0
- package/scripts/refresh-cursor-model-snapshots.mjs +29 -65
- package/scripts/steering-rpc-smoke.mjs +170 -65
- package/scripts/tmux-live-smoke.sh +152 -98
- package/scripts/visual-tui-smoke.mjs +659 -0
- package/shared/cursor-sdk-event-debug-env.d.mts +12 -0
- package/shared/cursor-sdk-event-debug-env.mjs +13 -0
- package/shared/cursor-sensitive-text.d.mts +1 -0
- package/{scripts/lib/cursor-probe-utils.mjs → shared/cursor-sensitive-text.mjs} +1 -13
- package/shared/cursor-setting-sources.d.mts +5 -0
- package/shared/cursor-setting-sources.mjs +22 -0
- package/src/context.ts +21 -12
- package/src/cursor-bridge-contract.ts +1 -3
- package/src/cursor-incomplete-tool-visibility.ts +72 -49
- package/src/cursor-mcp-timeout-override.ts +66 -11
- package/src/cursor-native-tool-display-registration.ts +63 -27
- package/src/cursor-native-tool-display-replay.ts +246 -143
- package/src/cursor-native-tool-display-state.ts +2 -0
- package/src/cursor-native-tool-display-tools.ts +149 -41
- package/src/cursor-provider-live-run-drain.ts +1 -52
- package/src/cursor-provider-run-finalizer.ts +235 -0
- package/src/cursor-provider-run-outcome.ts +149 -0
- package/src/cursor-provider-turn-api-key.ts +8 -0
- package/src/cursor-provider-turn-coordinator.ts +113 -440
- package/src/cursor-provider-turn-display-router.ts +216 -0
- package/src/cursor-provider-turn-emit.ts +59 -0
- package/src/cursor-provider-turn-finalize.ts +119 -0
- package/src/cursor-provider-turn-lifecycle-emitter.ts +97 -0
- package/src/cursor-provider-turn-message-offset.ts +15 -0
- package/src/cursor-provider-turn-prepare.ts +216 -0
- package/src/cursor-provider-turn-runner.ts +138 -0
- package/src/cursor-provider-turn-sdk-normalizer.ts +88 -0
- package/src/cursor-provider-turn-send.ts +103 -0
- package/src/cursor-provider-turn-shell-output.ts +107 -0
- package/src/cursor-provider-turn-tool-ledger.ts +126 -0
- package/src/cursor-provider-turn-types.ts +87 -0
- package/src/cursor-provider.ts +16 -482
- package/src/cursor-replay-activity-builders.ts +276 -0
- package/src/cursor-replay-source-names.ts +33 -0
- package/src/cursor-replay-summary-args.ts +191 -0
- package/src/cursor-replay-tool-details.ts +464 -0
- package/src/cursor-run-final-text.ts +56 -0
- package/src/cursor-sdk-abort-error-guard.ts +4 -0
- package/src/cursor-sdk-event-debug-constants.ts +14 -5
- package/src/cursor-sdk-event-debug.ts +8 -2
- package/src/cursor-sensitive-text.ts +3 -36
- package/src/cursor-session-agent.ts +265 -88
- package/src/cursor-setting-sources.ts +7 -10
- package/src/cursor-state.ts +232 -28
- package/src/cursor-tool-lifecycle.ts +17 -42
- package/src/cursor-tool-manifest.ts +41 -0
- package/src/cursor-tool-names.ts +18 -79
- package/src/cursor-tool-presentation-registry.ts +556 -0
- package/src/cursor-tool-transcript.ts +1 -1
- package/src/cursor-tool-visibility.ts +39 -0
- package/src/cursor-transcript-tool-formatters.ts +0 -59
- package/src/cursor-transcript-tool-specs.ts +169 -232
- package/src/cursor-transcript-utils.ts +0 -44
- package/src/cursor-web-tool-activity.ts +10 -60
- package/src/cursor-web-tool-args.ts +39 -0
- package/src/index.ts +4 -10
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
} from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
9
|
import { Agent } from "@cursor/sdk";
|
|
10
|
-
import type { ModelSelection, SDKAgent, SettingSource } from "@cursor/sdk";
|
|
10
|
+
import type { AgentModeOption, ModelSelection, SDKAgent, SettingSource } from "@cursor/sdk";
|
|
11
11
|
import type { Context } from "@earendil-works/pi-ai";
|
|
12
12
|
import {
|
|
13
13
|
getRegisteredCursorPiToolBridge,
|
|
@@ -26,22 +26,53 @@ export interface SessionCursorAgentSendState {
|
|
|
26
26
|
|
|
27
27
|
export interface SessionCursorAgentLease {
|
|
28
28
|
scopeKey: string;
|
|
29
|
+
poolKey: string;
|
|
30
|
+
instanceId: number;
|
|
29
31
|
agent: SDKAgent;
|
|
30
32
|
bridgeRun?: CursorPiToolBridgeRun;
|
|
31
33
|
sendState: SessionCursorAgentSendState;
|
|
32
34
|
created: boolean;
|
|
35
|
+
commitSend(context: Context, bootstrapped: boolean): void;
|
|
36
|
+
trackRunCompletion(completion: Promise<unknown>): void;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
interface
|
|
39
|
+
interface SessionCursorAgentPoolEntryBase {
|
|
36
40
|
poolKey: string;
|
|
41
|
+
instanceId: number;
|
|
37
42
|
scopeKey: string;
|
|
38
|
-
agent?: SDKAgent;
|
|
39
|
-
bridgeRun?: CursorPiToolBridgeRun;
|
|
40
43
|
sendState: SessionCursorAgentSendState;
|
|
41
|
-
creating?: Promise<SessionCursorAgentPoolEntry>;
|
|
42
|
-
creationGeneration?: number;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
interface SessionCursorAgentCreatingEntry extends SessionCursorAgentPoolEntryBase {
|
|
47
|
+
status: "creating";
|
|
48
|
+
creating: Promise<SessionCursorAgentReadyEntry>;
|
|
49
|
+
creationGeneration: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface SessionCursorAgentReadyEntry extends SessionCursorAgentPoolEntryBase {
|
|
53
|
+
status: "ready";
|
|
54
|
+
agent: SDKAgent;
|
|
55
|
+
bridgeRun?: CursorPiToolBridgeRun;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface SessionCursorAgentBusyEntry extends SessionCursorAgentPoolEntryBase {
|
|
59
|
+
status: "busy";
|
|
60
|
+
agent: SDKAgent;
|
|
61
|
+
bridgeRun?: CursorPiToolBridgeRun;
|
|
62
|
+
completionSettled: Promise<void>;
|
|
63
|
+
pendingCompletion: Promise<void>;
|
|
64
|
+
releaseBusyWait: () => void;
|
|
65
|
+
busyGeneration: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type SessionCursorAgentActiveEntry = SessionCursorAgentReadyEntry | SessionCursorAgentBusyEntry;
|
|
69
|
+
type SessionCursorAgentPoolEntry =
|
|
70
|
+
| SessionCursorAgentCreatingEntry
|
|
71
|
+
| SessionCursorAgentReadyEntry
|
|
72
|
+
| SessionCursorAgentBusyEntry;
|
|
73
|
+
|
|
74
|
+
type SessionCursorAgentPoolState = { status: "empty" } | SessionCursorAgentPoolEntry;
|
|
75
|
+
|
|
45
76
|
class SessionCursorAgentCreationSupersededError extends Error {
|
|
46
77
|
constructor() {
|
|
47
78
|
super("Cursor session agent creation was superseded");
|
|
@@ -72,6 +103,7 @@ function rethrowSupersededWhenReplacedByDifferentPoolKey(scopeKey: string, poolK
|
|
|
72
103
|
|
|
73
104
|
interface SessionCursorAgentCreateParams {
|
|
74
105
|
apiKey: string;
|
|
106
|
+
agentMode: AgentModeOption;
|
|
75
107
|
cwd: string;
|
|
76
108
|
modelSelection: ModelSelection;
|
|
77
109
|
settingSources?: SettingSource[];
|
|
@@ -92,6 +124,20 @@ const sessionAgentsByScope = new Map<string, SessionCursorAgentPoolEntry>();
|
|
|
92
124
|
const invalidatedScopeKeys = new Set<string>();
|
|
93
125
|
const terminalDisposedScopeKeys = new Set<string>();
|
|
94
126
|
const scopeCreationGenerations = new Map<string, number>();
|
|
127
|
+
const EMPTY_POOL_STATE: SessionCursorAgentPoolState = { status: "empty" };
|
|
128
|
+
let nextSessionAgentInstanceId = 1;
|
|
129
|
+
|
|
130
|
+
function allocateSessionAgentInstanceId(): number {
|
|
131
|
+
return nextSessionAgentInstanceId++;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getSessionCursorAgentPoolState(scopeKey: string): SessionCursorAgentPoolState {
|
|
135
|
+
return sessionAgentsByScope.get(scopeKey) ?? EMPTY_POOL_STATE;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isActivePoolEntry(entry: SessionCursorAgentPoolEntry | undefined): entry is SessionCursorAgentActiveEntry {
|
|
139
|
+
return entry?.status === "ready" || entry?.status === "busy";
|
|
140
|
+
}
|
|
95
141
|
|
|
96
142
|
function getScopeCreationGeneration(scopeKey: string): number {
|
|
97
143
|
return scopeCreationGenerations.get(scopeKey) ?? 0;
|
|
@@ -131,13 +177,13 @@ function buildSessionAgentPoolKey(scopeKey: string, params: SessionCursorAgentCr
|
|
|
131
177
|
}
|
|
132
178
|
|
|
133
179
|
async function disposePoolEntry(entry: SessionCursorAgentPoolEntry): Promise<void> {
|
|
180
|
+
if (!isActivePoolEntry(entry)) return;
|
|
134
181
|
entry.bridgeRun?.cancel("Cursor session agent disposed");
|
|
135
182
|
try {
|
|
136
183
|
await entry.bridgeRun?.dispose();
|
|
137
184
|
} catch {
|
|
138
185
|
// disposal failure should not block session replacement
|
|
139
186
|
}
|
|
140
|
-
if (!entry.agent) return;
|
|
141
187
|
try {
|
|
142
188
|
await entry.agent[Symbol.asyncDispose]();
|
|
143
189
|
} catch {
|
|
@@ -153,10 +199,12 @@ async function disposePoolEntryForScope(scopeKey: string, options?: { terminal?:
|
|
|
153
199
|
const entry = sessionAgentsByScope.get(scopeKey);
|
|
154
200
|
invalidatedScopeKeys.delete(scopeKey);
|
|
155
201
|
if (!entry) return;
|
|
156
|
-
const orphanedCreating = entry.creating;
|
|
157
202
|
sessionAgentsByScope.delete(scopeKey);
|
|
158
|
-
if (entry.
|
|
159
|
-
|
|
203
|
+
if (entry.status === "busy") {
|
|
204
|
+
entry.releaseBusyWait();
|
|
205
|
+
}
|
|
206
|
+
if (entry.status === "creating") {
|
|
207
|
+
entry.creating.catch(() => {
|
|
160
208
|
// In-flight Agent.create was orphaned by scope disposal; active waiters surface errors elsewhere.
|
|
161
209
|
});
|
|
162
210
|
return;
|
|
@@ -169,14 +217,96 @@ function createInitialSendState(): SessionCursorAgentSendState {
|
|
|
169
217
|
}
|
|
170
218
|
|
|
171
219
|
function bindBridgeToolRequest(
|
|
172
|
-
entry:
|
|
220
|
+
entry: SessionCursorAgentActiveEntry,
|
|
173
221
|
onBridgeToolRequest?: (request: CursorPiBridgeToolRequest) => void,
|
|
174
222
|
): void {
|
|
175
223
|
entry.bridgeRun?.setOnToolRequest(onBridgeToolRequest);
|
|
176
224
|
}
|
|
177
225
|
|
|
226
|
+
function commitSessionAgentSendForLease(
|
|
227
|
+
scopeKey: string,
|
|
228
|
+
poolKey: string,
|
|
229
|
+
instanceId: number,
|
|
230
|
+
context: Context,
|
|
231
|
+
bootstrapped: boolean,
|
|
232
|
+
): void {
|
|
233
|
+
const entry = sessionAgentsByScope.get(scopeKey);
|
|
234
|
+
if (!isActivePoolEntry(entry)) return;
|
|
235
|
+
if (entry.poolKey !== poolKey || entry.instanceId !== instanceId) return;
|
|
236
|
+
entry.sendState.bootstrapped = bootstrapped || entry.sendState.bootstrapped;
|
|
237
|
+
entry.sendState.contextFingerprint = computeCursorContextFingerprint(context);
|
|
238
|
+
if (bootstrapped) {
|
|
239
|
+
entry.sendState.incrementalSendCount = 0;
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
entry.sendState.incrementalSendCount += 1;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function normalizeRunCompletion(completion: Promise<unknown>): Promise<void> {
|
|
246
|
+
return Promise.resolve(completion).then(
|
|
247
|
+
() => undefined,
|
|
248
|
+
() => undefined,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function buildBusyPoolEntry(
|
|
253
|
+
entry: SessionCursorAgentActiveEntry,
|
|
254
|
+
completionSettled: Promise<void>,
|
|
255
|
+
): SessionCursorAgentBusyEntry {
|
|
256
|
+
let releaseBusyWait = (): void => {};
|
|
257
|
+
const releaseSignal = new Promise<"released">((resolve) => {
|
|
258
|
+
releaseBusyWait = () => resolve("released");
|
|
259
|
+
});
|
|
260
|
+
const pendingCompletion = Promise.race([
|
|
261
|
+
completionSettled.then(() => "completed" as const),
|
|
262
|
+
releaseSignal,
|
|
263
|
+
]).then((outcome) => {
|
|
264
|
+
const current = sessionAgentsByScope.get(entry.scopeKey);
|
|
265
|
+
if (
|
|
266
|
+
outcome === "completed" &&
|
|
267
|
+
current?.status === "busy" &&
|
|
268
|
+
current.poolKey === entry.poolKey &&
|
|
269
|
+
current.instanceId === entry.instanceId &&
|
|
270
|
+
current.pendingCompletion === pendingCompletion
|
|
271
|
+
) {
|
|
272
|
+
sessionAgentsByScope.set(entry.scopeKey, { ...current, status: "ready" });
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
...entry,
|
|
278
|
+
status: "busy",
|
|
279
|
+
completionSettled,
|
|
280
|
+
pendingCompletion,
|
|
281
|
+
releaseBusyWait,
|
|
282
|
+
busyGeneration: getScopeCreationGeneration(entry.scopeKey),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function trackSessionAgentRunCompletionForLease(
|
|
287
|
+
scopeKey: string,
|
|
288
|
+
poolKey: string,
|
|
289
|
+
instanceId: number,
|
|
290
|
+
completion: Promise<unknown>,
|
|
291
|
+
): void {
|
|
292
|
+
const entry = sessionAgentsByScope.get(scopeKey);
|
|
293
|
+
if (!isActivePoolEntry(entry)) return;
|
|
294
|
+
if (entry.poolKey !== poolKey || entry.instanceId !== instanceId) return;
|
|
295
|
+
|
|
296
|
+
const completionToTrack = normalizeRunCompletion(completion);
|
|
297
|
+
const completionSettled = (entry.status === "busy"
|
|
298
|
+
? Promise.all([entry.completionSettled, completionToTrack]).then(() => undefined)
|
|
299
|
+
: completionToTrack
|
|
300
|
+
);
|
|
301
|
+
if (entry.status === "busy") {
|
|
302
|
+
entry.releaseBusyWait();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
sessionAgentsByScope.set(scopeKey, buildBusyPoolEntry(entry, completionSettled));
|
|
306
|
+
}
|
|
307
|
+
|
|
178
308
|
function leaseFromEntry(
|
|
179
|
-
entry:
|
|
309
|
+
entry: SessionCursorAgentReadyEntry,
|
|
180
310
|
scopeKey: string,
|
|
181
311
|
params: SessionCursorAgentCreateParams,
|
|
182
312
|
created: boolean,
|
|
@@ -185,18 +315,54 @@ function leaseFromEntry(
|
|
|
185
315
|
entry.bridgeRun?.setDebugRecorder(params.debugRecorder);
|
|
186
316
|
return {
|
|
187
317
|
scopeKey,
|
|
188
|
-
|
|
318
|
+
poolKey: entry.poolKey,
|
|
319
|
+
instanceId: entry.instanceId,
|
|
320
|
+
agent: entry.agent,
|
|
189
321
|
bridgeRun: entry.bridgeRun,
|
|
190
322
|
sendState: entry.sendState,
|
|
191
323
|
created,
|
|
324
|
+
commitSend: (context, bootstrapped) => {
|
|
325
|
+
commitSessionAgentSendForLease(scopeKey, entry.poolKey, entry.instanceId, context, bootstrapped);
|
|
326
|
+
},
|
|
327
|
+
trackRunCompletion: (completion) => {
|
|
328
|
+
trackSessionAgentRunCompletionForLease(scopeKey, entry.poolKey, entry.instanceId, completion);
|
|
329
|
+
},
|
|
192
330
|
};
|
|
193
331
|
}
|
|
194
332
|
|
|
195
|
-
|
|
333
|
+
function getCurrentReadyPoolEntry(scopeKey: string, poolKey: string): SessionCursorAgentReadyEntry | undefined {
|
|
334
|
+
const current = sessionAgentsByScope.get(scopeKey);
|
|
335
|
+
if (current?.status !== "ready") return undefined;
|
|
336
|
+
if (current.poolKey !== poolKey) return undefined;
|
|
337
|
+
return current;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function tryLeaseReadyEntry(
|
|
341
|
+
entry: SessionCursorAgentActiveEntry,
|
|
196
342
|
scopeKey: string,
|
|
343
|
+
params: SessionCursorAgentCreateParams,
|
|
197
344
|
poolKey: string,
|
|
345
|
+
created: boolean,
|
|
346
|
+
): Promise<SessionCursorAgentLease | undefined> {
|
|
347
|
+
if (entry.status === "busy") {
|
|
348
|
+
await entry.pendingCompletion;
|
|
349
|
+
}
|
|
350
|
+
assertScopeAcceptsAcquire(scopeKey);
|
|
351
|
+
if (invalidatedScopeKeys.has(scopeKey)) {
|
|
352
|
+
await disposePoolEntryForScope(scopeKey);
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
const readyEntry = getCurrentReadyPoolEntry(scopeKey, poolKey);
|
|
356
|
+
if (!readyEntry) return undefined;
|
|
357
|
+
return leaseFromEntry(readyEntry, scopeKey, params, created);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function createSessionAgentEntry(
|
|
361
|
+
scopeKey: string,
|
|
362
|
+
instanceId: number,
|
|
363
|
+
sendState: SessionCursorAgentSendState,
|
|
198
364
|
params: SessionCursorAgentCreateParams,
|
|
199
|
-
): Promise<
|
|
365
|
+
): Promise<SessionCursorAgentReadyEntry> {
|
|
200
366
|
const registeredBridge = getRegisteredCursorPiToolBridge();
|
|
201
367
|
let bridgeRun: CursorPiToolBridgeRun | undefined;
|
|
202
368
|
if (registeredBridge) {
|
|
@@ -217,6 +383,7 @@ async function createSessionAgentEntry(
|
|
|
217
383
|
agent = await createAgent({
|
|
218
384
|
apiKey: params.apiKey,
|
|
219
385
|
model: params.modelSelection,
|
|
386
|
+
mode: params.agentMode,
|
|
220
387
|
local: params.settingSources ? { cwd: params.cwd, settingSources: params.settingSources } : { cwd: params.cwd },
|
|
221
388
|
...(bridgeRun?.mcpServers ? { mcpServers: bridgeRun.mcpServers } : {}),
|
|
222
389
|
});
|
|
@@ -233,11 +400,13 @@ async function createSessionAgentEntry(
|
|
|
233
400
|
}
|
|
234
401
|
|
|
235
402
|
return {
|
|
403
|
+
status: "ready",
|
|
236
404
|
poolKey: resolvedPoolKey,
|
|
405
|
+
instanceId,
|
|
237
406
|
scopeKey,
|
|
238
407
|
agent,
|
|
239
408
|
bridgeRun,
|
|
240
|
-
sendState
|
|
409
|
+
sendState,
|
|
241
410
|
};
|
|
242
411
|
}
|
|
243
412
|
|
|
@@ -249,95 +418,102 @@ export {
|
|
|
249
418
|
} from "./cursor-session-send-policy.js";
|
|
250
419
|
export { shouldBootstrapCursorContext, shouldBootstrapCursorSend } from "./context.js";
|
|
251
420
|
|
|
252
|
-
export function commitSessionAgentSend(scopeKey: string, context: Context, bootstrapped: boolean): void {
|
|
253
|
-
const entry = sessionAgentsByScope.get(scopeKey);
|
|
254
|
-
if (!entry) return;
|
|
255
|
-
entry.sendState.bootstrapped = bootstrapped || entry.sendState.bootstrapped;
|
|
256
|
-
entry.sendState.contextFingerprint = computeCursorContextFingerprint(context);
|
|
257
|
-
if (bootstrapped) {
|
|
258
|
-
entry.sendState.incrementalSendCount = 0;
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
entry.sendState.incrementalSendCount += 1;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
421
|
export function invalidateSessionAgent(scopeKey: string = getCursorSessionScopeKey()): void {
|
|
265
422
|
invalidatedScopeKeys.add(scopeKey);
|
|
266
423
|
}
|
|
267
424
|
|
|
268
425
|
export async function acquireSessionCursorAgent(params: SessionCursorAgentCreateParams): Promise<SessionCursorAgentLease> {
|
|
269
426
|
const scopeKey = getCursorSessionScopeKey();
|
|
270
|
-
assertScopeAcceptsAcquire(scopeKey);
|
|
271
|
-
if (invalidatedScopeKeys.has(scopeKey)) {
|
|
272
|
-
await disposePoolEntryForScope(scopeKey);
|
|
273
|
-
}
|
|
274
427
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
428
|
+
while (true) {
|
|
429
|
+
assertScopeAcceptsAcquire(scopeKey);
|
|
430
|
+
if (invalidatedScopeKeys.has(scopeKey)) {
|
|
431
|
+
await disposePoolEntryForScope(scopeKey);
|
|
432
|
+
}
|
|
280
433
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
434
|
+
const poolKey = buildSessionAgentPoolKey(scopeKey, params);
|
|
435
|
+
const state = getSessionCursorAgentPoolState(scopeKey);
|
|
284
436
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
437
|
+
if ((state.status === "ready" || state.status === "busy") && state.poolKey !== poolKey) {
|
|
438
|
+
await disposePoolEntryForScope(scopeKey);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (state.status === "ready") {
|
|
443
|
+
return leaseFromEntry(state, scopeKey, params, false);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (state.status === "busy") {
|
|
447
|
+
const busyGeneration = state.busyGeneration;
|
|
448
|
+
await state.pendingCompletion;
|
|
449
|
+
if (busyGeneration !== getScopeCreationGeneration(scopeKey)) continue;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (state.status === "creating") {
|
|
454
|
+
if (state.poolKey !== poolKey) {
|
|
455
|
+
await disposePoolEntryForScope(scopeKey);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
try {
|
|
459
|
+
await state.creating;
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (error instanceof SessionCursorAgentCreationSupersededError) {
|
|
462
|
+
assertScopeAcceptsAcquire(scopeKey);
|
|
463
|
+
rethrowSupersededWhenReplacedByDifferentPoolKey(scopeKey, poolKey, error);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
294
466
|
throw error;
|
|
295
467
|
}
|
|
468
|
+
continue;
|
|
296
469
|
}
|
|
297
|
-
entry = sessionAgentsByScope.get(scopeKey);
|
|
298
|
-
if (entry && entry.poolKey === poolKey && entry.agent && !entry.creating) {
|
|
299
|
-
return leaseFromEntry(entry, scopeKey, params, false);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
470
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
sendState
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
471
|
+
assertScopeAcceptsAcquire(scopeKey);
|
|
472
|
+
const creationGeneration = getScopeCreationGeneration(scopeKey);
|
|
473
|
+
const instanceId = allocateSessionAgentInstanceId();
|
|
474
|
+
const sendState = createInitialSendState();
|
|
475
|
+
let placeholder: SessionCursorAgentCreatingEntry;
|
|
476
|
+
const creating = createSessionAgentEntry(scopeKey, instanceId, sendState, params).then(async (createdEntry) => {
|
|
477
|
+
const stillCurrent =
|
|
478
|
+
sessionAgentsByScope.get(scopeKey) === placeholder &&
|
|
479
|
+
getScopeCreationGeneration(scopeKey) === placeholder.creationGeneration;
|
|
480
|
+
if (!stillCurrent) {
|
|
481
|
+
await disposePoolEntry(createdEntry);
|
|
482
|
+
if (sessionAgentsByScope.get(scopeKey) === placeholder) {
|
|
483
|
+
sessionAgentsByScope.delete(scopeKey);
|
|
484
|
+
}
|
|
485
|
+
throw new SessionCursorAgentCreationSupersededError();
|
|
486
|
+
}
|
|
487
|
+
sessionAgentsByScope.set(scopeKey, createdEntry);
|
|
488
|
+
return createdEntry;
|
|
489
|
+
});
|
|
490
|
+
placeholder = {
|
|
491
|
+
status: "creating",
|
|
492
|
+
poolKey,
|
|
493
|
+
instanceId,
|
|
494
|
+
scopeKey,
|
|
495
|
+
sendState,
|
|
496
|
+
creationGeneration,
|
|
497
|
+
creating,
|
|
498
|
+
};
|
|
499
|
+
sessionAgentsByScope.set(scopeKey, placeholder);
|
|
500
|
+
|
|
501
|
+
try {
|
|
502
|
+
const createdEntry = await creating;
|
|
503
|
+
const lease = await tryLeaseReadyEntry(createdEntry, scopeKey, params, poolKey, true);
|
|
504
|
+
if (lease) return lease;
|
|
505
|
+
continue;
|
|
506
|
+
} catch (error) {
|
|
317
507
|
if (sessionAgentsByScope.get(scopeKey) === placeholder) {
|
|
318
508
|
sessionAgentsByScope.delete(scopeKey);
|
|
319
509
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
sessionAgentsByScope.set(scopeKey, placeholder);
|
|
327
|
-
|
|
328
|
-
try {
|
|
329
|
-
const createdEntry = await creating;
|
|
330
|
-
return leaseFromEntry(createdEntry, scopeKey, params, true);
|
|
331
|
-
} catch (error) {
|
|
332
|
-
if (sessionAgentsByScope.get(scopeKey) === placeholder) {
|
|
333
|
-
sessionAgentsByScope.delete(scopeKey);
|
|
334
|
-
}
|
|
335
|
-
if (error instanceof SessionCursorAgentCreationSupersededError) {
|
|
336
|
-
assertScopeAcceptsAcquire(scopeKey);
|
|
337
|
-
rethrowSupersededWhenReplacedByDifferentPoolKey(scopeKey, poolKey, error);
|
|
338
|
-
return acquireSessionCursorAgent(params);
|
|
510
|
+
if (error instanceof SessionCursorAgentCreationSupersededError) {
|
|
511
|
+
assertScopeAcceptsAcquire(scopeKey);
|
|
512
|
+
rethrowSupersededWhenReplacedByDifferentPoolKey(scopeKey, poolKey, error);
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
throw error;
|
|
339
516
|
}
|
|
340
|
-
throw error;
|
|
341
517
|
}
|
|
342
518
|
}
|
|
343
519
|
|
|
@@ -383,6 +559,7 @@ export function registerCursorSessionAgent(_pi: CursorSessionAgentExtensionApi):
|
|
|
383
559
|
|
|
384
560
|
export const __testUtils = {
|
|
385
561
|
sessionAgentsByScope,
|
|
562
|
+
getSessionCursorAgentPoolState,
|
|
386
563
|
invalidateSessionAgent,
|
|
387
564
|
disposeSessionCursorAgent,
|
|
388
565
|
resetSessionCursorAgent,
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import type { SettingSource } from "@cursor/sdk";
|
|
2
|
+
/** Provider-facing wrapper; canonical parsing lives in shared/cursor-setting-sources.mjs. */
|
|
3
|
+
import {
|
|
4
|
+
CURSOR_SETTING_SOURCES_ENV as CURSOR_SETTING_SOURCES_ENV_JS,
|
|
5
|
+
resolveCursorSettingSources as resolveCursorSettingSourcesJs,
|
|
6
|
+
} from "../shared/cursor-setting-sources.mjs";
|
|
2
7
|
|
|
3
|
-
export const CURSOR_SETTING_SOURCES_ENV =
|
|
8
|
+
export const CURSOR_SETTING_SOURCES_ENV = CURSOR_SETTING_SOURCES_ENV_JS;
|
|
4
9
|
|
|
5
10
|
export function resolveCursorSettingSources(raw?: string): SettingSource[] | undefined {
|
|
6
|
-
|
|
7
|
-
if (!trimmed) return ["all"];
|
|
8
|
-
const normalized = trimmed.toLowerCase();
|
|
9
|
-
if (["0", "false", "off", "none", "omit", "disabled"].includes(normalized)) return undefined;
|
|
10
|
-
if (["1", "true", "on", "all"].includes(normalized)) return ["all"];
|
|
11
|
-
return trimmed
|
|
12
|
-
.split(",")
|
|
13
|
-
.map((entry) => entry.trim())
|
|
14
|
-
.filter((entry): entry is SettingSource => Boolean(entry));
|
|
11
|
+
return resolveCursorSettingSourcesJs(raw) as SettingSource[] | undefined;
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
export function getEffectiveCursorSettingSources(raw: string | undefined = process.env[CURSOR_SETTING_SOURCES_ENV]): SettingSource[] | undefined {
|