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,6 @@
1
1
  import type { ModelListItem } from "@cursor/sdk";
2
2
 
3
- // Generated with @cursor/sdk@1.0.32 from 42 Cursor models.
3
+ // Generated with @cursor/sdk@1.0.32 from 45 Cursor models.
4
4
  // Refresh with: npm run refresh:cursor-snapshots -- --write
5
5
  // Do not add secrets; this file stores public model metadata only.
6
6
  export const FALLBACK_MODEL_ITEMS = [
@@ -5599,6 +5599,185 @@ export const FALLBACK_MODEL_ITEMS = [
5599
5599
  }
5600
5600
  ]
5601
5601
  },
5602
+ {
5603
+ id: "claude-sonnet-5-5",
5604
+ displayName: "Claude Sonnet 5.5",
5605
+ parameters: [
5606
+ {
5607
+ id: "context",
5608
+ displayName: "Context",
5609
+ values: [
5610
+ {
5611
+ value: "300k",
5612
+ displayName: "300K"
5613
+ },
5614
+ {
5615
+ value: "1m",
5616
+ displayName: "1M"
5617
+ }
5618
+ ]
5619
+ },
5620
+ {
5621
+ id: "reasoning_effort",
5622
+ displayName: "Effort",
5623
+ values: [
5624
+ {
5625
+ value: "low",
5626
+ displayName: "Low"
5627
+ },
5628
+ {
5629
+ value: "medium",
5630
+ displayName: "Medium"
5631
+ },
5632
+ {
5633
+ value: "high",
5634
+ displayName: "High"
5635
+ },
5636
+ {
5637
+ value: "xhigh",
5638
+ displayName: "Extra High"
5639
+ },
5640
+ {
5641
+ value: "max",
5642
+ displayName: "Max"
5643
+ }
5644
+ ]
5645
+ }
5646
+ ],
5647
+ variants: [
5648
+ {
5649
+ params: [
5650
+ {
5651
+ id: "context",
5652
+ value: "300k"
5653
+ },
5654
+ {
5655
+ id: "reasoning_effort",
5656
+ value: "low"
5657
+ }
5658
+ ],
5659
+ displayName: "Claude Sonnet 5.5 Low"
5660
+ },
5661
+ {
5662
+ params: [
5663
+ {
5664
+ id: "context",
5665
+ value: "300k"
5666
+ },
5667
+ {
5668
+ id: "reasoning_effort",
5669
+ value: "medium"
5670
+ }
5671
+ ],
5672
+ displayName: "Claude Sonnet 5.5 Medium"
5673
+ },
5674
+ {
5675
+ params: [
5676
+ {
5677
+ id: "context",
5678
+ value: "300k"
5679
+ },
5680
+ {
5681
+ id: "reasoning_effort",
5682
+ value: "high"
5683
+ }
5684
+ ],
5685
+ displayName: "Claude Sonnet 5.5 High"
5686
+ },
5687
+ {
5688
+ params: [
5689
+ {
5690
+ id: "context",
5691
+ value: "300k"
5692
+ },
5693
+ {
5694
+ id: "reasoning_effort",
5695
+ value: "xhigh"
5696
+ }
5697
+ ],
5698
+ displayName: "Claude Sonnet 5.5 Extra High"
5699
+ },
5700
+ {
5701
+ params: [
5702
+ {
5703
+ id: "context",
5704
+ value: "300k"
5705
+ },
5706
+ {
5707
+ id: "reasoning_effort",
5708
+ value: "max"
5709
+ }
5710
+ ],
5711
+ displayName: "Claude Sonnet 5.5 Max"
5712
+ },
5713
+ {
5714
+ params: [
5715
+ {
5716
+ id: "context",
5717
+ value: "1m"
5718
+ },
5719
+ {
5720
+ id: "reasoning_effort",
5721
+ value: "low"
5722
+ }
5723
+ ],
5724
+ displayName: "Claude Sonnet 5.5 1M Low"
5725
+ },
5726
+ {
5727
+ params: [
5728
+ {
5729
+ id: "context",
5730
+ value: "1m"
5731
+ },
5732
+ {
5733
+ id: "reasoning_effort",
5734
+ value: "medium"
5735
+ }
5736
+ ],
5737
+ displayName: "Claude Sonnet 5.5 1M Medium"
5738
+ },
5739
+ {
5740
+ params: [
5741
+ {
5742
+ id: "context",
5743
+ value: "1m"
5744
+ },
5745
+ {
5746
+ id: "reasoning_effort",
5747
+ value: "high"
5748
+ }
5749
+ ],
5750
+ displayName: "Claude Sonnet 5.5 1M High",
5751
+ isDefault: true
5752
+ },
5753
+ {
5754
+ params: [
5755
+ {
5756
+ id: "context",
5757
+ value: "1m"
5758
+ },
5759
+ {
5760
+ id: "reasoning_effort",
5761
+ value: "xhigh"
5762
+ }
5763
+ ],
5764
+ displayName: "Claude Sonnet 5.5 1M Extra High"
5765
+ },
5766
+ {
5767
+ params: [
5768
+ {
5769
+ id: "context",
5770
+ value: "1m"
5771
+ },
5772
+ {
5773
+ id: "reasoning_effort",
5774
+ value: "max"
5775
+ }
5776
+ ],
5777
+ displayName: "Claude Sonnet 5.5 1M Max"
5778
+ }
5779
+ ]
5780
+ },
5602
5781
  {
5603
5782
  id: "composer-2",
5604
5783
  displayName: "Composer 2",
@@ -5979,6 +6158,114 @@ export const FALLBACK_MODEL_ITEMS = [
5979
6158
  }
5980
6159
  ]
