pi-cursor-sdk 0.5.2 → 0.5.3

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 (47) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +38 -17
  3. package/dist/context.js +2 -2
  4. package/dist/cursor-custom-subagent-definitions.js +1 -1
  5. package/dist/cursor-fallback-models.generated.js +402 -1
  6. package/dist/cursor-local-agent-ownership.js +17 -0
  7. package/dist/cursor-mcp-timeout-override.js +1 -1
  8. package/dist/cursor-pi-tool-bridge-diagnostics.js +4 -0
  9. package/dist/cursor-pi-tool-bridge-run.js +10 -3
  10. package/dist/cursor-provider-errors.js +2 -2
  11. package/dist/cursor-provider-turn-summary.js +1 -1
  12. package/dist/cursor-session-agent-cleanup.js +10 -1
  13. package/dist/cursor-session-agent-lifecycle.js +2 -2
  14. package/dist/cursor-session-agent-lineage.js +11 -7
  15. package/dist/cursor-session-agent-resume.js +15 -9
  16. package/dist/cursor-session-agent.js +22 -7
  17. package/dist/cursor-session-scope.js +27 -14
  18. package/dist/cursor-session-settings.js +3 -3
  19. package/dist/cursor-session-store.js +1 -1
  20. package/dist/cursor-state.js +17 -6
  21. package/dist/model-discovery.js +8 -9
  22. package/docs/cursor-live-smoke-checklist.md +2 -2
  23. package/docs/cursor-model-ux-spec.md +23 -14
  24. package/docs/cursor-native-tool-replay.md +6 -3
  25. package/docs/cursor-native-tool-visual-audit.md +1 -1
  26. package/docs/cursor-testing-lessons.md +24 -6
  27. package/docs/cursor-tool-surfaces.md +16 -1
  28. package/package.json +8 -8
  29. package/src/context.ts +2 -2
  30. package/src/cursor-custom-subagent-definitions.ts +1 -1
  31. package/src/cursor-fallback-models.generated.ts +402 -1
  32. package/src/cursor-local-agent-ownership.ts +18 -0
  33. package/src/cursor-mcp-timeout-override.ts +1 -1
  34. package/src/cursor-pi-tool-bridge-diagnostics.ts +17 -0
  35. package/src/cursor-pi-tool-bridge-run.ts +10 -3
  36. package/src/cursor-provider-errors.ts +2 -2
  37. package/src/cursor-provider-turn-summary.ts +1 -1
  38. package/src/cursor-session-agent-cleanup.ts +6 -1
  39. package/src/cursor-session-agent-lifecycle.ts +2 -2
  40. package/src/cursor-session-agent-lineage.ts +10 -5
  41. package/src/cursor-session-agent-resume.ts +14 -7
  42. package/src/cursor-session-agent.ts +23 -7
  43. package/src/cursor-session-scope.ts +33 -11
  44. package/src/cursor-session-settings.ts +3 -3
  45. package/src/cursor-session-store.ts +1 -1
  46. package/src/cursor-state.ts +25 -5
  47. package/src/model-discovery.ts +7 -8
@@ -1,4 +1,4 @@
1
- // Generated with @cursor/sdk@1.0.36 from 45 Cursor models.
1
+ // Generated with @cursor/sdk@1.0.37 from 46 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 = [
@@ -911,6 +911,407 @@ export const FALLBACK_MODEL_ITEMS = [
911
911
  }
912
912
  ]
913
913
  },
