pi-cursor-sdk 0.4.0 → 0.5.0

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 (125) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +65 -13
  3. package/dist/context-window-cache.js +0 -6
  4. package/dist/context.js +8 -0
  5. package/dist/cursor-cloud-lifecycle.js +122 -98
  6. package/dist/cursor-durable-fs.js +97 -3
  7. package/dist/cursor-fallback-models.generated.js +288 -1
  8. package/dist/cursor-fallback-warning.js +2 -2
  9. package/dist/cursor-http1.js +8 -8
  10. package/dist/cursor-live-run-coordinator.js +9 -13
  11. package/dist/cursor-native-replay-routing.js +1 -1
  12. package/dist/cursor-native-tool-display-registration.js +46 -22
  13. package/dist/cursor-native-tool-display-state.js +57 -22
  14. package/dist/cursor-native-tool-display-tools.js +5 -6
  15. package/dist/cursor-pi-tool-bridge-constants.js +23 -2
  16. package/dist/cursor-pi-tool-bridge-run.js +7 -3
  17. package/dist/cursor-pi-tool-bridge-snapshot.js +1 -1
  18. package/dist/cursor-pi-tool-bridge.js +31 -9
  19. package/dist/cursor-provider-binding.js +58 -0
  20. package/dist/cursor-provider-errors.js +28 -7
  21. package/dist/cursor-provider-lazy.js +23 -11
  22. package/dist/cursor-provider-live-run-drain.js +15 -12
  23. package/dist/cursor-provider-run-finalizer.js +19 -8
  24. package/dist/cursor-provider-turn-coordinator.js +1 -0
  25. package/dist/cursor-provider-turn-display-router.js +4 -0
  26. package/dist/cursor-provider-turn-emit.js +1 -0
  27. package/dist/cursor-provider-turn-finalize.js +29 -20
  28. package/dist/cursor-provider-turn-prepare.js +24 -11
  29. package/dist/cursor-provider-turn-runner.js +37 -11
  30. package/dist/cursor-provider-turn-send.js +22 -9
  31. package/dist/cursor-provider-turn-summary.js +89 -0
  32. package/dist/cursor-provider.js +9 -3
  33. package/dist/cursor-request-provenance.js +42 -0
  34. package/dist/cursor-ripgrep-path.js +14 -3
  35. package/dist/cursor-runtime-state.js +50 -43
  36. package/dist/cursor-sdk-billed-usage.js +54 -75
  37. package/dist/cursor-sdk-event-debug-session.js +5 -6
  38. package/dist/cursor-sdk-event-debug.js +9 -6
  39. package/dist/cursor-sdk-process-error-guard.js +8 -7
  40. package/dist/cursor-sdk-runtime.js +13 -0
  41. package/dist/cursor-session-agent-cleanup.js +2 -2
  42. package/dist/cursor-session-agent-lifecycle.js +18 -9
  43. package/dist/cursor-session-agent-lineage.js +28 -12
  44. package/dist/cursor-session-agent-resume.js +51 -35
  45. package/dist/cursor-session-agent.js +9 -9
  46. package/dist/cursor-session-compaction-prep.js +2 -7
  47. package/dist/cursor-session-scope.js +71 -39
  48. package/dist/cursor-session-settings.js +61 -0
  49. package/dist/cursor-skill-tool.js +32 -49
  50. package/dist/cursor-state.js +72 -62
  51. package/dist/cursor-usage-accounting.js +13 -70
  52. package/dist/cursor-usage-command.js +89 -0
  53. package/dist/cursor-usage-ledger.js +512 -0
  54. package/dist/index.js +11 -19
  55. package/dist/model-discovery.js +11 -19
  56. package/docs/cursor-dogfood-checklist.md +3 -3
  57. package/docs/cursor-live-smoke-checklist.md +15 -15
  58. package/docs/cursor-model-ux-spec.md +59 -18
  59. package/docs/cursor-native-tool-replay.md +5 -3
  60. package/docs/cursor-native-tool-visual-audit.md +8 -8
  61. package/docs/cursor-testing-lessons.md +47 -23
  62. package/docs/cursor-tool-surfaces.md +1 -1
  63. package/docs/platform-smoke-implementation.md +10 -6
  64. package/docs/platform-smoke.md +35 -46
  65. package/package.json +6 -6
  66. package/scripts/cloud-runtime-smoke.mjs +4 -2
  67. package/scripts/local-resume-smoke.mjs +1 -1
  68. package/scripts/platform-smoke/card-detect.mjs +2 -8
  69. package/scripts/platform-smoke/targets.mjs +8 -15
  70. package/scripts/platform-smoke/visual-evidence.mjs +7 -2
  71. package/scripts/tmux-live-smoke.sh +57 -90
  72. package/src/context-window-cache.ts +0 -6
  73. package/src/context.ts +8 -0
  74. package/src/cursor-cloud-lifecycle.ts +124 -98
  75. package/src/cursor-durable-fs.ts +65 -3
  76. package/src/cursor-fallback-models.generated.ts +288 -1
  77. package/src/cursor-fallback-warning.ts +2 -2
  78. package/src/cursor-http1.ts +8 -8
  79. package/src/cursor-live-run-coordinator.ts +12 -13
  80. package/src/cursor-native-replay-routing.ts +2 -1
  81. package/src/cursor-native-tool-display-registration.ts +48 -24
  82. package/src/cursor-native-tool-display-state.ts +65 -21
  83. package/src/cursor-native-tool-display-tools.ts +7 -4
  84. package/src/cursor-pi-tool-bridge-constants.ts +27 -2
  85. package/src/cursor-pi-tool-bridge-run.ts +11 -3
  86. package/src/cursor-pi-tool-bridge-snapshot.ts +1 -1
  87. package/src/cursor-pi-tool-bridge.ts +26 -9
  88. package/src/cursor-provider-binding.ts +62 -0
  89. package/src/cursor-provider-errors.ts +27 -7
  90. package/src/cursor-provider-lazy.ts +21 -15
  91. package/src/cursor-provider-live-run-drain.ts +20 -13
  92. package/src/cursor-provider-run-finalizer.ts +24 -15
  93. package/src/cursor-provider-turn-coordinator.ts +2 -0
  94. package/src/cursor-provider-turn-display-router.ts +5 -0
  95. package/src/cursor-provider-turn-emit.ts +3 -1
  96. package/src/cursor-provider-turn-finalize.ts +38 -23
  97. package/src/cursor-provider-turn-prepare.ts +23 -14
  98. package/src/cursor-provider-turn-runner.ts +40 -13
  99. package/src/cursor-provider-turn-send.ts +23 -15
  100. package/src/cursor-provider-turn-summary.ts +94 -0
  101. package/src/cursor-provider-turn-types.ts +37 -4
  102. package/src/cursor-provider.ts +25 -3
  103. package/src/cursor-request-provenance.ts +64 -0
  104. package/src/cursor-ripgrep-path.ts +15 -5
  105. package/src/cursor-runtime-state.ts +53 -48
  106. package/src/cursor-sdk-billed-usage.ts +54 -102
  107. package/src/cursor-sdk-event-debug-session.ts +5 -5
  108. package/src/cursor-sdk-event-debug.ts +10 -6
  109. package/src/cursor-sdk-process-error-guard.ts +7 -7
  110. package/src/cursor-sdk-runtime.ts +14 -0
  111. package/src/cursor-session-agent-cleanup.ts +2 -2
  112. package/src/cursor-session-agent-lifecycle.ts +14 -9
  113. package/src/cursor-session-agent-lineage.ts +26 -12
  114. package/src/cursor-session-agent-resume.ts +49 -36
  115. package/src/cursor-session-agent.ts +13 -10
  116. package/src/cursor-session-compaction-prep.ts +2 -7
  117. package/src/cursor-session-scope.ts +87 -39
  118. package/src/cursor-session-settings.ts +72 -0
  119. package/src/cursor-skill-tool.ts +37 -61
  120. package/src/cursor-state.ts +74 -67
  121. package/src/cursor-usage-accounting.ts +17 -82
  122. package/src/cursor-usage-command.ts +76 -0
  123. package/src/cursor-usage-ledger.ts +500 -0
  124. package/src/index.ts +14 -23
  125. package/src/model-discovery.ts +14 -22