5981
6160
  },
6161
+ {
6162
+ id: "glm-5p3",
6163
+ displayName: "GLM 5.3",
6164
+ parameters: [
6165
+ {
6166
+ id: "reasoning",
6167
+ displayName: "Reasoning",
6168
+ values: [
6169
+ {
6170
+ value: "low",
6171
+ displayName: "Low"
6172
+ },
6173
+ {
6174
+ value: "high",
6175
+ displayName: "High"
6176
+ },
6177
+ {
6178
+ value: "max",
6179
+ displayName: "Max"
6180
+ }
6181
+ ]
6182
+ }
6183
+ ],
6184
+ variants: [
6185
+ {
6186
+ params: [
6187
+ {
6188
+ id: "reasoning",
6189
+ value: "low"
6190
+ }
6191
+ ],
6192
+ displayName: "GLM 5.3 Low"
6193
+ },
6194
+ {
6195
+ params: [
6196
+ {
6197
+ id: "reasoning",
6198
+ value: "high"
6199
+ }
6200
+ ],
6201
+ displayName: "GLM 5.3 High"
6202
+ },
6203
+ {
6204
+ params: [
6205
+ {
6206
+ id: "reasoning",
6207
+ value: "max"
6208
+ }
6209
+ ],
6210
+ displayName: "GLM 5.3 Max",
6211
+ isDefault: true
6212
+ }
6213
+ ]
6214
+ },
6215
+ {
6216
+ id: "glm-5p3-flash",
6217
+ displayName: "GLM 5.3 Flash",
6218
+ parameters: [
6219
+ {
6220
+ id: "reasoning",
6221
+ displayName: "Reasoning",
6222
+ values: [
6223
+ {
6224
+ value: "low",
6225
+ displayName: "Low"
6226
+ },
6227
+ {
6228
+ value: "high",
6229
+ displayName: "High"
6230
+ },
6231
+ {
6232
+ value: "max",
6233
+ displayName: "Max"
6234
+ }
6235
+ ]
6236
+ }
6237
+ ],
6238
+ variants: [
6239
+ {
6240
+ params: [
6241
+ {
6242
+ id: "reasoning",
6243
+ value: "low"
6244
+ }
6245
+ ],
6246
+ displayName: "GLM 5.3 Flash Low"
6247
+ },
6248
+ {
6249
+ params: [
6250
+ {
6251
+ id: "reasoning",
6252
+ value: "high"
6253
+ }
6254
+ ],
6255
+ displayName: "GLM 5.3 Flash High"
6256
+ },
6257
+ {
6258
+ params: [
6259
+ {
6260
+ id: "reasoning",
6261
+ value: "max"
6262
+ }
6263
+ ],
6264
+ displayName: "GLM 5.3 Flash Max",
6265
+ isDefault: true
6266
+ }
6267
+ ]
6268
+ },
5982
6269
  {
5983
6270
  id: "gpt-5-mini",
5984
6271
  displayName: "GPT-5 Mini",
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { isCursorModel } from "./cursor-model.js";
3
3
  import { registerCursorModelLifecycle, type CursorModelLifecycleExtensionApi } from "./cursor-model-lifecycle.js";
4
- import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
4
+ import { cursorSessionScopeKeyForManager } from "./cursor-session-scope.js";
5
5
  import type { CursorModelFallbackIssue } from "./model-discovery.js";
6
6
 
7
7
  export type CursorFallbackWarningExtensionApi = CursorModelLifecycleExtensionApi;
@@ -14,7 +14,7 @@ export function registerCursorFallbackIssueWarning(
14
14
 
15
15
  registerCursorModelLifecycle(pi, (ctx: ExtensionContext) => {
16
16
  if (!isCursorModel(ctx.model) || !ctx.hasUI) return;
17
- const scopeKey = getCursorSessionScopeKey();
17
+ const scopeKey = cursorSessionScopeKeyForManager(ctx.sessionManager);
18
18
  if (warnedSessionScopeKeys.has(scopeKey)) return;
19
19
  warnedSessionScopeKeys.add(scopeKey);
20
20
  ctx.ui.notify(issue.message, "warning");
@@ -1,6 +1,7 @@
1
1
  import type { CursorSdkModule } from "./cursor-sdk-runtime.js";
2
2
  import type { CursorResolvedSetting } from "./cursor-config.js";
3
3
  import { asRecord } from "./cursor-record-utils.js";
4
+ import { getCursorSessionSettings } from "./cursor-session-settings.js";
4
5
 
5
6
  export const CURSOR_HTTP1_ENTRY_TYPE = "cursor-http1-state";
6
7
 
@@ -12,7 +13,6 @@ type CursorHttp1Sdk = {
12
13
  Cursor: Pick<CursorSdkModule["Cursor"], "configure">;
13
14
  };
14
15
 
15
- let sessionCursorHttp1Enabled: boolean | undefined;
16
16
  let globalPreferenceAuthoritative = false;
17
17
  let configuredCursor: CursorHttp1Sdk["Cursor"] | undefined;
18
18
 
@@ -20,16 +20,16 @@ export function isCursorHttp1EntryData(value: unknown): value is CursorHttp1Entr
20
20
  return typeof asRecord(value)?.enabled === "boolean";
21
21
  }
22
22
 
23
- export function getStoredCursorHttp1Enabled(): boolean | undefined {
24
- return sessionCursorHttp1Enabled;
23
+ export function getStoredCursorHttp1Enabled(scopeKey?: string): boolean | undefined {
24
+ return getCursorSessionSettings(scopeKey).http1;
25
25
  }
26
26
 
27
- export function setStoredCursorHttp1Enabled(enabled: boolean | undefined): void {
28
- sessionCursorHttp1Enabled = enabled;
27
+ export function setStoredCursorHttp1Enabled(enabled: boolean | undefined, scopeKey?: string): void {
28
+ getCursorSessionSettings(scopeKey).http1 = enabled;
29
29
  }
30
30
 
31
- export function getResolvedSessionCursorHttp1Enabled(): boolean | undefined {
32
- return globalPreferenceAuthoritative ? undefined : sessionCursorHttp1Enabled;
31
+ export function getResolvedSessionCursorHttp1Enabled(scopeKey?: string): boolean | undefined {
32
+ return globalPreferenceAuthoritative ? undefined : getStoredCursorHttp1Enabled(scopeKey);
33
33
  }
34
34
 
35
35
  export function setCursorHttp1GlobalPreferenceAuthoritative(authoritative: boolean): void {
@@ -58,7 +58,7 @@ export function configureCursorSdkHttp1(
58
58
 
59
59
  export const __testUtils = {
60
60
  reset(): void {
61
- sessionCursorHttp1Enabled = undefined;
61
+ setStoredCursorHttp1Enabled(undefined);
62
62
  globalPreferenceAuthoritative = false;
63
63
  configuredCursor = undefined;
64
64
  },
@@ -11,7 +11,7 @@ import {
11
11
  type CursorLiveToolResultConsumption,
12
12
  } from "./cursor-live-run-accounting.js";
13
13
  import type { CursorSdkTurnUsage } from "./cursor-usage-accounting.js";
14
- import type { CursorNativeToolDisplayItem } from "./cursor-native-tool-display-state.js";
14
+ import type { CursorNativeToolDisplayItem, CursorNativeToolDisplayState } from "./cursor-native-tool-display-state.js";
15
15
  import type { CursorPiBridgeToolRequest, CursorPiToolBridgeRun } from "./cursor-pi-tool-bridge.js";
16
16
  import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
17
17
  import type { CursorSdkEventDebugRecorder } from "./cursor-sdk-event-debug.js";
@@ -45,7 +45,7 @@ export interface CursorLiveRun {
45
45
  sdkRun?: CursorLiveSdkRun;
46
46
  ignoreFutureSdkTurnUsage?: boolean;
47
47
  accounting: CursorLiveRunAccountingState;
48
- billedTurnUsage?: CursorSdkTurnUsage;
48
+ onAbandon?: () => Promise<void>;
49
49
  pendingEvents: CursorLiveQueuedEvent[];
50
50
  textDeltas: string[];
51
51
  emittedText: string;
@@ -59,6 +59,7 @@ export interface CursorLiveRun {
59
59
  abortMessage?: string;
60
60
  chainUserInputAfterCompletion: boolean;
61
61
  debugRecorder?: CursorSdkEventDebugRecorder;
62
+ nativeDisplay?: CursorNativeToolDisplayState;
62
63
  }
63
64
 
64
65
  export interface CursorLiveRunCreateParams {
@@ -70,6 +71,7 @@ export interface CursorLiveRunCreateParams {
70
71
  promptInputTokens: number;
71
72
  textDeltas?: string[];
72
73
  debugRecorder?: CursorSdkEventDebugRecorder;
74
+ nativeDisplay?: CursorNativeToolDisplayState;
73
75
  }
74
76
 
75
77
  export interface CursorLiveRunCoordinatorDeps {
@@ -96,7 +98,7 @@ export interface CursorLiveRunCoordinator {
96
98
  consumeToolResults(run: CursorLiveRun, context: Context, getReplayId: CursorReplayIdResolver): CursorLiveToolResultConsumption;
97
99
  takeTurnInputTokens(run: CursorLiveRun, toolResultInputTokens: number): number;
98
100
  takeSdkTurnUsage(run: CursorLiveRun): CursorSdkTurnUsage | undefined;
99
- getPendingFromContext(context: Context, getReplayId: CursorReplayIdResolver): CursorLiveRun | undefined;
101
+ getPendingFromContext(context: Context, getReplayId: CursorReplayIdResolver, scopeKey: string): CursorLiveRun | undefined;
100
102
  getActiveForScope(scopeKey?: string): CursorLiveRun | undefined;
101
103
  isReady(run: CursorLiveRun): boolean;
102
104
  waitForProgress(run: CursorLiveRun, signal?: AbortSignal): Promise<void>;
@@ -293,6 +295,7 @@ export function createCursorLiveRunCoordinator(deps: CursorLiveRunCoordinatorDep
293
295
  bridgeRun: params.bridgeRun,
294
296
  sessionBridgeRun: params.sessionBridgeRun,
295
297
  sessionAgentScopeKey,
298
+ nativeDisplay: params.nativeDisplay,
296
299
  accounting: createCursorLiveRunAccountingState(params.promptInputTokens),
297
300
  pendingEvents: [],
298
301
  textDeltas: params.textDeltas ?? [],
@@ -420,7 +423,9 @@ export function createCursorLiveRunCoordinator(deps: CursorLiveRunCoordinatorDep
420
423
  return taken.sdkTurnUsage;
421
424
  },
422
425
 
423
- getPendingFromContext(context, getReplayId): CursorLiveRun | undefined {
426
+ getPendingFromContext(context, getReplayId, scopeKey): CursorLiveRun | undefined {
427
+ const run = getUndisposed(pendingRunIdsByScopeKey.get(scopeKey));
428
+ if (!run) return undefined;
424
429
  const messages = getCursorConversationMessages(context);
425
430
  let index = messages.length - 1;
426
431
  while (index >= 0 && messages[index]?.role === "user") {
@@ -430,15 +435,7 @@ export function createCursorLiveRunCoordinator(deps: CursorLiveRunCoordinatorDep
430
435
  for (; index >= 0; index -= 1) {
431
436
  const message = messages[index];
432
437
  if (message.role !== "toolResult") break;
433
- const replayId = getReplayId(message.toolCallId);
434
- if (replayId) {
435
- const replayRun = getUndisposed(replayId);
436
- if (replayRun) return replayRun;
437
- }
438
- for (const run of pendingRuns.values()) {
439
- if (run.disposed) continue;
440
- if (run.bridgeRun?.hasPendingPiToolCallId(message.toolCallId)) return run;
441
- }
438
+ if (matchesCursorLiveRunToolResult(run, message, getReplayId)) return run;
442
439
  }
443
440
  return undefined;
444
441
  },
@@ -531,11 +528,13 @@ export function createCursorLiveRunCoordinator(deps: CursorLiveRunCoordinatorDep
531
528
  }
532
529
  if (abandoned) {
533
530
  if (!run.done) {
531
+ const usageCompletion = run.onAbandon?.();
534
532
  try {
535
533
  await cancelCursorLiveSdkRun(run);
536
534
  } catch {
537
535
  // cancellation failure should not block session-agent abandonment
538
536
  }
537
+ await usageCompletion;
539
538
  }
540
539
  await deps.abandonSessionAgent(run.sessionAgentScopeKey);
541
540
  }
@@ -8,6 +8,7 @@ export interface NativeReplayRoutingInput {
8
8
  toolName: string;
9
9
  useNativeToolReplay: boolean;
10
10
  activeToolNames?: ReadonlySet<string>;
11
+ registeredToolNames?: ReadonlySet<string>;
11
12
  hasLiveRun: boolean;
12
13
  }
13
14
 
@@ -20,7 +21,7 @@ export function isNativeToolActiveInContext(toolName: string, activeToolNames?:
20
21
  * Extension resync (pi active tools) is separate; this uses context.tools snapshot only.
21
22
  */
22
23
  export function resolveNativeReplayDisposition(input: NativeReplayRoutingInput): NativeReplayDisposition {
23
- if (!input.useNativeToolReplay || !canRenderCursorToolNatively(input.toolName)) {
24
+ if (!input.useNativeToolReplay || !(input.registeredToolNames?.has(input.toolName) ?? canRenderCursorToolNatively(input.toolName))) {
24
25
  return "transcript_trace";
25
26
  }
26
27
  if (isNativeToolActiveInContext(input.toolName, input.activeToolNames) && input.hasLiveRun) {
@@ -13,9 +13,9 @@ import {
13
13
  isCursorNativeToolRegistrationRequested,
14
14
  NATIVE_CURSOR_TOOL_DISPLAY_ENV,
15
15
  readBooleanEnv,
16
- registeredNativeToolNames,
16
+ getCursorNativeToolDisplayState,
17
+ registerCursorNativeToolDisplayState,
17
18
  setCursorNativeToolDisplayRuntimeRequested,
18
- skippedNativeToolNames,
19
19
  } from "./cursor-native-tool-display-state.js";
20
20
  import { isCursorReplayToolName } from "./cursor-tool-presentation-registry.js";
21
21
  import { registerNativeCursorTool } from "./cursor-native-tool-display-tools.js";
@@ -30,7 +30,11 @@ function isCursorCorePiReplayToolName(toolName: string): toolName is (typeof CUR
30
30
  type CursorNativeToolActivationApi = Pick<ExtensionAPI, "getActiveTools" | "setActiveTools">;
31
31
  type CursorNativeToolRegistryApi = CursorNativeToolActivationApi & Pick<ExtensionAPI, "getAllTools" | "registerTool">;
32
32
 
33
- export interface CursorNativeToolDisplayExtensionApi extends CursorNativeToolRegistryApi, CursorModelLifecycleExtensionApi {}
33
+ export type CursorNativeToolDisplayExtensionApi = CursorNativeToolRegistryApi
34
+ & CursorModelLifecycleExtensionApi & Parameters<typeof registerCursorNativeToolDisplayState>[0];
35
+
36
+ // Pi filters getAllTools even after registration; new/reloaded extensions receive a new API.
37
+ const filteredRegistrations = new WeakMap<CursorNativeToolRegistryApi, Set<NativeCursorToolName>>();
34
38
 
35
39
  function hasNonBuiltinTool(pi: Pick<ExtensionAPI, "getAllTools">, toolName: NativeCursorToolName): boolean {
36
40
  const existingTool = pi.getAllTools().find((tool) => tool.name === toolName);
@@ -44,17 +48,29 @@ type NativeRegistrationContext = Pick<ExtensionContext, "mode" | "model"> & {
44
48
  function registerNativeCursorToolsFromSet(
45
49
  pi: CursorNativeToolRegistryApi,
46
50
  toolNames: readonly NativeCursorToolName[],
51
+ getCwd: () => string,
47
52
  ): NativeCursorToolName[] {
53
+ const state = getCursorNativeToolDisplayState(pi);
54
+ const { registeredNativeToolSources, skippedNativeToolNames } = state;
55
+ let filteredToolNames = filteredRegistrations.get(pi);
56
+ if (!filteredToolNames) {
57
+ filteredToolNames = new Set();
58
+ filteredRegistrations.set(pi, filteredToolNames);
59
+ }
48
60
  const newlySkippedToolNames: NativeCursorToolName[] = [];
49
61
  for (const toolName of toolNames) {
50
- if (registeredNativeToolNames.has(toolName) || skippedNativeToolNames.has(toolName)) continue;
62
+ if (registeredNativeToolSources.has(toolName) || filteredToolNames.has(toolName)) continue;
51
63
  if (hasNonBuiltinTool(pi, toolName)) {
52
- skippedNativeToolNames.add(toolName);
53
- newlySkippedToolNames.push(toolName);
64
+ if (!skippedNativeToolNames.has(toolName)) {
65
+ skippedNativeToolNames.add(toolName);
66
+ newlySkippedToolNames.push(toolName);
67
+ }
54
68
  continue;
55
69
  }
56
- registerNativeCursorTool(pi, toolName);
57
- registeredNativeToolNames.add(toolName);
70
+ registerNativeCursorTool(pi, toolName, getCwd, state);
71
+ const registeredTool = pi.getAllTools().find((tool) => tool.name === toolName);
72
+ if (registeredTool) registeredNativeToolSources.set(toolName, registeredTool.sourceInfo);
73
+ else filteredToolNames.add(toolName);
58
74
  }
59
75
  return newlySkippedToolNames;
60
76
  }
@@ -67,15 +83,12 @@ function notifySkippedNativeCursorToolsIfNeeded(ctx: NativeRegistrationContext,
67
83
  );
68
84
  }
69
85
 
70
- function hasAttemptedNativeCursorToolRegistration(): boolean {
71
- return registeredNativeToolNames.size > 0 || skippedNativeToolNames.size > 0;
72
- }
73
-
74
86
  function removeRegisteredNonCoreNativeCursorTools(pi: CursorNativeToolActivationApi): void {
75
- if (registeredNativeToolNames.size === 0) return;
87
+ const { registeredNativeToolSources } = getCursorNativeToolDisplayState(pi);
88
+ if (registeredNativeToolSources.size === 0) return;
76
89
  const activeToolNames = new Set(pi.getActiveTools());
77
90
  let changed = false;
78
- for (const toolName of registeredNativeToolNames) {
91
+ for (const toolName of registeredNativeToolSources.keys()) {
79
92
  if (isCursorCorePiReplayToolName(toolName)) continue;
80
93
  if (!activeToolNames.delete(toolName)) continue;
81
94
  changed = true;
@@ -87,7 +100,8 @@ export function syncRegisteredNativeCursorToolsForModel(
87
100
  pi: CursorNativeToolActivationApi,
88
101
  model: ExtensionContext["model"],
89
102
  ): void {
90
- if (registeredNativeToolNames.size === 0) return;
103
+ const { registeredNativeToolSources } = getCursorNativeToolDisplayState(pi);
104
+ if (registeredNativeToolSources.size === 0) return;
91
105
  if (!isCursorModel(model)) {
92
106
  removeRegisteredNonCoreNativeCursorTools(pi);
93
107
  return;
@@ -95,7 +109,7 @@ export function syncRegisteredNativeCursorToolsForModel(
95
109
  if (arePiToolsDisabled(pi)) return;
96
110
  const activeToolNames = new Set(pi.getActiveTools());
97
111
  let changed = false;
98
- for (const toolName of registeredNativeToolNames) {
112
+ for (const toolName of registeredNativeToolSources.keys()) {
99
113
  if (isCursorReplayToolName(toolName) && !CURSOR_MODEL_ACTIVE_REPLAY_TOOL_NAMES.some((activeReplayToolName) => activeReplayToolName === toolName)) continue;
100
114
  if (activeToolNames.has(toolName)) continue;
101
115
  activeToolNames.add(toolName);
@@ -104,31 +118,41 @@ export function syncRegisteredNativeCursorToolsForModel(
104
118
  if (changed) pi.setActiveTools([...activeToolNames]);
105
119
  }
106
120
 
107
- function ensureNativeCursorToolsRegisteredForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext): void {
108
- if (!isCursorModel(ctx.model) || hasAttemptedNativeCursorToolRegistration()) return;
121
+ function ensureNativeCursorToolsRegisteredForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext, getCwd: () => string): void {
122
+ if (!isCursorModel(ctx.model)) return;
109
123
 
110
124
  const nonCoreToolNames = NATIVE_CURSOR_TOOL_NAMES.filter((toolName) => !isCursorCorePiReplayToolName(toolName));
111
125
  const skippedToolNames = [
112
- ...registerNativeCursorToolsFromSet(pi, nonCoreToolNames),
113
- ...registerNativeCursorToolsFromSet(pi, CURSOR_CORE_PI_REPLAY_TOOL_NAMES),
126
+ ...registerNativeCursorToolsFromSet(pi, nonCoreToolNames, getCwd),
127
+ ...registerNativeCursorToolsFromSet(pi, CURSOR_CORE_PI_REPLAY_TOOL_NAMES, getCwd),
114
128
  ];
115
129
  notifySkippedNativeCursorToolsIfNeeded(ctx, skippedToolNames);
116
130
  }
117
131
 
118
- function ensureThenSyncNativeCursorToolsForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext): void {
132
+ function ensureThenSyncNativeCursorToolsForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext, getCwd: () => string): void {
133
+ const { registeredNativeToolSources } = getCursorNativeToolDisplayState(pi);
134
+ const currentTools = pi.getAllTools();
135
+ for (const [toolName, sourceInfo] of registeredNativeToolSources) {
136
+ if (!currentTools.some((tool) => tool.name === toolName && tool.sourceInfo === sourceInfo)) {
137
+ registeredNativeToolSources.delete(toolName);
138
+ }
139
+ }
119
140
  const requested = isCursorNativeToolRegistrationRequested(ctx.mode);
120
- setCursorNativeToolDisplayRuntimeRequested(requested);
141
+ setCursorNativeToolDisplayRuntimeRequested(requested, pi);
121
142
  if (!requested) {
122
143
  removeRegisteredNonCoreNativeCursorTools(pi);
123
144
  return;
124
145
  }
125
- ensureNativeCursorToolsRegisteredForModel(pi, ctx);
146
+ ensureNativeCursorToolsRegisteredForModel(pi, ctx, getCwd);
126
147
  syncRegisteredNativeCursorToolsForModel(pi, ctx.model);
127
148
  }
128
149
 
129
150
  export function registerCursorNativeToolDisplay(pi: CursorNativeToolDisplayExtensionApi): void {
151
+ registerCursorNativeToolDisplayState(pi);
152
+ let cwd = process.cwd();
130
153
  registerCursorModelLifecycle(pi, (ctx) => {
131
- ensureThenSyncNativeCursorToolsForModel(pi, ctx);
154
+ cwd = ctx.cwd;
155
+ ensureThenSyncNativeCursorToolsForModel(pi, ctx, () => cwd);
132
156
  });
133
157
  }
134
158