pi-cursor-sdk 0.1.55 → 0.1.57
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.
- package/CHANGELOG.md +60 -2
- package/README.md +100 -28
- package/docs/cursor-live-smoke-checklist.md +7 -7
- package/docs/cursor-model-ux-spec.md +48 -41
- package/docs/cursor-native-tool-replay.md +4 -4
- package/docs/cursor-native-tool-visual-audit.md +1 -1
- package/docs/cursor-testing-lessons.md +20 -7
- package/docs/cursor-tool-surfaces.md +13 -2
- package/docs/platform-smoke-implementation.md +220 -0
- package/docs/platform-smoke.md +183 -247
- package/package.json +39 -7
- package/platform-smoke.config.mjs +5 -1
- package/scripts/cloud-runtime-smoke.d.mts +3 -0
- package/scripts/cloud-runtime-smoke.mjs +502 -0
- package/scripts/debug-provider-events.mjs +7 -2
- package/scripts/isolated-cursor-smoke.sh +4 -6
- package/scripts/lib/cursor-child-process.d.mts +1 -0
- package/scripts/lib/cursor-child-process.mjs +137 -7
- package/scripts/lib/local-resume-smoke-harness.mjs +543 -0
- package/scripts/local-resume-cleanup-smoke.mjs +108 -0
- package/scripts/local-resume-smoke.d.mts +1 -0
- package/scripts/local-resume-smoke.mjs +642 -0
- package/scripts/platform-smoke/artifact-anchored-extract.d.mts +10 -0
- package/scripts/platform-smoke/artifact-anchored-extract.mjs +111 -0
- package/scripts/platform-smoke/artifact-bundle-chunk.mjs +57 -0
- package/scripts/platform-smoke/artifact-bundle-contract.mjs +34 -0
- package/scripts/platform-smoke/artifact-fs-safety.mjs +311 -0
- package/scripts/platform-smoke/artifact-openat-extract.c +335 -0
- package/scripts/platform-smoke/artifact-secrets.mjs +155 -0
- package/scripts/platform-smoke/artifacts.mjs +293 -65
- package/scripts/platform-smoke/card-detect.mjs +16 -4
- package/scripts/platform-smoke/crabbox-runner.mjs +45 -3
- package/scripts/platform-smoke/doctor.mjs +20 -10
- package/scripts/platform-smoke/live-suite-runner.mjs +18 -57
- package/scripts/platform-smoke/local-resume-runner.mjs +252 -0
- package/scripts/platform-smoke/local-resume-suites.d.mts +15 -0
- package/scripts/platform-smoke/local-resume-suites.mjs +104 -0
- package/scripts/platform-smoke/scenarios.mjs +16 -2
- package/scripts/platform-smoke/target-runtime.mjs +206 -0
- package/scripts/platform-smoke/targets.mjs +33 -141
- package/scripts/platform-smoke/visual-evidence.mjs +6 -7
- package/scripts/platform-smoke/wrapped-line-match.mjs +9 -0
- package/scripts/platform-smoke.mjs +40 -27
- package/scripts/refresh-cursor-model-snapshots.mjs +18 -6
- package/scripts/steering-rpc-smoke.mjs +12 -2
- package/scripts/tmux-live-smoke.sh +3 -5
- package/shared/cursor-cloud-lifecycle-constants.d.mts +3 -0
- package/shared/cursor-cloud-lifecycle-constants.mjs +7 -0
- package/shared/cursor-sensitive-text.mjs +7 -1
- package/src/context.ts +5 -2
- package/src/cursor-agents-context-registration.ts +7 -0
- package/src/cursor-agents-context.ts +3 -1
- package/src/cursor-api-key.ts +15 -1
- package/src/cursor-bridge-contract.ts +3 -0
- package/src/cursor-cloud-lifecycle.ts +733 -0
- package/src/cursor-cloud-options.ts +206 -0
- package/src/cursor-cloud-reporting.ts +246 -0
- package/src/cursor-config.ts +659 -0
- package/src/cursor-display-only-trace.ts +14 -0
- package/src/cursor-display-text.ts +8 -2
- package/src/cursor-durable-fs.ts +49 -0
- package/src/cursor-fallback-models.generated.ts +2045 -485
- package/src/cursor-live-run-accounting.ts +7 -1
- package/src/cursor-live-run-coordinator.ts +1 -0
- package/src/cursor-pi-tool-bridge-run.ts +14 -4
- package/src/cursor-provider-errors.ts +31 -5
- package/src/cursor-provider-live-run-drain.ts +5 -0
- package/src/cursor-provider-run-finalizer.ts +37 -28
- package/src/cursor-provider-run-outcome.ts +7 -2
- package/src/cursor-provider-turn-finalize.ts +77 -7
- package/src/cursor-provider-turn-prepare.ts +228 -12
- package/src/cursor-provider-turn-runner.ts +42 -13
- package/src/cursor-provider-turn-send.ts +59 -16
- package/src/cursor-provider-turn-types.ts +44 -10
- package/src/cursor-runtime-state.ts +478 -0
- package/src/cursor-sdk-event-debug.ts +46 -6
- package/src/cursor-sdk-process-error-guard.ts +101 -30
- package/src/cursor-session-agent-cleanup.ts +328 -0
- package/src/cursor-session-agent-resume.ts +439 -0
- package/src/cursor-session-agent.ts +109 -13
- package/src/cursor-session-scope.ts +35 -2
- package/src/cursor-session-send-policy.ts +1 -1
- package/src/cursor-skill-tool.ts +30 -11
- package/src/cursor-state.ts +112 -69
- package/src/cursor-tool-manifest.ts +1 -1
- package/src/cursor-usage-accounting.ts +14 -4
- package/src/index.ts +11 -2
- package/src/model-discovery.ts +10 -56
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext, SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
registerCursorCloudLifecycleLedger,
|
|
4
|
+
runCursorCloudLifecycleCommand,
|
|
5
|
+
} from "./cursor-cloud-lifecycle.js";
|
|
6
|
+
import {
|
|
7
|
+
CURSOR_AUTO_REVIEW_ENV,
|
|
8
|
+
CURSOR_CLOUD_ACK_ENV,
|
|
9
|
+
CURSOR_CLOUD_ALLOW_LOCAL_STATE_ENV,
|
|
10
|
+
CURSOR_CLOUD_BRANCH_ENV,
|
|
11
|
+
CURSOR_CLOUD_CONTEXT_ENV,
|
|
12
|
+
CURSOR_CLOUD_DIRECT_PUSH_ENV,
|
|
13
|
+
CURSOR_CLOUD_ENV_ENV,
|
|
14
|
+
CURSOR_CLOUD_ENV_FROM_FILES_ENV,
|
|
15
|
+
CURSOR_CLOUD_ENV_NAME_ENV,
|
|
16
|
+
CURSOR_CLOUD_ENV_TYPE_ENV,
|
|
17
|
+
CURSOR_CLOUD_REPO_ENV,
|
|
18
|
+
CURSOR_LOCAL_FORCE_ENV,
|
|
19
|
+
CURSOR_LOCAL_RESUME_ENV,
|
|
20
|
+
CURSOR_RUNTIME_ENV,
|
|
21
|
+
CURSOR_SANDBOX_ENV,
|
|
22
|
+
loadCursorSdkConfig,
|
|
23
|
+
loadCursorSdkProjectConfig,
|
|
24
|
+
loadCursorSdkUserConfig,
|
|
25
|
+
mergeCursorSdkConfig,
|
|
26
|
+
parseCursorSdkConfig,
|
|
27
|
+
parseExplicitCursorCloudEnvNames,
|
|
28
|
+
resolveCursorSdkConfig,
|
|
29
|
+
saveCursorSdkProjectConfig,
|
|
30
|
+
saveCursorSdkUserConfig,
|
|
31
|
+
type CursorExplicitSdkConfig,
|
|
32
|
+
type CursorResolvedSdkConfig,
|
|
33
|
+
type CursorResolvedSetting,
|
|
34
|
+
type CursorRuntime,
|
|
35
|
+
type CursorSdkConfig,
|
|
36
|
+
} from "./cursor-config.js";
|
|
37
|
+
import { asRecord } from "./cursor-record-utils.js";
|
|
38
|
+
|
|
39
|
+
export const CURSOR_RUNTIME_ENTRY_TYPE = "cursor-runtime-state";
|
|
40
|
+
|
|
41
|
+
export const CURSOR_CLOUD_ACK_DISCLOSURE = [
|
|
42
|
+
"Cursor Cloud executes this work remotely.",
|
|
43
|
+
"Fresh context is used by default; prior Pi context is included only with explicit bootstrap opt-in.",
|
|
44
|
+
"Pi-local tools and the Pi bridge are unavailable, and Pi environment variables are not forwarded.",
|
|
45
|
+
"Cursor may create branches, commit, push, and open pull requests.",
|
|
46
|
+
"Cloud agents remain until you archive or delete them.",
|
|
47
|
+
"Cloud Agents run in Max Mode, are billed at Cursor API pricing, and may require spend-limit setup.",
|
|
48
|
+
].join("\n\n");
|
|
49
|
+
|
|
50
|
+
interface CursorRuntimeEntryData {
|
|
51
|
+
runtime: CursorRuntime;
|
|
52
|
+
cloudAcknowledged?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type CursorRuntimeStateExtensionApi = Pick<
|
|
56
|
+
ExtensionAPI,
|
|
57
|
+
"appendEntry" | "getFlag" | "registerFlag" | "registerCommand" | "on"
|
|
58
|
+
>;
|
|
59
|
+
|
|
60
|
+
type CursorRuntimeContext = Pick<ExtensionContext, "cwd"> & Partial<Pick<ExtensionContext, "isProjectTrusted">>;
|
|
61
|
+
|
|
62
|
+
type CursorStatusRefresh = (ctx: ExtensionContext) => void;
|
|
63
|
+
|
|
64
|
+
let cliAutoReview = false;
|
|
65
|
+
let cliSandbox = false;
|
|
66
|
+
let cliLocalForce = false;
|
|
67
|
+
let cliLocalForceConsumed = false;
|
|
68
|
+
let cliLocalResume = false;
|
|
69
|
+
let cliNoLocalResume = false;
|
|
70
|
+
let envLocalForceConsumed = false;
|
|
71
|
+
let cliCursorRuntime: string | undefined;
|
|
72
|
+
let cliCursorCloudRepo: string | undefined;
|
|
73
|
+
let cliCursorCloudBranch: string | undefined;
|
|
74
|
+
let cliCursorCloudContext: string | undefined;
|
|
75
|
+
let cliCursorCloudDirectPush = false;
|
|
76
|
+
let cliCursorCloudAllowLocalState = false;
|
|
77
|
+
let cliCursorCloudEnv: string | undefined;
|
|
78
|
+
let cliCursorCloudEnvFromFiles = false;
|
|
79
|
+
let cliCursorCloudEnvType: string | undefined;
|
|
80
|
+
let cliCursorCloudEnvName: string | undefined;
|
|
81
|
+
let cliCursorCloudAck = false;
|
|
82
|
+
let sessionCursorRuntime: CursorRuntime | undefined;
|
|
83
|
+
let sessionCursorCloudAcknowledged = false;
|
|
84
|
+
|
|
85
|
+
function isCursorRuntimeEntryData(value: unknown): value is CursorRuntimeEntryData {
|
|
86
|
+
const record = asRecord(value);
|
|
87
|
+
if (!record) return false;
|
|
88
|
+
return (record.runtime === "local" || record.runtime === "cloud")
|
|
89
|
+
&& (record.cloudAcknowledged === undefined || typeof record.cloudAcknowledged === "boolean");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function stringFlagValue(value: boolean | string | undefined): string | undefined {
|
|
93
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getCursorCliConfig(): CursorExplicitSdkConfig {
|
|
97
|
+
const parsed = parseCursorSdkConfig({
|
|
98
|
+
cloud: {
|
|
99
|
+
repo: cliCursorCloudRepo,
|
|
100
|
+
branch: cliCursorCloudBranch,
|
|
101
|
+
...(cliCursorCloudDirectPush ? { directPush: true } : {}),
|
|
102
|
+
...(cliCursorCloudAllowLocalState ? { allowLocalState: true } : {}),
|
|
103
|
+
envNames: parseExplicitCursorCloudEnvNames(cliCursorCloudEnv, "--cursor-cloud-env"),
|
|
104
|
+
...(cliCursorCloudEnvFromFiles ? { envFromFiles: true } : {}),
|
|
105
|
+
environment: {
|
|
106
|
+
type: cliCursorCloudEnvType,
|
|
107
|
+
name: cliCursorCloudEnvName,
|
|
108
|
+
},
|
|
109
|
+
...(cliCursorCloudAck ? { acknowledged: true } : {}),
|
|
110
|
+
},
|
|
111
|
+
local: {
|
|
112
|
+
...(cliAutoReview ? { autoReview: true } : {}),
|
|
113
|
+
...(cliSandbox ? { sandboxOptions: { enabled: true } } : {}),
|
|
114
|
+
...(cliLocalForce ? { force: true } : {}),
|
|
115
|
+
...(cliNoLocalResume ? { resume: false } : cliLocalResume ? { resume: true } : {}),
|
|
116
|
+
},
|
|
117
|
+
}) ?? {};
|
|
118
|
+
return {
|
|
119
|
+
...parsed,
|
|
120
|
+
...(cliCursorRuntime ? { runtime: cliCursorRuntime } : {}),
|
|
121
|
+
...(cliCursorCloudContext
|
|
122
|
+
? { cloud: { ...parsed.cloud, contextHandoff: cliCursorCloudContext } }
|
|
123
|
+
: {}),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function getCursorSessionConfig(): CursorSdkConfig {
|
|
128
|
+
return sessionCursorRuntime
|
|
129
|
+
? {
|
|
130
|
+
runtime: sessionCursorRuntime,
|
|
131
|
+
...(sessionCursorCloudAcknowledged ? { cloud: { acknowledged: true } } : {}),
|
|
132
|
+
}
|
|
133
|
+
: {};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function resolveEffectiveCursorConfig(options: {
|
|
137
|
+
cwd: string;
|
|
138
|
+
projectTrusted?: boolean;
|
|
139
|
+
}): CursorResolvedSdkConfig {
|
|
140
|
+
const loadedConfig = loadCursorSdkConfig({ cwd: options.cwd, projectTrusted: options.projectTrusted === true });
|
|
141
|
+
return resolveCursorSdkConfig({
|
|
142
|
+
cli: getCursorCliConfig(),
|
|
143
|
+
session: getCursorSessionConfig(),
|
|
144
|
+
user: loadedConfig.user,
|
|
145
|
+
project: loadedConfig.project,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function resolveEffectiveCursorConfigForContext(ctx: CursorRuntimeContext): CursorResolvedSdkConfig {
|
|
150
|
+
return resolveEffectiveCursorConfig({ cwd: ctx.cwd, projectTrusted: ctx.isProjectTrusted?.() === true });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export type CursorRuntimeResolution =
|
|
154
|
+
| { kind: "valid"; runtime: CursorResolvedSetting<CursorRuntime> }
|
|
155
|
+
| { kind: "invalid"; message: string };
|
|
156
|
+
|
|
157
|
+
export function resolveCursorStatusRuntime(ctx: CursorRuntimeContext): CursorRuntimeResolution {
|
|
158
|
+
try {
|
|
159
|
+
return { kind: "valid", runtime: resolveEffectiveCursorConfigForContext(ctx).runtime };
|
|
160
|
+
} catch (error) {
|
|
161
|
+
return { kind: "invalid", message: error instanceof Error ? error.message : String(error) };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function formatResolvedCursorRuntime(runtime: CursorResolvedSetting<CursorRuntime>): string {
|
|
166
|
+
const cap = runtime.cappedBy;
|
|
167
|
+
return cap
|
|
168
|
+
? `${runtime.value} (source: ${runtime.source} safety cap over ${cap.cappedSource} ${cap.cappedValue})`
|
|
169
|
+
: `${runtime.value} (source: ${runtime.source})`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function formatCursorStatus(
|
|
173
|
+
runtime: CursorRuntime | "invalid",
|
|
174
|
+
fast: boolean | undefined,
|
|
175
|
+
mode: "agent" | "plan" | "invalid",
|
|
176
|
+
): string {
|
|
177
|
+
const parts = [`cursor:${runtime}`, fast === true ? "fast:on" : fast === false ? "fast:off" : "fast:n/a"];
|
|
178
|
+
if (mode === "invalid") parts.push("mode invalid");
|
|
179
|
+
else if (mode === "plan") parts.push("plan");
|
|
180
|
+
return parts.join(" · ");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function consumeCursorLocalForceOverride(resolved: { value: boolean; source: string }): boolean {
|
|
184
|
+
if (!resolved.value) return false;
|
|
185
|
+
if (resolved.source === "cli" && !cliLocalForceConsumed) {
|
|
186
|
+
cliLocalForce = false;
|
|
187
|
+
cliLocalForceConsumed = true;
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
if (resolved.source === "environment" && !envLocalForceConsumed) {
|
|
191
|
+
envLocalForceConsumed = true;
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function restoreSessionCursorRuntimeState(branch: readonly SessionEntry[]): void {
|
|
198
|
+
sessionCursorRuntime = undefined;
|
|
199
|
+
sessionCursorCloudAcknowledged = false;
|
|
200
|
+
for (const entry of branch) {
|
|
201
|
+
if (entry.type !== "custom" || entry.customType !== CURSOR_RUNTIME_ENTRY_TYPE) continue;
|
|
202
|
+
if (isCursorRuntimeEntryData(entry.data)) {
|
|
203
|
+
sessionCursorRuntime = entry.data.runtime;
|
|
204
|
+
sessionCursorCloudAcknowledged ||= entry.data.cloudAcknowledged === true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function restoreCursorCliState(pi: Pick<ExtensionAPI, "getFlag">): void {
|
|
210
|
+
cliAutoReview = pi.getFlag("cursor-auto-review") === true;
|
|
211
|
+
cliSandbox = pi.getFlag("cursor-sandbox") === true;
|
|
212
|
+
cliLocalForce = !cliLocalForceConsumed && pi.getFlag("cursor-local-force") === true;
|
|
213
|
+
cliLocalResume = pi.getFlag("cursor-local-resume") === true;
|
|
214
|
+
cliNoLocalResume = pi.getFlag("cursor-no-local-resume") === true;
|
|
215
|
+
cliCursorRuntime = stringFlagValue(pi.getFlag("cursor-runtime"));
|
|
216
|
+
cliCursorCloudRepo = stringFlagValue(pi.getFlag("cursor-cloud-repo"));
|
|
217
|
+
cliCursorCloudBranch = stringFlagValue(pi.getFlag("cursor-cloud-branch"));
|
|
218
|
+
cliCursorCloudContext = stringFlagValue(pi.getFlag("cursor-cloud-context"));
|
|
219
|
+
cliCursorCloudDirectPush = pi.getFlag("cursor-cloud-direct-push") === true;
|
|
220
|
+
cliCursorCloudAllowLocalState = pi.getFlag("cursor-cloud-allow-local-state") === true;
|
|
221
|
+
cliCursorCloudEnv = stringFlagValue(pi.getFlag("cursor-cloud-env"));
|
|
222
|
+
cliCursorCloudEnvFromFiles = pi.getFlag("cursor-cloud-env-from-files") === true;
|
|
223
|
+
cliCursorCloudEnvType = stringFlagValue(pi.getFlag("cursor-cloud-env-type"));
|
|
224
|
+
cliCursorCloudEnvName = stringFlagValue(pi.getFlag("cursor-cloud-env-name"));
|
|
225
|
+
cliCursorCloudAck = pi.getFlag("cursor-cloud-ack") === true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function persistCursorRuntimePreference(
|
|
229
|
+
pi: Pick<ExtensionAPI, "appendEntry">,
|
|
230
|
+
runtime: CursorRuntime,
|
|
231
|
+
cloudAcknowledged = false,
|
|
232
|
+
): void {
|
|
233
|
+
const acknowledged = sessionCursorCloudAcknowledged || cloudAcknowledged;
|
|
234
|
+
pi.appendEntry<CursorRuntimeEntryData>(CURSOR_RUNTIME_ENTRY_TYPE, {
|
|
235
|
+
runtime,
|
|
236
|
+
...(acknowledged ? { cloudAcknowledged: true } : {}),
|
|
237
|
+
});
|
|
238
|
+
sessionCursorRuntime = runtime;
|
|
239
|
+
sessionCursorCloudAcknowledged = acknowledged;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function registerCursorRuntimeFlags(pi: Pick<ExtensionAPI, "registerFlag">): void {
|
|
243
|
+
pi.registerFlag("cursor-runtime", {
|
|
244
|
+
description: `Select Cursor runtime for this run: local or cloud (or set ${CURSOR_RUNTIME_ENV})`,
|
|
245
|
+
type: "string",
|
|
246
|
+
default: "",
|
|
247
|
+
});
|
|
248
|
+
pi.registerFlag("cursor-cloud-repo", {
|
|
249
|
+
description: `Set Cursor cloud repository URL for this run (or set ${CURSOR_CLOUD_REPO_ENV})`,
|
|
250
|
+
type: "string",
|
|
251
|
+
default: "",
|
|
252
|
+
});
|
|
253
|
+
pi.registerFlag("cursor-cloud-branch", {
|
|
254
|
+
description: `Set Cursor cloud branch/ref for this run (or set ${CURSOR_CLOUD_BRANCH_ENV})`,
|
|
255
|
+
type: "string",
|
|
256
|
+
default: "",
|
|
257
|
+
});
|
|
258
|
+
pi.registerFlag("cursor-cloud-context", {
|
|
259
|
+
description: `Set Cursor cloud context handoff: never, fresh, or bootstrap (or set ${CURSOR_CLOUD_CONTEXT_ENV})`,
|
|
260
|
+
type: "string",
|
|
261
|
+
default: "",
|
|
262
|
+
});
|
|
263
|
+
pi.registerFlag("cursor-cloud-direct-push", {
|
|
264
|
+
description: `Allow Cursor cloud direct push for this run (or set ${CURSOR_CLOUD_DIRECT_PUSH_ENV}=1)`,
|
|
265
|
+
type: "boolean",
|
|
266
|
+
default: false,
|
|
267
|
+
});
|
|
268
|
+
pi.registerFlag("cursor-cloud-allow-local-state", {
|
|
269
|
+
description: `Allow Cursor cloud to proceed with local-only state for this run (or set ${CURSOR_CLOUD_ALLOW_LOCAL_STATE_ENV}=1)`,
|
|
270
|
+
type: "boolean",
|
|
271
|
+
default: false,
|
|
272
|
+
});
|
|
273
|
+
pi.registerFlag("cursor-cloud-env", {
|
|
274
|
+
description: `Reserved Cursor cloud env var names; env forwarding is not implemented yet (or set ${CURSOR_CLOUD_ENV_ENV})`,
|
|
275
|
+
type: "string",
|
|
276
|
+
default: "",
|
|
277
|
+
});
|
|
278
|
+
pi.registerFlag("cursor-cloud-env-from-files", {
|
|
279
|
+
description: `Reserved Cursor cloud env-file forwarding flag; not implemented yet (or set ${CURSOR_CLOUD_ENV_FROM_FILES_ENV}=1)`,
|
|
280
|
+
type: "boolean",
|
|
281
|
+
default: false,
|
|
282
|
+
});
|
|
283
|
+
pi.registerFlag("cursor-cloud-env-type", {
|
|
284
|
+
description: `Select Cursor-managed cloud environment type: cloud, pool, or machine (or set ${CURSOR_CLOUD_ENV_TYPE_ENV})`,
|
|
285
|
+
type: "string",
|
|
286
|
+
default: "",
|
|
287
|
+
});
|
|
288
|
+
pi.registerFlag("cursor-cloud-env-name", {
|
|
289
|
+
description: `Select Cursor-managed cloud environment name (or set ${CURSOR_CLOUD_ENV_NAME_ENV})`,
|
|
290
|
+
type: "string",
|
|
291
|
+
default: "",
|
|
292
|
+
});
|
|
293
|
+
pi.registerFlag("cursor-cloud-ack", {
|
|
294
|
+
description: `Acknowledge first-use Cursor cloud runtime risks for this run (or set ${CURSOR_CLOUD_ACK_ENV}=1)`,
|
|
295
|
+
type: "boolean",
|
|
296
|
+
default: false,
|
|
297
|
+
});
|
|
298
|
+
pi.registerFlag("cursor-auto-review", {
|
|
299
|
+
description: `Enable Cursor SDK local Auto-review for this run (or set ${CURSOR_AUTO_REVIEW_ENV}=1)`,
|
|
300
|
+
type: "boolean",
|
|
301
|
+
default: false,
|
|
302
|
+
});
|
|
303
|
+
pi.registerFlag("cursor-sandbox", {
|
|
304
|
+
description: `Enable Cursor SDK local sandboxing for this run (or set ${CURSOR_SANDBOX_ENV}=1)`,
|
|
305
|
+
type: "boolean",
|
|
306
|
+
default: false,
|
|
307
|
+
});
|
|
308
|
+
pi.registerFlag("cursor-local-force", {
|
|
309
|
+
description: `Force-expire a stuck local Cursor SDK run before sending this run (or set ${CURSOR_LOCAL_FORCE_ENV}=1)`,
|
|
310
|
+
type: "boolean",
|
|
311
|
+
default: false,
|
|
312
|
+
});
|
|
313
|
+
pi.registerFlag("cursor-local-resume", {
|
|
314
|
+
description: `Resume recorded local Cursor SDK agents for matching pi session branches (default; or set ${CURSOR_LOCAL_RESUME_ENV}=1)`,
|
|
315
|
+
type: "boolean",
|
|
316
|
+
default: false,
|
|
317
|
+
});
|
|
318
|
+
pi.registerFlag("cursor-no-local-resume", {
|
|
319
|
+
description: `Disable local Cursor SDK agent resume for this run (or set ${CURSOR_LOCAL_RESUME_ENV}=0)`,
|
|
320
|
+
type: "boolean",
|
|
321
|
+
default: false,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function confirmCloudAcknowledgement(
|
|
326
|
+
ctx: Pick<ExtensionContext, "hasUI" | "ui">,
|
|
327
|
+
alreadyAcknowledged: boolean,
|
|
328
|
+
): Promise<boolean> {
|
|
329
|
+
if (alreadyAcknowledged) return true;
|
|
330
|
+
if (!ctx.hasUI) {
|
|
331
|
+
ctx.ui.notify(
|
|
332
|
+
`Cursor cloud runtime requires first-use acknowledgement. Pass --cursor-cloud-ack or set ${CURSOR_CLOUD_ACK_ENV}=1, then retry.`,
|
|
333
|
+
"error",
|
|
334
|
+
);
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
const confirmed = await ctx.ui.confirm("Enable Cursor Cloud runtime?", CURSOR_CLOUD_ACK_DISCLOSURE);
|
|
338
|
+
if (!confirmed) ctx.ui.notify("Cursor cloud runtime change cancelled; no settings were written.", "info");
|
|
339
|
+
return confirmed;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function registerCursorRuntimeCommand(
|
|
343
|
+
pi: Pick<ExtensionAPI, "appendEntry" | "registerCommand">,
|
|
344
|
+
refreshStatus: CursorStatusRefresh,
|
|
345
|
+
): void {
|
|
346
|
+
pi.registerCommand("cursor-runtime", {
|
|
347
|
+
description: "Set Cursor runtime for this session: local or cloud",
|
|
348
|
+
handler: async (args, ctx) => {
|
|
349
|
+
const usage = "Usage: /cursor-runtime local|cloud [--save-user|--save-project]";
|
|
350
|
+
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
351
|
+
const raw = tokens[0];
|
|
352
|
+
const saveUser = tokens.includes("--save-user");
|
|
353
|
+
const saveProject = tokens.includes("--save-project");
|
|
354
|
+
const extra = tokens.slice(1).filter((token) => token !== "--save-user" && token !== "--save-project");
|
|
355
|
+
const currentResolution = resolveCursorStatusRuntime(ctx);
|
|
356
|
+
if (!raw) {
|
|
357
|
+
ctx.ui.notify(
|
|
358
|
+
currentResolution.kind === "invalid"
|
|
359
|
+
? `${currentResolution.message} ${usage}`
|
|
360
|
+
: `Cursor runtime is ${formatResolvedCursorRuntime(currentResolution.runtime)}. ${usage}`,
|
|
361
|
+
currentResolution.kind === "invalid" ? "error" : "info",
|
|
362
|
+
);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if ((raw !== "local" && raw !== "cloud") || extra.length > 0 || (saveUser && saveProject)) {
|
|
366
|
+
ctx.ui.notify(`Invalid Cursor runtime arguments. ${usage}`, "error");
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (currentResolution.kind === "invalid") {
|
|
370
|
+
ctx.ui.notify(`${currentResolution.message} Fix the explicit override before changing the session runtime.`, "error");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const cloudAcknowledged = raw === "cloud" && await confirmCloudAcknowledgement(
|
|
374
|
+
ctx,
|
|
375
|
+
resolveEffectiveCursorConfigForContext(ctx).cloud.acknowledged.value,
|
|
376
|
+
);
|
|
377
|
+
if (raw === "cloud" && !cloudAcknowledged) return;
|
|
378
|
+
if (saveUser || saveProject) {
|
|
379
|
+
try {
|
|
380
|
+
if (saveUser) {
|
|
381
|
+
const current = loadCursorSdkUserConfig();
|
|
382
|
+
saveCursorSdkUserConfig(mergeCursorSdkConfig(current, {
|
|
383
|
+
runtime: raw,
|
|
384
|
+
...(cloudAcknowledged ? { cloud: { acknowledged: true } } : {}),
|
|
385
|
+
}));
|
|
386
|
+
} else {
|
|
387
|
+
const current = loadCursorSdkProjectConfig(ctx.cwd, true) ?? {};
|
|
388
|
+
saveCursorSdkProjectConfig(ctx.cwd, mergeCursorSdkConfig(current, { runtime: raw }));
|
|
389
|
+
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
const effectiveResolution = resolveCursorStatusRuntime(ctx);
|
|
392
|
+
refreshStatus(ctx);
|
|
393
|
+
const effective = effectiveResolution.kind === "valid"
|
|
394
|
+
? ` Effective runtime remains ${formatResolvedCursorRuntime(effectiveResolution.runtime)}.`
|
|
395
|
+
: ` ${effectiveResolution.message}`;
|
|
396
|
+
ctx.ui.notify(
|
|
397
|
+
`Failed to save Cursor runtime preference to ${saveUser ? "user" : "project"} config: ${error instanceof Error ? error.message : String(error)}.${effective}`,
|
|
398
|
+
"error",
|
|
399
|
+
);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
persistCursorRuntimePreference(pi, raw, cloudAcknowledged);
|
|
405
|
+
} catch (error) {
|
|
406
|
+
const effectiveResolution = resolveCursorStatusRuntime(ctx);
|
|
407
|
+
refreshStatus(ctx);
|
|
408
|
+
const effective = effectiveResolution.kind === "valid"
|
|
409
|
+
? ` Effective runtime is ${formatResolvedCursorRuntime(effectiveResolution.runtime)}.`
|
|
410
|
+
: ` ${effectiveResolution.message}`;
|
|
411
|
+
const persisted = saveUser
|
|
412
|
+
? "User config was saved, but persisting the session runtime entry failed."
|
|
413
|
+
: saveProject
|
|
414
|
+
? "Project config was saved, but persisting the session runtime entry failed."
|
|
415
|
+
: "Persisting the session runtime entry failed.";
|
|
416
|
+
ctx.ui.notify(
|
|
417
|
+
`${persisted} ${error instanceof Error ? error.message : String(error)}.${effective}`,
|
|
418
|
+
"error",
|
|
419
|
+
);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const effectiveResolution = resolveCursorStatusRuntime(ctx);
|
|
423
|
+
refreshStatus(ctx);
|
|
424
|
+
const saved = saveUser
|
|
425
|
+
? " Saved to user config."
|
|
426
|
+
: saveProject
|
|
427
|
+
? " Saved to project config; cloud acknowledgement remains session/user-scoped."
|
|
428
|
+
: "";
|
|
429
|
+
const effective = effectiveResolution.kind === "valid"
|
|
430
|
+
? ` Effective runtime is ${formatResolvedCursorRuntime(effectiveResolution.runtime)}.`
|
|
431
|
+
: ` ${effectiveResolution.message}`;
|
|
432
|
+
ctx.ui.notify(
|
|
433
|
+
raw === "cloud"
|
|
434
|
+
? `Cursor runtime request saved for this session and cloud risk acknowledged.${effective}${saved}`
|
|
435
|
+
: `Cursor runtime request saved for this session.${effective}${saved}`,
|
|
436
|
+
"info",
|
|
437
|
+
);
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export function registerCursorCloudRuntimeControls(
|
|
443
|
+
pi: CursorRuntimeStateExtensionApi,
|
|
444
|
+
options: { refreshStatus: CursorStatusRefresh },
|
|
445
|
+
): void {
|
|
446
|
+
registerCursorCloudLifecycleLedger(pi);
|
|
447
|
+
registerCursorRuntimeFlags(pi);
|
|
448
|
+
registerCursorRuntimeCommand(pi, options.refreshStatus);
|
|
449
|
+
pi.registerCommand("cursor-cloud", {
|
|
450
|
+
description: "List, archive, or delete recorded Cursor cloud agents for this session branch",
|
|
451
|
+
handler: async (args, ctx) => {
|
|
452
|
+
await runCursorCloudLifecycleCommand(pi, args, ctx);
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function resetCursorRuntimeStateForTests(): void {
|
|
458
|
+
cliAutoReview = false;
|
|
459
|
+
cliSandbox = false;
|
|
460
|
+
cliLocalForce = false;
|
|
461
|
+
cliLocalForceConsumed = false;
|
|
462
|
+
cliLocalResume = false;
|
|
463
|
+
cliNoLocalResume = false;
|
|
464
|
+
envLocalForceConsumed = false;
|
|
465
|
+
cliCursorRuntime = undefined;
|
|
466
|
+
cliCursorCloudRepo = undefined;
|
|
467
|
+
cliCursorCloudBranch = undefined;
|
|
468
|
+
cliCursorCloudContext = undefined;
|
|
469
|
+
cliCursorCloudDirectPush = false;
|
|
470
|
+
cliCursorCloudAllowLocalState = false;
|
|
471
|
+
cliCursorCloudEnv = undefined;
|
|
472
|
+
cliCursorCloudEnvFromFiles = false;
|
|
473
|
+
cliCursorCloudEnvType = undefined;
|
|
474
|
+
cliCursorCloudEnvName = undefined;
|
|
475
|
+
cliCursorCloudAck = false;
|
|
476
|
+
sessionCursorRuntime = undefined;
|
|
477
|
+
sessionCursorCloudAcknowledged = false;
|
|
478
|
+
}
|
|
@@ -36,6 +36,8 @@ export {
|
|
|
36
36
|
resolveCursorSdkEventDebugBaseDir,
|
|
37
37
|
} from "./cursor-sdk-event-debug-constants.js";
|
|
38
38
|
|
|
39
|
+
const MAX_CURSOR_SDK_EVENT_DEBUG_JSONL_BYTES = 2 * 1024 * 1024;
|
|
40
|
+
|
|
39
41
|
export type CursorSdkDisplayDecisionAction =
|
|
40
42
|
| "skip-duplicate"
|
|
41
43
|
| "skip-incomplete-fast-local"
|
|
@@ -123,6 +125,22 @@ function snapshotCursorSdkEventDebugRecord(record: unknown): unknown {
|
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
|
|
128
|
+
function serializeCursorSdkEventDebugRecord(record: unknown): string {
|
|
129
|
+
try {
|
|
130
|
+
const seen = new WeakSet<object>();
|
|
131
|
+
return JSON.stringify(snapshotCursorSdkEventDebugRecord(record), (_key, value: unknown) => {
|
|
132
|
+
if (typeof value === "bigint") return value.toString();
|
|
133
|
+
if (value && typeof value === "object") {
|
|
134
|
+
if (seen.has(value)) return "[Circular]";
|
|
135
|
+
seen.add(value);
|
|
136
|
+
}
|
|
137
|
+
return value;
|
|
138
|
+
}) ?? JSON.stringify({ type: "artifact_serialization_error" });
|
|
139
|
+
} catch {
|
|
140
|
+
return JSON.stringify({ type: "artifact_serialization_error" });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
126
144
|
export function resolveCursorSdkEventDebugEnabled(env: Record<string, string | undefined> = process.env): boolean {
|
|
127
145
|
return parseEnvBoolean(env[CURSOR_SDK_EVENT_DEBUG_ENV], false);
|
|
128
146
|
}
|
|
@@ -200,7 +218,11 @@ export function attachCursorSdkEventDebugPiStreamTap(
|
|
|
200
218
|
if (!resolveCursorSdkEventDebugEnabled()) return;
|
|
201
219
|
const originalPush = stream.push.bind(stream);
|
|
202
220
|
stream.push = (event) => {
|
|
203
|
-
|
|
221
|
+
try {
|
|
222
|
+
sinkRef.current?.recordPiStreamEvent(event);
|
|
223
|
+
} catch {
|
|
224
|
+
// Debug capture must never block the underlying stream.
|
|
225
|
+
}
|
|
204
226
|
return originalPush(event);
|
|
205
227
|
};
|
|
206
228
|
}
|
|
@@ -229,7 +251,9 @@ export class CursorSdkEventDebugSink {
|
|
|
229
251
|
errors: 0,
|
|
230
252
|
};
|
|
231
253
|
private metadata: Record<string, unknown>;
|
|
232
|
-
private readonly jsonlBuffers = new Map<string,
|
|
254
|
+
private readonly jsonlBuffers = new Map<string, string[]>();
|
|
255
|
+
private readonly jsonlBufferBytes = new Map<string, number>();
|
|
256
|
+
private readonly truncatedJsonlFiles = new Set<string>();
|
|
233
257
|
private finalized = false;
|
|
234
258
|
private finalizationPromise: Promise<void> | undefined;
|
|
235
259
|
private waitResultRecorded = false;
|
|
@@ -511,6 +535,8 @@ export class CursorSdkEventDebugSink {
|
|
|
511
535
|
streamCaptureErrors: this.streamCaptureErrors.map((error) =>
|
|
512
536
|
error instanceof Error ? error.message : String(error),
|
|
513
537
|
),
|
|
538
|
+
jsonlByteLimit: MAX_CURSOR_SDK_EVENT_DEBUG_JSONL_BYTES,
|
|
539
|
+
truncatedJsonlFiles: [...this.truncatedJsonlFiles].sort(),
|
|
514
540
|
};
|
|
515
541
|
this.flushJsonlBuffers();
|
|
516
542
|
writeFileSync(join(this.artifactDir, ARTIFACTS.summary), `${JSON.stringify(summary, null, 2)}\n`);
|
|
@@ -582,24 +608,38 @@ export class CursorSdkEventDebugSink {
|
|
|
582
608
|
}
|
|
583
609
|
|
|
584
610
|
private bufferJsonl(fileName: string, record: unknown): void {
|
|
585
|
-
if (this.finalized) return;
|
|
611
|
+
if (this.finalized || this.truncatedJsonlFiles.has(fileName)) return;
|
|
612
|
+
const line = `${serializeCursorSdkEventDebugRecord(record)}\n`;
|
|
613
|
+
const marker = `${JSON.stringify({ type: "artifact_truncated", limitBytes: MAX_CURSOR_SDK_EVENT_DEBUG_JSONL_BYTES })}\n`;
|
|
614
|
+
const usedBytes = this.jsonlBufferBytes.get(fileName) ?? 0;
|
|
615
|
+
const lineBytes = Buffer.byteLength(line);
|
|
616
|
+
const markerBytes = Buffer.byteLength(marker);
|
|
586
617
|
const records = this.jsonlBuffers.get(fileName) ?? [];
|
|
587
|
-
|
|
618
|
+
if (usedBytes + lineBytes > MAX_CURSOR_SDK_EVENT_DEBUG_JSONL_BYTES - markerBytes) {
|
|
619
|
+
records.push(marker);
|
|
620
|
+
this.jsonlBuffers.set(fileName, records);
|
|
621
|
+
this.jsonlBufferBytes.set(fileName, usedBytes + markerBytes);
|
|
622
|
+
this.truncatedJsonlFiles.add(fileName);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
records.push(line);
|
|
588
626
|
this.jsonlBuffers.set(fileName, records);
|
|
627
|
+
this.jsonlBufferBytes.set(fileName, usedBytes + lineBytes);
|
|
589
628
|
}
|
|
590
629
|
|
|
591
630
|
private flushJsonlBuffers(): void {
|
|
592
631
|
for (const [fileName, records] of this.jsonlBuffers) {
|
|
593
|
-
|
|
594
|
-
writeFileSync(join(this.artifactDir, fileName), lines);
|
|
632
|
+
writeFileSync(join(this.artifactDir, fileName), records.join(""));
|
|
595
633
|
}
|
|
596
634
|
this.jsonlBuffers.clear();
|
|
635
|
+
this.jsonlBufferBytes.clear();
|
|
597
636
|
}
|
|
598
637
|
}
|
|
599
638
|
|
|
600
639
|
export const __testUtils = {
|
|
601
640
|
ARTIFACTS,
|
|
602
641
|
SESSION_MANIFEST,
|
|
642
|
+
MAX_CURSOR_SDK_EVENT_DEBUG_JSONL_BYTES,
|
|
603
643
|
slugSessionKey,
|
|
604
644
|
resetSessionDebugState: resetCursorSdkEventDebugSessionStateForTests,
|
|
605
645
|
};
|