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,4 +1,4 @@
1
- // Generated with @cursor/sdk@1.0.32 from 42 Cursor models.
1
+ // Generated with @cursor/sdk@1.0.32 from 45 Cursor models.
2
2
  // Refresh with: npm run refresh:cursor-snapshots -- --write
3
3
  // Do not add secrets; this file stores public model metadata only.
4
4
  export const FALLBACK_MODEL_ITEMS = [
@@ -5597,6 +5597,185 @@ export const FALLBACK_MODEL_ITEMS = [
5597
5597
  }
5598
5598
  ]
5599
5599
  },
5600
+ {
5601
+ id: "claude-sonnet-5-5",
5602
+ displayName: "Claude Sonnet 5.5",
5603
+ parameters: [
5604
+ {
5605
+ id: "context",
5606
+ displayName: "Context",
5607
+ values: [
5608
+ {
5609
+ value: "300k",
5610
+ displayName: "300K"
5611
+ },
5612
+ {
5613
+ value: "1m",
5614
+ displayName: "1M"
5615
+ }
5616
+ ]
5617
+ },
5618
+ {
5619
+ id: "reasoning_effort",
5620
+ displayName: "Effort",
5621
+ values: [
5622
+ {
5623
+ value: "low",
5624
+ displayName: "Low"
5625
+ },
5626
+ {
5627
+ value: "medium",
5628
+ displayName: "Medium"
5629
+ },
5630
+ {
5631
+ value: "high",
5632
+ displayName: "High"
5633
+ },
5634
+ {
5635
+ value: "xhigh",
5636
+ displayName: "Extra High"
5637
+ },
5638
+ {
5639
+ value: "max",
5640
+ displayName: "Max"
5641
+ }
5642
+ ]
5643
+ }
5644
+ ],
5645
+ variants: [
5646
+ {
5647
+ params: [
5648
+ {
5649
+ id: "context",
5650
+ value: "300k"
5651
+ },
5652
+ {
5653
+ id: "reasoning_effort",
5654
+ value: "low"
5655
+ }
5656
+ ],
5657
+ displayName: "Claude Sonnet 5.5 Low"
5658
+ },
5659
+ {
5660
+ params: [
5661
+ {
5662
+ id: "context",
5663
+ value: "300k"
5664
+ },
5665
+ {
5666
+ id: "reasoning_effort",
5667
+ value: "medium"
5668
+ }
5669
+ ],
5670
+ displayName: "Claude Sonnet 5.5 Medium"
5671
+ },
5672
+ {
5673
+ params: [
5674
+ {
5675
+ id: "context",
5676
+ value: "300k"
5677
+ },
5678
+ {
5679
+ id: "reasoning_effort",
5680
+ value: "high"
5681
+ }
5682
+ ],
5683
+ displayName: "Claude Sonnet 5.5 High"
5684
+ },
5685
+ {
5686
+ params: [
5687
+ {
5688
+ id: "context",
5689
+ value: "300k"
5690
+ },
5691
+ {
5692
+ id: "reasoning_effort",
5693
+ value: "xhigh"
5694
+ }
5695
+ ],
5696
+ displayName: "Claude Sonnet 5.5 Extra High"
5697
+ },
5698
+ {
5699
+ params: [
5700
+ {
5701
+ id: "context",
5702
+ value: "300k"
5703
+ },
5704
+ {
5705
+ id: "reasoning_effort",
5706
+ value: "max"
5707
+ }
5708
+ ],
5709
+ displayName: "Claude Sonnet 5.5 Max"
5710
+ },
5711
+ {
5712
+ params: [
5713
+ {
5714
+ id: "context",
5715
+ value: "1m"
5716
+ },
5717
+ {
5718
+ id: "reasoning_effort",
5719
+ value: "low"
5720
+ }
5721
+ ],
5722
+ displayName: "Claude Sonnet 5.5 1M Low"
5723
+ },
5724
+ {
5725
+ params: [
5726
+ {
5727
+ id: "context",
5728
+ value: "1m"
5729
+ },
5730
+ {
5731
+ id: "reasoning_effort",
5732
+ value: "medium"
5733
+ }
5734
+ ],
5735
+ displayName: "Claude Sonnet 5.5 1M Medium"
5736
+ },
5737
+ {
5738
+ params: [
5739
+ {
5740
+ id: "context",
5741
+ value: "1m"
5742
+ },
5743
+ {
5744
+ id: "reasoning_effort",
5745
+ value: "high"
5746
+ }
5747
+ ],
5748
+ displayName: "Claude Sonnet 5.5 1M High",
5749
+ isDefault: true
5750
+ },
5751
+ {
5752
+ params: [
5753
+ {
5754
+ id: "context",
5755
+ value: "1m"
5756
+ },
5757
+ {
5758
+ id: "reasoning_effort",
5759
+ value: "xhigh"
5760
+ }
5761
+ ],
5762
+ displayName: "Claude Sonnet 5.5 1M Extra High"
5763
+ },
5764
+ {
5765
+ params: [
5766
+ {
5767
+ id: "context",
5768
+ value: "1m"
5769
+ },
5770
+ {
5771
+ id: "reasoning_effort",
5772
+ value: "max"
5773
+ }
5774
+ ],
5775
+ displayName: "Claude Sonnet 5.5 1M Max"
5776
+ }
5777
+ ]
5778
+ },
5600
5779
  {
5601
5780
  id: "composer-2",
5602
5781
  displayName: "Composer 2",
@@ -5977,6 +6156,114 @@ export const FALLBACK_MODEL_ITEMS = [
5977
6156
  }
5978
6157
  ]