914
+ {
915
+ id: "claude-haiku-5-5",
916
+ displayName: "Claude Haiku 5.5",
917
+ parameters: [
918
+ {
919
+ id: "thinking",
920
+ displayName: "Thinking",
921
+ values: [
922
+ {
923
+ value: "false"
924
+ },
925
+ {
926
+ value: "true"
927
+ }
928
+ ]
929
+ },
930
+ {
931
+ id: "context",
932
+ displayName: "Context",
933
+ values: [
934
+ {
935
+ value: "300k",
936
+ displayName: "300K"
937
+ },
938
+ {
939
+ value: "1m",
940
+ displayName: "1M"
941
+ }
942
+ ]
943
+ },
944
+ {
945
+ id: "reasoning_effort",
946
+ displayName: "Effort",
947
+ values: [
948
+ {
949
+ value: "low",
950
+ displayName: "Low"
951
+ },
952
+ {
953
+ value: "medium",
954
+ displayName: "Medium"
955
+ },
956
+ {
957
+ value: "high",
958
+ displayName: "High"
959
+ },
960
+ {
961
+ value: "xhigh",
962
+ displayName: "Extra High"
963
+ },
964
+ {
965
+ value: "max",
966
+ displayName: "Max"
967
+ }
968
+ ]
969
+ }
970
+ ],
971
+ variants: [
972
+ {
973
+ params: [
974
+ {
975
+ id: "thinking",
976
+ value: "false"
977
+ },
978
+ {
979
+ id: "context",
980
+ value: "300k"
981
+ },
982
+ {
983
+ id: "reasoning_effort",
984
+ value: "low"
985
+ }
986
+ ],
987
+ displayName: "Claude Haiku 5.5 Low No Thinking"
988
+ },
989
+ {
990
+ params: [
991
+ {
992
+ id: "thinking",
993
+ value: "false"
994
+ },
995
+ {
996
+ id: "context",
997
+ value: "300k"
998
+ },
999
+ {
1000
+ id: "reasoning_effort",
1001
+ value: "medium"
1002
+ }
1003
+ ],
1004
+ displayName: "Claude Haiku 5.5 Medium No Thinking"
1005
+ },
1006
+ {
1007
+ params: [
1008
+ {
1009
+ id: "thinking",
1010
+ value: "false"
1011
+ },
1012
+ {
1013
+ id: "context",
1014
+ value: "300k"
1015
+ },
1016
+ {
1017
+ id: "reasoning_effort",
1018
+ value: "high"
1019
+ }
1020
+ ],
1021
+ displayName: "Claude Haiku 5.5 High No Thinking"
1022
+ },
1023
+ {
1024
+ params: [
1025
+ {
1026
+ id: "thinking",
1027
+ value: "false"
1028
+ },
1029
+ {
1030
+ id: "context",
1031
+ value: "300k"
1032
+ },
1033
+ {
1034
+ id: "reasoning_effort",
1035
+ value: "xhigh"
1036
+ }
1037
+ ],
1038
+ displayName: "Claude Haiku 5.5 Extra High No Thinking"
1039
+ },
1040
+ {
1041
+ params: [
1042
+ {
1043
+ id: "thinking",
1044
+ value: "false"
1045
+ },
1046
+ {
1047
+ id: "context",
1048
+ value: "300k"
1049
+ },
1050
+ {
1051
+ id: "reasoning_effort",
1052
+ value: "max"
1053
+ }
1054
+ ],
1055
+ displayName: "Claude Haiku 5.5 Max No Thinking"
1056
+ },
1057
+ {
1058
+ params: [
1059
+ {
1060
+ id: "thinking",
1061
+ value: "false"
1062
+ },
1063
+ {
1064
+ id: "context",
1065
+ value: "1m"
1066
+ },
1067
+ {
1068
+ id: "reasoning_effort",
1069
+ value: "low"
1070
+ }
1071
+ ],
1072
+ displayName: "Claude Haiku 5.5 1M Low No Thinking"
1073
+ },
1074
+ {
1075
+ params: [
1076
+ {
1077
+ id: "thinking",
1078
+ value: "false"
1079
+ },
1080
+ {
1081
+ id: "context",
1082
+ value: "1m"
1083
+ },
1084
+ {
1085
+ id: "reasoning_effort",
1086
+ value: "medium"
1087
+ }
1088
+ ],
1089
+ displayName: "Claude Haiku 5.5 1M Medium No Thinking"
1090
+ },
1091
+ {
1092
+ params: [
1093
+ {
1094
+ id: "thinking",
1095
+ value: "false"
1096
+ },
1097
+ {
1098
+ id: "context",
1099
+ value: "1m"
1100
+ },
1101
+ {
1102
+ id: "reasoning_effort",
1103
+ value: "high"
1104
+ }
1105
+ ],
1106
+ displayName: "Claude Haiku 5.5 1M High No Thinking"
1107
+ },
1108
+ {
1109
+ params: [
1110
+ {
1111
+ id: "thinking",
1112
+ value: "false"
1113
+ },
1114
+ {
1115
+ id: "context",
1116
+ value: "1m"
1117
+ },
1118
+ {
1119
+ id: "reasoning_effort",
1120
+ value: "xhigh"
1121
+ }
1122
+ ],
1123
+ displayName: "Claude Haiku 5.5 1M Extra High No Thinking"
1124
+ },
1125
+ {
1126
+ params: [
1127
+ {
1128
+ id: "thinking",
1129
+ value: "false"
1130
+ },
1131
+ {
1132
+ id: "context",
1133
+ value: "1m"
1134
+ },
1135
+ {
1136
+ id: "reasoning_effort",
1137
+ value: "max"
1138
+ }
1139
+ ],
1140
+ displayName: "Claude Haiku 5.5 1M Max No Thinking"
1141
+ },
1142
+ {
1143
+ params: [
1144
+ {
1145
+ id: "thinking",
1146
+ value: "true"
1147
+ },
1148
+ {
1149
+ id: "context",
1150
+ value: "300k"
1151
+ },
1152
+ {
1153
+ id: "reasoning_effort",
1154
+ value: "low"
1155
+ }
1156
+ ],
1157
+ displayName: "Claude Haiku 5.5 Low"
1158
+ },
1159
+ {
1160
+ params: [
1161
+ {
1162
+ id: "thinking",
1163
+ value: "true"
1164
+ },
1165
+ {
1166
+ id: "context",
1167
+ value: "300k"
1168
+ },
1169
+ {
1170
+ id: "reasoning_effort",
1171
+ value: "medium"
1172
+ }
1173
+ ],
1174
+ displayName: "Claude Haiku 5.5 Medium"
1175
+ },
1176
+ {
1177
+ params: [
1178
+ {
1179
+ id: "thinking",
1180
+ value: "true"
1181
+ },
1182
+ {
1183
+ id: "context",
1184
+ value: "300k"
1185
+ },
1186
+ {
1187
+ id: "reasoning_effort",
1188
+ value: "high"
1189
+ }
1190
+ ],
1191
+ displayName: "Claude Haiku 5.5 High"
1192
+ },
1193
+ {
1194
+ params: [
1195
+ {
1196
+ id: "thinking",
1197
+ value: "true"
1198
+ },
1199
+ {
1200
+ id: "context",
1201
+ value: "300k"
1202
+ },
1203
+ {
1204
+ id: "reasoning_effort",
1205
+ value: "xhigh"
1206
+ }
1207
+ ],
1208
+ displayName: "Claude Haiku 5.5 Extra High"
1209
+ },
1210
+ {
1211
+ params: [
1212
+ {
1213
+ id: "thinking",
1214
+ value: "true"
1215
+ },
1216
+ {
1217
+ id: "context",
1218
+ value: "300k"
1219
+ },
1220
+ {
1221
+ id: "reasoning_effort",
1222
+ value: "max"
1223
+ }
1224
+ ],
1225
+ displayName: "Claude Haiku 5.5 Max"
1226
+ },
1227
+ {
1228
+ params: [
1229
+ {
1230
+ id: "thinking",
1231
+ value: "true"
1232
+ },
1233
+ {
1234
+ id: "context",
1235
+ value: "1m"
1236
+ },
1237
+ {
1238
+ id: "reasoning_effort",
1239
+ value: "low"
1240
+ }
1241
+ ],
1242
+ displayName: "Claude Haiku 5.5 1M Low"
1243
+ },
1244
+ {
1245
+ params: [
1246
+ {
1247
+ id: "thinking",
1248
+ value: "true"
1249
+ },
1250
+ {
1251
+ id: "context",
1252
+ value: "1m"
1253
+ },
1254
+ {
1255
+ id: "reasoning_effort",
1256
+ value: "medium"
1257
+ }
1258
+ ],
1259
+ displayName: "Claude Haiku 5.5 1M Medium"
1260
+ },
1261
+ {
1262
+ params: [
1263
+ {
1264
+ id: "thinking",
1265
+ value: "true"
1266
+ },
1267
+ {
1268
+ id: "context",
1269
+ value: "1m"
1270
+ },
1271
+ {
1272
+ id: "reasoning_effort",
1273
+ value: "high"
1274
+ }
1275
+ ],
1276
+ displayName: "Claude Haiku 5.5 1M High",
1277
+ isDefault: true
1278
+ },
1279
+ {
1280
+ params: [
1281
+ {
1282
+ id: "thinking",
1283
+ value: "true"
1284
+ },
1285
+ {
1286
+ id: "context",
1287
+ value: "1m"
1288
+ },
1289
+ {
1290
+ id: "reasoning_effort",
1291
+ value: "xhigh"
1292
+ }
1293
+ ],
1294
+ displayName: "Claude Haiku 5.5 1M Extra High"
1295
+ },
1296
+ {
1297
+ params: [
1298
+ {
1299
+ id: "thinking",
1300
+ value: "true"
1301
+ },
1302
+ {
1303
+ id: "context",
1304
+ value: "1m"
1305
+ },
1306
+ {
1307
+ id: "reasoning_effort",
1308
+ value: "max"
1309
+ }
1310
+ ],
1311
+ displayName: "Claude Haiku 5.5 1M Max"
1312
+ }
1313
+ ]
1314
+ },
914
1315
  {
915
1316
  id: "claude-opus-4-5",
916
1317
  displayName: "Claude Opus 4.5",
@@ -0,0 +1,17 @@
1
+ // ponytail: process-local claims protect extension-owned handles and cleanup.
2
+ // Cross-process SDK ownership requires an upstream store/agent lease contract.
3
+ // Unproven disposal retains the exact handle claim until process exit.
4
+ const claims = new Set();
5
+ export function claimCursorLocalAgent(identity, agentId) {
6
+ const key = JSON.stringify([identity.stateRoot, agentId]);
7
+ if (claims.has(key))
8
+ return undefined;
9
+ claims.add(key);
10
+ let released = false;
11
+ return () => {
12
+ if (released)
13
+ return;
14
+ released = true;
15
+ claims.delete(key);
16
+ };
17
+ }
@@ -60,7 +60,7 @@ export function isCursorSdkMcpToolTimeoutStack(stack) {
60
60
  export function isCursorSdkMcpConnectTimeoutStack(stack) {
61
61
  if (!stack || !isCursorSdkMcpProtocolTimeoutStack(stack))
62
62
  return false;
63
- // SDK 1.0.36 minifies the Client class name; the protocol method and SDK
63
+ // The SDK minifies the Client class name; the protocol method and SDK
64
64
  // frame, not that constructor's spelling, establish this timeout purpose.
65
65
  return /^\s*at (?:[\w$]+\.)?(?:connect|listTools|getTools)\s*\(.*(?:node_modules[/\\]@cursor[/\\]sdk|@cursor\/sdk\/dist)/m.test(stack);
66
66
  }
@@ -15,6 +15,10 @@ function assertNeverDiagnosticEvent(_event) {
15
15
  }
16
16
  export function serializeCursorPiToolBridgeDiagnostic(event) {
17
17
  switch (event.event) {
18
+ case "mcp_initialized":
19
+ return { event: event.event, runId: event.runId };
20
+ case "mcp_tools_list":
21
+ return { event: event.event, runId: event.runId, toolCount: event.toolCount };
18
22
  case "run_created":
19
23
  return {
20
24
  event: event.event,
@@ -208,9 +208,16 @@ export class CursorPiToolBridgeRunImpl {
208
208
  const transport = new WebStandardStreamableHTTPServerTransport({
209
209
  sessionIdGenerator: randomUUID,
210
210
  });
211
- server.setRequestHandler("tools/list", async () => ({
212
- tools: this.snapshot.tools.map(snapshotToolToMcpTool),
213
- }));
211
+ // ponytail: server-side receipts only, not SDK/model consumption or ongoing
212
+ // connection health; upgrade when Cursor exposes public MCP lifecycle callbacks.
213
+ server.oninitialized = () => {
214
+ this.emitDiagnostic({ event: "mcp_initialized", runId: this.id });
215
+ };
216
+ server.setRequestHandler("tools/list", async () => {
217
+ const tools = this.snapshot.tools.map(snapshotToolToMcpTool);
218
+ this.emitDiagnostic({ event: "mcp_tools_list", runId: this.id, toolCount: tools.length });
219
+ return { tools };
220
+ });
214
221
  server.setRequestHandler("tools/call", async (request, context) => {
215
222
  return this.enqueueToolRequest(request.params.name, request.params.arguments, String(context.mcpReq.id), context.mcpReq.signal);
216
223
  });
@@ -140,7 +140,7 @@ function isCursorSdkStallAbortNetworkError(code, evidence, stackEvidence) {
140
140
  /\b(?:onStall|reportStall|StallDetected)\b/i.test(stackEvidence));
141
141
  }
142
142
  /**
143
- * @cursor/sdk@1.0.36 throws internal RetriableError (name/kind "RetriableError") with message
143
+ * @cursor/sdk@1.0.37 throws internal RetriableError (name/kind "RetriableError") with message
144
144
  * "Connection stalled" or "Connection stalled repeatedly" after fetchWithRetry exhausts stalls.
145
145
  * The ConnectError is only the cause; the top-level error is not a ConnectError.
146
146
  */
@@ -192,7 +192,7 @@ export function classifyCursorConnectError(error) {
192
192
  }
193
193
  const directAbort = (code === 1 || code === "canceled") &&
194
194
  (causeName === "AbortError" || /AbortError/.test(stack));
195
- // The vendored 1.0.36 transport wraps a canceled ConnectError as Unknown.
195
+ // The vendored SDK transport can wrap a canceled ConnectError as Unknown.
196
196
  // Require the observed canceled -> AbortError cause chain, not generic text.
197
197
  const wrappedAbort = (code === 2 || code === "unknown") &&
198
198
  causeName === "ConnectError" &&
@@ -33,7 +33,7 @@ export async function prepareCursorSummaryProviderTurn(prepareParams, selection)
33
33
  configureCursorSdkHttp1(sdk, resolvedConfig.local.useHttp1ForAgent);
34
34
  restoreCursorSdkOutputFilter = installCursorSdkOutputFilter();
35
35
  sessionStore = (await openCursorSessionStoreForScope({
36
- cwd, scopeKey: params.scope.scopeKey, persistent: false,
36
+ cwd, scopeKey: params.scope.persistentScopeKey ?? params.scope.scopeKey, persistent: false,
37
37
  })).sessionStore;
38
38
  throwIfAborted();
39
39
  agent = await suppressCursorSdkOutput(() => sdk.Agent.create({
@@ -3,6 +3,7 @@ import { fsyncExistingRegularFile } from "./cursor-durable-fs.js";
3
3
  import { scrubSensitiveText } from "./cursor-sensitive-text.js";
4
4
  import { loadCursorSdk } from "./cursor-sdk-runtime.js";
5
5
  import { cursorSessionScopeKeyForManager } from "./cursor-session-scope.js";
6
+ import { claimCursorLocalAgent } from "./cursor-local-agent-ownership.js";
6
7
  import { CURSOR_SESSION_AGENT_RESUME_ENTRY_TYPE, isCursorLocalAgentId, parseCursorSessionAgentResumeEntryData, readResumableCursorSessionAgentIds, resolveCursorSessionRepoRoot, } from "./cursor-session-agent-resume.js";
7
8
  import { withCursorSessionStoreIdentities, openCursorSessionStore, resolveCursorSessionStoreIdentity, } from "./cursor-session-store.js";
8
9
  export const CURSOR_SESSION_AGENT_CLEANUP_ENTRY_TYPE = "cursor-sdk-agent-cleanup";
@@ -260,7 +261,15 @@ export async function runCursorSessionAgentCleanupCommand(pi, args, ctx, storeRo
260
261
  openedStore = await openCursorSessionStore(ctx.cwd, identity, identities.workspaceRoot, identities.storeRoot);
261
262
  openedStores.set(identity.stateRoot, openedStore);
262
263
  }
263
- await operations.delete(agentId, { cwd: ctx.cwd, store: openedStore.store });
264
+ const release = claimCursorLocalAgent(identity, agentId);
265
+ if (!release)
266
+ throw new Error("Cursor local agent is still owned by an active runtime");
267
+ try {
268
+ await operations.delete(agentId, { cwd: ctx.cwd, store: openedStore.store });
269
+ }
270
+ finally {
271
+ release();
272
+ }
264
273
  deletedAgentIds.push(agentId);
265
274
  }
266
275
  catch (error) {
@@ -1,9 +1,9 @@
1
1
  import { clearCursorSdkHttp1 } from "./cursor-http1.js";
2
- import { cursorSessionScopeKeyForManager, hasLiveCursorSessions, onCursorSessionScopeKeyChange } from "./cursor-session-scope.js";
2
+ import { cursorRuntimeSessionScopeKeyForManager, hasLiveCursorSessions, onCursorSessionScopeKeyChange } from "./cursor-session-scope.js";
3
3
  import { disposeSessionCursorAgent, invalidateSessionAgent, resetSessionCursorAgent, } from "./cursor-session-agent.js";
4
4
  export function registerCursorSessionAgentLifecycle(pi) {
5
5
  let scopeKey;
6
- pi.on("session_start", (_event, ctx) => { scopeKey = cursorSessionScopeKeyForManager(ctx.sessionManager); });
6
+ pi.on("session_start", (_event, ctx) => { scopeKey = cursorRuntimeSessionScopeKeyForManager(ctx.sessionManager); });
7
7
  onCursorSessionScopeKeyChange(pi, async (previousScopeKey) => {
8
8
  await disposeSessionCursorAgent(previousScopeKey);
9
9
  });
@@ -52,7 +52,7 @@ function readRecordedAgentIds(entries, sessionId) {
52
52
  export function recordCursorSessionAgentLineage(agentId, turnScopeKey) {
53
53
  const sessionState = turnScopeKey === undefined
54
54
  ? state
55
- : statesByScope.get(turnScopeKey) ?? (state.scopeKey === turnScopeKey ? state : undefined);
55
+ : statesByScope.get(turnScopeKey) ?? ((state.runtimeScopeKey ?? state.scopeKey) === turnScopeKey ? state : undefined);
56
56
  if (!sessionState)
57
57
  return;
58
58
  const { appendEntry, sessionId, sessionFile, scopeKey, cwd } = sessionState;
@@ -83,24 +83,27 @@ export function registerCursorSessionAgentLineage(pi) {
83
83
  pi.on("session_start", (_event, ctx) => {
84
84
  state = sessionState;
85
85
  const record = sessionState;
86
- if (record.scopeKey && statesByScope.get(record.scopeKey) === record)
87
- statesByScope.delete(record.scopeKey);
86
+ if (record.runtimeScopeKey && statesByScope.get(record.runtimeScopeKey) === record)
87
+ statesByScope.delete(record.runtimeScopeKey);
88
88
  record.appendEntry = pi.appendEntry;
89
89
  record.sessionId = ctx.sessionManager.getSessionId();
90
90
  record.sessionFile = ctx.sessionManager.getSessionFile() ?? undefined;
91
- record.scopeKey = getCursorSessionScopeSnapshot(pi).scopeKey;
92
- statesByScope.set(record.scopeKey, record);
91
+ const scope = getCursorSessionScopeSnapshot(pi);
92
+ record.runtimeScopeKey = scope.scopeKey;
93
+ record.scopeKey = scope.persistentScopeKey ?? scope.scopeKey;
94
+ statesByScope.set(scope.scopeKey, record);
93
95
  record.cwd = ctx.cwd;
94
96
  record.recordedAgentIds = readRecordedAgentIds(ctx.sessionManager.getEntries(), record.sessionId);
95
97
  });
96
98
  pi.on("session_shutdown", () => {
97
99
  const state = sessionState;
98
- if (state.scopeKey && statesByScope.get(state.scopeKey) === state)
99
- statesByScope.delete(state.scopeKey);
100
+ if (state.runtimeScopeKey && statesByScope.get(state.runtimeScopeKey) === state)
101
+ statesByScope.delete(state.runtimeScopeKey);
100
102
  state.appendEntry = undefined;
101
103
  state.sessionId = undefined;
102
104
  state.sessionFile = undefined;
103
105
  state.scopeKey = undefined;
106
+ state.runtimeScopeKey = undefined;
104
107
  state.cwd = undefined;
105
108
  state.recordedAgentIds = new Set();
106
109
  });
@@ -111,6 +114,7 @@ function resetStateForTests() {
111
114
  state.sessionId = undefined;
112
115
  state.sessionFile = undefined;
113
116
  state.scopeKey = undefined;
117
+ state.runtimeScopeKey = undefined;
114
118
  state.cwd = undefined;
115
119
  state.recordedAgentIds = new Set();
116
120
  }
@@ -266,7 +266,7 @@ function restoreFromBranch(branch, allEntries, state) {
266
266
  }
267
267
  export function getMatchingCursorSessionAgentResumeHandle(poolKey, scopeKey) {
268
268
  const state = getResumeState(scopeKey);
269
- if (scopeKey !== undefined && state.scopeKey !== scopeKey)
269
+ if (scopeKey !== undefined && (state.runtimeScopeKey ?? state.scopeKey) !== scopeKey)
270
270
  return undefined;
271
271
  const handle = state.activeHandle;
272
272
  if (!handle || !isCursorLocalAgentId(handle.agentId))
@@ -292,7 +292,7 @@ export function getMatchingCursorSessionAgentResumeHandle(poolKey, scopeKey) {
292
292
  }
293
293
  export function persistCursorSessionAgentResumeHandle(input, scopeKey) {
294
294
  const state = getResumeState(scopeKey);
295
- if (scopeKey !== undefined && state.scopeKey !== scopeKey)
295
+ if (scopeKey !== undefined && (state.runtimeScopeKey ?? state.scopeKey) !== scopeKey)
296
296
  return;
297
297
  if (!isCursorLocalAgentId(input.agentId))
298
298
  return;
@@ -343,8 +343,9 @@ function flushPendingCursorSessionAgentResumeHandle(branch, state) {
343
343
  }
344
344
  }
345
345
  export function registerCursorSessionAgentResume(pi) {
346
+ const scope = getCursorSessionScopeSnapshot(pi);
346
347
  const sessionState = {
347
- appendEntry: pi.appendEntry, scopeKey: getCursorSessionScopeSnapshot(pi).scopeKey,
348
+ appendEntry: pi.appendEntry, scopeKey: scope.persistentScopeKey ?? scope.scopeKey,
348
349
  cwd: process.cwd(), branchPathHash: EMPTY_BRANCH_HASH,
349
350
  compactionGeneration: 0, unownedUserEntryIds: new Set(),
350
351
  };
@@ -355,10 +356,13 @@ export function registerCursorSessionAgentResume(pi) {
355
356
  restoreFromBranch(branch, entries.length > 0 ? entries : branch, state);
356
357
  };
357
358
  pi.on("session_start", (_event, ctx) => {
358
- if (statesByScope.get(state.scopeKey) === state)
359
- statesByScope.delete(state.scopeKey);
360
- state.scopeKey = getCursorSessionScopeSnapshot(pi).scopeKey;
361
- statesByScope.set(state.scopeKey, state);
359
+ const key = state.runtimeScopeKey ?? state.scopeKey;
360
+ if (statesByScope.get(key) === state)
361
+ statesByScope.delete(key);
362
+ const scope = getCursorSessionScopeSnapshot(pi);
363
+ state.runtimeScopeKey = scope.scopeKey;
364
+ state.scopeKey = scope.persistentScopeKey ?? scope.scopeKey;
365
+ statesByScope.set(scope.scopeKey, state);
362
366
  lastBoundState = state;
363
367
  state.sessionFile = ctx.sessionManager.getSessionFile?.() ?? undefined;
364
368
  state.sessionId = ctx.sessionManager.getSessionId?.() ?? undefined;
@@ -399,8 +403,9 @@ export function registerCursorSessionAgentResume(pi) {
399
403
  state.branchPathHash = hashBranchStep(state.branchPathHash, event.compactionEntry);
400
404
  });
401
405
  pi.on("session_shutdown", () => {
402
- if (statesByScope.get(state.scopeKey) === state)
403
- statesByScope.delete(state.scopeKey);
406
+ const key = state.runtimeScopeKey ?? state.scopeKey;
407
+ if (statesByScope.get(key) === state)
408
+ statesByScope.delete(key);
404
409
  state.appendEntry = undefined;
405
410
  state.pendingHandle = undefined;
406
411
  });
@@ -412,6 +417,7 @@ function resetStateForTests() {
412
417
  const state = lastBoundState;
413
418
  state.appendEntry = undefined;
414
419
  state.scopeKey = getCursorSessionScopeKey();
420
+ state.runtimeScopeKey = undefined;
415
421
  state.sessionFile = undefined;
416
422
  state.sessionId = undefined;
417
423
  state.cwd = process.cwd();