@emmaneugene/pi-cursor-sdk 0.3.7 → 0.4.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 (79) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +99 -126
  3. package/dist/cursor-agents-context-registration.js +1 -3
  4. package/dist/cursor-agents-context.js +2 -2
  5. package/dist/cursor-config.js +2 -290
  6. package/dist/cursor-provider-errors.js +3 -58
  7. package/dist/cursor-provider-live-run-drain.js +0 -3
  8. package/dist/cursor-provider-run-finalizer.js +4 -7
  9. package/dist/cursor-provider-run-outcome.js +1 -1
  10. package/dist/cursor-provider-turn-finalize.js +3 -55
  11. package/dist/cursor-provider-turn-prepare.js +8 -144
  12. package/dist/cursor-provider-turn-runner.js +9 -23
  13. package/dist/cursor-provider-turn-send.js +8 -35
  14. package/dist/cursor-runtime-state.js +6 -268
  15. package/dist/cursor-sdk-billed-usage.js +3 -18
  16. package/dist/cursor-sdk-platform-package.js +88 -0
  17. package/dist/cursor-session-agent.js +18 -14
  18. package/dist/cursor-skill-tool.js +12 -22
  19. package/dist/cursor-state.js +5 -8
  20. package/dist/cursor-usage-accounting.js +1 -1
  21. package/docs/cursor-dogfood-checklist.md +4 -4
  22. package/docs/cursor-live-smoke-checklist.md +23 -23
  23. package/docs/cursor-model-ux-spec.md +27 -30
  24. package/docs/cursor-native-tool-replay.md +4 -4
  25. package/docs/cursor-native-tool-visual-audit.md +7 -7
  26. package/docs/cursor-testing-lessons.md +23 -16
  27. package/docs/cursor-tool-surfaces.md +2 -4
  28. package/docs/platform-smoke-implementation.md +5 -5
  29. package/docs/platform-smoke.md +27 -72
  30. package/package.json +3 -17
  31. package/platform-smoke.config.mjs +2 -1
  32. package/scripts/lib/local-resume-smoke-harness.mjs +0 -18
  33. package/scripts/platform-smoke/card-detect.mjs +1 -1
  34. package/scripts/platform-smoke/local-resume-runner.mjs +1 -1
  35. package/scripts/platform-smoke/scenarios.mjs +1 -1
  36. package/scripts/platform-smoke/targets.mjs +14 -5
  37. package/src/cursor-agents-context-registration.ts +0 -5
  38. package/src/cursor-agents-context.ts +1 -3
  39. package/src/cursor-config.ts +8 -379
  40. package/src/cursor-provider-errors.ts +2 -62
  41. package/src/cursor-provider-live-run-drain.ts +0 -3
  42. package/src/cursor-provider-run-finalizer.ts +4 -12
  43. package/src/cursor-provider-run-outcome.ts +0 -3
  44. package/src/cursor-provider-turn-finalize.ts +3 -61
  45. package/src/cursor-provider-turn-prepare.ts +8 -165
  46. package/src/cursor-provider-turn-runner.ts +15 -31
  47. package/src/cursor-provider-turn-send.ts +7 -38
  48. package/src/cursor-provider-turn-types.ts +9 -30
  49. package/src/cursor-runtime-state.ts +6 -345
  50. package/src/cursor-sdk-billed-usage.ts +3 -24
  51. package/src/cursor-sdk-platform-package.ts +106 -0
  52. package/src/cursor-session-agent.ts +16 -12
  53. package/src/cursor-skill-tool.ts +10 -26
  54. package/src/cursor-state.ts +5 -10
  55. package/src/cursor-usage-accounting.ts +1 -3
  56. package/dist/cursor-cloud-lifecycle.js +0 -650
  57. package/dist/cursor-cloud-local-state.js +0 -460
  58. package/dist/cursor-cloud-options.js +0 -145
  59. package/dist/cursor-cloud-reporting.js +0 -222
  60. package/dist/cursor-ripgrep-path.js +0 -27
  61. package/scripts/cloud-runtime-smoke.d.mts +0 -42
  62. package/scripts/cloud-runtime-smoke.mjs +0 -623
  63. package/scripts/lib/cloud-smoke-artifacts.d.mts +0 -16
  64. package/scripts/lib/cloud-smoke-artifacts.mjs +0 -94
  65. package/scripts/lib/cloud-smoke-cleanup-evidence.d.mts +0 -209
  66. package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +0 -585
  67. package/scripts/lib/cloud-smoke-github.d.mts +0 -78
  68. package/scripts/lib/cloud-smoke-github.mjs +0 -331
  69. package/scripts/lib/cloud-smoke-pi-runner.d.mts +0 -42
  70. package/scripts/lib/cloud-smoke-pi-runner.mjs +0 -214
  71. package/scripts/lib/cloud-smoke-shutdown.d.mts +0 -67
  72. package/scripts/lib/cloud-smoke-shutdown.mjs +0 -133
  73. package/shared/cursor-cloud-lifecycle-constants.d.mts +0 -3
  74. package/shared/cursor-cloud-lifecycle-constants.mjs +0 -7
  75. package/src/cursor-cloud-lifecycle.ts +0 -733
  76. package/src/cursor-cloud-local-state.ts +0 -536
  77. package/src/cursor-cloud-options.ts +0 -182
  78. package/src/cursor-cloud-reporting.ts +0 -267
  79. package/src/cursor-ripgrep-path.ts +0 -32
