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
@@ -1,6 +1,5 @@
1
1
  import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, } from "@earendil-works/pi-coding-agent";
2
2
  import { Text } from "@earendil-works/pi-tui";
3
- import { getCursorSessionCwd } from "./cursor-session-scope.js";
4
3
  import { CURSOR_MODEL_ACTIVE_REPLAY_TOOL_NAMES, CURSOR_REPLAY_TOOL_NAMES, } from "./cursor-native-tool-names.js";
5
4
  import { isCursorReplayToolName } from "./cursor-tool-presentation-registry.js";
6
5
  import { createCursorReplayOnlyToolDefinition, isCursorReplayNativeEditDetails, isCursorReplayNativeWriteDetails, parseCursorReplayToolDetails, renderCursorReplayResult, renderNativeLookingCursorFileMutationCall, renderNativeLookingCursorReadReplayResult, } from "./cursor-native-tool-display-replay.js";
@@ -61,12 +60,12 @@ function getNativeReplayStrategy(toolName) {
61
60
  ? NATIVE_CURSOR_TOOL_STRATEGIES[toolName]
62
61
  : undefined;
63
62
  }
64
- export function wrapNativeCursorTool(definition, getCurrentDefinition) {
63
+ export function wrapNativeCursorTool(definition, getCurrentDefinition, state) {
65
64
  const strategy = getNativeReplayStrategy(definition.name);
66
65
  return {
67
66
  ...definition,
68
67
  async execute(toolCallId, params, signal, onUpdate, ctx) {
69
- const cursorDisplay = consumeCursorNativeToolDisplay(toolCallId);
68
+ const cursorDisplay = consumeCursorNativeToolDisplay(toolCallId, state, definition.name);
70
69
  if (cursorDisplay) {
71
70
  if (cursorDisplay.isError) {
72
71
  const text = cursorDisplay.result.content
@@ -114,8 +113,8 @@ export function createNativeCursorToolDefinition(toolName, cwd) {
114
113
  return createCursorReplayOnlyToolDefinition(toolName);
115
114
  throw new Error(`Unsupported Cursor native replay tool: ${toolName}`);
116
115
  }
117
- export function registerNativeCursorTool(pi, toolName) {
118
- const definition = createNativeCursorToolDefinition(toolName, getCursorSessionCwd());
119
- pi.registerTool(wrapNativeCursorTool(definition, () => createNativeCursorToolDefinition(toolName, getCursorSessionCwd())));
116
+ export function registerNativeCursorTool(pi, toolName, getCwd, state) {
117
+ const definition = createNativeCursorToolDefinition(toolName, getCwd());
118
+ pi.registerTool(wrapNativeCursorTool(definition, () => createNativeCursorToolDefinition(toolName, getCwd()), state));
120
119
  }
121
120
  export { CURSOR_MODEL_ACTIVE_REPLAY_TOOL_NAMES, CURSOR_REPLAY_TOOL_NAMES };
@@ -1,6 +1,27 @@
1
1
  export const MCP_SERVER_NAME = "pi_tools";
2
2
  export const MCP_ENDPOINT_ROOT = "/cursor-pi-tool-bridge";
3
- const CURSOR_PI_BRIDGE_TOOL_CALL_ID_PATTERN = /^cursor-pi-bridge-run-[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}-tool-\d+$/i;
3
+ export const CURSOR_PI_BRIDGE_TOOL_CALL_ID_MAX_LENGTH = 64;
4
+ const CURSOR_PI_BRIDGE_RUN_UUID_PATTERN = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i;
5
+ const CURSOR_PI_BRIDGE_TOOL_CALL_ID_PATTERN = /^cursor-pi-bridge-[0-9a-f]{32}-t\d+$/i;
6
+ const LEGACY_CURSOR_PI_BRIDGE_TOOL_CALL_ID_PATTERN = /^cursor-pi-bridge-run-[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}-tool-\d+$/i;
7
+ /**
8
+ * Build the provider-facing bridge ID while enforcing the 64-character
9
+ * call_id limit used by OpenAI-compatible Responses backends.
10
+ */
11
+ export function buildCursorPiBridgeToolCallId(runUuid, counter) {
12
+ if (!CURSOR_PI_BRIDGE_RUN_UUID_PATTERN.test(runUuid)) {
13
+ throw new Error("Cursor pi bridge run UUID must be canonical");
14
+ }
15
+ if (!Number.isSafeInteger(counter) || counter <= 0) {
16
+ throw new Error("Cursor pi bridge tool call counter must be a positive safe integer");
17
+ }
18
+ const toolCallId = `cursor-pi-bridge-${runUuid.replaceAll("-", "")}-t${counter}`;
19
+ if (toolCallId.length > CURSOR_PI_BRIDGE_TOOL_CALL_ID_MAX_LENGTH) {
20
+ throw new Error("Cursor pi bridge tool call ID limit exceeded");
21
+ }
22
+ return toolCallId;
23
+ }
4
24
  export function isCursorPiBridgeToolCallId(toolCallId) {
5
- return CURSOR_PI_BRIDGE_TOOL_CALL_ID_PATTERN.test(toolCallId);
25
+ return ((toolCallId.length <= CURSOR_PI_BRIDGE_TOOL_CALL_ID_MAX_LENGTH && CURSOR_PI_BRIDGE_TOOL_CALL_ID_PATTERN.test(toolCallId)) ||
26
+ LEGACY_CURSOR_PI_BRIDGE_TOOL_CALL_ID_PATTERN.test(toolCallId));
6
27
  }
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { Server as McpProtocolServer, WebStandardStreamableHTTPServerTransport, } from "@modelcontextprotocol/server";
3
3
  import { bridgeToolExecutionAbortTracker } from "./cursor-pi-tool-bridge-abort.js";
4
- import { MCP_ENDPOINT_ROOT, MCP_SERVER_NAME } from "./cursor-pi-tool-bridge-constants.js";
4
+ import { buildCursorPiBridgeToolCallId, MCP_ENDPOINT_ROOT, MCP_SERVER_NAME, } from "./cursor-pi-tool-bridge-constants.js";
5
5
  import { writeCursorPiToolBridgeDiagnostic, } from "./cursor-pi-tool-bridge-diagnostics.js";
6
6
  import { resolveCursorPiToolBridgeCallTimeoutMs } from "./cursor-pi-tool-bridge-env.js";
7
7
  import { asToolResultMessage, containsKnownMcpToolName, convertPiContentToMcpContent, normalizeMcpArgs, snapshotToolToMcpTool, waitForProtocolFlush, } from "./cursor-pi-tool-bridge-mcp.js";
@@ -15,6 +15,7 @@ export class CursorPiToolBridgeRunImpl {
15
15
  registry;
16
16
  env;
17
17
  endpointPath;
18
+ runUuid;
18
19
  callTimeoutMs;
19
20
  knownMcpToolNames;
20
21
  knownCursorMcpCallIds = new Set();
@@ -36,7 +37,9 @@ export class CursorPiToolBridgeRunImpl {
36
37
  this.enabled = enabled;
37
38
  this.onToolRequest = options.onToolRequest;
38
39
  this.debugRecorder = options.debugRecorder;
39
- this.id = `cursor-pi-bridge-run-${randomUUID()}`;
40
+ const runUuid = randomUUID();
41
+ this.runUuid = runUuid;
42
+ this.id = `cursor-pi-bridge-run-${runUuid}`;
40
43
  this.endpointPath = `${MCP_ENDPOINT_ROOT}/${randomUUID()}/mcp`;
41
44
  this.callTimeoutMs = resolveCursorPiToolBridgeCallTimeoutMs(env);
42
45
  this.knownMcpToolNames = new Set(snapshot.tools.map((tool) => tool.mcpToolName));
@@ -208,11 +211,12 @@ export class CursorPiToolBridgeRunImpl {
208
211
  return Promise.reject(new Error("Cursor pi tool bridge run is disposed"));
209
212
  this.toolCallCounter += 1;
210
213
  const bridgeCallId = `${this.id}-bridge-${this.toolCallCounter}`;
214
+ const piToolCallId = buildCursorPiBridgeToolCallId(this.runUuid, this.toolCallCounter);
211
215
  const request = {
212
216
  runId: this.id,
213
217
  bridgeCallId,
214
218
  cursorMcpCallId,
215
- piToolCallId: `${this.id}-tool-${this.toolCallCounter}`,
219
+ piToolCallId,
216
220
  piToolName,
217
221
  mcpToolName,
218
222
  args: normalizeMcpArgs(argsValue),
@@ -42,7 +42,7 @@ export function buildCursorPiToolBridgeSnapshot(pi, options = {}) {
42
42
  for (const tool of allTools) {
43
43
  if (!activeToolNames.has(tool.name))
44
44
  continue;
45
- if (isExcludedFromCursorBridgeExposure(tool.name) && isRegisteredCursorNativeToolName(tool.name))
45
+ if (isExcludedFromCursorBridgeExposure(tool.name) && isRegisteredCursorNativeToolName(tool.name, pi))
46
46
  continue;
47
47
  if (!exposeOverlappingBuiltins && isOverlappingCursorNativePiToolName(tool.name))
48
48
  continue;
@@ -7,6 +7,8 @@ import { LOOPBACK_HOST, CursorPiToolBridgeRegistry } from "./cursor-pi-tool-brid
7
7
  export { resolveCursorPiToolBridgeDebugEnabled } from "./cursor-pi-tool-bridge-diagnostics.js";
8
8
  export { CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV, CURSOR_PI_TOOL_BRIDGE_CALL_TIMEOUT_MS_ENV, CURSOR_PI_TOOL_BRIDGE_ENV, resolveCursorPiToolBridgeBuiltinsEnabled, resolveCursorPiToolBridgeCallTimeoutMs, resolveCursorPiToolBridgeEnabled, } from "./cursor-pi-tool-bridge-env.js";
9
9
  export { buildCursorPiToolBridgeSnapshot, buildCursorPiToolBridgeSurfaceSignature, } from "./cursor-pi-tool-bridge-snapshot.js";
10
+ const bridges = new Map();
11
+ const activeCallsByPi = new Map();
10
12
  let registeredCursorPiToolBridge;
11
13
  const WINDOWS_BRIDGE_ABORT_ENV = "PI_CURSOR_BRIDGE_TOOL_CALL_ID";
12
14
  function buildWindowsBridgeBashAbortCommand(command, marker) {
@@ -49,18 +51,27 @@ Get-CimInstance Win32_Process -Filter "Name = 'bash.exe' OR Name = 'sh.exe'" |
49
51
  });
50
52
  }
51
53
  export function registerCursorPiToolBridge(pi) {
52
- bridgeToolExecutionAbortTracker.abortAll("Cursor pi tool bridge extension reloaded");
53
- void registeredCursorPiToolBridge?.disposeAll("Cursor pi tool bridge extension reloaded");
54
+ const previous = bridges.get(pi);
55
+ if (previous) {
56
+ const reason = "Cursor pi tool bridge extension reloaded";
57
+ for (const id of activeCallsByPi.get(pi) ?? [])
58
+ bridgeToolExecutionAbortTracker.abort(id, reason);
59
+ void previous.disposeAll(reason);
60
+ }
54
61
  const bridge = new CursorPiToolBridgeRegistry(pi);
55
62
  registeredCursorPiToolBridge = bridge;
63
+ bridges.set(pi, bridge);
64
+ const activeCalls = new Set();
65
+ activeCallsByPi.set(pi, activeCalls);
56
66
  pi.on("tool_call", (event, ctx) => {
57
- if (registeredCursorPiToolBridge !== bridge)
67
+ if (bridges.get(pi) !== bridge)
58
68
  return undefined;
59
69
  if (!bridge.hasPendingPiToolCallId(event.toolCallId)) {
60
70
  return isCursorPiBridgeToolCallId(event.toolCallId)
61
71
  ? { block: true, reason: "Cursor pi bridge tool call is no longer pending" }
62
72
  : undefined;
63
73
  }
74
+ activeCalls.add(event.toolCallId);
64
75
  const windowsAbortMarker = installWindowsBridgeBashAbortMarker(event);
65
76
  const trackingStarted = bridgeToolExecutionAbortTracker.track(event.toolCallId, {
66
77
  signal: ctx.signal,
@@ -77,17 +88,26 @@ export function registerCursorPiToolBridge(pi) {
77
88
  return { block: true, reason: "Cursor pi bridge tool execution was aborted before it started" };
78
89
  });
79
90
  pi.on("tool_result", (event) => {
80
- bridgeToolExecutionAbortTracker.finish(event.toolCallId);
91
+ if (activeCalls.delete(event.toolCallId))
92
+ bridgeToolExecutionAbortTracker.finish(event.toolCallId);
81
93
  });
82
94
  pi.on("session_shutdown", async (event) => {
95
+ if (bridges.get(pi) !== bridge)
96
+ return;
83
97
  const reason = `Cursor pi tool bridge session shutdown: ${event.reason}`;
84
- bridgeToolExecutionAbortTracker.abortAll(reason);
98
+ for (const id of activeCalls)
99
+ bridgeToolExecutionAbortTracker.abort(id, reason);
100
+ activeCalls.clear();
101
+ bridges.delete(pi);
102
+ activeCallsByPi.delete(pi);
103
+ if (registeredCursorPiToolBridge === bridge)
104
+ registeredCursorPiToolBridge = [...bridges.values()].at(-1);
85
105
  await bridge.disposeAll(reason);
86
106
  });
87
107
  return bridge;
88
108
  }
89
- export function getRegisteredCursorPiToolBridge() {
90
- return registeredCursorPiToolBridge;
109
+ export function getRegisteredCursorPiToolBridge(pi) {
110
+ return pi ? bridges.get(pi) : registeredCursorPiToolBridge;
91
111
  }
92
112
  export const __testUtils = {
93
113
  CURSOR_PI_TOOL_BRIDGE_ENV,
@@ -116,8 +136,10 @@ export const __testUtils = {
116
136
  },
117
137
  resetRegisteredBridgeForTests() {
118
138
  bridgeToolExecutionAbortTracker.abortAll("Cursor pi tool bridge test reset");
119
- const bridge = registeredCursorPiToolBridge;
139
+ const registered = [...bridges.values()];
140
+ bridges.clear();
141
+ activeCallsByPi.clear();
120
142
  registeredCursorPiToolBridge = undefined;
121
- return bridge?.disposeAll("Cursor pi tool bridge test reset") ?? Promise.resolve();
143
+ return Promise.all(registered.map((bridge) => bridge.disposeAll("Cursor pi tool bridge test reset")));
122
144
  },
123
145
  };
@@ -0,0 +1,58 @@
1
+ import { CURSOR_API_KEY_CONFIG_VALUE } from "./cursor-api-key.js";
2
+ import { captureCursorCloudLifecycleRecorder } from "./cursor-cloud-lifecycle.js";
3
+ import { captureCursorUsageRecorder } from "./cursor-usage-ledger.js";
4
+ import { getCursorNativeToolDisplayState } from "./cursor-native-tool-display-state.js";
5
+ import { getRegisteredCursorPiToolBridge } from "./cursor-pi-tool-bridge.js";
6
+ import { createCursorLazyStream } from "./cursor-provider-lazy.js";
7
+ import { getCursorSessionScopeSnapshot } from "./cursor-session-scope.js";
8
+ import { captureCursorRequestProjection, resolveCursorRequestProvenance } from "./cursor-request-provenance.js";
9
+ export const CURSOR_PROVIDER_OWNERSHIP_ERROR = "Cursor provider ownership is unavailable: this request does not belong to the active Cursor registration. Bind this session's extensions and refresh Cursor models/reload it; use independent ModelRuntime instances for sibling sessions.";
10
+ const requestOwners = new WeakMap();
11
+ /** One closure per ExtensionAPI, including direct compaction/tree/bug-report streams. */
12
+ export function registerCursorProviderBinding(pi) {
13
+ const binding = { active: false, closed: false };
14
+ const operations = new WeakMap();
15
+ pi.on("session_before_compact", (event) => { operations.set(event.signal, "compaction"); });
16
+ pi.on("session_before_tree", (event) => { operations.set(event.signal, "tree"); });
17
+ pi.on("before_provider_headers", (event, ctx) => {
18
+ // Pi's SDK supplies this session-owned callback for ordinary and auxiliary
19
+ // streams. ModelRuntime forwards the same real headers object to the provider.
20
+ requestOwners.set(event.headers, {
21
+ binding,
22
+ scope: getCursorSessionScopeSnapshot(pi),
23
+ projection: captureCursorRequestProjection(ctx.sessionManager),
24
+ usageRecorder: captureCursorUsageRecorder(pi, ctx),
25
+ });
26
+ });
27
+ const stream = createCursorLazyStream((model, context, options) => {
28
+ const headers = options?.headers;
29
+ const owner = headers && requestOwners.get(headers);
30
+ if (headers)
31
+ requestOwners.delete(headers);
32
+ if (!binding.active || binding.closed)
33
+ throw new Error("Cursor provider binding is not active. Bind this session's extensions, or reload the session after shutdown.");
34
+ if (!owner)
35
+ throw new Error("Cursor provider ownership is unavailable: this call has no native session request receipt. Bare modelRegistry.streamSimple calls are unsupported for Cursor; use the owning AgentSession's stream path or an independent child AgentSession.");
36
+ if (owner.binding !== binding)
37
+ throw new Error(CURSOR_PROVIDER_OWNERSHIP_ERROR);
38
+ return {
39
+ request: resolveCursorRequestProvenance(owner.projection, model, context, options?.signal && operations.get(options.signal) || "normal"),
40
+ usageRecorder: owner.usageRecorder,
41
+ scope: owner.scope,
42
+ bridge: getRegisteredCursorPiToolBridge(pi),
43
+ nativeDisplay: getCursorNativeToolDisplayState(pi),
44
+ recordCloudLifecycle: captureCursorCloudLifecycleRecorder(pi),
45
+ };
46
+ });
47
+ pi.on("session_start", () => { if (!binding.closed)
48
+ binding.active = true; });
49
+ pi.on("session_shutdown", () => { binding.active = false; binding.closed = true; });
50
+ return (models) => {
51
+ if (binding.closed)
52
+ return;
53
+ pi.registerProvider("cursor", {
54
+ name: "Cursor", baseUrl: "https://cursor.com", apiKey: CURSOR_API_KEY_CONFIG_VALUE,
55
+ api: "cursor-sdk", models, streamSimple: stream,
56
+ });
57
+ };
58
+ }
@@ -1,11 +1,11 @@
1
1
  import { asRecord } from "./cursor-record-utils.js";
2
2
  import { scrubSensitiveText } from "./cursor-sensitive-text.js";
3
3
  export const MISSING_CURSOR_API_KEY_MESSAGE = "Cursor SDK runs require a Cursor SDK API key. Cursor Agent CLI/Desktop login is not reused. Run /login -> Use an API key -> Cursor, set CURSOR_API_KEY before starting pi, or restart pi with --api-key.";
4
- const GENERIC_CURSOR_SDK_ERROR_MESSAGE = "Cursor SDK request failed. The Cursor SDK API key may be missing, invalid, or unauthorized. Cursor Agent CLI/Desktop login is not reused. Run /login -> Use an API key -> Cursor, verify CURSOR_API_KEY, or pass --api-key, then retry.";
4
+ const GENERIC_CURSOR_SDK_ERROR_MESSAGE = "Cursor SDK request failed without further error details.";
5
5
  const AUTH_CURSOR_SDK_ERROR_MESSAGE = "Cursor SDK request failed because the Cursor SDK API key may be invalid or unauthorized. Cursor Agent CLI/Desktop login is not reused. Run /login -> Use an API key -> Cursor, verify CURSOR_API_KEY, or pass --api-key, then retry.";
6
6
  const CLOUD_AUTH_CURSOR_SDK_ERROR_MESSAGE = "Cursor Cloud Agents request failed because Cloud API authentication rejected the API key. Use a user API key from Cursor Dashboard -> API Keys or a service account API key from Team settings; Team Admin API keys are not supported as Cursor Cloud Agents credentials. Configure the key with /login -> Use an API key -> Cursor, CURSOR_API_KEY, or --api-key, then retry.";
7
7
  // Keep "Network error" aligned with pi's agent-level retry classifier.
8
- const NETWORK_CURSOR_SDK_ERROR_MESSAGE = "Network error: Cursor SDK request failed during network or service I/O. Check your connection; pi will retry automatically when auto-retry is enabled.";
8
+ const NETWORK_CURSOR_SDK_ERROR_MESSAGE = "Network error: Cursor SDK request failed during network or service I/O. Check your connection.";
9
9
  // Keep this phrase aligned with pi's agent-level retry classifier (`provider.?returned.?error`).
10
10
  const RETRYABLE_CURSOR_RUN_FAILURE_PREFIX = "Provider returned error: Cursor SDK run failed";
11
11
  function isGenericErrorMessage(message) {
@@ -284,23 +284,44 @@ export function resolveCursorSdkAbortCause(options) {
284
284
  return "live_run_disposed";
285
285
  return "unknown";
286
286
  }
287
+ function formatErrorDetail(error, apiKey, depth = 0) {
288
+ if (depth >= 3)
289
+ return "";
290
+ const record = asRecord(error);
291
+ const message = typeof error === "string" ? error : getErrorStringField(record, "message") ?? "";
292
+ const name = getErrorStringField(record, "name");
293
+ const code = getErrorEvidenceField(error, record, "code");
294
+ const scrub = (text, limit) => scrubSensitiveText(text, apiKey).trim().slice(0, limit);
295
+ const scrubbed = [
296
+ name && name !== "Error" ? `${scrub(name, 120)}:` : "",
297
+ code ? `(code: ${scrub(code, 120)})` : "",
298
+ scrub(message, 1600),
299
+ ].filter(Boolean).join(" ");
300
+ const cause = record?.cause === undefined ? "" : formatErrorDetail(record.cause, apiKey, depth + 1);
301
+ return cause ? `${scrubbed}\nCaused by: ${cause}` : scrubbed;
302
+ }
287
303
  export function sanitizeCursorProviderError(error, apiKey, runtimeTarget) {
288
304
  const record = asRecord(error);
289
- const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
305
+ const message = typeof error === "string" ? error : getErrorStringField(record, "message") ?? "";
290
306
  if (message === MISSING_CURSOR_API_KEY_MESSAGE)
291
307
  return MISSING_CURSOR_API_KEY_MESSAGE;
292
- const scrubbed = scrubSensitiveText(message, apiKey).trim();
308
+ const scrubbed = formatErrorDetail(error, apiKey);
309
+ // A loader failure is not an auth rejection, even inside an authentication wrapper.
310
+ if (/\b(?:ERR_MODULE_NOT_FOUND|MODULE_NOT_FOUND|ResolveMessage)\b|Cannot find (?:module|package)|does not provide an export named/i.test(scrubbed))
311
+ return scrubbed;
293
312
  const integrationMessage = runtimeTarget === "cloud"
294
- ? formatIntegrationNotConnectedError(error, record, scrubbed, apiKey)
313
+ ? formatIntegrationNotConnectedError(error, record, scrubSensitiveText(message, apiKey).trim(), apiKey)
295
314
  : undefined;
296
315
  if (integrationMessage)
297
316
  return integrationMessage;
298
317
  const connectClassification = classifyCursorConnectError(error);
299
318
  if ((runtimeTarget === "cloud" && getErrorName(error, record) === "AuthenticationError") ||
300
- connectClassification?.kind === "unauthenticated" ||
301
- isLikelyAuthError(scrubbed)) {
319
+ /\b(?:invalid|revoked|expired|missing) (?:user )?api key\b|\bapi key (?:is )?(?:invalid|revoked|expired|missing)\b/i.test(scrubbed)) {
302
320
  return runtimeTarget === "cloud" ? CLOUD_AUTH_CURSOR_SDK_ERROR_MESSAGE : AUTH_CURSOR_SDK_ERROR_MESSAGE;
303
321
  }
322
+ if (connectClassification?.kind === "unauthenticated" || isLikelyAuthError(scrubbed)) {
323
+ return `Cursor SDK authentication failed. ${scrubbed}`;
324
+ }
304
325
  if (connectClassification?.kind === "network" || isCursorSdkConnectionStalledError(error) || isLikelyNetworkTimeout(scrubbed))
305
326
  return NETWORK_CURSOR_SDK_ERROR_MESSAGE;
306
327
  if (isGenericCursorRunFailureMessage(scrubbed))
@@ -21,19 +21,31 @@ function makeProviderRuntimeErrorMessage(model, error, apiKey) {
21
21
  errorMessage: `Cursor provider runtime failed: ${sanitizeCursorProviderError(error, apiKey)}`,
22
22
  };
23
23
  }
24
- export function streamCursorLazy(model, context, options) {
25
- const outer = createAssistantMessageEventStream();
26
- queueMicrotask(async () => {
27
- try {
28
- for await (const event of streamCursor(model, context, options)) {
29
- outer.push(event);
30
- }
31
- }
32
- catch (error) {
24
+ export function createCursorLazyStream(capture) {
25
+ return (model, context, options) => {
26
+ const outer = createAssistantMessageEventStream();
27
+ const fail = (error) => {
33
28
  const message = makeProviderRuntimeErrorMessage(model, error, options?.apiKey);
34
29
  outer.push({ type: "error", reason: "error", error: message });
35
30
  outer.end(message);
31
+ };
32
+ let ownership;
33
+ try {
34
+ ownership = capture(model, context, options);
36
35
  }
37
- });
38
- return outer;
36
+ catch (error) {
37
+ fail(error);
38
+ return outer;
39
+ }
40
+ queueMicrotask(async () => {
41
+ try {
42
+ for await (const event of streamCursor(model, context, options, ownership))
43
+ outer.push(event);
44
+ }
45
+ catch (error) {
46
+ fail(error);
47
+ }
48
+ });
49
+ return outer;
50
+ };
39
51
  }
@@ -30,11 +30,11 @@ export function createCursorNativeReplayId() {
30
30
  function getCursorNativeReplayIdFromToolCallId(toolCallId) {
31
31
  return CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN.exec(toolCallId)?.[1];
32
32
  }
33
- export function getPendingCursorLiveRun(context) {
34
- return cursorLiveRuns.getPendingFromContext(context, getCursorNativeReplayIdFromToolCallId);
33
+ export function getPendingCursorLiveRun(context, scopeKey) {
34
+ return cursorLiveRuns.getPendingFromContext(context, getCursorNativeReplayIdFromToolCallId, scopeKey);
35
35
  }
36
- export function getActiveCursorLiveRunForCurrentScope() {
37
- return cursorLiveRuns.getActiveForScope();
36
+ export function getActiveCursorLiveRunForCurrentScope(scopeKey) {
37
+ return cursorLiveRuns.getActiveForScope(scopeKey);
38
38
  }
39
39
  function splitTextIntoReplayDeltas(text) {
40
40
  const deltas = [];
@@ -125,7 +125,7 @@ function emitCursorLiveQueuedEvent(turn, event, run) {
125
125
  turn.emitter.appendTextDelta(event.text);
126
126
  }
127
127
  }
128
- function emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, tools, debugRecorder) {
128
+ function emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, tools, occupancyFloor, debugRecorder) {
129
129
  const shouldTerminate = run.done && !run.finalText?.trim() && !cursorLiveRuns.peekEvent(run);
130
130
  for (const tool of tools) {
131
131
  const contentIndex = partial.content.length;
@@ -142,7 +142,7 @@ function emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolR
142
142
  const block = partial.content[contentIndex];
143
143
  if (block.type === "toolCall")
144
144
  stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial });
145
- if (recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate })) {
145
+ if (recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate }, run.nativeDisplay)) {
146
146
  run.recordedToolDisplayIds.push(tool.id);
147
147
  debugRecorder?.recordDrainEvent("native_tool_display_recorded", {
148
148
  toolId: tool.id,
@@ -154,6 +154,7 @@ function emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolR
154
154
  applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
155
155
  runtime: "local",
156
156
  turn: cursorLiveRuns.takeSdkTurnUsage(run),
157
+ occupancyFloor,
157
158
  });
158
159
  partial.stopReason = "toolUse";
159
160
  stream.push({ type: "done", reason: "toolUse", message: partial });
@@ -172,7 +173,7 @@ function emitInactiveCursorReplayTrace(turn, tools, debugRecorder) {
172
173
  turn.emitter.appendThinkingBlock(traceText);
173
174
  }
174
175
  }
175
- function emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, requests) {
176
+ function emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, requests, occupancyFloor) {
176
177
  for (const request of requests) {
177
178
  const contentIndex = partial.content.length;
178
179
  const serializedArgs = JSON.stringify(request.args);
@@ -191,6 +192,7 @@ function emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolR
191
192
  applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
192
193
  runtime: "local",
193
194
  turn: cursorLiveRuns.takeSdkTurnUsage(run),
195
+ occupancyFloor,
194
196
  });
195
197
  partial.stopReason = "toolUse";
196
198
  stream.push({ type: "done", reason: "toolUse", message: partial });
@@ -217,7 +219,7 @@ async function emitCursorLiveRunPendingToolUseTurn(turn, stream, partial, model,
217
219
  cursorLiveRuns.ignoreFutureSdkTurnUsage(run);
218
220
  if (options.mode === "emit")
219
221
  turn.emitter.closeAll();
220
- emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, active, debugRecorder);
222
+ emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, active, options.occupancyFloor, debugRecorder);
221
223
  }
222
224
  else {
223
225
  const requests = cursorLiveRuns.collectBridgeToolBatch(run);
@@ -227,7 +229,7 @@ async function emitCursorLiveRunPendingToolUseTurn(turn, stream, partial, model,
227
229
  cursorLiveRuns.ignoreFutureSdkTurnUsage(run);
228
230
  if (options.mode === "emit")
229
231
  turn.emitter.closeAll();
230
- emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, requests);
232
+ emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, requests, options.occupancyFloor);
231
233
  }
232
234
  return "tool_use";
233
235
  }
@@ -307,7 +309,7 @@ export async function drainCursorLiveRunTurn(stream, partial, model, context, ru
307
309
  applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
308
310
  runtime: "local",
309
311
  turn: cursorLiveRuns.takeSdkTurnUsage(run),
310
- billed: run.billedTurnUsage,
312
+ occupancyFloor: options.occupancyFloor,
311
313
  });
312
314
  if (run.resumeNotice) {
313
315
  emitDisplayOnlyTraceBlock(stream, partial, run.resumeNotice);
@@ -349,10 +351,10 @@ export async function drainCursorLiveRunTurn(stream, partial, model, context, ru
349
351
  });
350
352
  }
351
353
  }
352
- export async function drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, signal, turnDebugRecorder) {
354
+ export async function drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, signal, turnDebugRecorder, scopeKey, occupancyFloor) {
353
355
  turnDebugRecorder?.recordDrainEvent("pre_send_start", {});
354
356
  while (true) {
355
- const run = getPendingCursorLiveRun(context) ?? getActiveCursorLiveRunForCurrentScope();
357
+ const run = getPendingCursorLiveRun(context, scopeKey) ?? getActiveCursorLiveRunForCurrentScope(scopeKey);
356
358
  if (!run || run.disposed) {
357
359
  turnDebugRecorder?.recordDrainEvent("pre_send_end", { outcome: "continue_send", reason: "no_pending_run" });
358
360
  return "continue_send";
@@ -375,6 +377,7 @@ export async function drainExistingCursorLiveRunBeforeSend(stream, partial, mode
375
377
  mode: shouldChainUserInput ? "chain_user_input" : "emit",
376
378
  signal,
377
379
  debugRecorder: turnDebugRecorder,
380
+ occupancyFloor,
378
381
  });
379
382
  const mapped = drainOutcome === "chain_user_input" ? "continue_send" : "stream_ended";
380
383
  turnDebugRecorder?.recordDrainEvent("pre_send_iteration", {
@@ -3,7 +3,7 @@ import { classifyCursorRunEmission, getCursorRunAbortMessage, } from "./cursor-p
3
3
  import { formatCursorSdkAbortMessage, resolveCursorSdkAbortCause, sanitizeCursorProviderError, } from "./cursor-provider-errors.js";
4
4
  import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
5
5
  import { buildIncompleteCursorToolRunOutcome, } from "./cursor-incomplete-tool-visibility.js";
6
- import { awaitFinalizeCursorRunOutcome } from "./cursor-provider-turn-finalize.js";
6
+ import { awaitFinalizeCursorRunOutcome, recordCursorProviderTerminalUsage } from "./cursor-provider-turn-finalize.js";
7
7
  import { applyCursorUsage } from "./cursor-usage-accounting.js";
8
8
  import { hasUsableText } from "./cursor-record-utils.js";
9
9
  import { emitDisplayOnlyTraceBlock } from "./cursor-display-only-trace.js";
@@ -56,11 +56,15 @@ export class CursorRunFinalizer {
56
56
  applyLiveRunOutcome(finalized.outcome, prepared, runnerParams.context);
57
57
  })
58
58
  .catch((error) => {
59
- this.safeCleanup(() => discardIncompleteTools({ status: "error" }));
59
+ const aborted = error instanceof CursorLiveRunAbortError || runnerParams.options?.signal?.aborted === true;
60
+ this.safeCleanup(() => discardIncompleteTools({ status: aborted ? "cancelled" : "error" }));
60
61
  if (!liveRun.disposed) {
61
- cursorLiveRuns.markError(liveRun, sanitizeCursorProviderError(error, this.params.resolvedApiKey() ?? runnerParams.options?.apiKey, "local"));
62
+ if (aborted)
63
+ cursorLiveRuns.markCancelled(liveRun, this.abortMessage());
64
+ else
65
+ cursorLiveRuns.markError(liveRun, sanitizeCursorProviderError(error, this.params.resolvedApiKey() ?? runnerParams.options?.apiKey, "local"));
62
66
  }
63
- this.safeCleanup(() => sdkEventDebug?.recordWaitResult({ status: "error", error: String(error) }));
67
+ this.safeCleanup(() => sdkEventDebug?.recordWaitResult({ status: aborted ? "cancelled" : "error", error: String(error) }));
64
68
  this.safeCleanup(() => sdkEventDebug?.recordError("run_wait", error));
65
69
  });
66
70
  // Mark the pooled local agent busy as soon as the SDK run exists so auto-compaction summarization
@@ -104,6 +108,9 @@ export class CursorRunFinalizer {
104
108
  }
105
109
  async applyDirectOutcome(prepared, outcome, displayOnlyTraceBlock) {
106
110
  const { stream, partial, model, context } = this.params.runnerParams;
111
+ // Native summaries return at terminal emission, so release their temporary resources first.
112
+ if (prepared.execution === "summary")
113
+ await prepared.lifecycle.dispose().catch(() => { });
107
114
  prepared.runtime.turnCoordinator.closeTraceBlock();
108
115
  switch (classifyCursorRunEmission(outcome)) {
109
116
  case "cancelled":
@@ -120,7 +127,7 @@ export class CursorRunFinalizer {
120
127
  applyCursorUsage(partial, model, context, prepared.meta.promptInputTokens, {
121
128
  runtime: prepared.runtimeTarget,
122
129
  turn: prepared.runtime.turnCoordinator.lastSdkTurnUsage,
123
- billed: prepared.runtime.billedTurnUsage,
130
+ occupancyFloor: this.params.runnerParams.request.occupancyFloor,
124
131
  });
125
132
  if (prepared.meta.resumeNotice)
126
133
  emitDisplayOnlyTraceBlock(stream, partial, prepared.meta.resumeNotice);
@@ -131,9 +138,13 @@ export class CursorRunFinalizer {
131
138
  }
132
139
  }
133
140
  async applyErrorOutcome(prepared, error) {
141
+ const aborted = error instanceof CursorLiveRunAbortError || this.params.runnerParams.options?.signal?.aborted === true;
142
+ if (prepared && "usage" in prepared && (!prepared.runtime.sdkRun || prepared.runtime.kind !== "live")) {
143
+ await recordCursorProviderTerminalUsage(prepared, aborted ? "abort" : "error");
144
+ }
134
145
  this.safeCleanup(() => prepared?.runtime.turnCoordinator.discardIncompleteStartedToolCalls(buildIncompleteCursorToolRunOutcome({
135
- status: error instanceof CursorLiveRunAbortError ? "cancelled" : "error",
136
- signalAborted: error instanceof CursorLiveRunAbortError,
146
+ status: aborted ? "cancelled" : "error",
147
+ signalAborted: aborted,
137
148
  })));
138
149
  const activeLiveRun = prepared?.runtime.liveRun;
139
150
  if (activeLiveRun && !activeLiveRun.disposed) {
@@ -143,7 +154,7 @@ export class CursorRunFinalizer {
143
154
  await prepared?.lifecycle.abandon();
144
155
  }
145
156
  this.safeCleanup(() => this.params.sdkEventDebug()?.recordError("provider_stream", error));
146
- if (error instanceof CursorLiveRunAbortError) {
157
+ if (aborted) {
147
158
  this.params.sdkProcessErrorGuard.suppressAbortErrors();
148
159
  this.pushTerminalError(this.params.runnerParams.partial, "aborted", this.abortMessage());
149
160
  }
@@ -49,6 +49,7 @@ export class CursorSdkTurnCoordinator {
49
49
  liveRun: options.liveRun,
50
50
  useNativeToolReplay: options.useNativeToolReplay,
51
51
  activeToolNames: options.activeToolNames,
52
+ registeredToolNames: options.registeredToolNames,
52
53
  nativeReplayId: options.nativeReplayId,
53
54
  contentEmitter: this.contentEmitter,
54
55
  debugRecorder: options.debugRecorder,
@@ -15,6 +15,7 @@ export class CursorTurnDisplayRouter {
15
15
  liveRun;
16
16
  useNativeToolReplay;
17
17
  activeToolNames;
18
+ registeredToolNames;
18
19
  nativeReplayId;
19
20
  contentEmitter;
20
21
  debugRecorder;
@@ -27,6 +28,7 @@ export class CursorTurnDisplayRouter {
27
28
  this.liveRun = options.liveRun;
28
29
  this.useNativeToolReplay = options.useNativeToolReplay;
29
30
  this.activeToolNames = options.activeToolNames;
31
+ this.registeredToolNames = options.registeredToolNames;
30
32
  this.nativeReplayId = options.nativeReplayId;
31
33
  this.contentEmitter = options.contentEmitter;
32
34
  this.debugRecorder = options.debugRecorder;
@@ -41,6 +43,7 @@ export class CursorTurnDisplayRouter {
41
43
  toolName: display.toolName,
42
44
  useNativeToolReplay: this.useNativeToolReplay,
43
45
  activeToolNames: this.activeToolNames,
46
+ registeredToolNames: this.registeredToolNames,
44
47
  hasLiveRun: this.liveRun !== undefined,
45
48
  });
46
49
  if (disposition === "queue_replay" && this.liveRun) {
@@ -78,6 +81,7 @@ export class CursorTurnDisplayRouter {
78
81
  toolName: display.toolName,
79
82
  useNativeToolReplay: this.useNativeToolReplay,
80
83
  activeToolNames: this.activeToolNames,
84
+ registeredToolNames: this.registeredToolNames,
81
85
  hasLiveRun: this.liveRun !== undefined,
82
86
  });
83
87
  if (disposition === "queue_replay" && this.liveRun && reason !== "abort") {
@@ -21,6 +21,7 @@ export async function emitCursorLiveTurn(emitParams) {
21
21
  emitter,
22
22
  signal: options?.signal,
23
23
  debugRecorder: sdkEventDebug,
24
+ occupancyFloor: params.request.occupancyFloor,
24
25
  });
25
26
  });
26
27
  }