pi-cursor-sdk 0.1.62 → 0.3.6

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 (279) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +57 -53
  3. package/dist/bundled-context-windows.js +39 -0
  4. package/dist/context-window-cache.js +92 -0
  5. package/dist/context.js +338 -0
  6. package/dist/cursor-active-tools.js +3 -0
  7. package/dist/cursor-agent-message-web-tools.js +165 -0
  8. package/dist/cursor-agents-context-registration.js +17 -0
  9. package/dist/cursor-agents-context.js +113 -0
  10. package/dist/cursor-api-key.js +34 -0
  11. package/dist/cursor-bridge-contract.js +27 -0
  12. package/dist/cursor-cloud-lifecycle.js +650 -0
  13. package/dist/cursor-cloud-local-state.js +460 -0
  14. package/dist/cursor-cloud-options.js +145 -0
  15. package/dist/cursor-cloud-reporting.js +222 -0
  16. package/dist/cursor-compact-tool-summary.js +75 -0
  17. package/dist/cursor-config.js +613 -0
  18. package/dist/cursor-context-tools.js +4 -0
  19. package/dist/cursor-display-only-trace.js +11 -0
  20. package/dist/cursor-display-text.js +18 -0
  21. package/dist/cursor-durable-fs.js +53 -0
  22. package/dist/cursor-edit-diff.js +13 -0
  23. package/dist/cursor-env-boolean.js +19 -0
  24. package/dist/cursor-fallback-models.generated.js +7760 -0
  25. package/dist/cursor-fallback-warning.js +15 -0
  26. package/dist/cursor-http1.js +45 -0
  27. package/dist/cursor-incomplete-tool-visibility.js +83 -0
  28. package/dist/cursor-live-run-accounting.js +55 -0
  29. package/dist/cursor-live-run-coordinator.js +406 -0
  30. package/dist/cursor-mcp-timeout-override.js +116 -0
  31. package/dist/cursor-model-lifecycle.js +32 -0
  32. package/dist/cursor-model.js +5 -0
  33. package/dist/cursor-native-replay-routing.js +35 -0
  34. package/dist/cursor-native-replay-trace.js +28 -0
  35. package/dist/cursor-native-tool-display-registration.js +102 -0
  36. package/dist/cursor-native-tool-display-replay.js +428 -0
  37. package/dist/cursor-native-tool-display-state.js +70 -0
  38. package/dist/cursor-native-tool-display-tools.js +123 -0
  39. package/dist/cursor-native-tool-names.js +11 -0
  40. package/dist/cursor-partial-content-emitter.js +118 -0
  41. package/dist/cursor-pi-tool-bridge-abort.js +114 -0
  42. package/dist/cursor-pi-tool-bridge-constants.js +6 -0
  43. package/dist/cursor-pi-tool-bridge-diagnostics.js +125 -0
  44. package/dist/cursor-pi-tool-bridge-env.js +18 -0
  45. package/dist/cursor-pi-tool-bridge-mcp.js +100 -0
  46. package/dist/cursor-pi-tool-bridge-run.js +355 -0
  47. package/dist/cursor-pi-tool-bridge-server.js +163 -0
  48. package/dist/cursor-pi-tool-bridge-snapshot.js +63 -0
  49. package/dist/cursor-pi-tool-bridge-types.js +1 -0
  50. package/dist/cursor-pi-tool-bridge.js +123 -0
  51. package/dist/cursor-provider-errors.js +311 -0
  52. package/dist/cursor-provider-lazy.js +39 -0
  53. package/dist/cursor-provider-live-run-drain.js +412 -0
  54. package/dist/cursor-provider-overflow.js +90 -0
  55. package/dist/cursor-provider-run-finalizer.js +176 -0
  56. package/dist/cursor-provider-run-outcome.js +76 -0
  57. package/dist/cursor-provider-turn-coordinator.js +275 -0
  58. package/dist/cursor-provider-turn-display-router.js +158 -0
  59. package/dist/cursor-provider-turn-emit.js +35 -0
  60. package/dist/cursor-provider-turn-finalize.js +154 -0
  61. package/dist/cursor-provider-turn-lifecycle-emitter.js +126 -0
  62. package/dist/cursor-provider-turn-prepare.js +374 -0
  63. package/dist/cursor-provider-turn-runner.js +146 -0
  64. package/dist/cursor-provider-turn-sdk-normalizer.js +74 -0
  65. package/dist/cursor-provider-turn-send.js +138 -0
  66. package/dist/cursor-provider-turn-shell-output.js +117 -0
  67. package/dist/cursor-provider-turn-tool-ledger.js +99 -0
  68. package/dist/cursor-provider-turn-types.js +1 -0
  69. package/dist/cursor-provider.js +72 -0
  70. package/dist/cursor-question-tool.js +194 -0
  71. package/dist/cursor-record-utils.js +61 -0
  72. package/dist/cursor-replay-activity-builders.js +114 -0
  73. package/dist/cursor-replay-source-names.js +27 -0
  74. package/dist/cursor-replay-summary-args.js +105 -0
  75. package/dist/cursor-replay-tool-details.js +167 -0
  76. package/dist/cursor-ripgrep-path.js +27 -0
  77. package/dist/cursor-run-final-text.js +63 -0
  78. package/dist/cursor-runtime-state.js +380 -0
  79. package/dist/cursor-sdk-billed-usage.js +86 -0
  80. package/dist/cursor-sdk-event-debug-constants.js +33 -0
  81. package/dist/cursor-sdk-event-debug-session.js +107 -0
  82. package/dist/cursor-sdk-event-debug.js +461 -0
  83. package/dist/cursor-sdk-output-filter.js +1 -0
  84. package/dist/cursor-sdk-process-error-guard.js +221 -0
  85. package/dist/cursor-sdk-runtime.js +3 -0
  86. package/dist/cursor-sensitive-text.js +23 -0
  87. package/dist/cursor-session-agent-cleanup.js +311 -0
  88. package/dist/cursor-session-agent-lifecycle.js +32 -0
  89. package/dist/cursor-session-agent-lineage.js +103 -0
  90. package/dist/cursor-session-agent-resume.js +417 -0
  91. package/dist/cursor-session-agent.js +507 -0
  92. package/dist/cursor-session-compaction-prep.js +21 -0
  93. package/dist/cursor-session-scope.js +113 -0
  94. package/dist/cursor-session-send-policy.js +18 -0
  95. package/dist/cursor-session-store.js +105 -0
  96. package/dist/cursor-session-turn-queue.js +50 -0
  97. package/dist/cursor-setting-sources.js +15 -0
  98. package/dist/cursor-skill-tool.js +230 -0
  99. package/dist/cursor-state.js +519 -0
  100. package/dist/cursor-task-presentation.js +59 -0
  101. package/dist/cursor-tool-lifecycle.js +94 -0
  102. package/dist/cursor-tool-manifest.js +34 -0
  103. package/dist/cursor-tool-presentation-registry.js +305 -0
  104. package/dist/cursor-tool-result-display-readers.js +158 -0
  105. package/dist/cursor-tool-transcript.js +32 -0
  106. package/dist/cursor-tool-visibility.js +28 -0
  107. package/dist/cursor-transcript-tool-formatters.js +618 -0
  108. package/dist/cursor-transcript-tool-specs.js +284 -0
  109. package/dist/cursor-transcript-utils.js +170 -0
  110. package/dist/cursor-usage-accounting.js +166 -0
  111. package/dist/cursor-web-tool-activity.js +23 -0
  112. package/dist/cursor-web-tool-args.js +12 -0
  113. package/dist/index.js +82 -0
  114. package/dist/model-discovery.js +305 -0
  115. package/dist/model-list-cache.js +156 -0
  116. package/docs/cursor-dogfood-checklist.md +2 -1
  117. package/docs/cursor-live-smoke-checklist.md +15 -14
  118. package/docs/cursor-model-ux-spec.md +29 -8
  119. package/docs/cursor-native-tool-replay.md +13 -13
  120. package/docs/cursor-native-tool-visual-audit.md +6 -3
  121. package/docs/cursor-testing-lessons.md +7 -7
  122. package/docs/cursor-tool-surfaces.md +5 -5
  123. package/docs/platform-smoke.md +6 -6
  124. package/node_modules/fast-uri/index.js +37 -1
  125. package/node_modules/fast-uri/package.json +1 -1
  126. package/node_modules/fast-uri/test/security.test.js +136 -0
  127. package/node_modules/hono/dist/cjs/context.js +28 -13
  128. package/node_modules/hono/dist/cjs/helper/proxy/index.js +4 -0
  129. package/node_modules/hono/dist/cjs/helper/streaming/sse.js +1 -1
  130. package/node_modules/hono/dist/cjs/hono-base.js +3 -2
  131. package/node_modules/hono/dist/cjs/jsx/base.js +27 -23
  132. package/node_modules/hono/dist/cjs/jsx/hooks/index.js +16 -13
  133. package/node_modules/hono/dist/cjs/middleware/cache/index.js +103 -8
  134. package/node_modules/hono/dist/cjs/middleware/compress/index.js +5 -0
  135. package/node_modules/hono/dist/cjs/middleware/cors/index.js +2 -2
  136. package/node_modules/hono/dist/cjs/middleware/etag/index.js +1 -1
  137. package/node_modules/hono/dist/cjs/middleware/jwk/jwk.js +9 -4
  138. package/node_modules/hono/dist/cjs/middleware/jwt/jwt.js +9 -4
  139. package/node_modules/hono/dist/cjs/middleware/language/language.js +10 -6
  140. package/node_modules/hono/dist/cjs/middleware/method-not-allowed/index.js +90 -0
  141. package/node_modules/hono/dist/cjs/middleware/secure-headers/secure-headers.js +10 -4
  142. package/node_modules/hono/dist/cjs/request.js +7 -9
  143. package/node_modules/hono/dist/cjs/router/reg-exp-router/node.js +55 -56
  144. package/node_modules/hono/dist/cjs/router/reg-exp-router/router.js +64 -85
  145. package/node_modules/hono/dist/cjs/router/reg-exp-router/trie.js +13 -5
  146. package/node_modules/hono/dist/cjs/router.js +1 -1
  147. package/node_modules/hono/dist/cjs/utils/accept.js +1 -1
  148. package/node_modules/hono/dist/cjs/utils/cookie.js +3 -2
  149. package/node_modules/hono/dist/cjs/utils/url.js +8 -8
  150. package/node_modules/hono/dist/context.js +28 -13
  151. package/node_modules/hono/dist/helper/proxy/index.js +4 -0
  152. package/node_modules/hono/dist/helper/streaming/sse.js +1 -1
  153. package/node_modules/hono/dist/hono-base.js +3 -2
  154. package/node_modules/hono/dist/jsx/base.js +27 -23
  155. package/node_modules/hono/dist/jsx/hooks/index.js +16 -13
  156. package/node_modules/hono/dist/middleware/cache/index.js +103 -8
  157. package/node_modules/hono/dist/middleware/compress/index.js +5 -0
  158. package/node_modules/hono/dist/middleware/cors/index.js +2 -2
  159. package/node_modules/hono/dist/middleware/etag/index.js +1 -1
  160. package/node_modules/hono/dist/middleware/jwk/jwk.js +9 -4
  161. package/node_modules/hono/dist/middleware/jwt/jwt.js +9 -4
  162. package/node_modules/hono/dist/middleware/language/language.js +10 -6
  163. package/node_modules/hono/dist/middleware/method-not-allowed/index.js +68 -0
  164. package/node_modules/hono/dist/middleware/secure-headers/secure-headers.js +10 -4
  165. package/node_modules/hono/dist/request.js +8 -10
  166. package/node_modules/hono/dist/router/reg-exp-router/node.js +55 -56
  167. package/node_modules/hono/dist/router/reg-exp-router/router.js +64 -85
  168. package/node_modules/hono/dist/router/reg-exp-router/trie.js +13 -5
  169. package/node_modules/hono/dist/router.js +1 -1
  170. package/node_modules/hono/dist/types/adapter/aws-lambda/types.d.ts +9 -0
  171. package/node_modules/hono/dist/types/client/types.d.ts +1 -1
  172. package/node_modules/hono/dist/types/hono-base.d.ts +4 -3
  173. package/node_modules/hono/dist/types/jsx/base.d.ts +4 -2
  174. package/node_modules/hono/dist/types/jsx/dom/index.d.ts +5 -5
  175. package/node_modules/hono/dist/types/jsx/dom/intrinsic-element/components.d.ts +2 -2
  176. package/node_modules/hono/dist/types/jsx/dom/server.d.ts +5 -5
  177. package/node_modules/hono/dist/types/jsx/hooks/index.d.ts +8 -6
  178. package/node_modules/hono/dist/types/jsx/index.d.ts +5 -5
  179. package/node_modules/hono/dist/types/middleware/cache/index.d.ts +6 -4
  180. package/node_modules/hono/dist/types/middleware/cors/index.d.ts +1 -1
  181. package/node_modules/hono/dist/types/middleware/jsx-renderer/index.d.ts +2 -2
  182. package/node_modules/hono/dist/types/middleware/jwk/jwk.d.ts +2 -0
  183. package/node_modules/hono/dist/types/middleware/jwt/jwt.d.ts +2 -0
  184. package/node_modules/hono/dist/types/middleware/method-not-allowed/index.d.ts +49 -0
  185. package/node_modules/hono/dist/types/router/reg-exp-router/node.d.ts +1 -1
  186. package/node_modules/hono/dist/types/router/reg-exp-router/trie.d.ts +2 -1
  187. package/node_modules/hono/dist/types/router.d.ts +1 -1
  188. package/node_modules/hono/dist/types/utils/headers.d.ts +2 -2
  189. package/node_modules/hono/dist/types/utils/url.d.ts +1 -0
  190. package/node_modules/hono/dist/utils/accept.js +1 -1
  191. package/node_modules/hono/dist/utils/cookie.js +4 -3
  192. package/node_modules/hono/dist/utils/url.js +6 -7
  193. package/node_modules/hono/package.json +10 -3
  194. package/node_modules/ip-address/README.md +146 -126
  195. package/node_modules/ip-address/dist/common.d.ts +29 -0
  196. package/node_modules/ip-address/dist/common.js +44 -5
  197. package/node_modules/ip-address/dist/common.js.map +1 -1
  198. package/node_modules/ip-address/dist/ipv4.d.ts +8 -1
  199. package/node_modules/ip-address/dist/ipv4.js +24 -20
  200. package/node_modules/ip-address/dist/ipv4.js.map +1 -1
  201. package/node_modules/ip-address/dist/ipv6.d.ts +68 -3
  202. package/node_modules/ip-address/dist/ipv6.js +180 -53
  203. package/node_modules/ip-address/dist/ipv6.js.map +1 -1
  204. package/node_modules/ip-address/dist/v4/constants.js +5 -1
  205. package/node_modules/ip-address/dist/v4/constants.js.map +1 -1
  206. package/node_modules/ip-address/dist/v6/constants.js +3 -2
  207. package/node_modules/ip-address/dist/v6/constants.js.map +1 -1
  208. package/node_modules/ip-address/package.json +6 -4
  209. package/package.json +23 -11
  210. package/platform-smoke.config.mjs +1 -1
  211. package/scripts/build.mjs +126 -0
  212. package/scripts/cloud-runtime-smoke.mjs +3 -1
  213. package/scripts/debug-provider-events.mjs +3 -1
  214. package/scripts/debug-sdk-events.mjs +1 -1
  215. package/scripts/isolated-cursor-smoke.sh +5 -4
  216. package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +1 -1
  217. package/scripts/lib/cursor-visual-render.mjs +1 -1
  218. package/scripts/lib/ensure-built.mjs +16 -0
  219. package/scripts/lib/local-resume-smoke-harness.mjs +1 -1
  220. package/scripts/local-resume-smoke.mjs +3 -1
  221. package/scripts/platform-smoke/card-detect.mjs +1 -1
  222. package/scripts/platform-smoke/live-suite-runner.mjs +16 -40
  223. package/scripts/platform-smoke/platform-build-windows.ps1 +1 -1
  224. package/scripts/platform-smoke/render-ansi.mjs +1 -1
  225. package/scripts/platform-smoke/scenarios.mjs +1 -1
  226. package/scripts/platform-smoke/targets.mjs +2 -2
  227. package/scripts/platform-smoke.mjs +5 -5
  228. package/scripts/prepare.mjs +102 -0
  229. package/scripts/refresh-cursor-model-snapshots.mjs +16 -7
  230. package/scripts/steering-rpc-smoke.mjs +4 -2
  231. package/scripts/tmux-live-smoke.sh +11 -11
  232. package/scripts/visual-tui-smoke-self-test.mjs +4 -1
  233. package/scripts/visual-tui-smoke.mjs +51 -7
  234. package/shared/cursor-model-selection-identities.d.mts +21 -0
  235. package/shared/cursor-model-selection-identities.mjs +95 -0
  236. package/src/bundled-context-windows.ts +21 -12
  237. package/src/context.ts +1 -1
  238. package/src/cursor-agents-context-registration.ts +2 -2
  239. package/src/cursor-agents-context.ts +0 -2
  240. package/src/cursor-api-key.ts +5 -4
  241. package/src/cursor-cloud-reporting.ts +23 -2
  242. package/src/cursor-context-tools.ts +1 -1
  243. package/src/cursor-display-only-trace.ts +1 -1
  244. package/src/cursor-fallback-models.generated.ts +1726 -824
  245. package/src/cursor-live-run-accounting.ts +1 -1
  246. package/src/cursor-live-run-coordinator.ts +2 -1
  247. package/src/cursor-native-replay-routing.ts +1 -1
  248. package/src/cursor-native-tool-display-registration.ts +10 -11
  249. package/src/cursor-partial-content-emitter.ts +1 -1
  250. package/src/cursor-pi-tool-bridge-mcp.ts +1 -1
  251. package/src/cursor-pi-tool-bridge-run.ts +1 -1
  252. package/src/cursor-pi-tool-bridge-types.ts +1 -1
  253. package/src/cursor-provider-errors.ts +1 -1
  254. package/src/cursor-provider-lazy.ts +6 -5
  255. package/src/cursor-provider-live-run-drain.ts +2 -1
  256. package/src/cursor-provider-overflow.ts +1 -1
  257. package/src/cursor-provider-run-finalizer.ts +2 -1
  258. package/src/cursor-provider-turn-coordinator.ts +1 -1
  259. package/src/cursor-provider-turn-finalize.ts +12 -0
  260. package/src/cursor-provider-turn-lifecycle-emitter.ts +1 -1
  261. package/src/cursor-provider-turn-prepare.ts +1 -1
  262. package/src/cursor-provider-turn-types.ts +3 -1
  263. package/src/cursor-provider.ts +1 -1
  264. package/src/cursor-run-final-text.ts +1 -1
  265. package/src/cursor-sdk-billed-usage.ts +107 -0
  266. package/src/cursor-sdk-event-debug.ts +1 -1
  267. package/src/cursor-sdk-process-error-guard.ts +6 -4
  268. package/src/cursor-session-agent-lifecycle.ts +8 -9
  269. package/src/cursor-session-agent-resume.ts +23 -0
  270. package/src/cursor-session-agent.ts +1 -1
  271. package/src/cursor-session-compaction-prep.ts +4 -0
  272. package/src/cursor-session-send-policy.ts +1 -1
  273. package/src/cursor-tool-manifest.ts +1 -1
  274. package/src/cursor-usage-accounting.ts +71 -10
  275. package/src/index.ts +1 -1
  276. package/src/model-discovery.ts +32 -89
  277. package/tsconfig.build.json +8 -0
  278. package/tsconfig.json +16 -0
  279. package/node_modules/cross-spawn/node_modules/which/CHANGELOG.md +0 -166