@@ -64,12 +64,12 @@ function getCursorNativeReplayIdFromToolCallId(toolCallId: string): string | und
64
64
  return CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN.exec(toolCallId)?.[1];
65
65
  }
66
66
 
67
- export function getPendingCursorLiveRun(context: Context): CursorLiveRun | undefined {
68
- return cursorLiveRuns.getPendingFromContext(context, getCursorNativeReplayIdFromToolCallId);
67
+ export function getPendingCursorLiveRun(context: Context, scopeKey: string): CursorLiveRun | undefined {
68
+ return cursorLiveRuns.getPendingFromContext(context, getCursorNativeReplayIdFromToolCallId, scopeKey);
69
69
  }
70
70
 
71
- export function getActiveCursorLiveRunForCurrentScope(): CursorLiveRun | undefined {
72
- return cursorLiveRuns.getActiveForScope();
71
+ export function getActiveCursorLiveRunForCurrentScope(scopeKey?: string): CursorLiveRun | undefined {
72
+ return cursorLiveRuns.getActiveForScope(scopeKey);
73
73
  }
74
74
 
75
75
  function splitTextIntoReplayDeltas(text: string): string[] {
@@ -176,6 +176,7 @@ function emitCursorNativeToolUseTurn(
176
176
  run: CursorLiveRun,
177
177
  toolResultInputTokens: number,
178
178
  tools: CursorNativeToolDisplayItem[],
179
+ occupancyFloor: number | undefined,
179
180
  debugRecorder?: CursorSdkEventDebugRecorder,
180
181
  ): void {
181
182
  const shouldTerminate = run.done && !run.finalText?.trim() && !cursorLiveRuns.peekEvent(run);
@@ -193,7 +194,7 @@ function emitCursorNativeToolUseTurn(
193
194
  stream.push({ type: "toolcall_delta", contentIndex, delta: serializedArgs, partial });
194
195
  const block = partial.content[contentIndex];
195
196
  if (block.type === "toolCall") stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial });
196
- if (recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate })) {
197
+ if (recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate }, run.nativeDisplay)) {
197
198
  run.recordedToolDisplayIds.push(tool.id);
198
199
  debugRecorder?.recordDrainEvent("native_tool_display_recorded", {
199
200
  toolId: tool.id,
@@ -205,6 +206,7 @@ function emitCursorNativeToolUseTurn(
205
206
  applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
206
207
  runtime: "local",
207
208
  turn: cursorLiveRuns.takeSdkTurnUsage(run),
209
+ occupancyFloor,
208
210
  });
209
211
  partial.stopReason = "toolUse";
210
212
  stream.push({ type: "done", reason: "toolUse", message: partial });
@@ -236,6 +238,7 @@ function emitCursorBridgeToolUseTurn(
236
238
  run: CursorLiveRun,
237
239
  toolResultInputTokens: number,
238
240
  requests: CursorPiBridgeToolRequest[],
241
+ occupancyFloor: number | undefined,
239
242
  ): void {
240
243
  for (const request of requests) {
241
244
  const contentIndex = partial.content.length;
@@ -254,6 +257,7 @@ function emitCursorBridgeToolUseTurn(
254
257
  applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
255
258
  runtime: "local",
256
259
  turn: cursorLiveRuns.takeSdkTurnUsage(run),
260
+ occupancyFloor,
257
261
  });
258
262
  partial.stopReason = "toolUse";
259
263
  stream.push({ type: "done", reason: "toolUse", message: partial });
@@ -268,7 +272,7 @@ async function emitCursorLiveRunPendingToolUseTurn(
268
272
  context: Context,
269
273
  run: CursorLiveRun,
270
274
  toolResultInputTokens: number,
271
- options: { mode: CursorLiveRunDrainMode; signal?: AbortSignal; debugRecorder?: CursorSdkEventDebugRecorder },
275
+ options: { mode: CursorLiveRunDrainMode; signal?: AbortSignal; debugRecorder?: CursorSdkEventDebugRecorder; occupancyFloor?: number },
272
276
  ): Promise<"tool_use" | "handled" | undefined> {
273
277
  const debugRecorder = options.debugRecorder ?? run.debugRecorder;
274
278
  const eventType = cursorLiveRuns.peekEvent(run)?.type;
@@ -285,13 +289,13 @@ async function emitCursorLiveRunPendingToolUseTurn(
285
289
  }
286
290
  if (!sdkTurnEnded) cursorLiveRuns.ignoreFutureSdkTurnUsage(run);
287
291
  if (options.mode === "emit") turn.emitter.closeAll();
288
- emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, active, debugRecorder);
292
+ emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, active, options.occupancyFloor, debugRecorder);
289
293
  } else {
290
294
  const requests = cursorLiveRuns.collectBridgeToolBatch(run);
291
295
  if (requests.length === 0) return "handled";
292
296
  if (!sdkTurnEnded) cursorLiveRuns.ignoreFutureSdkTurnUsage(run);
293
297
  if (options.mode === "emit") turn.emitter.closeAll();
294
- emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, requests);
298
+ emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, requests, options.occupancyFloor);
295
299
  }
