@emmaneugene/pi-cursor-sdk 0.3.7 → 0.4.1

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 (91) hide show
  1. package/CHANGELOG.md +22 -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-extension-factory-guard.js +31 -0
  7. package/dist/cursor-pi-tool-bridge-server.js +3 -0
  8. package/dist/cursor-pi-tool-bridge.js +5 -0
  9. package/dist/cursor-provider-errors.js +3 -58
  10. package/dist/cursor-provider-live-run-drain.js +0 -3
  11. package/dist/cursor-provider-run-finalizer.js +4 -7
  12. package/dist/cursor-provider-run-outcome.js +1 -1
  13. package/dist/cursor-provider-turn-finalize.js +3 -55
  14. package/dist/cursor-provider-turn-prepare.js +8 -144
  15. package/dist/cursor-provider-turn-runner.js +9 -23
  16. package/dist/cursor-provider-turn-send.js +8 -35
  17. package/dist/cursor-runtime-state.js +6 -268
  18. package/dist/cursor-sdk-billed-usage.js +3 -18
  19. package/dist/cursor-sdk-platform-package.js +88 -0
  20. package/dist/cursor-session-agent.js +18 -14
  21. package/dist/cursor-skill-tool.js +12 -22
  22. package/dist/cursor-state.js +5 -8
  23. package/dist/cursor-usage-accounting.js +1 -1
  24. package/dist/index.js +65 -49
  25. package/docs/cursor-dogfood-checklist.md +4 -4
  26. package/docs/cursor-live-smoke-checklist.md +24 -24
  27. package/docs/cursor-model-ux-spec.md +27 -30
  28. package/docs/cursor-native-tool-replay.md +4 -4
  29. package/docs/cursor-native-tool-visual-audit.md +10 -10
  30. package/docs/cursor-testing-lessons.md +38 -16
  31. package/docs/cursor-tool-surfaces.md +2 -4
  32. package/docs/platform-smoke-implementation.md +5 -5
  33. package/docs/platform-smoke.md +28 -73
  34. package/node_modules/cross-spawn/node_modules/which/CHANGELOG.md +166 -0
  35. package/package.json +3 -17
  36. package/platform-smoke.config.mjs +2 -1
  37. package/scripts/lib/cursor-visual-render.mjs +12 -2
  38. package/scripts/lib/local-resume-smoke-harness.mjs +0 -18
  39. package/scripts/platform-smoke/card-detect.mjs +1 -1
  40. package/scripts/platform-smoke/local-resume-runner.mjs +1 -1
  41. package/scripts/platform-smoke/scenarios.mjs +1 -1
  42. package/scripts/platform-smoke/targets.mjs +14 -5
  43. package/scripts/visual-tui-smoke-self-test.mjs +9 -33
  44. package/scripts/visual-tui-smoke.mjs +54 -11
  45. package/src/cursor-agents-context-registration.ts +0 -5
  46. package/src/cursor-agents-context.ts +1 -3
  47. package/src/cursor-config.ts +8 -379
  48. package/src/cursor-extension-factory-guard.ts +57 -0
  49. package/src/cursor-pi-tool-bridge-server.ts +4 -0
  50. package/src/cursor-pi-tool-bridge.ts +5 -0
  51. package/src/cursor-provider-errors.ts +2 -62
  52. package/src/cursor-provider-live-run-drain.ts +0 -3
  53. package/src/cursor-provider-run-finalizer.ts +4 -12
  54. package/src/cursor-provider-run-outcome.ts +0 -3
  55. package/src/cursor-provider-turn-finalize.ts +3 -61
  56. package/src/cursor-provider-turn-prepare.ts +8 -165
  57. package/src/cursor-provider-turn-runner.ts +15 -31
  58. package/src/cursor-provider-turn-send.ts +7 -38
  59. package/src/cursor-provider-turn-types.ts +9 -30
  60. package/src/cursor-runtime-state.ts +6 -345
  61. package/src/cursor-sdk-billed-usage.ts +3 -24
  62. package/src/cursor-sdk-platform-package.ts +106 -0
  63. package/src/cursor-session-agent.ts +16 -12
  64. package/src/cursor-skill-tool.ts +10 -26
  65. package/src/cursor-state.ts +5 -10
  66. package/src/cursor-usage-accounting.ts +1 -3
  67. package/src/index.ts +70 -49
  68. package/dist/cursor-cloud-lifecycle.js +0 -650
  69. package/dist/cursor-cloud-local-state.js +0 -460
  70. package/dist/cursor-cloud-options.js +0 -145
  71. package/dist/cursor-cloud-reporting.js +0 -222
  72. package/dist/cursor-ripgrep-path.js +0 -27
  73. package/scripts/cloud-runtime-smoke.d.mts +0 -42
  74. package/scripts/cloud-runtime-smoke.mjs +0 -623
  75. package/scripts/lib/cloud-smoke-artifacts.d.mts +0 -16
  76. package/scripts/lib/cloud-smoke-artifacts.mjs +0 -94
  77. package/scripts/lib/cloud-smoke-cleanup-evidence.d.mts +0 -209
  78. package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +0 -585
  79. package/scripts/lib/cloud-smoke-github.d.mts +0 -78
  80. package/scripts/lib/cloud-smoke-github.mjs +0 -331
  81. package/scripts/lib/cloud-smoke-pi-runner.d.mts +0 -42
  82. package/scripts/lib/cloud-smoke-pi-runner.mjs +0 -214
  83. package/scripts/lib/cloud-smoke-shutdown.d.mts +0 -67
  84. package/scripts/lib/cloud-smoke-shutdown.mjs +0 -133
  85. package/shared/cursor-cloud-lifecycle-constants.d.mts +0 -3
  86. package/shared/cursor-cloud-lifecycle-constants.mjs +0 -7
  87. package/src/cursor-cloud-lifecycle.ts +0 -733
  88. package/src/cursor-cloud-local-state.ts +0 -536
  89. package/src/cursor-cloud-options.ts +0 -182
  90. package/src/cursor-cloud-reporting.ts +0 -267
  91. package/src/cursor-ripgrep-path.ts +0 -32
