@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.
- package/CHANGELOG.md +16 -0
- package/README.md +99 -126
- package/dist/cursor-agents-context-registration.js +1 -3
- package/dist/cursor-agents-context.js +2 -2
- package/dist/cursor-config.js +2 -290
- package/dist/cursor-provider-errors.js +3 -58
- package/dist/cursor-provider-live-run-drain.js +0 -3
- package/dist/cursor-provider-run-finalizer.js +4 -7
- package/dist/cursor-provider-run-outcome.js +1 -1
- package/dist/cursor-provider-turn-finalize.js +3 -55
- package/dist/cursor-provider-turn-prepare.js +8 -144
- package/dist/cursor-provider-turn-runner.js +9 -23
- package/dist/cursor-provider-turn-send.js +8 -35
- package/dist/cursor-runtime-state.js +6 -268
- package/dist/cursor-sdk-billed-usage.js +3 -18
- package/dist/cursor-sdk-platform-package.js +88 -0
- package/dist/cursor-session-agent.js +18 -14
- package/dist/cursor-skill-tool.js +12 -22
- package/dist/cursor-state.js +5 -8
- package/dist/cursor-usage-accounting.js +1 -1
- package/docs/cursor-dogfood-checklist.md +4 -4
- package/docs/cursor-live-smoke-checklist.md +23 -23
- package/docs/cursor-model-ux-spec.md +27 -30
- package/docs/cursor-native-tool-replay.md +4 -4
- package/docs/cursor-native-tool-visual-audit.md +7 -7
- package/docs/cursor-testing-lessons.md +23 -16
- package/docs/cursor-tool-surfaces.md +2 -4
- package/docs/platform-smoke-implementation.md +5 -5
- package/docs/platform-smoke.md +27 -72
- package/package.json +3 -17
- package/platform-smoke.config.mjs +2 -1
- package/scripts/lib/local-resume-smoke-harness.mjs +0 -18
- package/scripts/platform-smoke/card-detect.mjs +1 -1
- package/scripts/platform-smoke/local-resume-runner.mjs +1 -1
- package/scripts/platform-smoke/scenarios.mjs +1 -1
- package/scripts/platform-smoke/targets.mjs +14 -5
- package/src/cursor-agents-context-registration.ts +0 -5
- package/src/cursor-agents-context.ts +1 -3
- package/src/cursor-config.ts +8 -379
- package/src/cursor-provider-errors.ts +2 -62
- package/src/cursor-provider-live-run-drain.ts +0 -3
- package/src/cursor-provider-run-finalizer.ts +4 -12
- package/src/cursor-provider-run-outcome.ts +0 -3
- package/src/cursor-provider-turn-finalize.ts +3 -61
- package/src/cursor-provider-turn-prepare.ts +8 -165
- package/src/cursor-provider-turn-runner.ts +15 -31
- package/src/cursor-provider-turn-send.ts +7 -38
- package/src/cursor-provider-turn-types.ts +9 -30
- package/src/cursor-runtime-state.ts +6 -345
- package/src/cursor-sdk-billed-usage.ts +3 -24
- package/src/cursor-sdk-platform-package.ts +106 -0
- package/src/cursor-session-agent.ts +16 -12
- package/src/cursor-skill-tool.ts +10 -26
- package/src/cursor-state.ts +5 -10
- package/src/cursor-usage-accounting.ts +1 -3
- package/dist/cursor-cloud-lifecycle.js +0 -650
- package/dist/cursor-cloud-local-state.js +0 -460
- package/dist/cursor-cloud-options.js +0 -145
- package/dist/cursor-cloud-reporting.js +0 -222
- package/dist/cursor-ripgrep-path.js +0 -27
- package/scripts/cloud-runtime-smoke.d.mts +0 -42
- package/scripts/cloud-runtime-smoke.mjs +0 -623
- package/scripts/lib/cloud-smoke-artifacts.d.mts +0 -16
- package/scripts/lib/cloud-smoke-artifacts.mjs +0 -94
- package/scripts/lib/cloud-smoke-cleanup-evidence.d.mts +0 -209
- package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +0 -585
- package/scripts/lib/cloud-smoke-github.d.mts +0 -78
- package/scripts/lib/cloud-smoke-github.mjs +0 -331
- package/scripts/lib/cloud-smoke-pi-runner.d.mts +0 -42
- package/scripts/lib/cloud-smoke-pi-runner.mjs +0 -214
- package/scripts/lib/cloud-smoke-shutdown.d.mts +0 -67
- package/scripts/lib/cloud-smoke-shutdown.mjs +0 -133
- package/shared/cursor-cloud-lifecycle-constants.d.mts +0 -3
- package/shared/cursor-cloud-lifecycle-constants.mjs +0 -7
- package/src/cursor-cloud-lifecycle.ts +0 -733
- package/src/cursor-cloud-local-state.ts +0 -536
- package/src/cursor-cloud-options.ts +0 -182
- package/src/cursor-cloud-reporting.ts +0 -267
- package/src/cursor-ripgrep-path.ts +0 -32
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
import { randomUUID as nodeRandomUUID } from "node:crypto";
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { writeFileSync } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
|
|
6
|
-
const DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
|
|
7
|
-
export const CLOUD_SMOKE_REPO_NAME_PREFIX = "pi-cursor-cloud-smoke-";
|
|
8
|
-
export const CLOUD_SMOKE_OWNERSHIP_TOKEN_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
9
|
-
const OWNED_REPO_NAME_PATTERN = /^[^/]+\/pi-cursor-cloud-smoke-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/;
|
|
10
|
-
|
|
11
|
-
function fail(message, details = "") {
|
|
12
|
-
const error = new Error(message);
|
|
13
|
-
error.details = details;
|
|
14
|
-
throw error;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function cloudSmokeRepositoryDescription(ownershipToken) {
|
|
18
|
-
const token = String(ownershipToken ?? "").toLowerCase();
|
|
19
|
-
if (!CLOUD_SMOKE_OWNERSHIP_TOKEN_PATTERN.test(token)) fail("cloud smoke ownership token must be a lowercase UUID");
|
|
20
|
-
return `pi-cursor-sdk throwaway cloud smoke; ownership=${token}; safe to delete`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function assertOwnedThrowawayRepositoryHandle(repo) {
|
|
24
|
-
if (!repo || typeof repo !== "object" || Array.isArray(repo)) fail("throwaway repository handle must be an object");
|
|
25
|
-
const ownershipToken = String(repo.ownershipToken ?? "").toLowerCase();
|
|
26
|
-
if (!CLOUD_SMOKE_OWNERSHIP_TOKEN_PATTERN.test(ownershipToken)) {
|
|
27
|
-
fail("throwaway repository handle missing exact ownership token");
|
|
28
|
-
}
|
|
29
|
-
const fullName = String(repo.fullName ?? "").toLowerCase();
|
|
30
|
-
const match = fullName.match(OWNED_REPO_NAME_PATTERN);
|
|
31
|
-
if (!match || match[1] !== ownershipToken) {
|
|
32
|
-
fail("throwaway repository handle fullName must match pi-cursor-cloud-smoke-<ownership-token>");
|
|
33
|
-
}
|
|
34
|
-
const expectedDescription = cloudSmokeRepositoryDescription(ownershipToken);
|
|
35
|
-
if (repo.description !== expectedDescription) {
|
|
36
|
-
fail("throwaway repository handle description must include exact ownership marker");
|
|
37
|
-
}
|
|
38
|
-
return {
|
|
39
|
-
fullName,
|
|
40
|
-
ownershipToken,
|
|
41
|
-
description: expectedDescription,
|
|
42
|
-
repoUrl: typeof repo.repoUrl === "string" ? repo.repoUrl : `https://github.com/${fullName}.git`,
|
|
43
|
-
seedDir: typeof repo.seedDir === "string" ? repo.seedDir : undefined,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function runTimedCommand(commandName, commandArgs, options = {}) {
|
|
48
|
-
const run = options.spawnSync ?? spawnSync;
|
|
49
|
-
const result = run(commandName, commandArgs, {
|
|
50
|
-
cwd: options.cwd,
|
|
51
|
-
env: options.env ?? process.env,
|
|
52
|
-
encoding: "utf8",
|
|
53
|
-
maxBuffer: options.maxBuffer ?? 16 * 1024 * 1024,
|
|
54
|
-
timeout: options.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS,
|
|
55
|
-
});
|
|
56
|
-
if (result.error || result.status !== 0) {
|
|
57
|
-
fail(
|
|
58
|
-
`${options.label ?? commandName} failed${result.status === null ? "" : ` with exit ${result.status}`}`,
|
|
59
|
-
[result.error?.message, result.stderr, result.stdout].filter(Boolean).join("\n"),
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
return String(result.stdout ?? "").trim();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function authenticatedGitArgs(commandArgs) {
|
|
66
|
-
return ["-c", "credential.helper=", "-c", "credential.helper=!gh auth git-credential", ...commandArgs];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function isTransientCommandFailure(error) {
|
|
70
|
-
const text = `${error instanceof Error ? error.message : String(error)}\n${error?.details ?? ""}`;
|
|
71
|
-
return /HTTP\s*5\d\d|Service Unavailable|timed?\s*out|ECONNRESET|EAI_AGAIN/i.test(text);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function runRetryableCommand(command, commandName, commandArgs, commandOptions, options = {}) {
|
|
75
|
-
const sleep = options.sleep ?? ((ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms));
|
|
76
|
-
const attempts = options.verificationAttempts ?? 5;
|
|
77
|
-
let lastError;
|
|
78
|
-
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
79
|
-
try {
|
|
80
|
-
return command(commandName, commandArgs, commandOptions);
|
|
81
|
-
} catch (error) {
|
|
82
|
-
lastError = error;
|
|
83
|
-
if (!isTransientCommandFailure(error) || attempt === attempts) throw error;
|
|
84
|
-
sleep(options.verificationDelayMs ?? 500);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
throw lastError;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function normalizeCloudSmokeGitHubRepo(value) {
|
|
91
|
-
if (typeof value !== "string" || !value.trim()) return undefined;
|
|
92
|
-
let url;
|
|
93
|
-
try {
|
|
94
|
-
url = new URL(value.includes("://") ? value : `https://${value}`);
|
|
95
|
-
} catch {
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
|
-
if (url.protocol !== "https:" || url.hostname !== "github.com" || url.port || url.username || url.password || url.search || url.hash) {
|
|
99
|
-
return undefined;
|
|
100
|
-
}
|
|
101
|
-
const segments = url.pathname.replace(/\.git$/, "").split("/").filter(Boolean);
|
|
102
|
-
return segments.length === 2 ? `${segments[0]}/${segments[1]}`.toLowerCase() : undefined;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function explicitHttpStatus(stdout) {
|
|
106
|
-
const firstLine = String(stdout ?? "").split(/\r?\n/, 1)[0].trim();
|
|
107
|
-
return /^HTTP\/\S+\s+(\d{3})(?:\s|$)/i.exec(firstLine)?.[1];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function readRepositoryOwnership(fullName, options = {}) {
|
|
111
|
-
const run = options.spawnSync ?? spawnSync;
|
|
112
|
-
const probe = run("gh", ["api", "-i", `repos/${fullName}`], {
|
|
113
|
-
cwd: options.cwd,
|
|
114
|
-
env: options.env ?? process.env,
|
|
115
|
-
encoding: "utf8",
|
|
116
|
-
maxBuffer: 1024 * 1024,
|
|
117
|
-
timeout: options.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS,
|
|
118
|
-
});
|
|
119
|
-
const output = [probe.error?.message, probe.stdout, probe.stderr].filter(Boolean).join("\n");
|
|
120
|
-
if (probe.error) fail(`probe throwaway repository ownership transport failed for ${fullName}`, output);
|
|
121
|
-
const status = explicitHttpStatus(probe.stdout);
|
|
122
|
-
if (status === "404" && probe.status !== 0) return { exists: false, isPrivate: false, description: "" };
|
|
123
|
-
if (probe.status !== 0 || status !== "200") {
|
|
124
|
-
fail(`probe throwaway repository ownership did not return explicit HTTP 200/404 for ${fullName}`, output);
|
|
125
|
-
}
|
|
126
|
-
const bodyStart = String(probe.stdout ?? "").indexOf("{");
|
|
127
|
-
let parsed;
|
|
128
|
-
try {
|
|
129
|
-
parsed = JSON.parse(String(probe.stdout ?? "").slice(bodyStart));
|
|
130
|
-
} catch (error) {
|
|
131
|
-
fail(`probe throwaway repository ownership returned invalid JSON for ${fullName}`, error instanceof Error ? error.message : String(error));
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
exists: true,
|
|
135
|
-
isPrivate: parsed.private === true,
|
|
136
|
-
description: typeof parsed.description === "string" ? parsed.description : "",
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function probeRepositoryOwnership(fullName, options = {}) {
|
|
141
|
-
const sleep = options.sleep ?? ((ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms));
|
|
142
|
-
const attempts = options.verificationAttempts ?? 5;
|
|
143
|
-
let lastResult = { exists: false, isPrivate: false, description: "" };
|
|
144
|
-
let lastError;
|
|
145
|
-
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
146
|
-
try {
|
|
147
|
-
lastResult = readRepositoryOwnership(fullName, options);
|
|
148
|
-
lastError = undefined;
|
|
149
|
-
if (lastResult.exists) return lastResult;
|
|
150
|
-
} catch (error) {
|
|
151
|
-
lastError = error;
|
|
152
|
-
}
|
|
153
|
-
if (attempt < attempts) sleep(options.verificationDelayMs ?? 500);
|
|
154
|
-
}
|
|
155
|
-
if (lastError) throw lastError;
|
|
156
|
-
return lastResult;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function verifyOwnedRepository(repo, options = {}) {
|
|
160
|
-
const owned = assertOwnedThrowawayRepositoryHandle(repo);
|
|
161
|
-
const command = options.runCommand ?? ((name, args, opts = {}) => runTimedCommand(name, args, { ...opts, spawnSync: options.spawnSync }));
|
|
162
|
-
const view = JSON.parse(runRetryableCommand(command, "gh", ["repo", "view", owned.fullName, "--json", "isPrivate,description"], {
|
|
163
|
-
cwd: options.cwd,
|
|
164
|
-
label: "verify throwaway repository ownership marker",
|
|
165
|
-
}, options));
|
|
166
|
-
if (view.isPrivate !== true) fail("throwaway repository is not private");
|
|
167
|
-
if (view.description !== owned.description) fail("throwaway repository description does not match ownership marker");
|
|
168
|
-
return owned;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function createThrowawayRepository(artifactRoot, onOwned, options = {}) {
|
|
172
|
-
const command = options.runCommand ?? ((name, args, opts = {}) => runTimedCommand(name, args, { ...opts, spawnSync: options.spawnSync }));
|
|
173
|
-
const writeFile = options.writeFileSync ?? writeFileSync;
|
|
174
|
-
const randomUUID = options.randomUUID ?? nodeRandomUUID;
|
|
175
|
-
const ownershipToken = String(randomUUID()).toLowerCase();
|
|
176
|
-
if (!CLOUD_SMOKE_OWNERSHIP_TOKEN_PATTERN.test(ownershipToken)) fail("cloud smoke ownership token must be a lowercase UUID");
|
|
177
|
-
const cwd = options.cwd;
|
|
178
|
-
|
|
179
|
-
command("gh", ["auth", "status"], { cwd, label: "gh auth status" });
|
|
180
|
-
const owner = command("gh", ["api", "user", "--jq", ".login"], { cwd, label: "read gh user" });
|
|
181
|
-
if (!owner) fail("gh auth did not return a GitHub login");
|
|
182
|
-
const name = `${CLOUD_SMOKE_REPO_NAME_PREFIX}${ownershipToken}`;
|
|
183
|
-
const fullName = `${owner}/${name}`.toLowerCase();
|
|
184
|
-
const description = cloudSmokeRepositoryDescription(ownershipToken);
|
|
185
|
-
const seedDir = join(artifactRoot, "repo-seed");
|
|
186
|
-
const repo = {
|
|
187
|
-
fullName,
|
|
188
|
-
repoUrl: `https://github.com/${fullName}.git`,
|
|
189
|
-
seedDir,
|
|
190
|
-
ownershipToken,
|
|
191
|
-
description,
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const sleep = options.sleep ?? ((ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms));
|
|
195
|
-
const attempts = options.verificationAttempts ?? 5;
|
|
196
|
-
let createError;
|
|
197
|
-
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
198
|
-
try {
|
|
199
|
-
command("gh", ["repo", "create", fullName, "--private", "--description", description], {
|
|
200
|
-
cwd,
|
|
201
|
-
label: "create private throwaway repository",
|
|
202
|
-
});
|
|
203
|
-
createError = undefined;
|
|
204
|
-
break;
|
|
205
|
-
} catch (error) {
|
|
206
|
-
createError = error;
|
|
207
|
-
const probe = probeRepositoryOwnership(fullName, options);
|
|
208
|
-
if (probe.exists && probe.description === description && probe.isPrivate) {
|
|
209
|
-
// Creation was ambiguous but the exact ownership marker is present: expose cleanup only now.
|
|
210
|
-
onOwned?.(repo);
|
|
211
|
-
fail(
|
|
212
|
-
"throwaway repository create failed after ownership marker was observed",
|
|
213
|
-
[error instanceof Error ? error.message : String(error), error?.details].filter(Boolean).join("\n"),
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
if (probe.exists) {
|
|
217
|
-
const residual = new Error(`throwaway repository name exists without ownership marker; refusing cleanup: ${fullName}`);
|
|
218
|
-
residual.residualRepositoryName = fullName;
|
|
219
|
-
residual.details = [error instanceof Error ? error.message : String(error), error?.details].filter(Boolean).join("\n");
|
|
220
|
-
throw residual;
|
|
221
|
-
}
|
|
222
|
-
if (!isTransientCommandFailure(error) || attempt === attempts) throw error;
|
|
223
|
-
sleep(options.verificationDelayMs ?? 500);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
if (createError) throw createError;
|
|
227
|
-
|
|
228
|
-
// A successful create establishes ownership; expose cleanup before later verification/seeding can fail.
|
|
229
|
-
onOwned?.(repo);
|
|
230
|
-
verifyOwnedRepository(repo, options);
|
|
231
|
-
|
|
232
|
-
command("gh", ["repo", "clone", fullName, seedDir], { cwd, label: "clone throwaway repository" });
|
|
233
|
-
command("git", ["config", "user.name", "pi-cursor-sdk cloud smoke"], { cwd: seedDir });
|
|
234
|
-
command("git", ["config", "user.email", "pi-cursor-sdk-cloud-smoke@invalid.example"], { cwd: seedDir });
|
|
235
|
-
command("git", ["switch", "-c", "main"], { cwd: seedDir });
|
|
236
|
-
writeFile(join(seedDir, "README.md"), "# pi-cursor-sdk cloud smoke\n");
|
|
237
|
-
command("git", ["add", "README.md"], { cwd: seedDir });
|
|
238
|
-
command("git", ["commit", "-m", "seed main"], { cwd: seedDir });
|
|
239
|
-
command("git", authenticatedGitArgs(["push", "-u", "origin", "main"]), { cwd: seedDir });
|
|
240
|
-
runRetryableCommand(command, "gh", ["repo", "edit", fullName, "--default-branch", "main"], { cwd, label: "set throwaway default branch" }, options);
|
|
241
|
-
for (const branch of ["starting-ref", "direct-push"]) {
|
|
242
|
-
command("git", ["switch", "-c", branch, "main"], { cwd: seedDir });
|
|
243
|
-
writeFile(join(seedDir, `${branch}.txt`), `${branch} seed\n`);
|
|
244
|
-
command("git", ["add", `${branch}.txt`], { cwd: seedDir });
|
|
245
|
-
command("git", ["commit", "-m", `seed ${branch}`], { cwd: seedDir });
|
|
246
|
-
command("git", authenticatedGitArgs(["push", "-u", "origin", branch]), { cwd: seedDir });
|
|
247
|
-
}
|
|
248
|
-
if (command("git", ["status", "--porcelain"], { cwd: seedDir })) fail("throwaway seed repository is not clean");
|
|
249
|
-
const remoteRefs = command("git", authenticatedGitArgs(["ls-remote", "--heads", repo.repoUrl]), {
|
|
250
|
-
cwd,
|
|
251
|
-
label: "verify seeded throwaway branches",
|
|
252
|
-
});
|
|
253
|
-
for (const branch of ["main", "starting-ref", "direct-push"]) {
|
|
254
|
-
if (!remoteRefs.includes(`refs/heads/${branch}`)) fail(`throwaway repository is missing seeded ${branch} branch`);
|
|
255
|
-
}
|
|
256
|
-
return repo;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export function deleteThrowawayRepository(repo, options = {}) {
|
|
260
|
-
const owned = assertOwnedThrowawayRepositoryHandle(repo);
|
|
261
|
-
const command = options.runCommand ?? ((name, args, opts = {}) => runTimedCommand(name, args, { ...opts, spawnSync: options.spawnSync }));
|
|
262
|
-
const run = options.spawnSync ?? spawnSync;
|
|
263
|
-
const sleep = options.sleep ?? ((ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms));
|
|
264
|
-
const attempts = options.verificationAttempts ?? 5;
|
|
265
|
-
const cwd = options.cwd;
|
|
266
|
-
|
|
267
|
-
// Re-check remote ownership marker before any destructive delete.
|
|
268
|
-
verifyOwnedRepository(owned, options);
|
|
269
|
-
let deleteError;
|
|
270
|
-
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
271
|
-
try {
|
|
272
|
-
command("gh", ["repo", "delete", owned.fullName, "--yes"], { cwd, label: "delete throwaway repository" });
|
|
273
|
-
deleteError = undefined;
|
|
274
|
-
break;
|
|
275
|
-
} catch (error) {
|
|
276
|
-
deleteError = error;
|
|
277
|
-
const probe = probeRepositoryOwnership(owned.fullName, options);
|
|
278
|
-
if (!probe.exists) return { deleted: true, httpStatus: 404 };
|
|
279
|
-
if (!probe.isPrivate || probe.description !== owned.description) {
|
|
280
|
-
fail("throwaway repository ownership changed after delete failed; refusing another delete", error?.details ?? "");
|
|
281
|
-
}
|
|
282
|
-
if (attempt < attempts) sleep(options.verificationDelayMs ?? 500);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
if (deleteError) throw deleteError;
|
|
286
|
-
let output = "";
|
|
287
|
-
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
288
|
-
const probe = run("gh", ["api", "-i", `repos/${owned.fullName}`], {
|
|
289
|
-
cwd,
|
|
290
|
-
env: options.env ?? process.env,
|
|
291
|
-
encoding: "utf8",
|
|
292
|
-
maxBuffer: 1024 * 1024,
|
|
293
|
-
timeout: options.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS,
|
|
294
|
-
});
|
|
295
|
-
output = [probe.error?.message, probe.stdout, probe.stderr].filter(Boolean).join("\n");
|
|
296
|
-
if (!probe.error && probe.status !== 0 && explicitHttpStatus(probe.stdout) === "404") {
|
|
297
|
-
return { deleted: true, httpStatus: 404 };
|
|
298
|
-
}
|
|
299
|
-
if (attempt < attempts) sleep(options.verificationDelayMs ?? 500);
|
|
300
|
-
}
|
|
301
|
-
fail("throwaway repository deletion was not independently verified as HTTP 404", output);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
export function validatePrUrl(repo, prUrl, options = {}) {
|
|
305
|
-
const fullName = String(repo?.fullName ?? "").toLowerCase();
|
|
306
|
-
if (!fullName.includes("/")) fail("throwaway repository fullName is required to validate PR URLs");
|
|
307
|
-
const command = options.runCommand ?? ((name, args, opts = {}) => runTimedCommand(name, args, { ...opts, spawnSync: options.spawnSync }));
|
|
308
|
-
let url;
|
|
309
|
-
try {
|
|
310
|
-
url = new URL(prUrl);
|
|
311
|
-
} catch {
|
|
312
|
-
fail("cloud report returned an invalid PR URL", prUrl);
|
|
313
|
-
}
|
|
314
|
-
if (
|
|
315
|
-
url.protocol !== "https:"
|
|
316
|
-
|| url.username
|
|
317
|
-
|| url.password
|
|
318
|
-
|| url.search
|
|
319
|
-
|| url.hash
|
|
320
|
-
|| url.hostname !== "github.com"
|
|
321
|
-
|| !url.pathname.startsWith(`/${fullName}/pull/`)
|
|
322
|
-
) {
|
|
323
|
-
fail("cloud report returned a PR URL outside the throwaway repository", prUrl);
|
|
324
|
-
}
|
|
325
|
-
const result = JSON.parse(command("gh", ["pr", "view", prUrl, "--json", "url,state"], {
|
|
326
|
-
cwd: options.cwd,
|
|
327
|
-
label: "validate reported PR URL",
|
|
328
|
-
}));
|
|
329
|
-
if (result.url !== prUrl || typeof result.state !== "string") fail("gh could not validate reported PR URL", prUrl);
|
|
330
|
-
return result;
|
|
331
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { CloudSmokeShutdownController } from "./cloud-smoke-shutdown.mjs";
|
|
2
|
-
|
|
3
|
-
interface CloudSmokeRpcEvent {
|
|
4
|
-
type: string;
|
|
5
|
-
[key: string]: unknown;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
interface CloudSmokeRpcResponse extends CloudSmokeRpcEvent {
|
|
9
|
-
type: "response";
|
|
10
|
-
id: string;
|
|
11
|
-
success: boolean;
|
|
12
|
-
data?: { text?: string; [key: string]: unknown };
|
|
13
|
-
error?: unknown;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function createCloudSmokePiRunner(options: {
|
|
17
|
-
root: string;
|
|
18
|
-
model: string;
|
|
19
|
-
shutdown: CloudSmokeShutdownController;
|
|
20
|
-
buildEnv(artifactDir: string, options?: Record<string, unknown>): NodeJS.ProcessEnv;
|
|
21
|
-
buildWorkspace(artifactDir: string): string;
|
|
22
|
-
}): {
|
|
23
|
-
runPi(options: {
|
|
24
|
-
artifactDir: string;
|
|
25
|
-
envOptions?: Record<string, unknown>;
|
|
26
|
-
message: string;
|
|
27
|
-
sessionId: string;
|
|
28
|
-
timeoutMs: number;
|
|
29
|
-
}): Promise<{ code: number | null; signal: NodeJS.Signals | null; stdout: string; stderr: string }>;
|
|
30
|
-
startRpc(options: {
|
|
31
|
-
artifactDir: string;
|
|
32
|
-
contextHandoff?: string;
|
|
33
|
-
sessionId: string;
|
|
34
|
-
envOptions?: Record<string, unknown>;
|
|
35
|
-
}): Promise<{
|
|
36
|
-
events: CloudSmokeRpcEvent[];
|
|
37
|
-
send(type: string, extra?: Record<string, unknown>, timeoutMs?: number): Promise<CloudSmokeRpcResponse>;
|
|
38
|
-
stop(): Promise<void>;
|
|
39
|
-
throwIfFailed(): void;
|
|
40
|
-
readonly stderr: string;
|
|
41
|
-
}>;
|
|
42
|
-
};
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
CHILD_PROCESS_TREE_SPAWN_OPTIONS,
|
|
6
|
-
terminateChild,
|
|
7
|
-
} from "./cursor-child-process.mjs";
|
|
8
|
-
import {
|
|
9
|
-
awaitCloudSmokeShutdown,
|
|
10
|
-
createCloudSmokeTerminalFailureState,
|
|
11
|
-
installCloudSmokeChildErrorHandlers,
|
|
12
|
-
routeCloudSmokeChildClose,
|
|
13
|
-
stopCloudSmokeTrackedChild,
|
|
14
|
-
} from "./cloud-smoke-shutdown.mjs";
|
|
15
|
-
|
|
16
|
-
export function createCloudSmokePiRunner({ root, model, shutdown, buildEnv, buildWorkspace }) {
|
|
17
|
-
const findPiBin = () => {
|
|
18
|
-
const local = join(root, "node_modules", ".bin", process.platform === "win32" ? "pi.cmd" : "pi");
|
|
19
|
-
return existsSync(local) ? local : process.platform === "win32" ? "pi.cmd" : "pi";
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const spawnPi = (artifactDir, args, envOptions, stdio) => {
|
|
23
|
-
const sessionDir = join(artifactDir, "sessions");
|
|
24
|
-
mkdirSync(sessionDir, { recursive: true });
|
|
25
|
-
return spawn(findPiBin(), args(sessionDir), {
|
|
26
|
-
cwd: buildWorkspace(artifactDir),
|
|
27
|
-
env: buildEnv(artifactDir, envOptions),
|
|
28
|
-
stdio,
|
|
29
|
-
...CHILD_PROCESS_TREE_SPAWN_OPTIONS,
|
|
30
|
-
});
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const runPi = ({ artifactDir, envOptions = {}, message, sessionId, timeoutMs }) => {
|
|
34
|
-
shutdown.throwIfRequested();
|
|
35
|
-
const child = spawnPi(
|
|
36
|
-
artifactDir,
|
|
37
|
-
(sessionDir) => ["-e", root, "--model", model, "--approve", "--session-dir", sessionDir, "--session-id", sessionId, "-p", message],
|
|
38
|
-
envOptions,
|
|
39
|
-
["ignore", "pipe", "pipe"],
|
|
40
|
-
);
|
|
41
|
-
const tracking = shutdown.track(child);
|
|
42
|
-
let stdout = "";
|
|
43
|
-
let stderr = "";
|
|
44
|
-
child.stdout.setEncoding("utf8");
|
|
45
|
-
child.stderr.setEncoding("utf8");
|
|
46
|
-
child.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
47
|
-
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
48
|
-
return new Promise((resolveRun, rejectRun) => {
|
|
49
|
-
let settled = false;
|
|
50
|
-
let timeoutStarted = false;
|
|
51
|
-
let timeoutTermination = Promise.resolve();
|
|
52
|
-
const settle = (callback, value) => {
|
|
53
|
-
if (settled) return;
|
|
54
|
-
settled = true;
|
|
55
|
-
clearTimeout(timer);
|
|
56
|
-
shutdown.signal.removeEventListener("abort", onShutdown);
|
|
57
|
-
callback(value);
|
|
58
|
-
};
|
|
59
|
-
const onShutdown = () => {
|
|
60
|
-
const termination = Promise.allSettled([tracking, timeoutTermination]).then((results) => {
|
|
61
|
-
const failed = results.find((result) => result.status === "rejected");
|
|
62
|
-
if (failed) throw failed.reason;
|
|
63
|
-
});
|
|
64
|
-
void awaitCloudSmokeShutdown(shutdown, termination).then((error) => settle(rejectRun, error));
|
|
65
|
-
};
|
|
66
|
-
const timer = setTimeout(() => {
|
|
67
|
-
if (shutdown.signal.aborted) return onShutdown();
|
|
68
|
-
timeoutStarted = true;
|
|
69
|
-
timeoutTermination = terminateChild(child);
|
|
70
|
-
void timeoutTermination.then(
|
|
71
|
-
() => shutdown.signal.aborted
|
|
72
|
-
? onShutdown()
|
|
73
|
-
: settle(rejectRun, new Error(`pi cloud smoke timed out after ${timeoutMs}ms`)),
|
|
74
|
-
(error) => shutdown.signal.aborted
|
|
75
|
-
? onShutdown()
|
|
76
|
-
: settle(rejectRun, new Error(`pi cloud smoke timed out and cleanup failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error })),
|
|
77
|
-
);
|
|
78
|
-
}, timeoutMs);
|
|
79
|
-
shutdown.signal.addEventListener("abort", onShutdown, { once: true });
|
|
80
|
-
if (shutdown.signal.aborted) onShutdown();
|
|
81
|
-
void tracking.catch((error) => shutdown.signal.aborted ? onShutdown() : settle(rejectRun, error));
|
|
82
|
-
child.once("error", (error) => routeCloudSmokeChildClose(
|
|
83
|
-
shutdown,
|
|
84
|
-
timeoutStarted,
|
|
85
|
-
onShutdown,
|
|
86
|
-
(failure) => settle(rejectRun, failure),
|
|
87
|
-
error,
|
|
88
|
-
));
|
|
89
|
-
child.once("close", (code, signal) => routeCloudSmokeChildClose(
|
|
90
|
-
shutdown,
|
|
91
|
-
timeoutStarted,
|
|
92
|
-
onShutdown,
|
|
93
|
-
(result) => settle(resolveRun, result),
|
|
94
|
-
{ code, signal, stdout, stderr },
|
|
95
|
-
));
|
|
96
|
-
});
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const startRpc = async ({ artifactDir, contextHandoff = "fresh", sessionId, envOptions = {} }) => {
|
|
100
|
-
shutdown.throwIfRequested();
|
|
101
|
-
const child = spawnPi(
|
|
102
|
-
artifactDir,
|
|
103
|
-
(sessionDir) => ["--mode", "rpc", "-e", root, "--model", model, "--approve", "--session-dir", sessionDir, "--session-id", sessionId],
|
|
104
|
-
{ contextHandoff, ...envOptions },
|
|
105
|
-
["pipe", "pipe", "pipe"],
|
|
106
|
-
);
|
|
107
|
-
const tracking = shutdown.track(child);
|
|
108
|
-
let stderr = "";
|
|
109
|
-
const events = [];
|
|
110
|
-
const pending = new Map();
|
|
111
|
-
let requestId = 0;
|
|
112
|
-
let stdoutBuffer = "";
|
|
113
|
-
child.stdout.setEncoding("utf8");
|
|
114
|
-
child.stderr.setEncoding("utf8");
|
|
115
|
-
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
116
|
-
child.stdout.on("data", (chunk) => {
|
|
117
|
-
stdoutBuffer += chunk;
|
|
118
|
-
let newlineIndex;
|
|
119
|
-
while ((newlineIndex = stdoutBuffer.indexOf("\n")) >= 0) {
|
|
120
|
-
const line = stdoutBuffer.slice(0, newlineIndex);
|
|
121
|
-
stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1);
|
|
122
|
-
if (!line.trim()) continue;
|
|
123
|
-
let message;
|
|
124
|
-
try { message = JSON.parse(line); } catch { continue; }
|
|
125
|
-
if (message.type === "response" && pending.has(message.id)) {
|
|
126
|
-
if (shutdown.signal.aborted) {
|
|
127
|
-
rejectAfterShutdown();
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
const request = pending.get(message.id);
|
|
131
|
-
pending.delete(message.id);
|
|
132
|
-
clearTimeout(request.timer);
|
|
133
|
-
request.resolve(message);
|
|
134
|
-
} else events.push(message);
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
const rejectPending = (error) => {
|
|
138
|
-
for (const request of pending.values()) {
|
|
139
|
-
clearTimeout(request.timer);
|
|
140
|
-
request.reject(error);
|
|
141
|
-
}
|
|
142
|
-
pending.clear();
|
|
143
|
-
};
|
|
144
|
-
const terminalState = createCloudSmokeTerminalFailureState(rejectPending);
|
|
145
|
-
const rejectAfterShutdown = () => {
|
|
146
|
-
void awaitCloudSmokeShutdown(shutdown, tracking).then(rejectPending);
|
|
147
|
-
};
|
|
148
|
-
shutdown.signal.addEventListener("abort", rejectAfterShutdown, { once: true });
|
|
149
|
-
if (shutdown.signal.aborted) rejectAfterShutdown();
|
|
150
|
-
const routeRpcError = installCloudSmokeChildErrorHandlers(
|
|
151
|
-
child,
|
|
152
|
-
shutdown,
|
|
153
|
-
rejectAfterShutdown,
|
|
154
|
-
terminalState.record,
|
|
155
|
-
);
|
|
156
|
-
child.once("close", () => {
|
|
157
|
-
shutdown.signal.removeEventListener("abort", rejectAfterShutdown);
|
|
158
|
-
if (shutdown.signal.aborted) rejectAfterShutdown();
|
|
159
|
-
else terminalState.record(new Error(`cloud smoke RPC exited. Stderr: ${stderr}`));
|
|
160
|
-
});
|
|
161
|
-
const send = (type, extra = {}, timeoutMs = 120000) => new Promise((resolveRequest, rejectRequest) => {
|
|
162
|
-
try {
|
|
163
|
-
shutdown.throwIfRequested();
|
|
164
|
-
terminalState.throwIfFailed();
|
|
165
|
-
} catch (error) {
|
|
166
|
-
rejectRequest(error);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
const id = `cloud_smoke_${++requestId}`;
|
|
170
|
-
const timer = setTimeout(() => {
|
|
171
|
-
if (shutdown.signal.aborted) return rejectAfterShutdown();
|
|
172
|
-
pending.delete(id);
|
|
173
|
-
rejectRequest(new Error(`timeout waiting for ${type}. Stderr: ${stderr}`));
|
|
174
|
-
}, timeoutMs);
|
|
175
|
-
pending.set(id, { resolve: resolveRequest, reject: rejectRequest, timer });
|
|
176
|
-
try {
|
|
177
|
-
child.stdin.write(`${JSON.stringify({ id, type, ...extra })}\n`);
|
|
178
|
-
} catch (error) {
|
|
179
|
-
routeRpcError(error);
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
const stop = async () => {
|
|
183
|
-
shutdown.signal.removeEventListener("abort", rejectAfterShutdown);
|
|
184
|
-
if (!shutdown.signal.aborted) terminalState.record(new Error("cloud smoke RPC stopped"));
|
|
185
|
-
try {
|
|
186
|
-
const shutdownReason = await stopCloudSmokeTrackedChild(
|
|
187
|
-
shutdown,
|
|
188
|
-
tracking,
|
|
189
|
-
() => terminateChild(child, { graceMs: 15_000 }),
|
|
190
|
-
);
|
|
191
|
-
if (shutdownReason) rejectPending(shutdownReason);
|
|
192
|
-
} catch (error) {
|
|
193
|
-
rejectPending(error);
|
|
194
|
-
throw error;
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
try {
|
|
198
|
-
await tracking;
|
|
199
|
-
if (shutdown.signal.aborted) throw await awaitCloudSmokeShutdown(shutdown, tracking);
|
|
200
|
-
} catch (error) {
|
|
201
|
-
rejectPending(error);
|
|
202
|
-
throw error;
|
|
203
|
-
}
|
|
204
|
-
return {
|
|
205
|
-
events,
|
|
206
|
-
send,
|
|
207
|
-
stop,
|
|
208
|
-
throwIfFailed: terminalState.throwIfFailed,
|
|
209
|
-
get stderr() { return stderr; },
|
|
210
|
-
};
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
return { runPi, startRpc };
|
|
214
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type { ChildProcess } from "node:child_process";
|
|
2
|
-
|
|
3
|
-
export type CloudSmokeShutdownController = {
|
|
4
|
-
signal: AbortSignal;
|
|
5
|
-
track(child: ChildProcess): Promise<() => void>;
|
|
6
|
-
request(signalName: string): Promise<Error>;
|
|
7
|
-
readonly reason: Error | undefined;
|
|
8
|
-
wait(): Promise<void>;
|
|
9
|
-
throwIfRequested(): void;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export function createCloudSmokeShutdownController(
|
|
13
|
-
terminate: (child: ChildProcess) => void | Promise<void>,
|
|
14
|
-
): CloudSmokeShutdownController;
|
|
15
|
-
|
|
16
|
-
export function awaitCloudSmokeShutdown(
|
|
17
|
-
shutdown: CloudSmokeShutdownController,
|
|
18
|
-
tracking?: Promise<unknown>,
|
|
19
|
-
): Promise<Error>;
|
|
20
|
-
|
|
21
|
-
export function checkpointCloudSmokeShutdown(
|
|
22
|
-
shutdown: CloudSmokeShutdownController,
|
|
23
|
-
): Promise<void>;
|
|
24
|
-
|
|
25
|
-
export function createCloudSmokeTerminalFailureState(
|
|
26
|
-
rejectPending: (error: Error) => void,
|
|
27
|
-
): {
|
|
28
|
-
record(error: Error): void;
|
|
29
|
-
throwIfFailed(): void;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export function stopCloudSmokeTrackedChild(
|
|
33
|
-
shutdown: CloudSmokeShutdownController,
|
|
34
|
-
tracking: Promise<unknown>,
|
|
35
|
-
terminateChild: () => void | Promise<void>,
|
|
36
|
-
): Promise<Error | undefined>;
|
|
37
|
-
|
|
38
|
-
export function routeCloudSmokeChildError(
|
|
39
|
-
shutdown: CloudSmokeShutdownController,
|
|
40
|
-
onShutdown: () => void,
|
|
41
|
-
onError: (error: Error) => void,
|
|
42
|
-
error: Error,
|
|
43
|
-
): void;
|
|
44
|
-
|
|
45
|
-
export function installCloudSmokeChildErrorHandlers(
|
|
46
|
-
child: ChildProcess,
|
|
47
|
-
shutdown: CloudSmokeShutdownController,
|
|
48
|
-
onShutdown: () => void,
|
|
49
|
-
onError: (error: Error) => void,
|
|
50
|
-
): (error: Error) => void;
|
|
51
|
-
|
|
52
|
-
export function routeCloudSmokeChildClose<T>(
|
|
53
|
-
shutdown: CloudSmokeShutdownController,
|
|
54
|
-
timedOut: boolean,
|
|
55
|
-
onShutdown: () => void,
|
|
56
|
-
onClose: (result: T) => void,
|
|
57
|
-
result: T,
|
|
58
|
-
): void;
|
|
59
|
-
|
|
60
|
-
export function installCloudSmokeSignalHandlers(
|
|
61
|
-
shutdown: CloudSmokeShutdownController,
|
|
62
|
-
processLike?: {
|
|
63
|
-
on(signalName: string, handler: () => void): unknown;
|
|
64
|
-
off(signalName: string, handler: () => void): unknown;
|
|
65
|
-
},
|
|
66
|
-
onSignal?: (signalName: string) => void,
|
|
67
|
-
): () => void;
|