296
300
  return "tool_use";
297
301
  }
@@ -303,7 +307,7 @@ export async function drainCursorLiveRunTurn(
303
307
  context: Context,
304
308
  run: CursorLiveRun,
305
309
  toolResultInputTokens: number,
306
- options: { mode: CursorLiveRunDrainMode; signal?: AbortSignal; debugRecorder?: CursorSdkEventDebugRecorder; emitter?: CursorPartialContentEmitter },
310
+ options: { mode: CursorLiveRunDrainMode; signal?: AbortSignal; debugRecorder?: CursorSdkEventDebugRecorder; emitter?: CursorPartialContentEmitter; occupancyFloor?: number },
307
311
  ): Promise<CursorLiveRunDrainOutcome> {
308
312
  const debugRecorder = options.debugRecorder ?? run.debugRecorder;
309
313
  debugRecorder?.recordDrainEvent("turn_start", {
@@ -390,7 +394,7 @@ export async function drainCursorLiveRunTurn(
390
394
  applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
391
395
  runtime: "local",
392
396
  turn: cursorLiveRuns.takeSdkTurnUsage(run),
393
- billed: run.billedTurnUsage,
397
+ occupancyFloor: options.occupancyFloor,
394
398
  });
395
399
  if (run.resumeNotice) {
396
400
  emitDisplayOnlyTraceBlock(stream, partial, run.resumeNotice);
@@ -436,12 +440,14 @@ export async function drainExistingCursorLiveRunBeforeSend(
436
440
  partial: AssistantMessage,
437
441
  model: Model<Api>,
438
442
  context: Context,
439
- signal?: AbortSignal,
440
- turnDebugRecorder?: CursorSdkEventDebugRecorder,
443
+ signal: AbortSignal | undefined,
444
+ turnDebugRecorder: CursorSdkEventDebugRecorder | undefined,
445
+ scopeKey: string,
446
+ occupancyFloor?: number,
441
447
  ): Promise<LiveRunPreSendOutcome> {
442
448
  turnDebugRecorder?.recordDrainEvent("pre_send_start", {});
443
449
  while (true) {
444
- const run = getPendingCursorLiveRun(context) ?? getActiveCursorLiveRunForCurrentScope();
450
+ const run = getPendingCursorLiveRun(context, scopeKey) ?? getActiveCursorLiveRunForCurrentScope(scopeKey);
445
451
  if (!run || run.disposed) {
446
452
  turnDebugRecorder?.recordDrainEvent("pre_send_end", { outcome: "continue_send", reason: "no_pending_run" });
447
453
  return "continue_send";
@@ -462,6 +468,7 @@ export async function drainExistingCursorLiveRunBeforeSend(
462
468
  mode: shouldChainUserInput ? "chain_user_input" : "emit",
463
469
  signal,
464
470
  debugRecorder: turnDebugRecorder,
471
+ occupancyFloor,
465
472
  });
466
473
  const mapped = drainOutcome === "chain_user_input" ? "continue_send" : "stream_ended";
467
474
  turnDebugRecorder?.recordDrainEvent("pre_send_iteration", {
@@ -18,7 +18,7 @@ import {
18
18
  } from "./cursor-incomplete-tool-visibility.js";
19
19
  import type { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
20
20
  import type { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
21
- import { awaitFinalizeCursorRunOutcome } from "./cursor-provider-turn-finalize.js";
21
+ import { awaitFinalizeCursorRunOutcome, recordCursorProviderTerminalUsage } from "./cursor-provider-turn-finalize.js";
22
22
  import type {
23
23
  CursorProviderTurnPrepareResult,
24
24
  CursorProviderTurnRunnerParams,
@@ -26,6 +26,7 @@ import type {
26
26
  CursorProviderTurnSendResult,
27
27
  LiveCursorProviderTurnRuntime,
28
28
  LocalCursorProviderTurnPrepareResult,
29
+ StartedCursorProviderTurn,
29
30
  } from "./cursor-provider-turn-types.js";
30
31
  import { applyCursorUsage } from "./cursor-usage-accounting.js";
31
32
  import { hasUsableText } from "./cursor-record-utils.js";
@@ -33,15 +34,15 @@ import { emitDisplayOnlyTraceBlock } from "./cursor-display-only-trace.js";
33
34
  export type CursorTurnTerminalEvent =
34
35
  | {
35
36
  kind: "direct";
36
- prepared: CursorProviderTurnPrepareResult;
37
+ prepared: StartedCursorProviderTurn;
37
38
  outcome: CursorRunOutcome;
38
39
  displayOnlyTraceBlock?: string;
39
40
  }
40
- | { kind: "error"; prepared: CursorProviderTurnPrepareResult | undefined; error: unknown };
41
+ | { kind: "error"; prepared: CursorProviderTurnPrepareResult | StartedCursorProviderTurn | undefined; error: unknown };
41
42
 
42
43
  function applyLiveRunOutcome(
43
44
  outcome: CursorRunOutcome,
44
- prepared: LocalCursorProviderTurnPrepareResult & { runtime: LiveCursorProviderTurnRuntime },
45
+ prepared: StartedCursorProviderTurn & LocalCursorProviderTurnPrepareResult & { runtime: LiveCursorProviderTurnRuntime },
45
46
  context: CursorProviderTurnRunnerParams["context"],
46
47
  ): void {
47
48
  if (prepared.runtime.liveRun.disposed) return;
@@ -63,7 +64,7 @@ function applyLiveRunOutcome(
63
64
 
64
65
  export interface CursorLiveRunCompletion {
65
66
  waitCompletion: Promise<void>;
66
- prepared: CursorProviderTurnPrepareResult;
67
+ prepared: StartedCursorProviderTurn;
67
68
  }
68
69
 
69
70
  export interface CursorRunFinalizerParams {
@@ -76,7 +77,7 @@ export interface CursorRunFinalizerParams {
76
77
 
77
78
  export interface StartCursorLiveRunCompletionParams {
78
79
  send: CursorProviderTurnSend;
79
- prepared: LocalCursorProviderTurnPrepareResult & { runtime: LiveCursorProviderTurnRuntime };
80
+ prepared: StartedCursorProviderTurn & LocalCursorProviderTurnPrepareResult & { runtime: LiveCursorProviderTurnRuntime };
80
81
  modelId: string;
81
82
  discardIncompleteTools: (outcome: IncompleteCursorToolRunOutcomeInput) => void;
82
83
  }
@@ -110,14 +111,16 @@ export class CursorRunFinalizer {
110
111
  applyLiveRunOutcome(finalized.outcome, prepared, runnerParams.context);
111
112
  })
112
113
  .catch((error: unknown) => {
113
- this.safeCleanup(() => discardIncompleteTools({ status: "error" }));
114
+ const aborted = error instanceof CursorLiveRunAbortError || runnerParams.options?.signal?.aborted === true;
115
+ this.safeCleanup(() => discardIncompleteTools({ status: aborted ? "cancelled" : "error" }));
114
116
  if (!liveRun.disposed) {
115
- cursorLiveRuns.markError(
117
+ if (aborted) cursorLiveRuns.markCancelled(liveRun, this.abortMessage());
118
+ else cursorLiveRuns.markError(
116
119
  liveRun,
117
120
  sanitizeCursorProviderError(error, this.params.resolvedApiKey() ?? runnerParams.options?.apiKey, "local"),
118
121
  );
119
122
  }
120
- this.safeCleanup(() => sdkEventDebug?.recordWaitResult({ status: "error", error: String(error) }));
123
+ this.safeCleanup(() => sdkEventDebug?.recordWaitResult({ status: aborted ? "cancelled" : "error", error: String(error) }));
121
124
  this.safeCleanup(() => sdkEventDebug?.recordError("run_wait", error));
122
125
  });
123
126
  // Mark the pooled local agent busy as soon as the SDK run exists so auto-compaction summarization
@@ -166,11 +169,13 @@ export class CursorRunFinalizer {
166
169
  }
167
170
 
168
171
  private async applyDirectOutcome(
169
- prepared: CursorProviderTurnPrepareResult,
172
+ prepared: StartedCursorProviderTurn,
170
173
  outcome: CursorRunOutcome,
171
174
  displayOnlyTraceBlock: string | undefined,
172
175
  ): Promise<void> {
173
176
  const { stream, partial, model, context } = this.params.runnerParams;
177
+ // Native summaries return at terminal emission, so release their temporary resources first.
178
+ if (prepared.execution === "summary") await prepared.lifecycle.dispose().catch(() => {});
174
179
  prepared.runtime.turnCoordinator.closeTraceBlock();
175
180
  switch (classifyCursorRunEmission(outcome)) {
176
181
  case "cancelled":
@@ -189,7 +194,7 @@ export class CursorRunFinalizer {
189
194
  applyCursorUsage(partial, model, context, prepared.meta.promptInputTokens, {
190
195
  runtime: prepared.runtimeTarget,
191
196
  turn: prepared.runtime.turnCoordinator.lastSdkTurnUsage,
192
- billed: prepared.runtime.billedTurnUsage,
197
+ occupancyFloor: this.params.runnerParams.request.occupancyFloor,
193
198
  });
194
199
  if (prepared.meta.resumeNotice) emitDisplayOnlyTraceBlock(stream, partial, prepared.meta.resumeNotice);
195
200
  if (displayOnlyTraceBlock) emitDisplayOnlyTraceBlock(stream, partial, displayOnlyTraceBlock);
@@ -198,11 +203,15 @@ export class CursorRunFinalizer {
198
203
  }
199
204
  }
200
205
 
201
- private async applyErrorOutcome(prepared: CursorProviderTurnPrepareResult | undefined, error: unknown): Promise<void> {
206
+ private async applyErrorOutcome(prepared: CursorProviderTurnPrepareResult | StartedCursorProviderTurn | undefined, error: unknown): Promise<void> {
207
+ const aborted = error instanceof CursorLiveRunAbortError || this.params.runnerParams.options?.signal?.aborted === true;
208
+ if (prepared && "usage" in prepared && (!prepared.runtime.sdkRun || prepared.runtime.kind !== "live")) {
209
+ await recordCursorProviderTerminalUsage(prepared, aborted ? "abort" : "error");
210
+ }
202
211
  this.safeCleanup(() => prepared?.runtime.turnCoordinator.discardIncompleteStartedToolCalls(
203
212
  buildIncompleteCursorToolRunOutcome({
204
- status: error instanceof CursorLiveRunAbortError ? "cancelled" : "error",
205
- signalAborted: error instanceof CursorLiveRunAbortError,
213
+ status: aborted ? "cancelled" : "error",
214
+ signalAborted: aborted,
206
215
  }),
207
216
  ));
208
217
  const activeLiveRun = prepared?.runtime.liveRun;
@@ -212,7 +221,7 @@ export class CursorRunFinalizer {
212
221
  await prepared?.lifecycle.abandon();
213
222
  }
214
223
  this.safeCleanup(() => this.params.sdkEventDebug()?.recordError("provider_stream", error));
215
- if (error instanceof CursorLiveRunAbortError) {
224
+ if (aborted) {
216
225
  this.params.sdkProcessErrorGuard.suppressAbortErrors();
217
226
  this.pushTerminalError(this.params.runnerParams.partial, "aborted", this.abortMessage());
218
227
  } else {
@@ -43,6 +43,7 @@ export interface CursorSdkTurnCoordinatorOptions {
43
43
  liveRun?: CursorLiveRun;
44
44
  useNativeToolReplay: boolean;
45
45
  activeToolNames?: ReadonlySet<string>;
46
+ registeredToolNames?: ReadonlySet<string>;
46
47
  nativeReplayId: string;
47
48
  textDeltas: string[];
48
49
  debugRecorder?: CursorSdkEventDebugRecorder;
@@ -86,6 +87,7 @@ export class CursorSdkTurnCoordinator {
86
87
  liveRun: options.liveRun,
87
88
  useNativeToolReplay: options.useNativeToolReplay,
88
89
  activeToolNames: options.activeToolNames,
90
+ registeredToolNames: options.registeredToolNames,
89
91
  nativeReplayId: options.nativeReplayId,
90
92
  contentEmitter: this.contentEmitter,
91
93
  debugRecorder: options.debugRecorder,
@@ -29,6 +29,7 @@ export interface CursorTurnDisplayRouterOptions {
29
29
  liveRun?: CursorLiveRun;
30
30
  useNativeToolReplay: boolean;
31
31
  activeToolNames?: ReadonlySet<string>;
32
+ registeredToolNames?: ReadonlySet<string>;
32
33
  nativeReplayId: string;
33
34
  contentEmitter: CursorPartialContentEmitter;
34
35
  debugRecorder?: CursorSdkEventDebugRecorder;
@@ -44,6 +45,7 @@ export class CursorTurnDisplayRouter {
44
45
  private readonly liveRun?: CursorLiveRun;
45
46
  private readonly useNativeToolReplay: boolean;
46
47
  private readonly activeToolNames?: ReadonlySet<string>;
48
+ private readonly registeredToolNames?: ReadonlySet<string>;
47
49
  private readonly nativeReplayId: string;
48
50
  private readonly contentEmitter: CursorPartialContentEmitter;
49
51
  private readonly debugRecorder?: CursorSdkEventDebugRecorder;
@@ -57,6 +59,7 @@ export class CursorTurnDisplayRouter {
57
59
  this.liveRun = options.liveRun;
58
60
  this.useNativeToolReplay = options.useNativeToolReplay;
59
61
  this.activeToolNames = options.activeToolNames;
62
+ this.registeredToolNames = options.registeredToolNames;
60
63
  this.nativeReplayId = options.nativeReplayId;
61
64
  this.contentEmitter = options.contentEmitter;
62
65
  this.debugRecorder = options.debugRecorder;
@@ -75,6 +78,7 @@ export class CursorTurnDisplayRouter {
75
78
  toolName: display.toolName,
76
79
  useNativeToolReplay: this.useNativeToolReplay,
77
80
  activeToolNames: this.activeToolNames,
81
+ registeredToolNames: this.registeredToolNames,
78
82
  hasLiveRun: this.liveRun !== undefined,
79
83
  });
80
84
 
@@ -122,6 +126,7 @@ export class CursorTurnDisplayRouter {
122
126
  toolName: display.toolName,
123
127
  useNativeToolReplay: this.useNativeToolReplay,
124
128
  activeToolNames: this.activeToolNames,
129
+ registeredToolNames: this.registeredToolNames,
125
130
  hasLiveRun: this.liveRun !== undefined,
126
131
  });
127
132
 
@@ -13,12 +13,13 @@ import {
13
13
  import type {
14
14
  CursorProviderTurnPrepareResult,
15
15
  CursorProviderTurnRunnerParams,
16
+ StartedCursorProviderTurn,
16
17
  } from "./cursor-provider-turn-types.js";
17
18
  import type { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
18
19
 
19
20
  export interface EmitCursorLiveTurnParams {
20
21
  params: CursorProviderTurnRunnerParams;
21
- prepared: CursorProviderTurnPrepareResult;
22
+ prepared: StartedCursorProviderTurn;
22
23
  sdkEventDebug: CursorSdkEventDebugSink | undefined;
23
24
  discardIncompleteTools: (outcome: IncompleteCursorToolRunOutcomeInput) => void;
24
25
  }
@@ -42,6 +43,7 @@ export async function emitCursorLiveTurn(emitParams: EmitCursorLiveTurnParams):
42
43
  emitter,
43
44
  signal: options?.signal,
44
45
  debugRecorder: sdkEventDebug,
46
+ occupancyFloor: params.request.occupancyFloor,
45
47
  });
46
48
  });
47
49
  } catch (caught) {
@@ -5,7 +5,6 @@ import {
5
5
  formatCursorCloudRunReport,
6
6
  type CursorCloudRunReport,
7
7
  } from "./cursor-cloud-reporting.js";
8
- import { recordCursorCloudLifecycleRun } from "./cursor-cloud-lifecycle.js";
9
8
  import { getCheckpointContextWindow, saveCachedContextWindow } from "./context-window-cache.js";
10
9
  import { scrubSensitiveText } from "./cursor-sensitive-text.js";
11
10
  import type { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
@@ -15,9 +14,8 @@ import {
15
14
  resolveCursorRunOutcome,
16
15
  type CursorRunOutcome,
17
16
  } from "./cursor-provider-run-outcome.js";
18
- import type { CursorProviderTurnPrepareResult } from "./cursor-provider-turn-types.js";
17
+ import type { StartedCursorProviderTurn } from "./cursor-provider-turn-types.js";
19
18
  import { loadCursorSdk } from "./cursor-sdk-runtime.js";
20
- import { attachCursorSdkBilledTurnUsage } from "./cursor-sdk-billed-usage.js";
21
19
 
22
20
  export async function cacheSdkContextWindow(
23
21
  agentId: string,
@@ -45,7 +43,7 @@ export async function cacheSdkContextWindow(
45
43
 
46
44
  export interface BuildCursorRunOutcomeParams {
47
45
  waitResult: Awaited<ReturnType<Awaited<ReturnType<SDKAgent["send"]>>["wait"]>>;
48
- prepared: CursorProviderTurnPrepareResult;
46
+ prepared: StartedCursorProviderTurn;
49
47
  signal?: AbortSignal;
50
48
  runResultFallback?: string;
51
49
  runErrorFallback?: RunError;
@@ -117,7 +115,7 @@ function recordCursorCloudReportingError(
117
115
 
118
116
  export interface AwaitFinalizeCursorRunOutcomeParams {
119
117
  run: Awaited<ReturnType<SDKAgent["send"]>>;
120
- prepared: CursorProviderTurnPrepareResult;
118
+ prepared: StartedCursorProviderTurn;
121
119
  cursorAgentMessageOffset: number | undefined;
122
120
  modelId: string;
123
121
  signal?: AbortSignal;
@@ -137,10 +135,32 @@ export interface FinalizedCursorRunOutcome {
137
135
  displayOnlyTraceBlock?: string;
138
136
  }
139
137
 
140
- /** Single wait/finalize path for SDK runs: wait, debug capture, transcript replay, incomplete tools, artifacts, context cache. */
138
+ export async function recordCursorProviderTerminalUsage(
139
+ prepared: StartedCursorProviderTurn,
140
+ status: "success" | "error" | "abort",
141
+ waitUsage?: unknown,
142
+ ) {
143
+ if (prepared.runtime.usageTerminalRecorded) return undefined;
144
+ prepared.runtime.usageTerminalRecorded = true;
145
+ try {
146
+ return await prepared.usage.recordTerminal({ status, waitUsage, handleUsage: prepared.runtime.sdkRun?.usage });
147
+ } catch (error) {
148
+ prepared.usage.notePersistenceFailure(error);
149
+ return undefined;
150
+ }
151
+ }
152
+
153
+ /** Single wait/finalize path for SDK runs: accounting, replay, incomplete tools, artifacts, context cache. */
141
154
  export async function awaitFinalizeCursorRunOutcome(params: AwaitFinalizeCursorRunOutcomeParams): Promise<FinalizedCursorRunOutcome> {
142
155
  const apiKey = params.resolvedApiKey ?? params.optionsApiKey;
143
- const waitResult = params.waitResult ?? (await params.run.wait());
156
+ params.prepared.runtime.sdkRun = params.run;
157
+ let waitResult: Awaited<ReturnType<typeof params.run.wait>>;
158
+ try {
159
+ waitResult = params.waitResult ?? (await params.run.wait());
160
+ } catch (error) {
161
+ await recordCursorProviderTerminalUsage(params.prepared, params.signal?.aborted ? "abort" : "error");
162
+ throw error;
163
+ }
144
164
  const outcome = buildCursorRunOutcomeFromWait({
145
165
  waitResult,
146
166
  prepared: params.prepared,
@@ -150,16 +170,11 @@ export async function awaitFinalizeCursorRunOutcome(params: AwaitFinalizeCursorR
150
170
  resolvedApiKey: params.resolvedApiKey,
151
171
  optionsApiKey: params.optionsApiKey,
152
172
  });
153
- const billed = await attachCursorSdkBilledTurnUsage({
154
- agent: params.prepared.agent,
155
- agentId: params.run.agentId,
156
- runtime: params.prepared.runtimeTarget,
157
- runId: params.run.id,
158
- });
159
- params.prepared.runtime.billedTurnUsage = billed.turn;
160
- if (params.prepared.runtime.liveRun) {
161
- params.prepared.runtime.liveRun.billedTurnUsage = billed.turn;
162
- }
173
+ const billing = await recordCursorProviderTerminalUsage(
174
+ params.prepared,
175
+ outcome.kind === "finished" ? "success" : outcome.kind === "cancelled" ? "abort" : "error",
176
+ waitResult.usage,
177
+ );
163
178
  let displayOnlyTraceBlock: string | undefined;
164
179
  if (params.prepared.runtimeTarget === "cloud" && isCursorRunFinishedSuccessfully(outcome)) {
165
180
  let report: CursorCloudRunReport = { agentId: params.run.agentId, runId: params.run.id, branches: [] };
@@ -169,13 +184,13 @@ export async function awaitFinalizeCursorRunOutcome(params: AwaitFinalizeCursorR
169
184
  run: params.run,
170
185
  waitResult,
171
186
  apiKey,
172
- agentUsage: billed.agentUsage,
187
+ agentUsage: billing ? billing.status === "observed-pending-settlement" ? billing.snapshot : null : undefined,
173
188
  });
174
189
  } catch (error) {
175
190
  recordCursorCloudReportingError(params.sdkEventDebug, error, apiKey);
176
191
  }
177
192
  try {
178
- recordCursorCloudLifecycleRun(report, { apiKey });
193
+ params.prepared.recordCloudLifecycle(report, apiKey);
179
194
  } catch (error) {
180
195
  recordCursorCloudReportingError(params.sdkEventDebug, error, apiKey);
181
196
  }
@@ -195,12 +210,12 @@ export async function awaitFinalizeCursorRunOutcome(params: AwaitFinalizeCursorR
195
210
  } catch {
196
211
  // Debug reporting must never affect provider execution.
197
212
  }
198
- if (params.prepared.runtimeTarget === "local" && isCursorRunFinishedSuccessfully(outcome)) {
213
+ if (params.prepared.runtimeTarget === "local" && params.prepared.execution === "conversation" && isCursorRunFinishedSuccessfully(outcome)) {
199
214
  await replayCursorTranscriptWebToolCalls(
200
215
  params.run.agentId,
201
216
  params.prepared.cwd,
202
217
  params.cursorAgentMessageOffset,
203
- params.prepared.sessionAgentLease.store,
218
+ params.prepared.store,
204
219
  params.prepared.runtime.turnCoordinator,
205
220
  params.sdkEventDebug,
206
221
  );
@@ -211,12 +226,12 @@ export async function awaitFinalizeCursorRunOutcome(params: AwaitFinalizeCursorR
211
226
  } catch {
212
227
  // Debug artifact failures must never affect provider execution.
213
228
  }
214
- if (params.prepared.runtimeTarget === "local" && params.cacheContextWindow !== false) {
229
+ if (params.prepared.runtimeTarget === "local" && params.prepared.execution === "conversation" && params.cacheContextWindow !== false) {
215
230
  await cacheSdkContextWindow(
216
231
  params.contextWindowAgentId ?? params.run.agentId,
217
232
  params.modelId,
218
233
  params.prepared.cwd,
219
- params.prepared.sessionAgentLease.store,
234
+ params.prepared.store,
220
235
  );
221
236
  }
222
237
  return { outcome, displayOnlyTraceBlock };
@@ -22,7 +22,6 @@ import {
22
22
  createCursorNativeReplayId,
23
23
  cursorLiveRuns,
24
24
  getActiveCursorLiveRunForCurrentScope,
25
- getPendingCursorLiveRun,
26
25
  } from "./cursor-provider-live-run-drain.js";
27
26
  import {
28
27
  getCursorProviderAgentModeOrThrow,
@@ -38,17 +37,14 @@ import {
38
37
  preflightCursorCloudRuntime,
39
38
  } from "./cursor-cloud-options.js";
40
39
  import { inspectCursorCloudLocalState } from "./cursor-cloud-local-state.js";
41
- import { getCursorSessionName, getCursorSessionProjectTrusted } from "./cursor-session-scope.js";
40
+ import { getCursorSessionProjectTrusted } from "./cursor-session-scope.js";
42
41
  import { resolveCursorPiToolBridgeEnabled } from "./cursor-pi-tool-bridge-env.js";
43
42
  import {
44
43
  buildCursorToolManifestText,
45
44
  resolveCursorToolManifestEnabled,
46
45
  } from "./cursor-tool-manifest.js";
47
46
  import { isCursorNativeToolDisplayRuntimeEnabled } from "./cursor-native-tool-display-state.js";
48
- import {
49
- createCursorCloudLifecyclePersistenceError,
50
- recordCursorCloudLifecycleSafely,
51
- } from "./cursor-cloud-lifecycle.js";
47
+ import { createCursorCloudLifecyclePersistenceError } from "./cursor-cloud-lifecycle.js";
52
48
  import { MISSING_CURSOR_API_KEY_MESSAGE } from "./cursor-provider-errors.js";
53
49
  import { CursorSdkTurnCoordinator } from "./cursor-provider-turn-coordinator.js";
54
50
  import { resolveCursorApiKey } from "./cursor-api-key.js";
@@ -62,6 +58,7 @@ import type {
62
58
  } from "./cursor-provider-turn-types.js";
63
59
  import type { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
64
60
  import type { SessionCursorAgentLease } from "./cursor-session-agent.js";
61
+ import { prepareCursorSummaryProviderTurn } from "./cursor-provider-turn-summary.js";
65
62
 
66
63
  export interface PrepareCursorProviderTurnParams {
67
64
  params: CursorProviderTurnRunnerParams;
@@ -94,8 +91,8 @@ function buildCursorCloudPromptContext(context: Context, handoff: "fresh" | "boo
94
91
 
95
92
  const CLOUD_SEND_PLAN: CursorSessionSendPlan = { mode: "bootstrap", resetAgent: false, reason: "initial" };
96
93
 
97
- export function resolveCursorProviderTurnConfig(cwd: string) {
98
- return resolveEffectiveCursorConfig({ cwd, projectTrusted: getCursorSessionProjectTrusted() });
94
+ export function resolveCursorProviderTurnConfig(cwd: string, projectTrusted = getCursorSessionProjectTrusted(), scopeKey?: string) {
95
+ return resolveEffectiveCursorConfig({ cwd, projectTrusted, scopeKey });
99
96
  }
100
97
 
101
98
  function buildCloudCursorProviderTurnLifecycle(agent: SDKAgent): CursorProviderTurnLifecycle {
@@ -143,7 +140,7 @@ async function prepareCursorCloudProviderTurn(
143
140
  hasPriorContext: getCursorConversationMessages(context).length > 1,
144
141
  });
145
142
  if (!preflight.ok) throw new Error(formatCursorCloudPreflightError(preflight));
146
- if (getPendingCursorLiveRun(context) || getActiveCursorLiveRunForCurrentScope()) {
143
+ if (getActiveCursorLiveRunForCurrentScope(params.scope.scopeKey)) {
147
144
  throw new Error("Cursor cloud runtime cannot start while a local Cursor live run is pending; finish or abort the local run, then retry.");
148
145
  }
149
146
 
@@ -166,11 +163,11 @@ async function prepareCursorCloudProviderTurn(
166
163
  modelSelection: selection,
167
164
  agentMode,
168
165
  resolvedConfig,
169
- name: getCursorSessionName(),
166
+ name: params.scope.sessionName,
170
167
  })),
171
168
  );
172
169
  cloudAgentForCleanup = agent;
173
- if (!recordCursorCloudLifecycleSafely({ agentId: agent.agentId }, resolvedApiKey)) {
170
+ if (!params.recordCloudLifecycle({ agentId: agent.agentId }, resolvedApiKey)) {
174
171
  throw createCursorCloudLifecyclePersistenceError(agent.agentId, "intent", undefined, resolvedApiKey);
175
172
  }
176
173
  sdkEventDebug?.recordProviderMeta({ runtime: "cloud", cloudAgentId: agent.agentId, phase: "agent_created" });
@@ -210,6 +207,8 @@ async function prepareCursorCloudProviderTurn(
210
207
  cloudAgentForCleanup = undefined;
211
208
  return {
212
209
  runtimeTarget: "cloud",
210
+ execution: "conversation",
211
+ recordCloudLifecycle: params.recordCloudLifecycle,
213
212
  agent,
214
213
  cwd,
215
214
  payload: {
@@ -272,6 +271,8 @@ async function prepareCursorLocalProviderTurn(
272
271
  let liveRunForBridgeQueue: CursorLiveRun | undefined;
273
272
 
274
273
  const sessionAgentAcquireParams = {
274
+ scope: params.scope,
275
+ bridge: params.bridge,
275
276
  apiKey: resolvedApiKey,
276
277
  agentMode,
277
278
  cwd,
@@ -341,7 +342,7 @@ async function prepareCursorLocalProviderTurn(
341
342
  };
342
343
  const sessionBridgeRun = bridgeRun;
343
344
  const promptInputTokens = estimateCursorPromptTokens(prompt, promptOptions);
344
- const useNativeToolReplay = isCursorNativeToolDisplayRuntimeEnabled();
345
+ const useNativeToolReplay = isCursorNativeToolDisplayRuntimeEnabled(params.nativeDisplay);
345
346
  const activeToolNames = getActiveContextToolNames(context);
346
347
  sdkEventDebug?.recordProviderMeta({
347
348
  model: {
@@ -378,6 +379,7 @@ async function prepareCursorLocalProviderTurn(
378
379
  promptInputTokens,
379
380
  textDeltas,
380
381
  debugRecorder: sdkEventDebug,
382
+ nativeDisplay: params.nativeDisplay,
381
383
  })
382
384
  : undefined;
383
385
  if (liveRun) {
@@ -394,6 +396,7 @@ async function prepareCursorLocalProviderTurn(
394
396
  liveRun,
395
397
  useNativeToolReplay,
396
398
  activeToolNames,
399
+ registeredToolNames: params.nativeDisplay ? new Set(params.nativeDisplay.registeredNativeToolSources.keys()) : undefined,
397
400
  nativeReplayId,
398
401
  textDeltas,
399
402
  debugRecorder: sdkEventDebug,
@@ -402,6 +405,9 @@ async function prepareCursorLocalProviderTurn(
402
405
  completed = true;
403
406
  return {
404
407
  runtimeTarget: "local",
408
+ execution: "conversation",
409
+ store: sessionAgentLease.store,
410
+ storeIdentity: sessionAgentLease.storeIdentity,
405
411
  agent,
406
412
  cwd,
407
413
  payload: sendPayload,
@@ -449,9 +455,12 @@ export async function prepareCursorProviderTurn(
449
455
  const { params, resolvedConfig } = prepareParams;
450
456
  const { model, options } = params;
451
457
 
452
- const agentMode = getCursorProviderAgentModeOrThrow();
453
- const fastEnabled = resolvedConfig.runtime.value === "cloud" ? undefined : getEffectiveFastForModelId(model.id);
458
+ const fastEnabled = resolvedConfig.runtime.value === "cloud" ? undefined : getEffectiveFastForModelId(model.id, params.scope.scopeKey);
454
459
  const selection = buildCursorModelSelection(model.id, options?.reasoning ?? "off", fastEnabled);
460
+ if (resolvedConfig.runtime.value === "local" && params.request.purpose !== "normal") {
461
+ return prepareCursorSummaryProviderTurn(prepareParams, selection);
462
+ }
463
+ const agentMode = getCursorProviderAgentModeOrThrow(params.scope.scopeKey);
455
464
  const context: PrepareCursorProviderTurnContext = { ...prepareParams, agentMode, selection, fastEnabled };
456
465
 
457
466
  return resolvedConfig.runtime.value === "cloud"