@@ -1,623 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- mkdirSync,
4
- mkdtempSync,
5
- renameSync,
6
- rmSync,
7
- writeFileSync,
8
- } from "node:fs";
9
- import { tmpdir } from "node:os";
10
- import { dirname, join, resolve } from "node:path";
11
- import { fileURLToPath, pathToFileURL } from "node:url";
12
- import { terminateChild } from "./lib/cursor-child-process.mjs";
13
- import { ensureBuilt } from "./lib/ensure-built.mjs";
14
- import { buildCursorSmokeEnv } from "./lib/cursor-smoke-env.mjs";
15
- import {
16
- checkpointCloudSmokeShutdown,
17
- createCloudSmokeShutdownController,
18
- installCloudSmokeSignalHandlers,
19
- } from "./lib/cloud-smoke-shutdown.mjs";
20
- import {
21
- assertAgentDeleted,
22
- assertCloudSmokeEvidenceSafe,
23
- buildCloudSmokeEvidenceProvenance,
24
- cleanupCloudAgent,
25
- coordinateCloudSmokeReleaseGate,
26
- listCloudSmokePackageSourcePaths,
27
- projectCloudSmokeMatrixEvidence,
28
- validateCloudSmokeMatrixEvidence,
29
- } from "./lib/cloud-smoke-cleanup-evidence.mjs";
30
- import {
31
- assertOwnedThrowawayRepositoryHandle,
32
- authenticatedGitArgs,
33
- cloudSmokeRepositoryDescription,
34
- createThrowawayRepository,
35
- deleteThrowawayRepository,
36
- normalizeCloudSmokeGitHubRepo,
37
- runTimedCommand,
38
- validatePrUrl,
39
- } from "./lib/cloud-smoke-github.mjs";
40
- import { CLOUD_AGENT_ID_PATTERN } from "../shared/cursor-cloud-lifecycle-constants.mjs";
41
- import {
42
- cloudAgentIdsFromLifecycleArtifacts,
43
- cloudAgentIdsFromMetadata,
44
- cloudLifecycleRecords,
45
- readCloudRunReport,
46
- readCloudSmokeMetadata,
47
- readLatestCloudSmokeMetadata,
48
- } from "./lib/cloud-smoke-artifacts.mjs";
49
- import { createCloudSmokePiRunner } from "./lib/cloud-smoke-pi-runner.mjs";
50
- import { scrubSensitiveText } from "../shared/cursor-sensitive-text.mjs";
51
-
52
- const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
53
- const EVIDENCE_PATH = join(root, "docs", "evidence", "cursor-cloud-smoke-matrix-latest.json");
54
- const MODEL = "cursor/grok-4.6";
55
- const CLOUD_RUN_ID_PATTERN = /^run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
56
- const cloudSmokeShutdown = createCloudSmokeShutdownController((child) => terminateChild(child, { graceMs: 15_000 }));
57
- const argv = process.argv.slice(2);
58
- const args = new Set(argv);
59
- const CLOUD_ENV_NAMES = [
60
- "PI_CURSOR_RUNTIME",
61
- "PI_CURSOR_CLOUD_ACK",
62
- "PI_CURSOR_CLOUD_ALLOW_LOCAL_STATE",
63
- "PI_CURSOR_CLOUD_CONTEXT",
64
- "PI_CURSOR_CLOUD_REPO",
65
- "PI_CURSOR_CLOUD_BRANCH",
66
- "PI_CURSOR_CLOUD_DIRECT_PUSH",
67
- "PI_CURSOR_CLOUD_AUTO_CREATE_PR",
68
- "PI_CURSOR_CLOUD_SKIP_REVIEWER_REQUEST",
69
- "PI_CURSOR_CLOUD_ENV",
70
- "PI_CURSOR_CLOUD_ENV_FROM_FILES",
71
- "PI_CURSOR_CLOUD_ENV_TYPE",
72
- "PI_CURSOR_CLOUD_ENV_NAME",
73
- ];
74
-
75
- function scrubSmokeText(value) {
76
- return scrubSensitiveText(String(value), process.env.CURSOR_API_KEY);
77
- }
78
-
79
- class SmokeFailure extends Error {
80
- constructor(message, details = "") {
81
- super(message);
82
- this.details = details;
83
- }
84
- }
85
-
86
- function printHelp() {
87
- console.log(`Required live Cursor cloud release smoke for pi-cursor-sdk.
88
-
89
- Usage:
90
- npm run smoke:cloud
91
- npm run smoke:cloud:context
92
- node scripts/cloud-runtime-smoke.mjs [--context-matrix]
93
-
94
- The no-flag release gate creates and deletes one private throwaway GitHub repository and runs named lanes for cancel, explicit repo/startingRef branch reporting, direct push, missing branch failure, lifecycle delete, and account-conditional artifacts/raw usage. It requires current gh auth with private-repo create/push/delete access and Cursor Cloud access to that repository.
95
-
96
- Environment:
97
- CURSOR_API_KEY Required for cloud runs and verified agent cleanup.
98
- CURSOR_CLOUD_SMOKE_TIMEOUT_MS Timeout in ms per lane (default: 300000).
99
- CURSOR_CLOUD_SMOKE_ENV_TYPE Optional context-matrix Cursor env type: cloud, pool, or machine.
100
- CURSOR_CLOUD_SMOKE_ENV_NAME Optional context-matrix env name, used only with type.
101
- CURSOR_CLOUD_SMOKE_KEEP_ARTIFACTS Keep temp artifacts after success when set to 1.
102
-
103
- Options:
104
- --context-matrix Optional sessionful fresh-vs-bootstrap context handoff proof.
105
-
106
- Exit codes:
107
- 0 every selected lane and destructive throwaway cleanup proof passed
108
- 1 missing prerequisite, run/assertion failure, or cleanup verification failure
109
- 2 invalid command-line usage`);
110
- }
111
-
112
- if (args.has("-h") || args.has("--help")) {
113
- printHelp();
114
- process.exit(0);
115
- }
116
-
117
- const unknownArgs = argv.filter((arg) => arg !== "--context-matrix");
118
- const laneArgs = argv.filter((arg) => arg === "--context-matrix");
119
- if (unknownArgs.length > 0 || laneArgs.length > 1) {
120
- const message = unknownArgs.length > 0
121
- ? `unknown argument(s): ${unknownArgs.join(", ")}`
122
- : "only one smoke lane may be selected";
123
- console.error(scrubSmokeText(`[cloud-smoke] usage error: ${message}\nRun with --help for usage.`));
124
- process.exit(2);
125
- }
126
-
127
- function fail(message, details = "") {
128
- throw new SmokeFailure(message, details);
129
- }
130
-
131
- function reportFailure(error) {
132
- console.error(scrubSmokeText(`[cloud-smoke] FAIL: ${error instanceof Error ? error.message : String(error)}`));
133
- if (error instanceof SmokeFailure && error.details) console.error(scrubSmokeText(error.details));
134
- else if (error?.details) console.error(scrubSmokeText(error.details));
135
- }
136
-
137
- function optionalSmokeValue(name) {
138
- const value = process.env[name]?.trim();
139
- return value || undefined;
140
- }
141
-
142
- export function buildCloudSmokeEnv(artifactDir, options = {}) {
143
- const agentDir = join(artifactDir, "agent");
144
- mkdirSync(agentDir, { recursive: true });
145
- const env = buildCursorSmokeEnv({ settingSources: "none", eventDebugDir: artifactDir });
146
- for (const name of CLOUD_ENV_NAMES) delete env[name];
147
- const smokeEnvType = options.repoUrl ? undefined : optionalSmokeValue("CURSOR_CLOUD_SMOKE_ENV_TYPE");
148
- const smokeEnvName = options.repoUrl ? undefined : optionalSmokeValue("CURSOR_CLOUD_SMOKE_ENV_NAME");
149
- Object.assign(env, {
150
- PI_CURSOR_RUNTIME: "cloud",
151
- PI_CURSOR_CLOUD_ACK: "1",
152
- PI_CURSOR_CLOUD_ALLOW_LOCAL_STATE: "1",
153
- PI_CODING_AGENT_DIR: agentDir,
154
- PI_CURSOR_CLOUD_CONTEXT: options.contextHandoff ?? "fresh",
155
- ...(options.repoUrl ? { PI_CURSOR_CLOUD_REPO: options.repoUrl } : {}),
156
- ...(options.startingRef ? { PI_CURSOR_CLOUD_BRANCH: options.startingRef } : {}),
157
- ...(options.directPush ? { PI_CURSOR_CLOUD_DIRECT_PUSH: "1" } : {}),
158
- ...(smokeEnvType ? { PI_CURSOR_CLOUD_ENV_TYPE: smokeEnvType } : {}),
159
- ...(smokeEnvType && smokeEnvName ? { PI_CURSOR_CLOUD_ENV_NAME: smokeEnvName } : {}),
160
- });
161
- delete env.PI_CURSOR_PI_TOOL_BRIDGE_DEBUG;
162
- return env;
163
- }
164
-
165
- export function buildCloudSmokeWorkspace(artifactDir) {
166
- const workspaceDir = join(artifactDir, "workspace");
167
- mkdirSync(workspaceDir, { recursive: true });
168
- return workspaceDir;
169
- }
170
-
171
- export {
172
- assertCloudSmokeEvidenceSafe,
173
- assertOwnedThrowawayRepositoryHandle,
174
- buildCloudSmokeEvidenceProvenance,
175
- cloudSmokeRepositoryDescription,
176
- cloudAgentIdsFromLifecycleArtifacts,
177
- coordinateCloudSmokeReleaseGate,
178
- listCloudSmokePackageSourcePaths,
179
- normalizeCloudSmokeGitHubRepo,
180
- projectCloudSmokeMatrixEvidence,
181
- validateCloudSmokeMatrixEvidence,
182
- };
183
-
184
- const { runPi, startRpc } = createCloudSmokePiRunner({
185
- root,
186
- model: MODEL,
187
- shutdown: cloudSmokeShutdown,
188
- buildEnv: buildCloudSmokeEnv,
189
- buildWorkspace: buildCloudSmokeWorkspace,
190
- });
191
-
192
- function command(commandName, commandArgs, options = {}) {
193
- try {
194
- return runTimedCommand(commandName, commandArgs, { cwd: options.cwd ?? root, ...options });
195
- } catch (error) {
196
- fail(error instanceof Error ? error.message : String(error), error?.details ?? "");
197
- }
198
- }
199
-
200
- async function runSuccessfulPrintLane({ artifactDir, envOptions, message, marker, sessionId, timeoutMs }) {
201
- const run = await runPi({ artifactDir, envOptions, message, sessionId, timeoutMs });
202
- if (run.code !== 0) fail(`pi cloud lane exited ${run.code}${run.signal ? ` (${run.signal})` : ""}`, `${run.stderr}\n${run.stdout}`.trim());
203
- if (!run.stdout.includes(marker)) fail(`cloud lane output missing ${marker}`, `${run.stderr}\n${run.stdout}`.trim());
204
- const latest = readLatestCloudSmokeMetadata(artifactDir);
205
- if (!latest) fail("cloud lane metadata missing", artifactDir);
206
- const report = assertCloudMetadata(latest.metadata, latest.metadataPath);
207
- const agentId = latest.metadata.run.agentId;
208
- const runId = latest.metadata.run.runId;
209
- assertLaneEvidence(artifactDir, agentId, runId);
210
- return { agentId, runId, report };
211
- }
212
-
213
- function assertCloudMetadata(metadata, metadataPath, options = {}) {
214
- if (metadata.providerMeta?.runtime !== "cloud") fail("provider metadata did not record cloud runtime", metadataPath);
215
- if (metadata.send?.bridgeEnabled !== false) fail("cloud send unexpectedly enabled pi bridge", metadataPath);
216
- if (metadata.send?.useNativeToolReplay !== false) fail("cloud send unexpectedly enabled native replay", metadataPath);
217
- if (metadata.send?.agentMode !== "agent") fail("cloud send did not use agent mode", metadataPath);
218
- if (!CLOUD_AGENT_ID_PATTERN.test(metadata.run?.agentId ?? "")) fail("cloud run did not return an exact cloud agent id", metadataPath);
219
- if (!CLOUD_RUN_ID_PATTERN.test(metadata.run?.runId ?? "")) fail("cloud run did not return an exact run id", metadataPath);
220
- const report = readCloudRunReport({ metadataPath, metadata });
221
- if (!report && options.requireReport === false) return undefined;
222
- if (!report) fail("cloud report was not retained after the print-mode lane exited", metadataPath);
223
- if (report.agentId !== metadata.run.agentId || report.runId !== metadata.run.runId) fail("cloud report IDs did not match run metadata", metadataPath);
224
- if (!Array.isArray(report.branches)) fail("cloud report branches were not an array", metadataPath);
225
- if (report.artifacts !== undefined) {
226
- if (!Array.isArray(report.artifacts) || report.artifacts.length > 10) fail("cloud report artifacts exceeded the bounded array contract", metadataPath);
227
- for (const artifact of report.artifacts) {
228
- if (typeof artifact?.path !== "string" || artifact.path.length === 0 || artifact.path.length > 240 || !Number.isFinite(artifact.sizeBytes) || artifact.sizeBytes < 0 || typeof artifact.updatedAt !== "string" || artifact.updatedAt.length > 240 || Number.isNaN(Date.parse(artifact.updatedAt))) {
229
- fail("cloud report artifact did not match the bounded known shape", metadataPath);
230
- }
231
- }
232
- }
233
- if (report.usage !== undefined) {
234
- if (!report.usage || typeof report.usage !== "object" || Array.isArray(report.usage)) fail("cloud report usage was not an object", metadataPath);
235
- for (const usage of [report.usage.totalUsage, report.usage.runUsage].filter(Boolean)) {
236
- for (const key of ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens", "totalTokens"]) {
237
- if (!Number.isFinite(usage[key]) || usage[key] < 0) fail("cloud report usage did not match the known token shape", metadataPath);
238
- }
239
- }
240
- }
241
- return report;
242
- }
243
-
244
- function assertLaneEvidence(artifactDir, expectedAgentId, expectedRunId) {
245
- const metadataIds = cloudAgentIdsFromMetadata(artifactDir);
246
- const lifecycle = cloudLifecycleRecords(artifactDir);
247
- if (!metadataIds.includes(expectedAgentId)) fail("lane agent ID missing from metadata", expectedAgentId);
248
- if (!lifecycle.some(({ data }) => data.agentId === expectedAgentId)) fail("lane agent ID missing from canonical lifecycle JSONL/journal", expectedAgentId);
249
- if (expectedRunId && !lifecycle.some(({ data }) => data.agentId === expectedAgentId && data.runId === expectedRunId)) {
250
- fail("lane run ID missing from canonical lifecycle JSONL/journal", expectedRunId);
251
- }
252
- }
253
-
254
- async function waitFor(predicate, timeoutMs, errorMessage) {
255
- const started = Date.now();
256
- while (Date.now() - started < timeoutMs) {
257
- cloudSmokeShutdown.throwIfRequested();
258
- const value = await predicate();
259
- if (value) return value;
260
- await new Promise((resolveWait) => setTimeout(resolveWait, 250));
261
- }
262
- throw new Error(errorMessage);
263
- }
264
-
265
- async function waitForAgentSettled(rpc, fromIndex, timeoutMs) {
266
- await waitFor(
267
- () => {
268
- rpc.throwIfFailed();
269
- return rpc.events.slice(fromIndex).some((event) => event.type === "agent_settled");
270
- },
271
- timeoutMs,
272
- `timeout waiting for agent_settled. Stderr: ${rpc.stderr}`,
273
- );
274
- }
275
-
276
- async function readLastAssistantText(rpc) {
277
- const response = await rpc.send("get_last_assistant_text");
278
- if (!response.success) fail("failed to read last assistant text", response.error);
279
- return typeof response.data?.text === "string" ? response.data.text : "";
280
- }
281
-
282
- async function promptAndRead({ rpc, artifactDir, message, timeoutMs, expectedContextHandoff }) {
283
- const fromIndex = rpc.events.length;
284
- const response = await rpc.send("prompt", { message }, timeoutMs);
285
- if (!response.success) fail("cloud prompt was rejected", response.error);
286
- await waitForAgentSettled(rpc, fromIndex, timeoutMs);
287
- const text = await readLastAssistantText(rpc);
288
- const latest = readLatestCloudSmokeMetadata(artifactDir);
289
- if (!latest) fail("cloud smoke metadata missing", artifactDir);
290
- const report = assertCloudMetadata(latest.metadata, latest.metadataPath, { requireReport: !expectedContextHandoff });
291
- if (expectedContextHandoff && latest.metadata.providerMeta?.contextHandoff !== expectedContextHandoff) {
292
- fail("cloud metadata recorded wrong context handoff", JSON.stringify({ expected: expectedContextHandoff, actual: latest.metadata.providerMeta?.contextHandoff }));
293
- }
294
- const agentId = latest.metadata.run.agentId;
295
- const runId = latest.metadata.run.runId;
296
- assertLaneEvidence(artifactDir, agentId, runId);
297
- return { text, agentId, runId, report };
298
- }
299
-
300
- function lastNonEmptyLine(text) {
301
- return String(text ?? "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).at(-1) ?? "";
302
- }
303
-
304
- async function runContextScenario({ artifactRoot, contextHandoff, timeoutMs }) {
305
- const artifactDir = join(artifactRoot, `context-${contextHandoff}`);
306
- mkdirSync(artifactDir, { recursive: true });
307
- const marker = `CLOUD_CONTEXT_${contextHandoff}_${Date.now()}`;
308
- const rpc = await startRpc({ artifactDir, contextHandoff, sessionId: `cloud-context-${contextHandoff}-${Date.now()}` });
309
- try {
310
- const first = await promptAndRead({ rpc, artifactDir, message: `Remember exact marker ${marker}. Reply exactly FIRST_OK.`, timeoutMs, expectedContextHandoff: contextHandoff });
311
- if (lastNonEmptyLine(first.text) !== "FIRST_OK") fail(`cloud ${contextHandoff} setup turn did not return FIRST_OK`, first.text);
312
- const second = await promptAndRead({ rpc, artifactDir, message: "What exact CLOUD_CONTEXT marker did I ask you to remember earlier in this pi session? Reply exactly MARKER=<marker> if visible, otherwise NO_MARKER.", timeoutMs, expectedContextHandoff: contextHandoff });
313
- return { contextHandoff, marker, first, second };
314
- } finally {
315
- await rpc.stop();
316
- }
317
- }
318
-
319
- async function runContextMatrix({ artifactRoot, timeoutMs }) {
320
- const fresh = await runContextScenario({ artifactRoot, contextHandoff: "fresh", timeoutMs });
321
- if (fresh.second.text.includes(fresh.marker) || lastNonEmptyLine(fresh.second.text) !== "NO_MARKER") fail("fresh cloud context leaked prior context", fresh.second.text);
322
- const bootstrap = await runContextScenario({ artifactRoot, contextHandoff: "bootstrap", timeoutMs });
323
- if (lastNonEmptyLine(bootstrap.second.text) !== `MARKER=${bootstrap.marker}`) fail("bootstrap cloud context omitted prior context", bootstrap.second.text);
324
- return [fresh, bootstrap];
325
- }
326
-
327
- async function runBranchAndLifecycleLane({ artifactRoot, repo, timeoutMs, Agent }) {
328
- const artifactDir = join(artifactRoot, "lane-branch-lifecycle");
329
- const timestamp = Date.now();
330
- const marker = `BRANCH_PROOF_${timestamp}`;
331
- const branchName = `cursor/cloud-smoke-${timestamp}`;
332
- const sessionId = `cloud-branch-${timestamp}`;
333
- const envOptions = { repoUrl: repo.repoUrl, startingRef: "starting-ref" };
334
- const result = await runSuccessfulPrintLane({
335
- artifactDir,
336
- envOptions,
337
- message: `In this repository, create branch-proof.txt containing exactly ${marker} followed by a newline, commit it on a new branch named ${branchName}, push that branch, then reply exactly BRANCH_LANE_OK.`,
338
- marker: "BRANCH_LANE_OK",
339
- sessionId,
340
- timeoutMs,
341
- });
342
- for (const branch of result.report.branches) {
343
- if (normalizeCloudSmokeGitHubRepo(branch.repoUrl) !== repo.fullName.toLowerCase()) fail("branch lane reported a different repository", branch.repoUrl);
344
- if (branch.prUrl) {
345
- try {
346
- await validatePrUrl(repo, branch.prUrl, { cwd: root });
347
- } catch (error) {
348
- fail(error instanceof Error ? error.message : String(error), error?.details ?? "");
349
- }
350
- }
351
- }
352
- command("git", ["check-ref-format", "--branch", branchName], { label: "validate cloud branch name" });
353
- const remoteRef = "refs/remotes/origin/cloud-smoke-branch-proof";
354
- command("git", authenticatedGitArgs(["fetch", "origin", `+refs/heads/${branchName}:${remoteRef}`]), { cwd: repo.seedDir, label: "fetch cloud branch proof" });
355
- const startingCommit = command("git", ["rev-parse", "refs/remotes/origin/starting-ref"], { cwd: repo.seedDir });
356
- const branchCommit = command("git", ["rev-parse", remoteRef], { cwd: repo.seedDir });
357
- if (startingCommit === branchCommit) fail("cloud working branch did not advance from startingRef");
358
- command("git", ["merge-base", "--is-ancestor", startingCommit, branchCommit], { cwd: repo.seedDir, label: "verify cloud branch startingRef ancestry" });
359
- const remoteContent = command("git", ["show", `${remoteRef}:branch-proof.txt`], { cwd: repo.seedDir, label: "read cloud branch proof" });
360
- if (remoteContent !== marker) fail("cloud branch proof content did not match marker", remoteContent);
361
-
362
- const rpc = await startRpc({ artifactDir, sessionId, envOptions });
363
- try {
364
- const commandResponse = await rpc.send("prompt", { message: `/cursor-cloud delete ${result.agentId} --yes` }, timeoutMs);
365
- if (!commandResponse.success) fail("lifecycle delete command failed", commandResponse.error);
366
- } finally {
367
- await rpc.stop();
368
- }
369
- try {
370
- await assertAgentDeleted(Agent, result.agentId);
371
- } catch (error) {
372
- fail(error instanceof Error ? error.message : String(error), error?.details ?? "");
373
- }
374
- if (!cloudLifecycleRecords(artifactDir).some(({ data }) => data.agentId === result.agentId && data.action === "delete")) fail("lifecycle delete result missing from canonical journal/JSONL", result.agentId);
375
- return [
376
- {
377
- name: "explicit-https-repo-starting-ref-branch-pr-reporting",
378
- status: "passed",
379
- agentId: result.agentId,
380
- runId: result.runId,
381
- branchReportObserved: result.report.branches.some((branch) => Boolean(branch.branch)),
382
- startingRefAncestryVerified: true,
383
- remoteContentVerified: true,
384
- prUrlReturned: result.report.branches.some((branch) => Boolean(branch.prUrl)),
385
- report: result.report,
386
- },
387
- {
388
- name: "lifecycle-delete",
389
- status: "passed",
390
- agentId: result.agentId,
391
- runId: result.runId,
392
- lifecycleDeleteVerified: true,
393
- },
394
- ];
395
- }
396
-
397
- async function runDirectPushLane({ artifactRoot, repo, timeoutMs }) {
398
- const artifactDir = join(artifactRoot, "lane-direct-push");
399
- const timestamp = Date.now();
400
- const marker = `DIRECT_PUSH_PROOF_${timestamp}`;
401
- const before = command("git", authenticatedGitArgs(["ls-remote", repo.repoUrl, "refs/heads/direct-push"]), { label: "read direct-push branch before lane" }).split(/\s+/)[0];
402
- const result = await runSuccessfulPrintLane({
403
- artifactDir,
404
- envOptions: { repoUrl: repo.repoUrl, startingRef: "direct-push", directPush: true },
405
- message: `Create direct-push-proof.txt containing exactly ${marker} followed by a newline, commit and push it to the current direct-push branch, then reply exactly DIRECT_PUSH_LANE_OK.`,
406
- marker: "DIRECT_PUSH_LANE_OK",
407
- sessionId: `cloud-direct-push-${timestamp}`,
408
- timeoutMs,
409
- });
410
- command("git", authenticatedGitArgs(["fetch", "origin", "+refs/heads/direct-push:refs/remotes/origin/direct-push"]), { cwd: repo.seedDir, label: "fetch direct-push proof" });
411
- const after = command("git", ["rev-parse", "refs/remotes/origin/direct-push"], { cwd: repo.seedDir });
412
- if (!before || before === after) fail("direct-push remote branch content did not change");
413
- const content = command("git", ["show", "refs/remotes/origin/direct-push:direct-push-proof.txt"], { cwd: repo.seedDir, label: "read direct-push proof" });
414
- if (content !== marker) fail("direct-push remote content did not match marker", content);
415
- return { name: "direct-push-opt-in", status: "passed", agentId: result.agentId, runId: result.runId, remoteContentChanged: true, report: result.report };
416
- }
417
-
418
- async function runCancelLane({ artifactRoot, repo, timeoutMs, Agent }) {
419
- const artifactDir = join(artifactRoot, "lane-cancel");
420
- const rpc = await startRpc({ artifactDir, sessionId: `cloud-cancel-${Date.now()}`, envOptions: { repoUrl: repo.repoUrl, startingRef: "starting-ref" } });
421
- const eventStart = rpc.events.length;
422
- try {
423
- const response = await rpc.send("prompt", { message: "Run the shell command `sleep 300`, wait for it, then reply CANCEL_LANE_UNEXPECTED_COMPLETION." }, timeoutMs);
424
- if (!response.success) fail("cancel lane prompt was rejected", response.error);
425
- let nextRunProbeAt = 0;
426
- const identity = await waitFor(async () => {
427
- rpc.throwIfFailed();
428
- const latest = readLatestCloudSmokeMetadata(artifactDir);
429
- const agentId = latest?.metadata.run?.agentId;
430
- if (!CLOUD_AGENT_ID_PATTERN.test(agentId ?? "")) return undefined;
431
- if (CLOUD_RUN_ID_PATTERN.test(latest.metadata.run?.runId ?? "")) {
432
- return { agentId, runId: latest.metadata.run.runId, runIdSource: "metadata" };
433
- }
434
- if (Date.now() < nextRunProbeAt) return undefined;
435
- nextRunProbeAt = Date.now() + 2_000;
436
- try {
437
- const page = await Agent.listRuns(agentId, { runtime: "cloud", apiKey: process.env.CURSOR_API_KEY, limit: 10 });
438
- const run = page.items.find((item) => CLOUD_RUN_ID_PATTERN.test(item.id ?? ""));
439
- return run ? { agentId, runId: run.id, runIdSource: "agent-list-runs" } : undefined;
440
- } catch {
441
- return undefined;
442
- }
443
- }, timeoutMs, "cancel lane did not capture exact agent and run IDs before abort");
444
- const { agentId, runId, runIdSource } = identity;
445
- const abortResponse = await rpc.send("abort", {}, 120000);
446
- if (!abortResponse.success) fail("cancel lane abort request failed", abortResponse.error);
447
- await waitForAgentSettled(rpc, eventStart, timeoutMs);
448
- await waitFor(() => {
449
- rpc.throwIfFailed();
450
- try {
451
- assertLaneEvidence(artifactDir, agentId, runId);
452
- return true;
453
- } catch {
454
- return false;
455
- }
456
- }, timeoutMs, "cancel lane did not retain exact agent/run lifecycle evidence after abort");
457
- const terminal = await waitFor(async () => {
458
- rpc.throwIfFailed();
459
- const run = await Agent.getRun(runId, { runtime: "cloud", agentId, apiKey: process.env.CURSOR_API_KEY });
460
- return run.status === "cancelled" ? run : run.status === "finished" || run.status === "error" ? fail(`cancel lane reached wrong terminal state ${run.status}`) : undefined;
461
- }, timeoutMs, "cancel lane SDK status did not reach cancelled");
462
- return { name: "cancel", status: "passed", agentId, runId, runIdSource, terminalStatus: terminal.status, idsCapturedBeforeAbort: true };
463
- } finally {
464
- await rpc.stop();
465
- }
466
- }
467
-
468
- async function runMissingBranchLane({ artifactRoot, repo, timeoutMs }) {
469
- const artifactDir = join(artifactRoot, "lane-missing-branch");
470
- const timestamp = Date.now();
471
- const missingRef = `missing-${timestamp}`;
472
- const run = await runPi({
473
- artifactDir,
474
- envOptions: { repoUrl: repo.repoUrl, startingRef: missingRef },
475
- message: "Reply MISSING_BRANCH_UNEXPECTED_SUCCESS.",
476
- sessionId: `cloud-missing-${timestamp}`,
477
- timeoutMs,
478
- });
479
- const failureOutput = `${run.stdout}\n${run.stderr}`;
480
- if (run.code === 0 || run.stdout.includes("MISSING_BRANCH_UNEXPECTED_SUCCESS") || !/(branch|ref).*(missing|not found|exist|invalid)|(missing|not found|exist|invalid).*(branch|ref)/i.test(failureOutput)) {
481
- fail("missing branch did not fail closed with branch/ref evidence", failureOutput);
482
- }
483
- const agentIds = new Set();
484
- for (const { metadataPath, metadata } of readCloudSmokeMetadata(artifactDir)) {
485
- const agentId = metadata.run?.agentId ?? metadata.providerMeta?.cloudAgentId;
486
- const runId = metadata.run?.runId;
487
- if (agentId) {
488
- if (!CLOUD_AGENT_ID_PATTERN.test(agentId)) fail("missing-branch path recorded malformed agent ID", metadataPath);
489
- agentIds.add(agentId);
490
- assertLaneEvidence(artifactDir, agentId, runId);
491
- }
492
- }
493
- return { name: "missing-branch-failure", status: "passed", expectedFailureObserved: true, agentIds: [...agentIds].sort() };
494
- }
495
-
496
- function accountConditionalLane(lanes) {
497
- const reports = lanes.map((lane) => lane.report).filter(Boolean);
498
- return {
499
- name: "passive-artifacts-and-raw-usage",
500
- status: "passed",
501
- artifactsObserved: reports.some((report) => Array.isArray(report.artifacts) && report.artifacts.length > 0),
502
- rawUsageObserved: reports.some((report) => report.usage && Object.keys(report.usage).length > 0),
503
- observationsValidated: true,
504
- };
505
- }
506
-
507
- function stageEvidenceSummary(summary) {
508
- const text = assertCloudSmokeEvidenceSafe(validateCloudSmokeMatrixEvidence(summary));
509
- mkdirSync(dirname(EVIDENCE_PATH), { recursive: true });
510
- const temporary = `${EVIDENCE_PATH}.tmp-${process.pid}`;
511
- writeFileSync(temporary, text, { mode: 0o644 });
512
- return temporary;
513
- }
514
-
515
- function commitEvidenceSummary(temporary) {
516
- try {
517
- renameSync(temporary, EVIDENCE_PATH);
518
- } catch (error) {
519
- rmSync(temporary, { force: true });
520
- throw error;
521
- }
522
- }
523
-
524
- async function main() {
525
- if (!process.env.CURSOR_API_KEY) fail("CURSOR_API_KEY is required for cloud smoke and verified cleanup");
526
- const timeoutMs = Number(process.env.CURSOR_CLOUD_SMOKE_TIMEOUT_MS || 300000);
527
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) fail("CURSOR_CLOUD_SMOKE_TIMEOUT_MS must be a positive number");
528
- const artifactRoot = mkdtempSync(join(tmpdir(), "pi-cursor-cloud-smoke-"));
529
- installCloudSmokeSignalHandlers(cloudSmokeShutdown, process, () => { process.exitCode = 1; });
530
- let failure;
531
- console.error(scrubSmokeText(`[cloud-smoke] artifacts: ${artifactRoot}`));
532
- const { Agent } = await import("@cursor/sdk");
533
- const contextMatrix = args.has("--context-matrix");
534
- try {
535
- await coordinateCloudSmokeReleaseGate({
536
- throwIfInterrupted: () => cloudSmokeShutdown.throwIfRequested(),
537
- run: async (state) => {
538
- cloudSmokeShutdown.throwIfRequested();
539
- if (contextMatrix) {
540
- state.lanes = await runContextMatrix({ artifactRoot, timeoutMs });
541
- console.error("[cloud-smoke] context matrix passed");
542
- return;
543
- }
544
- try {
545
- state.repository = createThrowawayRepository(
546
- artifactRoot,
547
- (created) => { state.repository = created; },
548
- { cwd: root },
549
- );
550
- } catch (error) {
551
- fail(error instanceof Error ? error.message : String(error), error?.details ?? "");
552
- }
553
- const repo = state.repository;
554
- state.lanes.push(await runCancelLane({ artifactRoot, repo, timeoutMs, Agent }));
555
- state.lanes.push(...await runBranchAndLifecycleLane({ artifactRoot, repo, timeoutMs, Agent }));
556
- state.lanes.push(await runDirectPushLane({ artifactRoot, repo, timeoutMs }));
557
- state.lanes.push(await runMissingBranchLane({ artifactRoot, repo, timeoutMs }));
558
- state.lanes.push(accountConditionalLane(state.lanes));
559
- },
560
- harvestAgentIds: (state) => {
561
- const allAgentIds = new Set([
562
- ...cloudAgentIdsFromMetadata(artifactRoot),
563
- ...cloudAgentIdsFromLifecycleArtifacts(artifactRoot),
564
- ]);
565
- for (const lane of state.lanes) {
566
- if (CLOUD_AGENT_ID_PATTERN.test(lane.agentId ?? "")) allAgentIds.add(lane.agentId);
567
- for (const turn of [lane.first, lane.second]) {
568
- if (CLOUD_AGENT_ID_PATTERN.test(turn?.agentId ?? "")) allAgentIds.add(turn.agentId);
569
- }
570
- }
571
- return allAgentIds;
572
- },
573
- cleanupAgent: (agentId) => cleanupCloudAgent(Agent, agentId),
574
- onAgentCleanupError: (error, agentId) => {
575
- console.error(scrubSmokeText(`[cloud-smoke] cleanup failed for ${agentId}: ${error instanceof Error ? error.message : String(error)}`));
576
- },
577
- cleanupRepository: (repo) => deleteThrowawayRepository(repo, { cwd: root }),
578
- onRepositoryCleanupError: (error, repo) => {
579
- console.error(scrubSmokeText(`[cloud-smoke] repository cleanup failed for ${repo.fullName}: ${error instanceof Error ? error.message : String(error)}`));
580
- },
581
- writeEvidence: async ({ lanes, cleanup, throwawayRepository }) => {
582
- if (contextMatrix) return;
583
- const summary = projectCloudSmokeMatrixEvidence({
584
- model: MODEL,
585
- lanes,
586
- cleanup,
587
- throwawayRepository,
588
- provenance: buildCloudSmokeEvidenceProvenance({ root }),
589
- });
590
- const temporary = stageEvidenceSummary(summary);
591
- try {
592
- await checkpointCloudSmokeShutdown(cloudSmokeShutdown);
593
- commitEvidenceSummary(temporary);
594
- } catch (error) {
595
- rmSync(temporary, { force: true });
596
- throw error;
597
- }
598
- },
599
- });
600
- await checkpointCloudSmokeShutdown(cloudSmokeShutdown);
601
- } catch (error) {
602
- failure = error;
603
- }
604
- if (process.env.CURSOR_CLOUD_SMOKE_KEEP_ARTIFACTS !== "1" && !failure) rmSync(artifactRoot, { recursive: true, force: true });
605
- else console.error(scrubSmokeText(`[cloud-smoke] retained artifacts: ${artifactRoot}`));
606
- if (!failure) {
607
- try {
608
- await checkpointCloudSmokeShutdown(cloudSmokeShutdown);
609
- } catch (error) {
610
- failure = error;
611
- }
612
- }
613
- if (failure) throw failure;
614
- console.log(contextMatrix ? "cloud-context-smoke-ok" : "cloud-smoke-ok");
615
- }
616
-
617
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
618
- ensureBuilt();
619
- main().catch((error) => {
620
- reportFailure(error);
621
- process.exit(1);
622
- });
623
- }
@@ -1,16 +0,0 @@
1
- export interface CloudSmokeMetadataRecord {
2
- metadataPath: string;
3
- metadata: Record<string, unknown>;
4
- }
5
-
6
- export interface CloudSmokeLifecycleRecord {
7
- path: string;
8
- data: Record<string, unknown> & { agentId: string };
9
- }
10
-
11
- export function readCloudSmokeMetadata(artifactDir: string): CloudSmokeMetadataRecord[];
12
- export function readLatestCloudSmokeMetadata(artifactDir: string): CloudSmokeMetadataRecord | undefined;
13
- export function cloudAgentIdsFromMetadata(artifactDir: string): string[];
14
- export function cloudLifecycleRecords(artifactDir: string): CloudSmokeLifecycleRecord[];
15
- export function cloudAgentIdsFromLifecycleArtifacts(artifactDir: string): string[];
16
- export function readCloudRunReport(input: CloudSmokeMetadataRecord): Record<string, unknown> | undefined;