@@ -48,13 +48,7 @@ interface CursorProviderTurnRuntimeBase {
48
48
  billedTurnUsage?: CursorSdkTurnUsage;
49
49
  }
50
50
 
51
- /**
52
- * Runtime-agnostic lifecycle operations for a prepared turn.
53
- *
54
- * Local implementations delegate to the session agent lease; cloud
55
- * implementations no-op the local-only operations (commitSend,
56
- * trackRunCompletion, abandon) and dispose the cloud agent instead.
57
- */
51
+ /** Lifecycle operations for a prepared local turn, delegated to the session agent lease. */
58
52
  export interface CursorProviderTurnLifecycle {
59
53
  trackRunCompletion(completion: Promise<unknown>): void;
60
54
  commitSend(context: Context, bootstrapped: boolean): void;
@@ -74,7 +68,13 @@ export interface LiveCursorProviderTurnRuntime extends CursorProviderTurnRuntime
74
68
 
75
69
  export type CursorProviderTurnRuntime = DirectCursorProviderTurnRuntime | LiveCursorProviderTurnRuntime;
76
70
 
77
- interface CursorProviderTurnPrepareResultBase {
71
+ /**
72
+ * Single owned model for a prepared local provider turn.
73
+ *
74
+ * Send, finalize, and cleanup phases receive this immutable object instead of
75
+ * keeping parallel liveRun/turnCoordinator/resource bags in sync by convention.
76
+ */
77
+ export interface CursorProviderTurnPrepareResult {
78
78
  agent: SDKAgent;
79
79
  cwd: string;
80
80
  payload: CursorProviderTurnSendPayload;
@@ -83,33 +83,12 @@ interface CursorProviderTurnPrepareResultBase {
83
83
  textDeltas: string[];
84
84
  restoreCursorSdkOutputFilter: () => void;
85
85
  lifecycle: CursorProviderTurnLifecycle;
86
- }
87
-
88
- export interface LocalCursorProviderTurnPrepareResult extends CursorProviderTurnPrepareResultBase {
89
- runtimeTarget: "local";
90
- runtime: CursorProviderTurnRuntime;
91
86
  sessionAgentScopeKey: string;
92
87
  sessionAgentLease: SessionCursorAgentLease;
93
88
  localForce: CursorResolvedSetting<boolean>;
89
+ runtime: CursorProviderTurnRuntime;
94
90
  }
95
91
 
96
- export interface CloudCursorProviderTurnPrepareResult extends CursorProviderTurnPrepareResultBase {
97
- runtimeTarget: "cloud";
98
- runtime: DirectCursorProviderTurnRuntime;
99
- sessionAgentScopeKey?: undefined;
100
- sessionAgentLease?: undefined;
101
- }
102
-
103
- /**
104
- * Single owned model for a prepared provider turn.
105
- *
106
- * Send, finalize, and cleanup phases receive this immutable object instead of
107
- * keeping parallel liveRun/turnCoordinator/resource bags in sync by convention.
108
- */
109
- export type CursorProviderTurnPrepareResult =
110
- | LocalCursorProviderTurnPrepareResult
111
- | CloudCursorProviderTurnPrepareResult;
112
-
113
92
  export interface CursorProviderTurnSend {
114
93
  run: Awaited<ReturnType<SDKAgent["send"]>>;
115
94
  cursorAgentMessageOffset: number | undefined;
@@ -1,106 +1,38 @@
1
- import type { ExtensionAPI, ExtensionContext, SessionEntry } from "@earendil-works/pi-coding-agent";
2
- import {
3
- registerCursorCloudLifecycleLedger,
4
- runCursorCloudLifecycleCommand,
5
- } from "./cursor-cloud-lifecycle.js";
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
6
2
  import {
7
3
  CURSOR_AUTO_REVIEW_ENV,
8
- CURSOR_CLOUD_ACK_ENV,
9
- CURSOR_CLOUD_ALLOW_LOCAL_STATE_ENV,
10
- CURSOR_CLOUD_AUTO_CREATE_PR_ENV,
11
- CURSOR_CLOUD_BRANCH_ENV,
12
- CURSOR_CLOUD_CONTEXT_ENV,
13
- CURSOR_CLOUD_DIRECT_PUSH_ENV,
14
- CURSOR_CLOUD_ENV_ENV,
15
- CURSOR_CLOUD_ENV_FROM_FILES_ENV,
16
- CURSOR_CLOUD_ENV_NAME_ENV,
17
- CURSOR_CLOUD_ENV_TYPE_ENV,
18
- CURSOR_CLOUD_REPO_ENV,
19
- CURSOR_CLOUD_SKIP_REVIEWER_REQUEST_ENV,
20
4
  CURSOR_LOCAL_FORCE_ENV,
21
5
  CURSOR_LOCAL_RESUME_ENV,
22
- CURSOR_RUNTIME_ENV,
23
6
  CURSOR_SANDBOX_ENV,
24
- getCursorSdkProjectConfigPath,
25
- getCursorSdkUserConfigPath,
26
7
  loadCursorSdkConfig,
27
- mergeCursorSdkConfigForUpdate,
28
- parseExplicitCursorCloudEnvNames,
29
8
  resolveCursorSdkConfig,
30
- updateCursorSdkConfig,
31
9
  type CursorExplicitSdkConfig,
32
10
  type CursorResolvedSdkConfig,
33
11
  type CursorResolvedSetting,
34
- type CursorRuntime,
35
12
  type CursorSdkConfig,
36
13
  } from "./cursor-config.js";
37
- import { asRecord } from "./cursor-record-utils.js";
38
14
  import { getResolvedSessionCursorHttp1Enabled } from "./cursor-http1.js";
39
15
  import { getCursorSessionCwd, getCursorSessionProjectTrusted } from "./cursor-session-scope.js";
40
16
 
41
- export const CURSOR_RUNTIME_ENTRY_TYPE = "cursor-runtime-state";
42
-
43
- export const CURSOR_CLOUD_ACK_DISCLOSURE = [
44
- "Cursor Cloud executes this work remotely.",
45
- "Fresh context is used by default; prior Pi context is included only with explicit bootstrap opt-in.",
46
- "Pi-local tools and the Pi bridge are unavailable, and Pi environment variables are not forwarded.",
47
- "Cursor may create branches, commit, push, and open pull requests.",
48
- "Cloud agents remain until you archive or delete them.",
49
- "Cloud Agents run in Max Mode, are billed at Cursor API pricing, and may require spend-limit setup.",
50
- ].join("\n\n");
51
-
52
- interface CursorRuntimeEntryData {
53
- runtime: CursorRuntime;
54
- cloudAcknowledged?: boolean;
55
- }
56
-
57
- export type CursorRuntimeStateExtensionApi = Pick<
58
- ExtensionAPI,
59
- "appendEntry" | "getFlag" | "registerFlag" | "registerCommand" | "on"
60
- >;
17
+ export type CursorRuntimeStateExtensionApi = Pick<ExtensionAPI, "getFlag" | "registerFlag">;
61
18
 
62
19
  type CursorRuntimeContext = Pick<ExtensionContext, "cwd">;
63
20
 
64
- type CursorStatusRefresh = (ctx: ExtensionContext) => void;
65
-
66
21
  interface CursorCliConfigSnapshot {
67
22
  config: CursorExplicitSdkConfig;
68
- cloudEnvNames?: string;
69
23
  }
70
24
 
71
25
  let cliCursorSnapshot: CursorCliConfigSnapshot = { config: {} };
72
26
  let cliLocalForceConsumed = false;
73
27
  let envLocalForceConsumed = false;
74
- let sessionCursorRuntime: CursorRuntime | undefined;
75
- let sessionCursorCloudAcknowledged = false;
76
-
77
- function isCursorRuntimeEntryData(value: unknown): value is CursorRuntimeEntryData {
78
- const record = asRecord(value);
79
- if (!record) return false;
80
- return (record.runtime === "local" || record.runtime === "cloud")
81
- && (record.cloudAcknowledged === undefined || typeof record.cloudAcknowledged === "boolean");
82
- }
83
-
84
- function stringFlagValue(value: boolean | string | undefined): string | undefined {
85
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
86
- }
87
28
 
88
29
  export function getCursorCliConfig(): CursorExplicitSdkConfig {
89
- const config = structuredClone(cliCursorSnapshot.config);
90
- const envNames = parseExplicitCursorCloudEnvNames(cliCursorSnapshot.cloudEnvNames, "--cursor-cloud-env");
91
- if (envNames) config.cloud = { ...config.cloud, envNames };
92
- return config;
30
+ return structuredClone(cliCursorSnapshot.config);
93
31
  }
94
32
 
95
33
  export function getCursorSessionConfig(): CursorSdkConfig {
96
34
  const useHttp1ForAgent = getResolvedSessionCursorHttp1Enabled();
97
35
  return {
98
- ...(sessionCursorRuntime
99
- ? {
100
- runtime: sessionCursorRuntime,
101
- ...(sessionCursorCloudAcknowledged ? { cloud: { acknowledged: true } } : {}),
102
- }
103
- : {}),
104
36
  ...(useHttp1ForAgent === undefined ? {} : { local: { useHttp1ForAgent } }),
105
37
  };
106
38
  }
@@ -128,7 +60,6 @@ export function resolveEffectiveCursorConfigForContext(ctx: CursorRuntimeContext
128
60
  export type CursorRuntimeResolution =
129
61
  | {
130
62
  kind: "valid";
131
- runtime: CursorResolvedSetting<CursorRuntime>;
132
63
  useHttp1ForAgent: CursorResolvedSetting<boolean>;
133
64
  }
134
65
  | { kind: "invalid"; message: string };
@@ -138,7 +69,6 @@ export function resolveCursorStatusRuntime(ctx: CursorRuntimeContext): CursorRun
138
69
  const config = resolveEffectiveCursorConfigForContext(ctx);
139
70
  return {
140
71
  kind: "valid",
141
- runtime: config.runtime,
142
72
  useHttp1ForAgent: config.local.useHttp1ForAgent,
143
73
  };
144
74
  } catch (error) {
@@ -146,21 +76,13 @@ export function resolveCursorStatusRuntime(ctx: CursorRuntimeContext): CursorRun
146
76
  }
147
77
  }
148
78
 
149
- export function formatResolvedCursorRuntime(runtime: CursorResolvedSetting<CursorRuntime>): string {
150
- const cap = runtime.cappedBy;
151
- return cap
152
- ? `${runtime.value} (source: ${runtime.source} safety cap over ${cap.cappedSource} ${cap.cappedValue})`
153
- : `${runtime.value} (source: ${runtime.source})`;
154
- }
155
-
156
79
  export function formatCursorStatus(
157
- runtime: CursorRuntime | "invalid",
158
80
  fast: boolean | undefined,
159
81
  mode: "agent" | "plan" | "invalid",
160
82
  useHttp1ForAgent = false,
161
83
  ): string {
162
- const parts = [`cursor:${runtime}`, fast === true ? "fast:on" : fast === false ? "fast:off" : "fast:n/a"];
163
- if (runtime === "local" && useHttp1ForAgent) parts.push("http1");
84
+ const parts = ["cursor", fast === true ? "fast:on" : fast === false ? "fast:off" : "fast:n/a"];
85
+ if (useHttp1ForAgent) parts.push("http1");
164
86
  if (mode === "invalid") parts.push("mode invalid");
165
87
  else if (mode === "plan") parts.push("plan");
166
88
  return parts.join(" · ");
@@ -183,40 +105,7 @@ export function consumeCursorLocalForceOverride(resolved: { value: boolean; sour
183
105
  return false;
184
106
  }
185
107
 
186
- export function restoreSessionCursorRuntimeState(branch: readonly SessionEntry[]): void {
187
- sessionCursorRuntime = undefined;
188
- sessionCursorCloudAcknowledged = false;
189
- for (const entry of branch) {
190
- if (entry.type !== "custom" || entry.customType !== CURSOR_RUNTIME_ENTRY_TYPE) continue;
191
- if (isCursorRuntimeEntryData(entry.data)) {
192
- sessionCursorRuntime = entry.data.runtime;
193
- sessionCursorCloudAcknowledged ||= entry.data.cloudAcknowledged === true;
194
- }
195
- }
196
- }
197
-
198
108
  export function restoreCursorCliState(pi: Pick<ExtensionAPI, "getFlag">): void {
199
- const runtime = stringFlagValue(pi.getFlag("cursor-runtime"));
200
- const repo = stringFlagValue(pi.getFlag("cursor-cloud-repo"));
201
- const branch = stringFlagValue(pi.getFlag("cursor-cloud-branch"));
202
- const contextHandoff = stringFlagValue(pi.getFlag("cursor-cloud-context"));
203
- const environmentType = stringFlagValue(pi.getFlag("cursor-cloud-env-type"));
204
- const environmentName = stringFlagValue(pi.getFlag("cursor-cloud-env-name"));
205
- const cloudEnvNames = stringFlagValue(pi.getFlag("cursor-cloud-env"));
206
- const cloud: NonNullable<CursorExplicitSdkConfig["cloud"]> = {
207
- ...(repo ? { repo } : {}),
208
- ...(branch ? { branch } : {}),
209
- ...(contextHandoff ? { contextHandoff } : {}),
210
- ...(pi.getFlag("cursor-cloud-direct-push") === true ? { directPush: true } : {}),
211
- ...(pi.getFlag("cursor-cloud-auto-create-pr") === true ? { autoCreatePR: true } : {}),
212
- ...(pi.getFlag("cursor-cloud-skip-reviewer-request") === true ? { skipReviewerRequest: true } : {}),
213
- ...(pi.getFlag("cursor-cloud-allow-local-state") === true ? { allowLocalState: true } : {}),
214
- ...(pi.getFlag("cursor-cloud-env-from-files") === true ? { envFromFiles: true } : {}),
215
- ...(environmentType || environmentName
216
- ? { environment: { ...(environmentType ? { type: environmentType } : {}), ...(environmentName ? { name: environmentName } : {}) } }
217
- : {}),
218
- ...(pi.getFlag("cursor-cloud-ack") === true ? { acknowledged: true } : {}),
219
- };
220
109
  const local: NonNullable<CursorExplicitSdkConfig["local"]> = {
221
110
  ...(pi.getFlag("cursor-auto-review") === true ? { autoReview: true } : {}),
222
111
  ...(pi.getFlag("cursor-sandbox") === true ? { sandboxOptions: { enabled: true } } : {}),
@@ -229,94 +118,12 @@ export function restoreCursorCliState(pi: Pick<ExtensionAPI, "getFlag">): void {
229
118
  };
230
119
  cliCursorSnapshot = {
231
120
  config: {
232
- ...(runtime ? { runtime } : {}),
233
- ...(Object.keys(cloud).length ? { cloud } : {}),
234
121
  ...(Object.keys(local).length ? { local } : {}),
235
122
  },
236
- ...(cloudEnvNames ? { cloudEnvNames } : {}),
237
123
  };
238
124
  }
239
125
 
240
- function persistCursorRuntimePreference(
241
- pi: Pick<ExtensionAPI, "appendEntry">,
242
- runtime: CursorRuntime,
243
- cloudAcknowledged = false,
244
- ): void {
245
- const acknowledged = sessionCursorCloudAcknowledged || cloudAcknowledged;
246
- pi.appendEntry<CursorRuntimeEntryData>(CURSOR_RUNTIME_ENTRY_TYPE, {
247
- runtime,
248
- ...(acknowledged ? { cloudAcknowledged: true } : {}),
249
- });
250
- sessionCursorRuntime = runtime;
251
- sessionCursorCloudAcknowledged = acknowledged;
252
- }
253
-
254
- function registerCursorRuntimeFlags(pi: Pick<ExtensionAPI, "registerFlag">): void {
255
- pi.registerFlag("cursor-runtime", {
256
- description: `Select Cursor runtime for this run: local or cloud (or set ${CURSOR_RUNTIME_ENV})`,
257
- type: "string",
258
- default: "",
259
- });
260
- pi.registerFlag("cursor-cloud-repo", {
261
- description: `Set Cursor cloud repository URL for this run (or set ${CURSOR_CLOUD_REPO_ENV})`,
262
- type: "string",
263
- default: "",
264
- });
265
- pi.registerFlag("cursor-cloud-branch", {
266
- description: `Set Cursor cloud branch/ref for this run (or set ${CURSOR_CLOUD_BRANCH_ENV})`,
267
- type: "string",
268
- default: "",
269
- });
270
- pi.registerFlag("cursor-cloud-context", {
271
- description: `Set Cursor cloud context handoff: never, fresh, or bootstrap (or set ${CURSOR_CLOUD_CONTEXT_ENV})`,
272
- type: "string",
273
- default: "",
274
- });
275
- pi.registerFlag("cursor-cloud-direct-push", {
276
- description: `Allow Cursor cloud direct push for this run (or set ${CURSOR_CLOUD_DIRECT_PUSH_ENV}=1)`,
277
- type: "boolean",
278
- default: false,
279
- });
280
- pi.registerFlag("cursor-cloud-auto-create-pr", {
281
- description: `Ask Cursor cloud to create a pull request for this run (or set ${CURSOR_CLOUD_AUTO_CREATE_PR_ENV}=1)`,
282
- type: "boolean",
283
- default: false,
284
- });
285
- pi.registerFlag("cursor-cloud-skip-reviewer-request", {
286
- description: `Ask Cursor cloud not to request you as a reviewer for this run (or set ${CURSOR_CLOUD_SKIP_REVIEWER_REQUEST_ENV}=1)`,
287
- type: "boolean",
288
- default: false,
289
- });
290
- pi.registerFlag("cursor-cloud-allow-local-state", {
291
- description: `Allow Cursor cloud to proceed with local-only state for this run (or set ${CURSOR_CLOUD_ALLOW_LOCAL_STATE_ENV}=1)`,
292
- type: "boolean",
293
- default: false,
294
- });
295
- pi.registerFlag("cursor-cloud-env", {
296
- description: `Reserved Cursor cloud env var names; env forwarding is not implemented yet (or set ${CURSOR_CLOUD_ENV_ENV})`,
297
- type: "string",
298
- default: "",
299
- });
300
- pi.registerFlag("cursor-cloud-env-from-files", {
301
- description: `Reserved Cursor cloud env-file forwarding flag; not implemented yet (or set ${CURSOR_CLOUD_ENV_FROM_FILES_ENV}=1)`,
302
- type: "boolean",
303
- default: false,
304
- });
305
- pi.registerFlag("cursor-cloud-env-type", {
306
- description: `Select Cursor-managed cloud environment type: cloud, pool, or machine (or set ${CURSOR_CLOUD_ENV_TYPE_ENV})`,
307
- type: "string",
308
- default: "",
309
- });
310
- pi.registerFlag("cursor-cloud-env-name", {
311
- description: `Select Cursor-managed cloud environment name (or set ${CURSOR_CLOUD_ENV_NAME_ENV})`,
312
- type: "string",
313
- default: "",
314
- });
315
- pi.registerFlag("cursor-cloud-ack", {
316
- description: `Acknowledge first-use Cursor cloud runtime risks for this run (or set ${CURSOR_CLOUD_ACK_ENV}=1)`,
317
- type: "boolean",
318
- default: false,
319
- });
126
+ export function registerCursorLocalRuntimeFlags(pi: Pick<ExtensionAPI, "registerFlag">): void {
320
127
  pi.registerFlag("cursor-auto-review", {
321
128
  description: `Enable Cursor SDK local Auto-review for this run (or set ${CURSOR_AUTO_REVIEW_ENV}=1)`,
322
129
  type: "boolean",
@@ -344,154 +151,8 @@ function registerCursorRuntimeFlags(pi: Pick<ExtensionAPI, "registerFlag">): voi
344
151
  });
345
152
  }
346
153
 
347
- async function confirmCloudAcknowledgement(
348
- ctx: Pick<ExtensionContext, "hasUI" | "ui">,
349
- alreadyAcknowledged: boolean,
350
- ): Promise<boolean> {
351
- if (alreadyAcknowledged) return true;
352
- if (!ctx.hasUI) {
353
- ctx.ui.notify(
354
- `Cursor cloud runtime requires first-use acknowledgement. Pass --cursor-cloud-ack or set ${CURSOR_CLOUD_ACK_ENV}=1, then retry.`,
355
- "error",
356
- );
357
- return false;
358
- }
359
- const confirmed = await ctx.ui.confirm("Enable Cursor Cloud runtime?", CURSOR_CLOUD_ACK_DISCLOSURE);
360
- if (!confirmed) ctx.ui.notify("Cursor cloud runtime change cancelled; no settings were written.", "info");
361
- return confirmed;
362
- }
363
-
364
- function registerCursorRuntimeCommand(
365
- pi: Pick<ExtensionAPI, "appendEntry" | "registerCommand">,
366
- refreshStatus: CursorStatusRefresh,
367
- ): void {
368
- pi.registerCommand("cursor-runtime", {
369
- description: "Set Cursor runtime for this session: local or cloud",
370
- handler: async (args, ctx) => {
371
- const usage = "Usage: /cursor-runtime local|cloud [--save-user|--save-project]";
372
- const tokens = args.trim().split(/\s+/).filter(Boolean);
373
- const raw = tokens[0];
374
- const saveUser = tokens.includes("--save-user");
375
- const saveProject = tokens.includes("--save-project");
376
- const extra = tokens.slice(1).filter((token) => token !== "--save-user" && token !== "--save-project");
377
- const currentResolution = resolveCursorStatusRuntime(ctx);
378
- if (!raw) {
379
- ctx.ui.notify(
380
- currentResolution.kind === "invalid"
381
- ? `${currentResolution.message} ${usage}`
382
- : `Cursor runtime is ${formatResolvedCursorRuntime(currentResolution.runtime)}. ${usage}`,
383
- currentResolution.kind === "invalid" ? "error" : "info",
384
- );
385
- return;
386
- }
387
- if ((raw !== "local" && raw !== "cloud") || extra.length > 0 || (saveUser && saveProject)) {
388
- ctx.ui.notify(`Invalid Cursor runtime arguments. ${usage}`, "error");
389
- return;
390
- }
391
- if (currentResolution.kind === "invalid") {
392
- ctx.ui.notify(`${currentResolution.message} Fix the explicit override before changing the session runtime.`, "error");
393
- return;
394
- }
395
- if (saveProject && (getCursorSessionCwd() !== ctx.cwd || !getCursorSessionProjectTrusted())) {
396
- ctx.ui.notify(
397
- "Cannot save Cursor project config without explicit project-trust provenance. Ensure .pi/settings.json or another Pi project resource exists, trust the project, then restart pi; or restart with --approve. Project-local package installs must use --approve on every run that reads or writes .pi/cursor-sdk.json.",
398
- "error",
399
- );
400
- return;
401
- }
402
- const cloudAcknowledged = raw === "cloud" && await confirmCloudAcknowledgement(
403
- ctx,
404
- resolveEffectiveCursorConfigForContext(ctx).cloud.acknowledged.value,
405
- );
406
- if (raw === "cloud" && !cloudAcknowledged) return;
407
- if (saveUser || saveProject) {
408
- try {
409
- if (saveUser) {
410
- updateCursorSdkConfig(
411
- getCursorSdkUserConfigPath(),
412
- (current) => mergeCursorSdkConfigForUpdate(current, {
413
- runtime: raw,
414
- ...(cloudAcknowledged ? { cloud: { acknowledged: true } } : {}),
415
- }),
416
- { newFileMode: 0o600 },
417
- );
418
- } else {
419
- updateCursorSdkConfig(
420
- getCursorSdkProjectConfigPath(ctx.cwd),
421
- (current) => mergeCursorSdkConfigForUpdate(current, { runtime: raw }),
422
- );
423
- }
424
- } catch (error) {
425
- const effectiveResolution = resolveCursorStatusRuntime(ctx);
426
- refreshStatus(ctx);
427
- const effective = effectiveResolution.kind === "valid"
428
- ? ` Effective runtime remains ${formatResolvedCursorRuntime(effectiveResolution.runtime)}.`
429
- : ` ${effectiveResolution.message}`;
430
- ctx.ui.notify(
431
- `Failed to save Cursor runtime preference to ${saveUser ? "user" : "project"} config: ${error instanceof Error ? error.message : String(error)}.${effective}`,
432
- "error",
433
- );
434
- return;
435
- }
436
- }
437
- try {
438
- persistCursorRuntimePreference(pi, raw, cloudAcknowledged);
439
- } catch (error) {
440
- const effectiveResolution = resolveCursorStatusRuntime(ctx);
441
- refreshStatus(ctx);
442
- const effective = effectiveResolution.kind === "valid"
443
- ? ` Effective runtime is ${formatResolvedCursorRuntime(effectiveResolution.runtime)}.`
444
- : ` ${effectiveResolution.message}`;
445
- const persisted = saveUser
446
- ? "User config was saved, but persisting the session runtime entry failed."
447
- : saveProject
448
- ? "Project config was saved, but persisting the session runtime entry failed."
449
- : "Persisting the session runtime entry failed.";
450
- ctx.ui.notify(
451
- `${persisted} ${error instanceof Error ? error.message : String(error)}.${effective}`,
452
- "error",
453
- );
454
- return;
455
- }
456
- const effectiveResolution = resolveCursorStatusRuntime(ctx);
457
- refreshStatus(ctx);
458
- const saved = saveUser
459
- ? " Saved to user config."
460
- : saveProject
461
- ? " Saved to project config; cloud acknowledgement remains session/user-scoped."
462
- : "";
463
- const effective = effectiveResolution.kind === "valid"
464
- ? ` Effective runtime is ${formatResolvedCursorRuntime(effectiveResolution.runtime)}.`
465
- : ` ${effectiveResolution.message}`;
466
- ctx.ui.notify(
467
- raw === "cloud"
468
- ? `Cursor runtime request saved for this session and cloud risk acknowledged.${effective}${saved}`
469
- : `Cursor runtime request saved for this session.${effective}${saved}`,
470
- "info",
471
- );
472
- },
473
- });
474
- }
475
-
476
- export function registerCursorCloudRuntimeControls(
477
- pi: CursorRuntimeStateExtensionApi,
478
- options: { refreshStatus: CursorStatusRefresh },
479
- ): void {
480
- registerCursorCloudLifecycleLedger(pi);
481
- registerCursorRuntimeFlags(pi);
482
- registerCursorRuntimeCommand(pi, options.refreshStatus);
483
- pi.registerCommand("cursor-cloud", {
484
- description: "List, archive, or delete recorded Cursor cloud agents for this session branch",
485
- handler: async (args, ctx) => {
486
- await runCursorCloudLifecycleCommand(pi, args, ctx);
487
- },
488
- });
489
- }
490
-
491
154
  export function resetCursorRuntimeStateForTests(): void {
492
155
  cliCursorSnapshot = { config: {} };
493
156
  cliLocalForceConsumed = false;
494
157
  envLocalForceConsumed = false;
495
- sessionCursorRuntime = undefined;
496
- sessionCursorCloudAcknowledged = false;
497
158
  }
@@ -1,5 +1,4 @@
1
1
  import type { SDKAgent } from "@cursor/sdk";
2
- import type { CursorRuntime } from "./cursor-config.js";
3
2
  import { asRecord, getArray, getString } from "./cursor-record-utils.js";
4
3
  import { readCursorSdkTurnUsage, type CursorSdkTurnUsage } from "./cursor-usage-accounting.js";
5
4
 
@@ -9,10 +8,6 @@ const BILLED_USAGE_TIMEOUT_MS = 5000;
9
8
  // Upgrade to session-scoped storage if multi-day processes retain enough agent IDs to matter.
10
9
  const seenBilledRunIdsByAgent = new Map<string, Set<string>>();
11
10
 
12
- export function isCursorSdkClientMintedRunId(runId: string): boolean {
13
- return runId.startsWith("run-");
14
- }
15
-
16
11
  export function sumCursorSdkTurnUsage(usages: readonly CursorSdkTurnUsage[]): CursorSdkTurnUsage | undefined {
17
12
  if (usages.length === 0) return undefined;
18
13
  return usages.reduce(
@@ -52,7 +47,7 @@ function withTimeout<T>(promise: Promise<T>, timeoutMs = BILLED_USAGE_TIMEOUT_MS
52
47
 
53
48
  export function selectCursorBilledTurnUsage(
54
49
  agentUsage: unknown,
55
- options: { runtime: CursorRuntime; runId?: string; seenRunIds?: ReadonlySet<string> },
50
+ options: { seenRunIds?: ReadonlySet<string> },
56
51
  ): { turn?: CursorSdkTurnUsage; runIds: string[] } {
57
52
  const runs = (getArray(asRecord(agentUsage), "runs") ?? []).flatMap((item) => {
58
53
  const record = asRecord(item);
@@ -60,40 +55,24 @@ export function selectCursorBilledTurnUsage(
60
55
  const usage = readCursorSdkTurnUsage(record?.usage);
61
56
  return runId && usage ? [{ runId, usage }] : [];
62
57
  });
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
58
  const unseen = runs.filter((run) => !options.seenRunIds?.has(run.runId));
68
59
  return { turn: sumCursorSdkTurnUsage(unseen.map((run) => run.usage)), runIds: unseen.map((run) => run.runId) };
69
60
  }
70
61
 
71
62
  export async function fetchCursorSdkAgentUsage(
72
63
  agent: SDKAgent,
73
- options: { runtime: CursorRuntime; runId?: string },
74
64
  ): Promise<unknown | undefined> {
75
65
  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)));
66
+ return withTimeout(Promise.resolve(agent.getUsage()));
81
67
  }
82
68
 
83
69
  export async function attachCursorSdkBilledTurnUsage(options: {
84
70
  agent: SDKAgent;
85
71
  agentId: string;
86
- runtime: CursorRuntime;
87
- runId?: string;
88
72
  }): Promise<{ agentUsage?: unknown; turn?: CursorSdkTurnUsage }> {
89
- const agentUsage = await fetchCursorSdkAgentUsage(options.agent, {
90
- runtime: options.runtime,
91
- runId: options.runId,
92
- });
73
+ const agentUsage = await fetchCursorSdkAgentUsage(options.agent);
93
74
  if (!agentUsage) return {};
94
75
  const selected = selectCursorBilledTurnUsage(agentUsage, {
95
- runtime: options.runtime,
96
- runId: options.runtime === "cloud" ? options.runId : undefined,
97
76
  seenRunIds: peekCursorBilledUsageRunIds(options.agentId),
98
77
  });
99
78
  rememberCursorBilledUsageRunIds(options.agentId, selected.runIds);
@@ -0,0 +1,106 @@
1
+ import { accessSync, constants, existsSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, isAbsolute, join } from "node:path";
4
+
5
+ const RIPGREP_ENV = "CURSOR_RIPGREP_PATH";
6
+ const TREE_SITTER_VENDOR_ENV = "CURSOR_TREE_SITTER_VENDOR_DIR";
7
+
8
+ function cursorSdkPlatformPackageName(): string {
9
+ return `@cursor/sdk-${process.platform}-${process.arch}`;
10
+ }
11
+
12
+ function resolveCursorSdkPlatformPackageDir(
13
+ fromModuleUrl: string | URL = import.meta.url,
14
+ ): string | undefined {
15
+ try {
16
+ const require = createRequire(fromModuleUrl);
17
+ const sdkEntry = require.resolve("@cursor/sdk");
18
+ return dirname(
19
+ require.resolve(`${cursorSdkPlatformPackageName()}/package.json`, {
20
+ paths: [dirname(sdkEntry)],
21
+ }),
22
+ );
23
+ } catch {
24
+ return undefined;
25
+ }
26
+ }
27
+
28
+ function resolveCursorSdkPlatformBinary(
29
+ binaryName: string,
30
+ fromModuleUrl: string | URL = import.meta.url,
31
+ ): string | undefined {
32
+ try {
33
+ const packageDirectory = resolveCursorSdkPlatformPackageDir(fromModuleUrl);
34
+ if (!packageDirectory) return undefined;
35
+ const binaryPath = join(packageDirectory, "bin", binaryName);
36
+ accessSync(binaryPath, constants.X_OK);
37
+ return binaryPath;
38
+ } catch {
39
+ return undefined;
40
+ }
41
+ }
42
+
43
+ export function resolveBundledCursorRipgrepPath(
44
+ fromModuleUrl: string | URL = import.meta.url,
45
+ ): string | undefined {
46
+ return resolveCursorSdkPlatformBinary(process.platform === "win32" ? "rg.exe" : "rg", fromModuleUrl);
47
+ }
48
+
49
+ export function ensureCursorRipgrepPath(): string | undefined {
50
+ const configuredPath = process.env[RIPGREP_ENV];
51
+ if (configuredPath && isAbsolute(configuredPath)) return configuredPath;
52
+
53
+ const bundledPath = resolveBundledCursorRipgrepPath();
54
+ if (bundledPath) process.env[RIPGREP_ENV] = bundledPath;
55
+ return bundledPath;
56
+ }
57
+
58
+ export function resolveBundledCursorTreeSitterVendorDir(
59
+ fromModuleUrl: string | URL = import.meta.url,
60
+ ): string | undefined {
61
+ const packageDirectory = resolveCursorSdkPlatformPackageDir(fromModuleUrl);
62
+ if (!packageDirectory) return undefined;
63
+ const vendorDir = join(packageDirectory, "vendor");
64
+ if (!existsSync(join(vendorDir, "tree-sitter", "index.js"))) return undefined;
65
+ return vendorDir;
66
+ }
67
+
68
+ export function ensureCursorTreeSitterVendorDir(): string | undefined {
69
+ const configuredPath = process.env[TREE_SITTER_VENDOR_ENV];
70
+ if (configuredPath && isAbsolute(configuredPath)) return configuredPath;
71
+
72
+ const bundledPath = resolveBundledCursorTreeSitterVendorDir();
73
+ if (bundledPath) process.env[TREE_SITTER_VENDOR_ENV] = bundledPath;
74
+ return bundledPath;
75
+ }
76
+
77
+ export function resolveBundledCursorSandboxPath(
78
+ fromModuleUrl: string | URL = import.meta.url,
79
+ ): string | undefined {
80
+ return resolveCursorSdkPlatformBinary(
81
+ process.platform === "win32" ? "cursorsandbox.exe" : "cursorsandbox",
82
+ fromModuleUrl,
83
+ );
84
+ }
85
+
86
+ /**
87
+ * The SDK finds `cursorsandbox` by walking from `process.argv[1]`, not from
88
+ * `require.resolve`. Point that walk at the installed platform package for the
89
+ * duration of local `Agent.create` / `Agent.resume`.
90
+ */
91
+ export async function runWithCursorSdkPlatformPackageVisible<T>(
92
+ run: () => Promise<T>,
93
+ fromModuleUrl: string | URL = import.meta.url,
94
+ ): Promise<T> {
95
+ const packageDirectory = resolveCursorSdkPlatformPackageDir(fromModuleUrl);
96
+ if (!packageDirectory) return run();
97
+ const hostEntry = join(packageDirectory, "package.json");
98
+ if (!existsSync(hostEntry)) return run();
99
+ const previousArgv1 = process.argv[1];
100
+ process.argv[1] = hostEntry;
101
+ try {
102
+ return await run();
103
+ } finally {
104
+ if (process.argv[1] === hostEntry) process.argv[1] = previousArgv1;
105
+ }
106
+ }