5979
6158
  },
6159
+ {
6160
+ id: "glm-5p3",
6161
+ displayName: "GLM 5.3",
6162
+ parameters: [
6163
+ {
6164
+ id: "reasoning",
6165
+ displayName: "Reasoning",
6166
+ values: [
6167
+ {
6168
+ value: "low",
6169
+ displayName: "Low"
6170
+ },
6171
+ {
6172
+ value: "high",
6173
+ displayName: "High"
6174
+ },
6175
+ {
6176
+ value: "max",
6177
+ displayName: "Max"
6178
+ }
6179
+ ]
6180
+ }
6181
+ ],
6182
+ variants: [
6183
+ {
6184
+ params: [
6185
+ {
6186
+ id: "reasoning",
6187
+ value: "low"
6188
+ }
6189
+ ],
6190
+ displayName: "GLM 5.3 Low"
6191
+ },
6192
+ {
6193
+ params: [
6194
+ {
6195
+ id: "reasoning",
6196
+ value: "high"
6197
+ }
6198
+ ],
6199
+ displayName: "GLM 5.3 High"
6200
+ },
6201
+ {
6202
+ params: [
6203
+ {
6204
+ id: "reasoning",
6205
+ value: "max"
6206
+ }
6207
+ ],
6208
+ displayName: "GLM 5.3 Max",
6209
+ isDefault: true
6210
+ }
6211
+ ]
6212
+ },
6213
+ {
6214
+ id: "glm-5p3-flash",
6215
+ displayName: "GLM 5.3 Flash",
6216
+ parameters: [
6217
+ {
6218
+ id: "reasoning",
6219
+ displayName: "Reasoning",
6220
+ values: [
6221
+ {
6222
+ value: "low",
6223
+ displayName: "Low"
6224
+ },
6225
+ {
6226
+ value: "high",
6227
+ displayName: "High"
6228
+ },
6229
+ {
6230
+ value: "max",
6231
+ displayName: "Max"
6232
+ }
6233
+ ]
6234
+ }
6235
+ ],
6236
+ variants: [
6237
+ {
6238
+ params: [
6239
+ {
6240
+ id: "reasoning",
6241
+ value: "low"
6242
+ }
6243
+ ],
6244
+ displayName: "GLM 5.3 Flash Low"
6245
+ },
6246
+ {
6247
+ params: [
6248
+ {
6249
+ id: "reasoning",
6250
+ value: "high"
6251
+ }
6252
+ ],
6253
+ displayName: "GLM 5.3 Flash High"
6254
+ },
6255
+ {
6256
+ params: [
6257
+ {
6258
+ id: "reasoning",
6259
+ value: "max"
6260
+ }
6261
+ ],
6262
+ displayName: "GLM 5.3 Flash Max",
6263
+ isDefault: true
6264
+ }
6265
+ ]
6266
+ },
5980
6267
  {
5981
6268
  id: "gpt-5-mini",
5982
6269
  displayName: "GPT-5 Mini",
@@ -1,12 +1,12 @@
1
1
  import { isCursorModel } from "./cursor-model.js";
2
2
  import { registerCursorModelLifecycle } from "./cursor-model-lifecycle.js";
3
- import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
3
+ import { cursorSessionScopeKeyForManager } from "./cursor-session-scope.js";
4
4
  export function registerCursorFallbackIssueWarning(pi, issue) {
5
5
  const warnedSessionScopeKeys = new Set();
6
6
  registerCursorModelLifecycle(pi, (ctx) => {
7
7
  if (!isCursorModel(ctx.model) || !ctx.hasUI)
8
8
  return;
9
- const scopeKey = getCursorSessionScopeKey();
9
+ const scopeKey = cursorSessionScopeKeyForManager(ctx.sessionManager);
10
10
  if (warnedSessionScopeKeys.has(scopeKey))
11
11
  return;
12
12
  warnedSessionScopeKeys.add(scopeKey);
@@ -1,19 +1,19 @@
1
1
  import { asRecord } from "./cursor-record-utils.js";
2
+ import { getCursorSessionSettings } from "./cursor-session-settings.js";
2
3
  export const CURSOR_HTTP1_ENTRY_TYPE = "cursor-http1-state";
3
- let sessionCursorHttp1Enabled;
4
4
  let globalPreferenceAuthoritative = false;
5
5
  let configuredCursor;
6
6
  export function isCursorHttp1EntryData(value) {
7
7
  return typeof asRecord(value)?.enabled === "boolean";
8
8
  }
9
- export function getStoredCursorHttp1Enabled() {
10
- return sessionCursorHttp1Enabled;
9
+ export function getStoredCursorHttp1Enabled(scopeKey) {
10
+ return getCursorSessionSettings(scopeKey).http1;
11
11
  }
12
- export function setStoredCursorHttp1Enabled(enabled) {
13
- sessionCursorHttp1Enabled = enabled;
12
+ export function setStoredCursorHttp1Enabled(enabled, scopeKey) {
13
+ getCursorSessionSettings(scopeKey).http1 = enabled;
14
14
  }
15
- export function getResolvedSessionCursorHttp1Enabled() {
16
- return globalPreferenceAuthoritative ? undefined : sessionCursorHttp1Enabled;
15
+ export function getResolvedSessionCursorHttp1Enabled(scopeKey) {
16
+ return globalPreferenceAuthoritative ? undefined : getStoredCursorHttp1Enabled(scopeKey);
17
17
  }
18
18
  export function setCursorHttp1GlobalPreferenceAuthoritative(authoritative) {
19
19
  globalPreferenceAuthoritative = authoritative;
@@ -38,7 +38,7 @@ export function configureCursorSdkHttp1(sdk, setting) {
38
38
  }
39
39
  export const __testUtils = {
40
40
  reset() {
41
- sessionCursorHttp1Enabled = undefined;
41
+ setStoredCursorHttp1Enabled(undefined);
42
42
  globalPreferenceAuthoritative = false;
43
43
  configuredCursor = undefined;
44
44
  },
@@ -153,6 +153,7 @@ export function createCursorLiveRunCoordinator(deps) {
153
153
  bridgeRun: params.bridgeRun,
154
154
  sessionBridgeRun: params.sessionBridgeRun,
155
155
  sessionAgentScopeKey,
156
+ nativeDisplay: params.nativeDisplay,
156
157
  accounting: createCursorLiveRunAccountingState(params.promptInputTokens),
157
158
  pendingEvents: [],
158
159
  textDeltas: params.textDeltas ?? [],
@@ -270,7 +271,10 @@ export function createCursorLiveRunCoordinator(deps) {
270
271
  run.accounting = taken.state;
271
272
  return taken.sdkTurnUsage;
272
273
  },
273
- getPendingFromContext(context, getReplayId) {
274
+ getPendingFromContext(context, getReplayId, scopeKey) {
275
+ const run = getUndisposed(pendingRunIdsByScopeKey.get(scopeKey));
276
+ if (!run)
277
+ return undefined;
274
278
  const messages = getCursorConversationMessages(context);
275
279
  let index = messages.length - 1;
276
280
  while (index >= 0 && messages[index]?.role === "user") {
@@ -280,18 +284,8 @@ export function createCursorLiveRunCoordinator(deps) {
280
284
  const message = messages[index];
281
285
  if (message.role !== "toolResult")
282
286
  break;
283
- const replayId = getReplayId(message.toolCallId);
284
- if (replayId) {
285
- const replayRun = getUndisposed(replayId);
286
- if (replayRun)
287
- return replayRun;
288
- }
289
- for (const run of pendingRuns.values()) {
290
- if (run.disposed)
291
- continue;
292
- if (run.bridgeRun?.hasPendingPiToolCallId(message.toolCallId))
293
- return run;
294
- }
287
+ if (matchesCursorLiveRunToolResult(run, message, getReplayId))
288
+ return run;
295
289
  }
296
290
  return undefined;
297
291
  },
@@ -389,12 +383,14 @@ export function createCursorLiveRunCoordinator(deps) {
389
383
  }
390
384
  if (abandoned) {
391
385
  if (!run.done) {
386
+ const usageCompletion = run.onAbandon?.();
392
387
  try {
393
388
  await cancelCursorLiveSdkRun(run);
394
389
  }
395
390
  catch {
396
391
  // cancellation failure should not block session-agent abandonment
397
392
  }
393
+ await usageCompletion;
398
394
  }
399
395
  await deps.abandonSessionAgent(run.sessionAgentScopeKey);
400
396
  }
@@ -8,7 +8,7 @@ export function isNativeToolActiveInContext(toolName, activeToolNames) {
8
8
  * Extension resync (pi active tools) is separate; this uses context.tools snapshot only.
9
9
  */
10
10
  export function resolveNativeReplayDisposition(input) {
11
- if (!input.useNativeToolReplay || !canRenderCursorToolNatively(input.toolName)) {
11
+ if (!input.useNativeToolReplay || !(input.registeredToolNames?.has(input.toolName) ?? canRenderCursorToolNatively(input.toolName))) {
12
12
  return "transcript_trace";
13
13
  }
14
14
  if (isNativeToolActiveInContext(input.toolName, input.activeToolNames) && input.hasLiveRun) {
@@ -2,7 +2,7 @@ import { arePiToolsDisabled } from "./cursor-active-tools.js";
2
2
  import { CURSOR_MODEL_ACTIVE_REPLAY_TOOL_NAMES, isNativeCursorToolName, NATIVE_CURSOR_TOOL_NAMES, } from "./cursor-native-tool-names.js";
3
3
  import { isCursorModel } from "./cursor-model.js";
4
4
  import { registerCursorModelLifecycle } from "./cursor-model-lifecycle.js";
5
- import { isCursorNativeToolDisplayRequested, isCursorNativeToolRegistrationRequested, NATIVE_CURSOR_TOOL_DISPLAY_ENV, readBooleanEnv, registeredNativeToolNames, setCursorNativeToolDisplayRuntimeRequested, skippedNativeToolNames, } from "./cursor-native-tool-display-state.js";
5
+ import { isCursorNativeToolDisplayRequested, isCursorNativeToolRegistrationRequested, NATIVE_CURSOR_TOOL_DISPLAY_ENV, readBooleanEnv, getCursorNativeToolDisplayState, registerCursorNativeToolDisplayState, setCursorNativeToolDisplayRuntimeRequested, } from "./cursor-native-tool-display-state.js";
6
6
  import { isCursorReplayToolName } from "./cursor-tool-presentation-registry.js";
7
7
  import { registerNativeCursorTool } from "./cursor-native-tool-display-tools.js";
8
8
  export const CURSOR_CORE_PI_REPLAY_TOOL_NAMES = ["read", "bash", "edit", "write"];
@@ -10,22 +10,37 @@ const CORE_PI_TOOL_NAMES = new Set(CURSOR_CORE_PI_REPLAY_TOOL_NAMES);
10
10
  function isCursorCorePiReplayToolName(toolName) {
11
11
  return CORE_PI_TOOL_NAMES.has(toolName);
12
12
  }
13
+ // Pi filters getAllTools even after registration; new/reloaded extensions receive a new API.
14
+ const filteredRegistrations = new WeakMap();
13
15
  function hasNonBuiltinTool(pi, toolName) {
14
16
  const existingTool = pi.getAllTools().find((tool) => tool.name === toolName);
15
17
  return existingTool !== undefined && existingTool.sourceInfo.source !== "builtin";
16
18
  }
17
- function registerNativeCursorToolsFromSet(pi, toolNames) {
19
+ function registerNativeCursorToolsFromSet(pi, toolNames, getCwd) {
20
+ const state = getCursorNativeToolDisplayState(pi);
21
+ const { registeredNativeToolSources, skippedNativeToolNames } = state;
22
+ let filteredToolNames = filteredRegistrations.get(pi);
23
+ if (!filteredToolNames) {
24
+ filteredToolNames = new Set();
25
+ filteredRegistrations.set(pi, filteredToolNames);
26
+ }
18
27
  const newlySkippedToolNames = [];
19
28
  for (const toolName of toolNames) {
20
- if (registeredNativeToolNames.has(toolName) || skippedNativeToolNames.has(toolName))
29
+ if (registeredNativeToolSources.has(toolName) || filteredToolNames.has(toolName))
21
30
  continue;
22
31
  if (hasNonBuiltinTool(pi, toolName)) {
23
- skippedNativeToolNames.add(toolName);
24
- newlySkippedToolNames.push(toolName);
32
+ if (!skippedNativeToolNames.has(toolName)) {
33
+ skippedNativeToolNames.add(toolName);
34
+ newlySkippedToolNames.push(toolName);
35
+ }
25
36
  continue;
26
37
  }
27
- registerNativeCursorTool(pi, toolName);
28
- registeredNativeToolNames.add(toolName);
38
+ registerNativeCursorTool(pi, toolName, getCwd, state);
39
+ const registeredTool = pi.getAllTools().find((tool) => tool.name === toolName);
40
+ if (registeredTool)
41
+ registeredNativeToolSources.set(toolName, registeredTool.sourceInfo);
42
+ else
43
+ filteredToolNames.add(toolName);
29
44
  }
30
45
  return newlySkippedToolNames;
31
46
  }
@@ -34,15 +49,13 @@ function notifySkippedNativeCursorToolsIfNeeded(ctx, skippedToolNames) {
34
49
  return;
35
50
  ctx.ui.notify(`Cursor native tool replay skipped for ${skippedToolNames.join(", ")} because another extension already provides ${skippedToolNames.length === 1 ? "that tool" : "those tools"}. Cursor will use scrubbed activity transcripts for skipped tools.`, "warning");
36
51
  }
37
- function hasAttemptedNativeCursorToolRegistration() {
38
- return registeredNativeToolNames.size > 0 || skippedNativeToolNames.size > 0;
39
- }
40
52
  function removeRegisteredNonCoreNativeCursorTools(pi) {
41
- if (registeredNativeToolNames.size === 0)
53
+ const { registeredNativeToolSources } = getCursorNativeToolDisplayState(pi);
54
+ if (registeredNativeToolSources.size === 0)
42
55
  return;
43
56
  const activeToolNames = new Set(pi.getActiveTools());
44
57
  let changed = false;
45
- for (const toolName of registeredNativeToolNames) {
58
+ for (const toolName of registeredNativeToolSources.keys()) {
46
59
  if (isCursorCorePiReplayToolName(toolName))
47
60
  continue;
48
61
  if (!activeToolNames.delete(toolName))
@@ -53,7 +66,8 @@ function removeRegisteredNonCoreNativeCursorTools(pi) {
53
66
  pi.setActiveTools([...activeToolNames]);
54
67
  }
55
68
  export function syncRegisteredNativeCursorToolsForModel(pi, model) {
56
- if (registeredNativeToolNames.size === 0)
69
+ const { registeredNativeToolSources } = getCursorNativeToolDisplayState(pi);
70
+ if (registeredNativeToolSources.size === 0)
57
71
  return;
58
72
  if (!isCursorModel(model)) {
59
73
  removeRegisteredNonCoreNativeCursorTools(pi);
@@ -63,7 +77,7 @@ export function syncRegisteredNativeCursorToolsForModel(pi, model) {
63
77
  return;
64
78
  const activeToolNames = new Set(pi.getActiveTools());
65
79
  let changed = false;
66
- for (const toolName of registeredNativeToolNames) {
80
+ for (const toolName of registeredNativeToolSources.keys()) {
67
81
  if (isCursorReplayToolName(toolName) && !CURSOR_MODEL_ACTIVE_REPLAY_TOOL_NAMES.some((activeReplayToolName) => activeReplayToolName === toolName))
68
82
  continue;
69
83
  if (activeToolNames.has(toolName))
@@ -74,29 +88,39 @@ export function syncRegisteredNativeCursorToolsForModel(pi, model) {
74
88
  if (changed)
75
89
  pi.setActiveTools([...activeToolNames]);
76
90
  }
77
- function ensureNativeCursorToolsRegisteredForModel(pi, ctx) {
78
- if (!isCursorModel(ctx.model) || hasAttemptedNativeCursorToolRegistration())
91
+ function ensureNativeCursorToolsRegisteredForModel(pi, ctx, getCwd) {
92
+ if (!isCursorModel(ctx.model))
79
93
  return;
80
94
  const nonCoreToolNames = NATIVE_CURSOR_TOOL_NAMES.filter((toolName) => !isCursorCorePiReplayToolName(toolName));
81
95
  const skippedToolNames = [
82
- ...registerNativeCursorToolsFromSet(pi, nonCoreToolNames),
83
- ...registerNativeCursorToolsFromSet(pi, CURSOR_CORE_PI_REPLAY_TOOL_NAMES),
96
+ ...registerNativeCursorToolsFromSet(pi, nonCoreToolNames, getCwd),
97
+ ...registerNativeCursorToolsFromSet(pi, CURSOR_CORE_PI_REPLAY_TOOL_NAMES, getCwd),
84
98
  ];
85
99
  notifySkippedNativeCursorToolsIfNeeded(ctx, skippedToolNames);
86
100
  }
87
- function ensureThenSyncNativeCursorToolsForModel(pi, ctx) {
101
+ function ensureThenSyncNativeCursorToolsForModel(pi, ctx, getCwd) {
102
+ const { registeredNativeToolSources } = getCursorNativeToolDisplayState(pi);
103
+ const currentTools = pi.getAllTools();
104
+ for (const [toolName, sourceInfo] of registeredNativeToolSources) {
105
+ if (!currentTools.some((tool) => tool.name === toolName && tool.sourceInfo === sourceInfo)) {
106
+ registeredNativeToolSources.delete(toolName);
107
+ }
108
+ }
88
109
  const requested = isCursorNativeToolRegistrationRequested(ctx.mode);
89
- setCursorNativeToolDisplayRuntimeRequested(requested);
110
+ setCursorNativeToolDisplayRuntimeRequested(requested, pi);
90
111
  if (!requested) {
91
112
  removeRegisteredNonCoreNativeCursorTools(pi);
92
113
  return;
93
114
  }
94
- ensureNativeCursorToolsRegisteredForModel(pi, ctx);
115
+ ensureNativeCursorToolsRegisteredForModel(pi, ctx, getCwd);
95
116
  syncRegisteredNativeCursorToolsForModel(pi, ctx.model);
96
117
  }
97
118
  export function registerCursorNativeToolDisplay(pi) {
119
+ registerCursorNativeToolDisplayState(pi);
120
+ let cwd = process.cwd();
98
121
  registerCursorModelLifecycle(pi, (ctx) => {
99
- ensureThenSyncNativeCursorToolsForModel(pi, ctx);
122
+ cwd = ctx.cwd;
123
+ ensureThenSyncNativeCursorToolsForModel(pi, ctx, () => cwd);
100
124
  });
101
125
  }
102
126
  export { isNativeCursorToolName, isCursorNativeToolDisplayRequested };
@@ -1,10 +1,36 @@
1
1
  import { parseOptionalEnvBoolean } from "./cursor-env-boolean.js";
2
+ import { normalizeCursorToolName } from "./cursor-tool-presentation-registry.js";
2
3
  export const NATIVE_CURSOR_TOOL_DISPLAY_ENV = "PI_CURSOR_NATIVE_TOOL_DISPLAY";
3
4
  export const NATIVE_CURSOR_TOOL_REGISTRATION_ENV = "PI_CURSOR_REGISTER_NATIVE_TOOLS";
4
- export const registeredNativeToolNames = new Set();
5
- export const skippedNativeToolNames = new Set();
5
+ function createDisplayState() {
6
+ return { registeredNativeToolSources: new Map(), skippedNativeToolNames: new Set(), runtimeRequested: false, resultIds: new Set() };
7
+ }
8
+ let lastDisplayState = createDisplayState();
9
+ const displayStates = new Map();
10
+ // IDs index results; the owning display state and tool authorize consumption.
6
11
  export const nativeToolResults = new Map();
7
- let nativeToolDisplayRuntimeRequested = false;
12
+ const resultStates = new Map();
13
+ export function getCursorNativeToolDisplayState(pi) {
14
+ if (!pi)
15
+ return lastDisplayState;
16
+ const state = displayStates.get(pi);
17
+ if (!state)
18
+ throw new Error("Cursor native display binding is no longer active.");
19
+ return state;
20
+ }
21
+ export function registerCursorNativeToolDisplayState(pi) {
22
+ const state = createDisplayState();
23
+ displayStates.set(pi, state);
24
+ lastDisplayState = state;
25
+ pi.on("session_shutdown", () => {
26
+ displayStates.delete(pi);
27
+ for (const id of state.resultIds) {
28
+ nativeToolResults.delete(id);
29
+ resultStates.delete(id);
30
+ }
31
+ state.resultIds.clear();
32
+ });
33
+ }
8
34
  export function readBooleanEnv(name, env = process.env) {
9
35
  return parseOptionalEnvBoolean(env[name]);
10
36
  }
@@ -19,34 +45,42 @@ export function isCursorNativeToolDisplayRequested(mode) {
19
45
  export function isCursorNativeToolRegistrationRequested(mode) {
20
46
  return mode !== "print" && readBooleanEnv(NATIVE_CURSOR_TOOL_REGISTRATION_ENV) !== false && isCursorNativeToolDisplayRequested(mode);
21
47
  }
22
- export function setCursorNativeToolDisplayRuntimeRequested(requested) {
23
- nativeToolDisplayRuntimeRequested = requested;
48
+ export function setCursorNativeToolDisplayRuntimeRequested(requested, pi) {
49
+ getCursorNativeToolDisplayState(pi).runtimeRequested = requested;
24
50
  }
25
51
  export function isCursorNativeToolDisplayEnabled() {
26
- return registeredNativeToolNames.size > 0;
52
+ return getCursorNativeToolDisplayState().registeredNativeToolSources.size > 0;
27
53
  }
28
- export function isCursorNativeToolDisplayRuntimeEnabled() {
29
- return nativeToolDisplayRuntimeRequested && readBooleanEnv(NATIVE_CURSOR_TOOL_DISPLAY_ENV) !== false && registeredNativeToolNames.size > 0;
54
+ export function isCursorNativeToolDisplayRuntimeEnabled(state = getCursorNativeToolDisplayState()) {
55
+ return state.runtimeRequested && readBooleanEnv(NATIVE_CURSOR_TOOL_DISPLAY_ENV) !== false && state.registeredNativeToolSources.size > 0;
30
56
  }
31
57
  export function canRenderCursorToolNatively(toolName) {
32
- return registeredNativeToolNames.has(toolName);
58
+ return getCursorNativeToolDisplayState().registeredNativeToolSources.has(toolName);
33
59
  }
34
- export function isRegisteredCursorNativeToolName(toolName) {
35
- return registeredNativeToolNames.has(toolName);
60
+ export function isRegisteredCursorNativeToolName(toolName, pi) {
61
+ return (pi ? displayStates.get(pi) : lastDisplayState)?.registeredNativeToolSources.has(toolName) === true;
36
62
  }
37
- export function recordCursorNativeToolDisplay(item) {
38
- if (!canRenderCursorToolNatively(item.toolName))
63
+ export function recordCursorNativeToolDisplay(item, state = getCursorNativeToolDisplayState()) {
64
+ if (!state.registeredNativeToolSources.has(item.toolName))
39
65
  return false;
40
66
  nativeToolResults.set(item.id, item);
67
+ resultStates.set(item.id, state);
68
+ state.resultIds.add(item.id);
41
69
  return true;
42
70
  }
43
71
  export function deleteCursorNativeToolDisplay(id) {
44
72
  nativeToolResults.delete(id);
73
+ resultStates.get(id)?.resultIds.delete(id);
74
+ resultStates.delete(id);
45
75
  }
46
- export function consumeCursorNativeToolDisplay(id) {
76
+ export function consumeCursorNativeToolDisplay(id, state, toolName) {
47
77
  const item = nativeToolResults.get(id);
48
- if (item)
49
- nativeToolResults.delete(id);
78
+ if (item) {
79
+ if (resultStates.get(id) !== state || normalizeCursorToolName(item.toolName) !== normalizeCursorToolName(toolName)) {
80
+ throw new Error(`No owned Cursor ${toolName} result was available. This replay-only call does not execute work.`);
81
+ }
82
+ deleteCursorNativeToolDisplay(id);
83
+ }
50
84
  return item;
51
85
  }
52
86
  export function isCursorReplayToolCallId(toolCallId) {
@@ -57,14 +91,15 @@ export function isCursorFileMutationToolName(toolName) {
57
91
  }
58
92
  export const __testUtils = {
59
93
  nativeToolResultCount: () => nativeToolResults.size,
60
- registerNativeToolNameForTests(toolName) {
61
- nativeToolDisplayRuntimeRequested = true;
62
- registeredNativeToolNames.add(toolName);
94
+ registerNativeToolNameForTests(toolName, pi) {
95
+ const state = getCursorNativeToolDisplayState(pi);
96
+ state.runtimeRequested = true;
97
+ state.registeredNativeToolSources.set(toolName, { path: "test", source: "inline", scope: "temporary", origin: "top-level" });
63
98
  },
64
99
  reset() {
65
- nativeToolDisplayRuntimeRequested = false;
66
- registeredNativeToolNames.clear();
67
- skippedNativeToolNames.clear();
100
+ lastDisplayState = createDisplayState();
101
+ displayStates.clear();
68
102
  nativeToolResults.clear();
103
+ resultStates.clear();
69
104
  },
70
105
  };