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.
Files changed (89) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +72 -11
  3. package/docs/cursor-dogfood-checklist.md +57 -0
  4. package/docs/cursor-live-smoke-checklist.md +116 -10
  5. package/docs/cursor-model-ux-spec.md +60 -19
  6. package/docs/cursor-native-tool-replay.md +21 -11
  7. package/docs/cursor-native-tool-visual-audit.md +104 -59
  8. package/docs/cursor-testing-lessons.md +10 -5
  9. package/docs/cursor-tool-surfaces.md +69 -0
  10. package/package.json +37 -11
  11. package/scripts/debug-provider-events.d.mts +59 -0
  12. package/scripts/debug-provider-events.mjs +70 -175
  13. package/scripts/debug-sdk-events.d.mts +90 -0
  14. package/scripts/debug-sdk-events.mjs +36 -98
  15. package/scripts/fixtures/plan-strip-shim/index.ts +12 -0
  16. package/scripts/isolated-cursor-smoke.sh +264 -102
  17. package/scripts/lib/cursor-child-process.d.mts +10 -0
  18. package/scripts/lib/cursor-child-process.mjs +50 -0
  19. package/scripts/lib/cursor-cli-args.d.mts +63 -0
  20. package/scripts/lib/cursor-cli-args.mjs +129 -0
  21. package/scripts/lib/cursor-script-fail.d.mts +1 -0
  22. package/scripts/lib/cursor-script-fail.mjs +13 -0
  23. package/scripts/lib/cursor-sdk-output-filter.d.mts +5 -0
  24. package/scripts/lib/cursor-smoke-env.d.mts +38 -0
  25. package/scripts/lib/cursor-smoke-env.mjs +81 -0
  26. package/scripts/lib/cursor-smoke-shell.sh +174 -0
  27. package/scripts/lib/cursor-visual-render.d.mts +15 -0
  28. package/scripts/lib/cursor-visual-render.mjs +131 -0
  29. package/scripts/probe-mcp-coldstart.mjs +226 -0
  30. package/scripts/refresh-cursor-model-snapshots.mjs +29 -65
  31. package/scripts/steering-rpc-smoke.mjs +170 -65
  32. package/scripts/tmux-live-smoke.sh +152 -98
  33. package/scripts/visual-tui-smoke.mjs +659 -0
  34. package/shared/cursor-sdk-event-debug-env.d.mts +12 -0
  35. package/shared/cursor-sdk-event-debug-env.mjs +13 -0
  36. package/shared/cursor-sensitive-text.d.mts +1 -0
  37. package/{scripts/lib/cursor-probe-utils.mjs → shared/cursor-sensitive-text.mjs} +1 -13
  38. package/shared/cursor-setting-sources.d.mts +5 -0
  39. package/shared/cursor-setting-sources.mjs +22 -0
  40. package/src/context.ts +21 -12
  41. package/src/cursor-bridge-contract.ts +1 -3
  42. package/src/cursor-incomplete-tool-visibility.ts +72 -49
  43. package/src/cursor-mcp-timeout-override.ts +66 -11
  44. package/src/cursor-native-tool-display-registration.ts +63 -27
  45. package/src/cursor-native-tool-display-replay.ts +246 -143
  46. package/src/cursor-native-tool-display-state.ts +2 -0
  47. package/src/cursor-native-tool-display-tools.ts +149 -41
  48. package/src/cursor-provider-live-run-drain.ts +1 -52
  49. package/src/cursor-provider-run-finalizer.ts +235 -0
  50. package/src/cursor-provider-run-outcome.ts +149 -0
  51. package/src/cursor-provider-turn-api-key.ts +8 -0
  52. package/src/cursor-provider-turn-coordinator.ts +113 -440
  53. package/src/cursor-provider-turn-display-router.ts +216 -0
  54. package/src/cursor-provider-turn-emit.ts +59 -0
  55. package/src/cursor-provider-turn-finalize.ts +119 -0
  56. package/src/cursor-provider-turn-lifecycle-emitter.ts +97 -0
  57. package/src/cursor-provider-turn-message-offset.ts +15 -0
  58. package/src/cursor-provider-turn-prepare.ts +216 -0
  59. package/src/cursor-provider-turn-runner.ts +138 -0
  60. package/src/cursor-provider-turn-sdk-normalizer.ts +88 -0
  61. package/src/cursor-provider-turn-send.ts +103 -0
  62. package/src/cursor-provider-turn-shell-output.ts +107 -0
  63. package/src/cursor-provider-turn-tool-ledger.ts +126 -0
  64. package/src/cursor-provider-turn-types.ts +87 -0
  65. package/src/cursor-provider.ts +16 -482
  66. package/src/cursor-replay-activity-builders.ts +276 -0
  67. package/src/cursor-replay-source-names.ts +33 -0
  68. package/src/cursor-replay-summary-args.ts +191 -0
  69. package/src/cursor-replay-tool-details.ts +464 -0
  70. package/src/cursor-run-final-text.ts +56 -0
  71. package/src/cursor-sdk-abort-error-guard.ts +4 -0
  72. package/src/cursor-sdk-event-debug-constants.ts +14 -5
  73. package/src/cursor-sdk-event-debug.ts +8 -2
  74. package/src/cursor-sensitive-text.ts +3 -36
  75. package/src/cursor-session-agent.ts +265 -88
  76. package/src/cursor-setting-sources.ts +7 -10
  77. package/src/cursor-state.ts +232 -28
  78. package/src/cursor-tool-lifecycle.ts +17 -42
  79. package/src/cursor-tool-manifest.ts +41 -0
  80. package/src/cursor-tool-names.ts +18 -79
  81. package/src/cursor-tool-presentation-registry.ts +556 -0
  82. package/src/cursor-tool-transcript.ts +1 -1
  83. package/src/cursor-tool-visibility.ts +39 -0
  84. package/src/cursor-transcript-tool-formatters.ts +0 -59
  85. package/src/cursor-transcript-tool-specs.ts +169 -232
  86. package/src/cursor-transcript-utils.ts +0 -44
  87. package/src/cursor-web-tool-activity.ts +10 -60
  88. package/src/cursor-web-tool-args.ts +39 -0
  89. 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 SessionCursorAgentPoolEntry {
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.creating || !entry.agent) {
159
- orphanedCreating?.catch(() => {
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: SessionCursorAgentPoolEntry,
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: SessionCursorAgentPoolEntry,
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
- agent: entry.agent!,
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
- async function createSessionAgentEntry(
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<SessionCursorAgentPoolEntry> {
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: createInitialSendState(),
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
- const poolKey = buildSessionAgentPoolKey(scopeKey, params);
276
- const existing = sessionAgentsByScope.get(scopeKey);
277
- if (existing?.poolKey === poolKey && !existing.creating) {
278
- return leaseFromEntry(existing, scopeKey, params, false);
279
- }
428
+ while (true) {
429
+ assertScopeAcceptsAcquire(scopeKey);
430
+ if (invalidatedScopeKeys.has(scopeKey)) {
431
+ await disposePoolEntryForScope(scopeKey);
432
+ }
280
433
 
281
- if (existing && existing.poolKey !== poolKey) {
282
- await disposePoolEntryForScope(scopeKey);
283
- }
434
+ const poolKey = buildSessionAgentPoolKey(scopeKey, params);
435
+ const state = getSessionCursorAgentPoolState(scopeKey);
284
436
 
285
- let entry = sessionAgentsByScope.get(scopeKey);
286
- if (entry?.creating) {
287
- try {
288
- await entry.creating;
289
- } catch (error) {
290
- if (error instanceof SessionCursorAgentCreationSupersededError) {
291
- assertScopeAcceptsAcquire(scopeKey);
292
- rethrowSupersededWhenReplacedByDifferentPoolKey(scopeKey, poolKey, error);
293
- } else {
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
- assertScopeAcceptsAcquire(scopeKey);
304
- const creationGeneration = getScopeCreationGeneration(scopeKey);
305
- const placeholder: SessionCursorAgentPoolEntry = {
306
- poolKey,
307
- scopeKey,
308
- sendState: createInitialSendState(),
309
- creationGeneration,
310
- };
311
- const creating = createSessionAgentEntry(scopeKey, poolKey, params).then(async (createdEntry) => {
312
- const stillCurrent =
313
- sessionAgentsByScope.get(scopeKey) === placeholder &&
314
- getScopeCreationGeneration(scopeKey) === placeholder.creationGeneration;
315
- if (!stillCurrent) {
316
- await disposePoolEntry(createdEntry);
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
- throw new SessionCursorAgentCreationSupersededError();
321
- }
322
- sessionAgentsByScope.set(scopeKey, createdEntry);
323
- return createdEntry;
324
- });
325
- placeholder.creating = creating;
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 = "PI_CURSOR_SETTING_SOURCES";
8
+ export const CURSOR_SETTING_SOURCES_ENV = CURSOR_SETTING_SOURCES_ENV_JS;
4
9
 
5
10
  export function resolveCursorSettingSources(raw?: string): SettingSource[] | undefined {
6
- const trimmed = raw?.trim();
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 {