@@ -1,4 +1,4 @@
1
- import type { Context, Message, ToolResultMessage } from "@earendil-works/pi-ai/compat";
1
+ import type { Context, Message, ToolResultMessage } from "@earendil-works/pi-ai";
2
2
  import { CURSOR_APPROX_CHARS_PER_TOKEN, estimateCursorPromptMessageTokens } from "./context.js";
3
3
  import type { CursorSdkTurnUsage } from "./cursor-usage-accounting.js";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { Context, ToolResultMessage } from "@earendil-works/pi-ai/compat";
1
+ import type { Context, ToolResultMessage } from "@earendil-works/pi-ai";
2
2
  import type { SDKAgent } from "@cursor/sdk";
3
3
  import {
4
4
  consumeCursorLiveToolResults,
@@ -43,6 +43,7 @@ export interface CursorLiveRun {
43
43
  sdkRun?: CursorLiveSdkRun;
44
44
  ignoreFutureSdkTurnUsage?: boolean;
45
45
  accounting: CursorLiveRunAccountingState;
46
+ billedTurnUsage?: CursorSdkTurnUsage;
46
47
  pendingEvents: CursorLiveQueuedEvent[];
47
48
  textDeltas: string[];
48
49
  emittedText: string;
@@ -1,6 +1,6 @@
1
1
  import { canRenderCursorToolNatively } from "./cursor-native-tool-display-state.js";
2
2
  import { getActiveContextToolNames } from "./cursor-context-tools.js";
3
- import type { Context } from "@earendil-works/pi-ai/compat";
3
+ import type { Context } from "@earendil-works/pi-ai";
4
4
 
5
5
  export type NativeReplayDisposition = "queue_replay" | "inactive_trace" | "transcript_trace";
6
6
 
@@ -18,6 +18,7 @@ import {
18
18
  skippedNativeToolNames,
19
19
  } from "./cursor-native-tool-display-state.js";
20
20
  import { isCursorReplayToolName } from "./cursor-tool-presentation-registry.js";
21
+ import { registerNativeCursorTool } from "./cursor-native-tool-display-tools.js";
21
22
 
22
23
  export const CURSOR_CORE_PI_REPLAY_TOOL_NAMES = ["read", "bash", "edit", "write"] as const;
23
24
  const CORE_PI_TOOL_NAMES = new Set<string>(CURSOR_CORE_PI_REPLAY_TOOL_NAMES);
@@ -40,12 +41,11 @@ type NativeRegistrationContext = Pick<ExtensionContext, "mode" | "model"> & {
40
41
  ui: Pick<ExtensionContext["ui"], "notify">;
41
42
  };
42
43
 
43
- async function registerNativeCursorToolsFromSet(
44
+ function registerNativeCursorToolsFromSet(
44
45
  pi: CursorNativeToolRegistryApi,
45
46
  toolNames: readonly NativeCursorToolName[],
46
- ): Promise<NativeCursorToolName[]> {
47
+ ): NativeCursorToolName[] {
47
48
  const newlySkippedToolNames: NativeCursorToolName[] = [];
48
- let registerNativeCursorTool: ((pi: CursorNativeToolRegistryApi, toolName: NativeCursorToolName) => void) | undefined;
49
49
  for (const toolName of toolNames) {
50
50
  if (registeredNativeToolNames.has(toolName) || skippedNativeToolNames.has(toolName)) continue;
51
51
  if (hasNonBuiltinTool(pi, toolName)) {
@@ -53,7 +53,6 @@ async function registerNativeCursorToolsFromSet(
53
53
  newlySkippedToolNames.push(toolName);
54
54
  continue;
55
55
  }
56
- registerNativeCursorTool ??= (await import("./cursor-native-tool-display-tools.js")).registerNativeCursorTool;
57
56
  registerNativeCursorTool(pi, toolName);
58
57
  registeredNativeToolNames.add(toolName);
59
58
  }
@@ -105,31 +104,31 @@ export function syncRegisteredNativeCursorToolsForModel(
105
104
  if (changed) pi.setActiveTools([...activeToolNames]);
106
105
  }
107
106
 
108
- async function ensureNativeCursorToolsRegisteredForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext): Promise<void> {
107
+ function ensureNativeCursorToolsRegisteredForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext): void {
109
108
  if (!isCursorModel(ctx.model) || hasAttemptedNativeCursorToolRegistration()) return;
110
109
 
111
110
  const nonCoreToolNames = NATIVE_CURSOR_TOOL_NAMES.filter((toolName) => !isCursorCorePiReplayToolName(toolName));
112
111
  const skippedToolNames = [
113
- ...(await registerNativeCursorToolsFromSet(pi, nonCoreToolNames)),
114
- ...(await registerNativeCursorToolsFromSet(pi, CURSOR_CORE_PI_REPLAY_TOOL_NAMES)),
112
+ ...registerNativeCursorToolsFromSet(pi, nonCoreToolNames),
113
+ ...registerNativeCursorToolsFromSet(pi, CURSOR_CORE_PI_REPLAY_TOOL_NAMES),
115
114
  ];
116
115
  notifySkippedNativeCursorToolsIfNeeded(ctx, skippedToolNames);
117
116
  }
118
117
 
119
- async function ensureThenSyncNativeCursorToolsForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext): Promise<void> {
118
+ function ensureThenSyncNativeCursorToolsForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext): void {
120
119
  const requested = isCursorNativeToolRegistrationRequested(ctx.mode);
121
120
  setCursorNativeToolDisplayRuntimeRequested(requested);
122
121
  if (!requested) {
123
122
  removeRegisteredNonCoreNativeCursorTools(pi);
124
123
  return;
125
124
  }
126
- await ensureNativeCursorToolsRegisteredForModel(pi, ctx);
125
+ ensureNativeCursorToolsRegisteredForModel(pi, ctx);
127
126
  syncRegisteredNativeCursorToolsForModel(pi, ctx.model);
128
127
  }
129
128
 
130
129
  export function registerCursorNativeToolDisplay(pi: CursorNativeToolDisplayExtensionApi): void {
131
- registerCursorModelLifecycle(pi, async (ctx) => {
132
- await ensureThenSyncNativeCursorToolsForModel(pi, ctx);
130
+ registerCursorModelLifecycle(pi, (ctx) => {
131
+ ensureThenSyncNativeCursorToolsForModel(pi, ctx);
133
132
  });
134
133
  }
135
134
 
@@ -1,4 +1,4 @@
1
- import type { AssistantMessage, AssistantMessageEventStream } from "@earendil-works/pi-ai/compat";
1
+ import type { AssistantMessage, AssistantMessageEventStream } from "@earendil-works/pi-ai";
2
2
 
3
3
  const DEFAULT_THINKING_TRACE_MAX_CHARS = 50000;
4
4
 
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import type { Context, ToolResultMessage } from "@earendil-works/pi-ai/compat";
2
+ import type { Context, ToolResultMessage } from "@earendil-works/pi-ai";
3
3
  import type { CallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
4
4
  import { buildCursorPiBridgeMcpToolDescription, CURSOR_PI_BRIDGE_MCP_TOOL_PREFIX } from "./cursor-bridge-contract.js";
5
5
  import type { CursorPiBridgeToolDefinition, CursorPiMcpInputSchema } from "./cursor-pi-tool-bridge-types.js";
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import type { IncomingMessage, ServerResponse } from "node:http";
3
3
  import type { McpServerConfig } from "@cursor/sdk";
4
- import type { Context, ToolResultMessage } from "@earendil-works/pi-ai/compat";
4
+ import type { Context, ToolResultMessage } from "@earendil-works/pi-ai";
5
5
  import { Server as McpProtocolServer } from "@modelcontextprotocol/sdk/server/index.js";
6
6
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
7
7
  import {
@@ -1,5 +1,5 @@
1
1
  import type { McpServerConfig } from "@cursor/sdk";
2
- import type { Context, ToolResultMessage } from "@earendil-works/pi-ai/compat";
2
+ import type { Context, ToolResultMessage } from "@earendil-works/pi-ai";
3
3
  import type { CursorSdkEventDebugRecorder } from "./cursor-sdk-event-debug.js";
4
4
  import type {
5
5
  ExtensionAPI,
@@ -166,7 +166,7 @@ function isCursorSdkStallAbortNetworkError(code: unknown, evidence: string, stac
166
166
  }
167
167
 
168
168
  /**
169
- * @cursor/sdk@1.0.23 throws internal RetriableError (name/kind "RetriableError") with message
169
+ * @cursor/sdk@1.0.27 throws internal RetriableError (name/kind "RetriableError") with message
170
170
  * "Connection stalled" or "Connection stalled repeatedly" after fetchWithRetry exhausts stalls.
171
171
  * The ConnectError is only the cause; the top-level error is not a ConnectError.
172
172
  */
@@ -6,9 +6,11 @@ import {
6
6
  type Context,
7
7
  type Model,
8
8
  type SimpleStreamOptions,
9
- } from "@earendil-works/pi-ai/compat";
9
+ } from "@earendil-works/pi-ai";
10
+ import { streamCursor } from "./cursor-provider.js";
11
+ import { sanitizeCursorProviderError } from "./cursor-provider-errors.js";
10
12
 
11
- function makeProviderLoadErrorMessage(model: Model<Api>, error: unknown): AssistantMessage {
13
+ function makeProviderRuntimeErrorMessage(model: Model<Api>, error: unknown, apiKey?: string): AssistantMessage {
12
14
  return {
13
15
  role: "assistant",
14
16
  content: [],
@@ -25,7 +27,7 @@ function makeProviderLoadErrorMessage(model: Model<Api>, error: unknown): Assist
25
27
  },
26
28
  stopReason: "error",
27
29
  timestamp: Date.now(),
28
- errorMessage: `Failed to load Cursor provider runtime: ${error instanceof Error ? error.message : String(error)}`,
30
+ errorMessage: `Cursor provider runtime failed: ${sanitizeCursorProviderError(error, apiKey)}`,
29
31
  };
30
32
  }
31
33
 
@@ -37,12 +39,11 @@ export function streamCursorLazy(
37
39
  const outer = createAssistantMessageEventStream();
38
40
  queueMicrotask(async () => {
39
41
  try {
40
- const { streamCursor } = await import("./cursor-provider.js");
41
42
  for await (const event of streamCursor(model, context, options)) {
42
43
  outer.push(event);
43
44
  }
44
45
  } catch (error) {
45
- const message = makeProviderLoadErrorMessage(model, error);
46
+ const message = makeProviderRuntimeErrorMessage(model, error, options?.apiKey);
46
47
  outer.push({ type: "error", reason: "error", error: message });
47
48
  outer.end(message);
48
49
  }
@@ -4,7 +4,7 @@ import {
4
4
  type AssistantMessageEventStream,
5
5
  type Context,
6
6
  type Model,
7
- } from "@earendil-works/pi-ai/compat";
7
+ } from "@earendil-works/pi-ai";
8
8
  import { scheduler } from "node:timers/promises";
9
9
  import {
10
10
  CursorLiveRunAbortError,
@@ -383,6 +383,7 @@ export async function drainCursorLiveRunTurn(
383
383
  applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
384
384
  runtime: "local",
385
385
  turn: cursorLiveRuns.takeSdkTurnUsage(run),
386
+ billed: run.billedTurnUsage,
386
387
  });
387
388
  if (run.resumeNotice) {
388
389
  emitDisplayOnlyTraceBlock(stream, partial, run.resumeNotice);
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import type { AssistantMessage } from "@earendil-works/pi-ai/compat";
2
+ import type { AssistantMessage } from "@earendil-works/pi-ai";
3
3
  import { CURSOR_PROVIDER } from "./cursor-model.js";
4
4
 
5
5
  /**
@@ -1,4 +1,4 @@
1
- import type { AssistantMessage } from "@earendil-works/pi-ai/compat";
1
+ import type { AssistantMessage } from "@earendil-works/pi-ai";
2
2
  import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
3
3
  import {
4
4
  classifyCursorRunEmission,
@@ -186,6 +186,7 @@ export class CursorRunFinalizer {
186
186
  applyCursorUsage(partial, model, context, prepared.meta.promptInputTokens, {
187
187
  runtime: prepared.runtimeTarget,
188
188
  turn: prepared.runtime.turnCoordinator.lastSdkTurnUsage,
189
+ billed: prepared.runtime.billedTurnUsage,
189
190
  });
190
191
  if (prepared.meta.resumeNotice) emitDisplayOnlyTraceBlock(stream, partial, prepared.meta.resumeNotice);
191
192
  if (displayOnlyTraceBlock) emitDisplayOnlyTraceBlock(stream, partial, displayOnlyTraceBlock);
@@ -1,4 +1,4 @@
1
- import type { AssistantMessage, AssistantMessageEventStream } from "@earendil-works/pi-ai/compat";
1
+ import type { AssistantMessage, AssistantMessageEventStream } from "@earendil-works/pi-ai";
2
2
  import type { InteractionUpdate } from "@cursor/sdk";
3
3
  import type { CursorLiveRun } from "./cursor-live-run-coordinator.js";
4
4
  import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
@@ -17,6 +17,7 @@ import {
17
17
  } from "./cursor-provider-run-outcome.js";
18
18
  import type { CursorProviderTurnPrepareResult } from "./cursor-provider-turn-types.js";
19
19
  import { loadCursorSdk } from "./cursor-sdk-runtime.js";
20
+ import { attachCursorSdkBilledTurnUsage } from "./cursor-sdk-billed-usage.js";
20
21
 
21
22
  export async function cacheSdkContextWindow(
22
23
  agentId: string,
@@ -149,6 +150,16 @@ export async function awaitFinalizeCursorRunOutcome(params: AwaitFinalizeCursorR
149
150
  resolvedApiKey: params.resolvedApiKey,
150
151
  optionsApiKey: params.optionsApiKey,
151
152
  });
153
+ const billed = await attachCursorSdkBilledTurnUsage({
154
+ agent: params.prepared.agent,
155
+ agentId: params.run.agentId,
156
+ runtime: params.prepared.runtimeTarget,
157
+ runId: params.run.id,
158
+ });
159
+ params.prepared.runtime.billedTurnUsage = billed.turn;
160
+ if (params.prepared.runtime.liveRun) {
161
+ params.prepared.runtime.liveRun.billedTurnUsage = billed.turn;
162
+ }
152
163
  let displayOnlyTraceBlock: string | undefined;
153
164
  if (params.prepared.runtimeTarget === "cloud" && isCursorRunFinishedSuccessfully(outcome)) {
154
165
  let report: CursorCloudRunReport = { agentId: params.run.agentId, runId: params.run.id, branches: [] };
@@ -158,6 +169,7 @@ export async function awaitFinalizeCursorRunOutcome(params: AwaitFinalizeCursorR
158
169
  run: params.run,
159
170
  waitResult,
160
171
  apiKey,
172
+ agentUsage: billed.agentUsage,
161
173
  });
162
174
  } catch (error) {
163
175
  recordCursorCloudReportingError(params.sdkEventDebug, error, apiKey);
@@ -1,4 +1,4 @@
1
- import type { AssistantMessage, AssistantMessageEventStream } from "@earendil-works/pi-ai/compat";
1
+ import type { AssistantMessage, AssistantMessageEventStream } from "@earendil-works/pi-ai";
2
2
  import type { CursorLiveRun } from "./cursor-live-run-coordinator.js";
3
3
  import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
4
4
  import { CursorPartialContentEmitter } from "./cursor-partial-content-emitter.js";
@@ -1,4 +1,4 @@
1
- import type { Context, SimpleStreamOptions } from "@earendil-works/pi-ai/compat";
1
+ import type { Context, SimpleStreamOptions } from "@earendil-works/pi-ai";
2
2
  import type { AgentModeOption, ModelSelection, SDKAgent } from "@cursor/sdk";
3
3
  import { configureCursorSdkHttp1 } from "./cursor-http1.js";
4
4
  import { installCursorMcpToolTimeoutOverride } from "./cursor-mcp-timeout-override.js";
@@ -5,7 +5,7 @@ import type {
5
5
  Context,
6
6
  Model,
7
7
  SimpleStreamOptions,
8
- } from "@earendil-works/pi-ai/compat";
8
+ } from "@earendil-works/pi-ai";
9
9
  import type { AgentModeOption, ModelSelection, SDKAgent, SDKImage } from "@cursor/sdk";
10
10
  import type { CursorLiveRun } from "./cursor-live-run-coordinator.js";
11
11
  import type { SessionCursorAgentLease } from "./cursor-session-agent.js";
@@ -14,6 +14,7 @@ import type { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
14
14
  import type { CursorSdkTurnCoordinator } from "./cursor-provider-turn-coordinator.js";
15
15
  import type { CursorPrompt } from "./context.js";
16
16
  import type { CursorResolvedSetting } from "./cursor-config.js";
17
+ import type { CursorSdkTurnUsage } from "./cursor-usage-accounting.js";
17
18
 
18
19
  export interface CursorProviderTurnRunnerParams {
19
20
  model: Model<Api>;
@@ -44,6 +45,7 @@ export interface CursorProviderTurnSendMeta {
44
45
 
45
46
  interface CursorProviderTurnRuntimeBase {
46
47
  turnCoordinator: CursorSdkTurnCoordinator;
48
+ billedTurnUsage?: CursorSdkTurnUsage;
47
49
  }
48
50
 
49
51
  /**
@@ -6,7 +6,7 @@ import {
6
6
  createAssistantMessageEventStream,
7
7
  type Model,
8
8
  type SimpleStreamOptions,
9
- } from "@earendil-works/pi-ai/compat";
9
+ } from "@earendil-works/pi-ai";
10
10
  import {
11
11
  cursorLiveRuns,
12
12
  DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS,
@@ -1,4 +1,4 @@
1
- import type { AssistantMessage } from "@earendil-works/pi-ai/compat";
1
+ import type { AssistantMessage } from "@earendil-works/pi-ai";
2
2
  import { asRecord, hasUsableText } from "./cursor-record-utils.js";
3
3
 
4
4
  function isCursorTextBoundary(text: string, index: number): boolean {
@@ -0,0 +1,107 @@
1
+ import type { SDKAgent } from "@cursor/sdk";
2
+ import type { CursorRuntime } from "./cursor-config.js";
3
+ import { asRecord, getArray, getString } from "./cursor-record-utils.js";
4
+ import { readCursorSdkTurnUsage, type CursorSdkTurnUsage } from "./cursor-usage-accounting.js";
5
+
6
+ const BILLED_USAGE_TIMEOUT_MS = 5000;
7
+
8
+ // ponytail: process-lifetime watermark of billed usage UUIDs per agentId; reset on process exit.
9
+ // Upgrade to session-scoped storage if multi-day processes retain enough agent IDs to matter.
10
+ const seenBilledRunIdsByAgent = new Map<string, Set<string>>();
11
+
12
+ export function isCursorSdkClientMintedRunId(runId: string): boolean {
13
+ return runId.startsWith("run-");
14
+ }
15
+
16
+ export function sumCursorSdkTurnUsage(usages: readonly CursorSdkTurnUsage[]): CursorSdkTurnUsage | undefined {
17
+ if (usages.length === 0) return undefined;
18
+ return usages.reduce(
19
+ (total, usage) => ({
20
+ inputTokens: total.inputTokens + usage.inputTokens,
21
+ outputTokens: total.outputTokens + usage.outputTokens,
22
+ cacheReadTokens: total.cacheReadTokens + usage.cacheReadTokens,
23
+ cacheWriteTokens: total.cacheWriteTokens + usage.cacheWriteTokens,
24
+ }),
25
+ );
26
+ }
27
+
28
+ export function peekCursorBilledUsageRunIds(agentId: string): ReadonlySet<string> {
29
+ return seenBilledRunIdsByAgent.get(agentId) ?? new Set();
30
+ }
31
+
32
+ export function rememberCursorBilledUsageRunIds(agentId: string, runIds: readonly string[]): void {
33
+ if (runIds.length === 0) return;
34
+ let seen = seenBilledRunIdsByAgent.get(agentId);
35
+ if (!seen) {
36
+ seen = new Set();
37
+ seenBilledRunIdsByAgent.set(agentId, seen);
38
+ }
39
+ for (const runId of runIds) seen.add(runId);
40
+ }
41
+
42
+ function withTimeout<T>(promise: Promise<T>, timeoutMs = BILLED_USAGE_TIMEOUT_MS): Promise<T | undefined> {
43
+ let timer: ReturnType<typeof setTimeout> | undefined;
44
+ const timeout = new Promise<undefined>((resolve) => {
45
+ timer = setTimeout(() => resolve(undefined), timeoutMs);
46
+ timer.unref?.();
47
+ });
48
+ return Promise.race([promise.catch(() => undefined), timeout]).finally(() => {
49
+ if (timer) clearTimeout(timer);
50
+ });
51
+ }
52
+
53
+ export function selectCursorBilledTurnUsage(
54
+ agentUsage: unknown,
55
+ options: { runtime: CursorRuntime; runId?: string; seenRunIds?: ReadonlySet<string> },
56
+ ): { turn?: CursorSdkTurnUsage; runIds: string[] } {
57
+ const runs = (getArray(asRecord(agentUsage), "runs") ?? []).flatMap((item) => {
58
+ const record = asRecord(item);
59
+ const runId = getString(record, "runId");
60
+ const usage = readCursorSdkTurnUsage(record?.usage);
61
+ return runId && usage ? [{ runId, usage }] : [];
62
+ });
63
+ if (options.runtime === "cloud" && options.runId) {
64
+ const match = runs.find((run) => run.runId === options.runId);
65
+ return match ? { turn: match.usage, runIds: [match.runId] } : { runIds: [] };
66
+ }
67
+ const unseen = runs.filter((run) => !options.seenRunIds?.has(run.runId));
68
+ return { turn: sumCursorSdkTurnUsage(unseen.map((run) => run.usage)), runIds: unseen.map((run) => run.runId) };
69
+ }
70
+
71
+ export async function fetchCursorSdkAgentUsage(
72
+ agent: SDKAgent,
73
+ options: { runtime: CursorRuntime; runId?: string },
74
+ ): Promise<unknown | undefined> {
75
+ if (typeof agent.getUsage !== "function") return undefined;
76
+ const query =
77
+ options.runtime === "cloud" && options.runId && isCursorSdkClientMintedRunId(options.runId)
78
+ ? { runId: options.runId }
79
+ : undefined;
80
+ return withTimeout(Promise.resolve(agent.getUsage(query)));
81
+ }
82
+
83
+ export async function attachCursorSdkBilledTurnUsage(options: {
84
+ agent: SDKAgent;
85
+ agentId: string;
86
+ runtime: CursorRuntime;
87
+ runId?: string;
88
+ }): Promise<{ agentUsage?: unknown; turn?: CursorSdkTurnUsage }> {
89
+ const agentUsage = await fetchCursorSdkAgentUsage(options.agent, {
90
+ runtime: options.runtime,
91
+ runId: options.runId,
92
+ });
93
+ if (!agentUsage) return {};
94
+ const selected = selectCursorBilledTurnUsage(agentUsage, {
95
+ runtime: options.runtime,
96
+ runId: options.runtime === "cloud" ? options.runId : undefined,
97
+ seenRunIds: peekCursorBilledUsageRunIds(options.agentId),
98
+ });
99
+ rememberCursorBilledUsageRunIds(options.agentId, selected.runIds);
100
+ return { agentUsage, turn: selected.turn };
101
+ }
102
+
103
+ export const __testUtils = {
104
+ reset(): void {
105
+ seenBilledRunIdsByAgent.clear();
106
+ },
107
+ };
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { copyFileSync, existsSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
- import type { AssistantMessageEventStream } from "@earendil-works/pi-ai/compat";
4
+ import type { AssistantMessageEventStream } from "@earendil-works/pi-ai";
5
5
  import type { AgentModeOption, InteractionUpdate } from "@cursor/sdk";
6
6
  import type { CursorPiToolBridgeDiagnosticEvent } from "./cursor-pi-tool-bridge-diagnostics.js";
7
7
  import { serializeCursorPiToolBridgeDiagnostic } from "./cursor-pi-tool-bridge-diagnostics.js";
@@ -75,10 +75,12 @@ function isCursorSdkAbortError(error: unknown): boolean {
75
75
  // The exact observed incident: the Cursor SDK 1.0.23 local shell executor writes a
76
76
  // spawned child's stdin without a stream 'error' listener, so a child exiting while
77
77
  // a write is in flight surfaces a raw `write EPIPE` uncaught exception whose stack
78
- // is exactly the single async pipe-write completion frame. Pi's own piped-stdout or
79
- // dead-terminal EPIPE normally surfaces through the synchronous write-dispatch path
80
- // with multiple frames (afterWriteDispatched/Socket._writeGeneric) and must stay
81
- // fatal per Unix convention, so anything beyond this one-frame contract is rejected.
78
+ // is exactly the single async pipe-write completion frame. Installed 1.0.27 attaches
79
+ // a no-op `error` listener before that write; keep this guard as defense in depth.
80
+ // Pi's own piped-stdout or dead-terminal EPIPE normally surfaces through the
81
+ // synchronous write-dispatch path with multiple frames (afterWriteDispatched /
82
+ // Socket._writeGeneric) and must stay fatal per Unix convention, so anything beyond
83
+ // this one-frame contract is rejected.
82
84
  const OBSERVED_CLOSED_PIPE_STACK_FRAME =
83
85
  /^\s+at WriteWrap\.onWriteComplete \[as oncomplete\] \(node:internal\/stream_base_commons:\d+:\d+\)$/;
84
86
 
@@ -7,6 +7,11 @@ import type {
7
7
  } from "@earendil-works/pi-coding-agent";
8
8
  import { clearCursorSdkHttp1 } from "./cursor-http1.js";
9
9
  import { onCursorSessionScopeKeyChange } from "./cursor-session-scope.js";
10
+ import {
11
+ disposeSessionCursorAgent,
12
+ invalidateSessionAgent,
13
+ resetSessionCursorAgent,
14
+ } from "./cursor-session-agent.js";
10
15
 
11
16
  export interface CursorSessionAgentLifecycleExtensionApi {
12
17
  on(event: "session_shutdown", handler: ExtensionHandler<SessionShutdownEvent>): void;
@@ -18,11 +23,9 @@ export interface CursorSessionAgentLifecycleExtensionApi {
18
23
 
19
24
  export function registerCursorSessionAgentLifecycle(pi: CursorSessionAgentLifecycleExtensionApi): void {
20
25
  onCursorSessionScopeKeyChange(async (previousScopeKey) => {
21
- const { disposeSessionCursorAgent } = await import("./cursor-session-agent.js");
22
26
  await disposeSessionCursorAgent(previousScopeKey);
23
27
  });
24
28
  pi.on("session_shutdown", async (event) => {
25
- const { disposeSessionCursorAgent, resetSessionCursorAgent } = await import("./cursor-session-agent.js");
26
29
  try {
27
30
  if (event.reason === "reload") {
28
31
  await resetSessionCursorAgent();
@@ -33,20 +36,16 @@ export function registerCursorSessionAgentLifecycle(pi: CursorSessionAgentLifecy
33
36
  clearCursorSdkHttp1();
34
37
  }
35
38
  });
36
- pi.on("session_compact", async () => {
37
- const { invalidateSessionAgent } = await import("./cursor-session-agent.js");
39
+ pi.on("session_compact", () => {
38
40
  invalidateSessionAgent();
39
41
  });
40
- pi.on("session_before_tree", async () => {
41
- const { invalidateSessionAgent } = await import("./cursor-session-agent.js");
42
+ pi.on("session_before_tree", () => {
42
43
  invalidateSessionAgent();
43
44
  });
44
45
  pi.on("session_tree", async () => {
45
- const { resetSessionCursorAgent } = await import("./cursor-session-agent.js");
46
46
  await resetSessionCursorAgent();
47
47
  });
48
- pi.on("model_select", async () => {
49
- const { invalidateSessionAgent } = await import("./cursor-session-agent.js");
48
+ pi.on("model_select", () => {
50
49
  invalidateSessionAgent();
51
50
  });
52
51
  }
@@ -81,6 +81,19 @@ const state: CursorSessionResumeState = {
81
81
  unownedUserEntryIds: new Set(),
82
82
  };
83
83
 
84
+ // Compaction summarizer sends commit a one-message resume handle. Drop it so the
85
+ // next normal turn_end cannot persist that lineage (#223).
86
+ let resumeHandlePersistSuppressed = false;
87
+
88
+ export function suppressCursorSessionAgentResumeHandlePersist(): void {
89
+ resumeHandlePersistSuppressed = true;
90
+ state.pendingHandle = undefined;
91
+ }
92
+
93
+ export function allowCursorSessionAgentResumeHandlePersist(): void {
94
+ resumeHandlePersistSuppressed = false;
95
+ }
96
+
84
97
  function hashParts(parts: readonly string[]): string {
85
98
  const hash = createHash("sha256");
86
99
  for (const part of parts) {
@@ -360,6 +373,7 @@ export function getMatchingCursorSessionAgentResumeHandle(poolKey: string): Curs
360
373
  }
361
374
 
362
375
  export function persistCursorSessionAgentResumeHandle(input: PendingCursorSessionAgentResumeHandle): void {
376
+ if (resumeHandlePersistSuppressed) return;
363
377
  if (!isCursorLocalAgentId(input.agentId)) return;
364
378
  state.pendingHandle = {
365
379
  runtime: input.runtime,
@@ -371,6 +385,11 @@ export function persistCursorSessionAgentResumeHandle(input: PendingCursorSessio
371
385
  }
372
386
 
373
387
  function flushPendingCursorSessionAgentResumeHandle(branch: readonly SessionEntry[]): void {
388
+ if (resumeHandlePersistSuppressed) {
389
+ state.pendingHandle = undefined;
390
+ restoreFromBranch(branch);
391
+ return;
392
+ }
374
393
  restoreFromBranch(branch);
375
394
  const pending = state.pendingHandle;
376
395
  state.pendingHandle = undefined;
@@ -442,6 +461,8 @@ export function registerCursorSessionAgentResume(pi: CursorSessionAgentResumeExt
442
461
  restoreFromSessionManager(ctx.sessionManager);
443
462
  });
444
463
  pi.on("session_compact", (event, ctx) => {
464
+ state.pendingHandle = undefined;
465
+ resumeHandlePersistSuppressed = false;
445
466
  const branch = ctx.sessionManager.getBranch();
446
467
  if (branch.length > 0) {
447
468
  restoreFromSessionManager(ctx.sessionManager);
@@ -471,6 +492,7 @@ function resetStateForTests(): void {
471
492
  state.lastBranchHandle = undefined;
472
493
  state.pendingHandle = undefined;
473
494
  state.unownedUserEntryIds = new Set();
495
+ resumeHandlePersistSuppressed = false;
474
496
  }
475
497
 
476
498
  export const __testUtils = {
@@ -479,4 +501,5 @@ export const __testUtils = {
479
501
  reset: resetStateForTests,
480
502
  set: setStateForTests,
481
503
  state,
504
+ isResumeHandlePersistSuppressed: () => resumeHandlePersistSuppressed,
482
505
  };
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import type { AgentModeOption, LocalAgentOptions, LocalAgentStore, ModelSelection, SDKAgent, SettingSource } from "@cursor/sdk";
3
- import type { Context } from "@earendil-works/pi-ai/compat";
3
+ import type { Context } from "@earendil-works/pi-ai";
4
4
  import {
5
5
  getRegisteredCursorPiToolBridge,
6
6
  type CursorPiBridgeToolRequest,
@@ -1,15 +1,19 @@
1
1
  import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
2
2
  import { resetSessionCursorAgent } from "./cursor-session-agent.js";
3
+ import { suppressCursorSessionAgentResumeHandlePersist } from "./cursor-session-agent-resume.js";
3
4
  import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
4
5
 
5
6
  /**
6
7
  * Prepare the pooled Cursor session agent for pi compaction summarization.
7
8
  * Releases any scoped live-run drain state still tied to the pooled agent, then
8
9
  * disposes the pool entry so summarization acquires a clean SDK agent.
10
+ * Suppresses local-resume handle persist so the summarizer send cannot leak
11
+ * a one-message handle into the next normal turn (#223).
9
12
  */
10
13
  export async function prepareCursorSessionForCompaction(
11
14
  scopeKey: string = getCursorSessionScopeKey(),
12
15
  ): Promise<void> {
16
+ suppressCursorSessionAgentResumeHandlePersist();
13
17
  while (true) {
14
18
  const run = cursorLiveRuns.getActiveForScope(scopeKey);
15
19
  if (!run || run.disposed) break;
@@ -1,4 +1,4 @@
1
- import type { Context } from "@earendil-works/pi-ai/compat";
1
+ import type { Context } from "@earendil-works/pi-ai";
2
2
  import {
3
3
  buildCursorIncrementalPrompt,
4
4
  buildCursorPrompt,
@@ -4,7 +4,7 @@ import type { CursorPiToolBridgeSnapshot } from "./cursor-pi-tool-bridge-types.j
4
4
  export const CURSOR_TOOL_MANIFEST_ENV = "PI_CURSOR_TOOL_MANIFEST";
5
5
 
6
6
  /**
7
- * Representative @cursor/sdk@1.0.23 local-agent ToolType values; actual exposure can vary by run.
7
+ * Representative @cursor/sdk@1.0.27 local-agent ToolType values; actual exposure can vary by run.
8
8
  * See docs/cursor-native-tool-replay.md#sdk-tooltype-replay-matrix.
9
9
  */
10
10
  export const CURSOR_HOST_TOOL_MANIFEST_SUMMARY =