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
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cursor-sdk",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "pi provider extension backed by @cursor/sdk local agents",
|
|
3
|
+
"version": "0.1.57",
|
|
4
|
+
"description": "pi provider extension backed by @cursor/sdk local and cloud agents",
|
|
5
5
|
"author": "Mitch Fultz (https://github.com/fitchmultz)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
"scripts/visual-tui-smoke.mjs",
|
|
31
31
|
"scripts/visual-tui-smoke-self-test.mjs",
|
|
32
32
|
"scripts/isolated-cursor-smoke.sh",
|
|
33
|
+
"scripts/cloud-runtime-smoke.mjs",
|
|
34
|
+
"scripts/cloud-runtime-smoke.d.mts",
|
|
35
|
+
"scripts/local-resume-smoke.mjs",
|
|
36
|
+
"scripts/local-resume-cleanup-smoke.mjs",
|
|
37
|
+
"scripts/local-resume-smoke.d.mts",
|
|
33
38
|
"scripts/fixtures/plan-strip-shim",
|
|
34
39
|
"scripts/validate-smoke-jsonl.mjs",
|
|
35
40
|
"scripts/probe-mcp-coldstart.mjs",
|
|
@@ -39,6 +44,13 @@
|
|
|
39
44
|
"scripts/debug-provider-events.d.mts",
|
|
40
45
|
"platform-smoke.config.mjs",
|
|
41
46
|
"scripts/platform-smoke.mjs",
|
|
47
|
+
"scripts/platform-smoke/artifact-bundle-chunk.mjs",
|
|
48
|
+
"scripts/platform-smoke/artifact-bundle-contract.mjs",
|
|
49
|
+
"scripts/platform-smoke/artifact-fs-safety.mjs",
|
|
50
|
+
"scripts/platform-smoke/artifact-anchored-extract.mjs",
|
|
51
|
+
"scripts/platform-smoke/artifact-anchored-extract.d.mts",
|
|
52
|
+
"scripts/platform-smoke/artifact-openat-extract.c",
|
|
53
|
+
"scripts/platform-smoke/artifact-secrets.mjs",
|
|
42
54
|
"scripts/platform-smoke/assertions.mjs",
|
|
43
55
|
"scripts/platform-smoke/artifacts.mjs",
|
|
44
56
|
"scripts/platform-smoke/card-detect.mjs",
|
|
@@ -46,16 +58,22 @@
|
|
|
46
58
|
"scripts/platform-smoke/doctor.mjs",
|
|
47
59
|
"scripts/platform-smoke/live-suite-runner.mjs",
|
|
48
60
|
"scripts/platform-smoke/jsonl-text.mjs",
|
|
61
|
+
"scripts/platform-smoke/local-resume-runner.mjs",
|
|
62
|
+
"scripts/platform-smoke/local-resume-suites.mjs",
|
|
63
|
+
"scripts/platform-smoke/local-resume-suites.d.mts",
|
|
49
64
|
"scripts/platform-smoke/platform-build-windows.ps1",
|
|
50
65
|
"scripts/platform-smoke/pty-capture.mjs",
|
|
51
66
|
"scripts/platform-smoke/render-ansi.mjs",
|
|
52
67
|
"scripts/platform-smoke/scenarios.mjs",
|
|
68
|
+
"scripts/platform-smoke/target-runtime.mjs",
|
|
53
69
|
"scripts/platform-smoke/targets.mjs",
|
|
54
70
|
"scripts/platform-smoke/visual-evidence.mjs",
|
|
71
|
+
"scripts/platform-smoke/wrapped-line-match.mjs",
|
|
55
72
|
"scripts/lib/cursor-cli-args.mjs",
|
|
56
73
|
"scripts/lib/cursor-cli-args.d.mts",
|
|
57
74
|
"scripts/lib/cursor-child-process.mjs",
|
|
58
75
|
"scripts/lib/cursor-child-process.d.mts",
|
|
76
|
+
"scripts/lib/local-resume-smoke-harness.mjs",
|
|
59
77
|
"scripts/lib/cursor-script-fail.mjs",
|
|
60
78
|
"scripts/lib/cursor-script-fail.d.mts",
|
|
61
79
|
"scripts/lib/cursor-smoke-env.mjs",
|
|
@@ -76,6 +94,7 @@
|
|
|
76
94
|
"docs/cursor-native-tool-replay.md",
|
|
77
95
|
"docs/cursor-native-tool-visual-audit.md",
|
|
78
96
|
"docs/platform-smoke.md",
|
|
97
|
+
"docs/platform-smoke-implementation.md",
|
|
79
98
|
"LICENSE",
|
|
80
99
|
"CHANGELOG.md"
|
|
81
100
|
],
|
|
@@ -92,15 +111,28 @@
|
|
|
92
111
|
"test": "vitest run",
|
|
93
112
|
"test:watch": "vitest",
|
|
94
113
|
"refresh:cursor-snapshots": "node scripts/refresh-cursor-model-snapshots.mjs",
|
|
114
|
+
"check:cursor-snapshots": "node scripts/refresh-cursor-model-snapshots.mjs --check",
|
|
95
115
|
"smoke:live": "scripts/tmux-live-smoke.sh",
|
|
96
116
|
"smoke:visual": "node scripts/visual-tui-smoke.mjs",
|
|
97
117
|
"smoke:isolated": "scripts/isolated-cursor-smoke.sh",
|
|
118
|
+
"smoke:cloud": "node scripts/cloud-runtime-smoke.mjs",
|
|
119
|
+
"smoke:cloud:context": "node scripts/cloud-runtime-smoke.mjs --context-matrix",
|
|
120
|
+
"smoke:local-resume": "node scripts/local-resume-smoke.mjs",
|
|
121
|
+
"smoke:local-resume:safety": "node scripts/local-resume-smoke.mjs --safety",
|
|
122
|
+
"smoke:local-resume:tool-surface": "node scripts/local-resume-smoke.mjs --tool-surface",
|
|
123
|
+
"smoke:local-resume:abort": "node scripts/local-resume-smoke.mjs --abort",
|
|
124
|
+
"smoke:local-resume:tree": "node scripts/local-resume-smoke.mjs --tree",
|
|
125
|
+
"smoke:local-resume:copy-switch": "node scripts/local-resume-smoke.mjs --copy-switch",
|
|
126
|
+
"smoke:local-resume:fallback": "node scripts/local-resume-smoke.mjs --fallback",
|
|
127
|
+
"smoke:local-resume:compaction": "node scripts/local-resume-smoke.mjs --compaction",
|
|
128
|
+
"smoke:local-resume:default-dry-run": "node scripts/local-resume-smoke.mjs --default-dry-run",
|
|
129
|
+
"smoke:local-resume:cleanup": "node scripts/local-resume-smoke.mjs --cleanup",
|
|
98
130
|
"smoke:steering": "node scripts/steering-rpc-smoke.mjs",
|
|
99
131
|
"smoke:jsonl": "node scripts/validate-smoke-jsonl.mjs",
|
|
100
132
|
"debug:sdk-events": "node scripts/debug-sdk-events.mjs",
|
|
101
133
|
"debug:provider-events": "node scripts/debug-provider-events.mjs",
|
|
102
134
|
"debug:mcp-coldstart": "node scripts/probe-mcp-coldstart.mjs",
|
|
103
|
-
"check:platform-smoke": "node --check platform-smoke.config.mjs && node --check scripts/platform-smoke.mjs && node --check scripts/platform-smoke/assertions.mjs && node --check scripts/platform-smoke/artifacts.mjs && node --check scripts/platform-smoke/card-detect.mjs && node --check scripts/platform-smoke/crabbox-runner.mjs && node --check scripts/platform-smoke/doctor.mjs && node --check scripts/platform-smoke/jsonl-text.mjs && node --check scripts/platform-smoke/live-suite-runner.mjs && node --check scripts/platform-smoke/pty-capture.mjs && node --check scripts/platform-smoke/render-ansi.mjs && node --check scripts/platform-smoke/scenarios.mjs && node --check scripts/platform-smoke/targets.mjs && node --check scripts/platform-smoke/visual-evidence.mjs && vitest run test/smoke-tooling.test.ts",
|
|
135
|
+
"check:platform-smoke": "node --check platform-smoke.config.mjs && node --check scripts/cloud-runtime-smoke.mjs && node --check scripts/local-resume-cleanup-smoke.mjs && node --check scripts/local-resume-smoke.mjs && node --check scripts/lib/local-resume-smoke-harness.mjs && node --check scripts/platform-smoke.mjs && node --check scripts/platform-smoke/artifact-bundle-chunk.mjs && node --check scripts/platform-smoke/artifact-bundle-contract.mjs && node --check scripts/platform-smoke/artifact-fs-safety.mjs && node --check scripts/platform-smoke/artifact-anchored-extract.mjs && node --check scripts/platform-smoke/artifact-secrets.mjs && node --check scripts/platform-smoke/assertions.mjs && node --check scripts/platform-smoke/artifacts.mjs && node --check scripts/platform-smoke/card-detect.mjs && node --check scripts/platform-smoke/crabbox-runner.mjs && node --check scripts/platform-smoke/doctor.mjs && node --check scripts/platform-smoke/jsonl-text.mjs && node --check scripts/platform-smoke/live-suite-runner.mjs && node --check scripts/platform-smoke/local-resume-runner.mjs && node --check scripts/platform-smoke/pty-capture.mjs && node --check scripts/platform-smoke/render-ansi.mjs && node --check scripts/platform-smoke/scenarios.mjs && node --check scripts/platform-smoke/target-runtime.mjs && node --check scripts/platform-smoke/targets.mjs && node --check scripts/platform-smoke/visual-evidence.mjs && node --check scripts/platform-smoke/wrapped-line-match.mjs && vitest run test/platform-artifact-boundaries.test.ts test/platform-smoke-artifact-transport.test.ts test/smoke-cli-package-contracts.test.ts test/smoke-tooling.test.ts",
|
|
104
136
|
"smoke:platform": "node scripts/platform-smoke.mjs",
|
|
105
137
|
"smoke:platform:doctor": "node scripts/platform-smoke.mjs doctor",
|
|
106
138
|
"smoke:platform:macos": "node scripts/platform-smoke.mjs run --target macos",
|
|
@@ -109,7 +141,7 @@
|
|
|
109
141
|
"smoke:platform:all": "npm run smoke:platform:doctor && node scripts/platform-smoke.mjs run --target macos,ubuntu,windows-native"
|
|
110
142
|
},
|
|
111
143
|
"dependencies": {
|
|
112
|
-
"@cursor/sdk": "1.0.
|
|
144
|
+
"@cursor/sdk": "1.0.23",
|
|
113
145
|
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
114
146
|
},
|
|
115
147
|
"peerDependencies": {
|
|
@@ -119,9 +151,9 @@
|
|
|
119
151
|
"typebox": "*"
|
|
120
152
|
},
|
|
121
153
|
"devDependencies": {
|
|
122
|
-
"@earendil-works/pi-ai": "0.80.
|
|
123
|
-
"@earendil-works/pi-coding-agent": "0.80.
|
|
124
|
-
"@earendil-works/pi-tui": "0.80.
|
|
154
|
+
"@earendil-works/pi-ai": "0.80.5",
|
|
155
|
+
"@earendil-works/pi-coding-agent": "0.80.5",
|
|
156
|
+
"@earendil-works/pi-tui": "0.80.5",
|
|
125
157
|
"@xterm/xterm": "^6.0.0",
|
|
126
158
|
"node-pty": "^1.1.0",
|
|
127
159
|
"playwright": "^1.60.0",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Platform smoke configuration for pi-cursor-sdk.
|
|
2
2
|
// Reusable across pi extensions: change package name, model IDs, scenarios, and card matrix only.
|
|
3
3
|
|
|
4
|
+
import { LOCAL_RESUME_SUITE_NAMES } from "./scripts/platform-smoke/local-resume-suites.mjs";
|
|
5
|
+
|
|
4
6
|
export default {
|
|
5
7
|
packageName: "pi-cursor-sdk",
|
|
6
8
|
cursorModel: "cursor/composer-2-5",
|
|
@@ -16,12 +18,14 @@ export default {
|
|
|
16
18
|
"cursor-native-visual-matrix",
|
|
17
19
|
"cursor-bridge-visual-matrix",
|
|
18
20
|
"cursor-abort-cleanup",
|
|
21
|
+
...LOCAL_RESUME_SUITE_NAMES,
|
|
19
22
|
],
|
|
20
23
|
requiredCrabbox: {
|
|
21
24
|
install: "Homebrew package or PLATFORM_SMOKE_CRABBOX override",
|
|
22
25
|
minVersion: "0.26.0",
|
|
23
26
|
},
|
|
24
|
-
ubuntuContainerImage: "
|
|
27
|
+
ubuntuContainerImage: "pi-cursor-sdk-platform-node:24.16-root",
|
|
28
|
+
ubuntuContainerBaseImage: "cimg/node:24.16",
|
|
25
29
|
nodeValidationMajor: 24,
|
|
26
30
|
windowsParallels: {
|
|
27
31
|
sourceVm: "pi-extension-windows-template",
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export function buildCloudSmokeEnv(artifactDir: string, options?: { contextHandoff?: "fresh" | "bootstrap" | "never" }): NodeJS.ProcessEnv;
|
|
2
|
+
export function buildCloudSmokeWorkspace(artifactDir: string): string;
|
|
3
|
+
export function cloudAgentIdsFromLifecycleArtifacts(artifactDir: string): string[];
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
import {
|
|
8
|
+
CHILD_PROCESS_TREE_SPAWN_OPTIONS,
|
|
9
|
+
terminateChild,
|
|
10
|
+
} from "./lib/cursor-child-process.mjs";
|
|
11
|
+
import { buildCursorSmokeEnv } from "./lib/cursor-smoke-env.mjs";
|
|
12
|
+
import {
|
|
13
|
+
CLOUD_AGENT_ID_PATTERN,
|
|
14
|
+
CLOUD_LIFECYCLE_ENTRY_TYPE,
|
|
15
|
+
CLOUD_LIFECYCLE_JOURNAL_PREFIX,
|
|
16
|
+
} from "../shared/cursor-cloud-lifecycle-constants.mjs";
|
|
17
|
+
import { scrubSensitiveText } from "../shared/cursor-sensitive-text.mjs";
|
|
18
|
+
|
|
19
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
20
|
+
const argv = process.argv.slice(2);
|
|
21
|
+
const args = new Set(argv);
|
|
22
|
+
const CLOUD_ENV_NAMES = [
|
|
23
|
+
"PI_CURSOR_RUNTIME",
|
|
24
|
+
"PI_CURSOR_CLOUD_ACK",
|
|
25
|
+
"PI_CURSOR_CLOUD_ALLOW_LOCAL_STATE",
|
|
26
|
+
"PI_CURSOR_CLOUD_CONTEXT",
|
|
27
|
+
"PI_CURSOR_CLOUD_REPO",
|
|
28
|
+
"PI_CURSOR_CLOUD_BRANCH",
|
|
29
|
+
"PI_CURSOR_CLOUD_DIRECT_PUSH",
|
|
30
|
+
"PI_CURSOR_CLOUD_ENV",
|
|
31
|
+
"PI_CURSOR_CLOUD_ENV_FROM_FILES",
|
|
32
|
+
"PI_CURSOR_CLOUD_ENV_TYPE",
|
|
33
|
+
"PI_CURSOR_CLOUD_ENV_NAME",
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
function scrubSmokeText(value) {
|
|
37
|
+
return scrubSensitiveText(String(value), process.env.CURSOR_API_KEY);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class SmokeFailure extends Error {
|
|
41
|
+
constructor(message, details = "") {
|
|
42
|
+
super(message);
|
|
43
|
+
this.details = details;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function printHelp() {
|
|
48
|
+
console.log(`Opt-in live Cursor cloud smoke for pi-cursor-sdk.
|
|
49
|
+
|
|
50
|
+
Usage:
|
|
51
|
+
npm run smoke:cloud
|
|
52
|
+
npm run smoke:cloud:context
|
|
53
|
+
node scripts/cloud-runtime-smoke.mjs [--context-matrix]
|
|
54
|
+
|
|
55
|
+
Environment:
|
|
56
|
+
CURSOR_API_KEY Required for the cloud run and archival cleanup.
|
|
57
|
+
CURSOR_CLOUD_SMOKE_MODEL Cursor model id (default: cursor/composer-2-5).
|
|
58
|
+
CURSOR_CLOUD_SMOKE_TIMEOUT_MS Timeout in ms (default: 300000).
|
|
59
|
+
CURSOR_CLOUD_SMOKE_ENV_TYPE Optional Cursor-managed env type: cloud, pool, or machine.
|
|
60
|
+
CURSOR_CLOUD_SMOKE_ENV_NAME Optional Cursor-managed env name, used only with type.
|
|
61
|
+
CURSOR_CLOUD_SMOKE_KEEP_ARTIFACTS Keep temp artifacts when set to 1.
|
|
62
|
+
|
|
63
|
+
Options:
|
|
64
|
+
--context-matrix Run sessionful fresh-vs-bootstrap context handoff proof.
|
|
65
|
+
|
|
66
|
+
Exit codes:
|
|
67
|
+
0 cloud run passed and artifact contract matched
|
|
68
|
+
1 missing entitlement/auth, run failure, assertion failure, or cleanup failure
|
|
69
|
+
2 invalid command-line usage`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (args.has("-h") || args.has("--help")) {
|
|
73
|
+
printHelp();
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const unknownArgs = argv.filter((arg) => arg !== "--context-matrix");
|
|
78
|
+
const laneArgs = argv.filter((arg) => arg === "--context-matrix");
|
|
79
|
+
if (unknownArgs.length > 0 || laneArgs.length > 1) {
|
|
80
|
+
const message = unknownArgs.length > 0
|
|
81
|
+
? `unknown argument(s): ${unknownArgs.join(", ")}`
|
|
82
|
+
: "only one smoke lane may be selected";
|
|
83
|
+
console.error(scrubSmokeText(`[cloud-smoke] usage error: ${message}\nRun with --help for usage.`));
|
|
84
|
+
process.exit(2);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function fail(message, details = "") {
|
|
88
|
+
throw new SmokeFailure(message, details);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function reportFailure(error) {
|
|
92
|
+
console.error(scrubSmokeText(`[cloud-smoke] FAIL: ${error instanceof Error ? error.message : String(error)}`));
|
|
93
|
+
if (error instanceof SmokeFailure && error.details) console.error(scrubSmokeText(error.details));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function findPiBin() {
|
|
97
|
+
const local = join(root, "node_modules", ".bin", process.platform === "win32" ? "pi.cmd" : "pi");
|
|
98
|
+
return existsSync(local) ? local : process.platform === "win32" ? "pi.cmd" : "pi";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function optionalSmokeValue(name) {
|
|
102
|
+
const value = process.env[name]?.trim();
|
|
103
|
+
return value ? value : undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function buildCloudSmokeEnv(artifactDir, options = {}) {
|
|
107
|
+
const agentDir = join(artifactDir, "agent");
|
|
108
|
+
mkdirSync(agentDir, { recursive: true });
|
|
109
|
+
const env = buildCursorSmokeEnv({ settingSources: "none", eventDebugDir: artifactDir });
|
|
110
|
+
for (const name of CLOUD_ENV_NAMES) delete env[name];
|
|
111
|
+
const smokeEnvType = optionalSmokeValue("CURSOR_CLOUD_SMOKE_ENV_TYPE");
|
|
112
|
+
const smokeEnvName = optionalSmokeValue("CURSOR_CLOUD_SMOKE_ENV_NAME");
|
|
113
|
+
Object.assign(env, {
|
|
114
|
+
PI_CURSOR_RUNTIME: "cloud",
|
|
115
|
+
PI_CURSOR_CLOUD_ACK: "1",
|
|
116
|
+
PI_CURSOR_CLOUD_ALLOW_LOCAL_STATE: "1",
|
|
117
|
+
PI_CODING_AGENT_DIR: agentDir,
|
|
118
|
+
PI_CURSOR_CLOUD_CONTEXT: options.contextHandoff ?? "fresh",
|
|
119
|
+
...(smokeEnvType ? { PI_CURSOR_CLOUD_ENV_TYPE: smokeEnvType } : {}),
|
|
120
|
+
...(smokeEnvType && smokeEnvName ? { PI_CURSOR_CLOUD_ENV_NAME: smokeEnvName } : {}),
|
|
121
|
+
});
|
|
122
|
+
delete env.PI_CURSOR_PI_TOOL_BRIDGE_DEBUG;
|
|
123
|
+
return env;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function buildCloudSmokeWorkspace(artifactDir) {
|
|
127
|
+
const workspaceDir = join(artifactDir, "workspace");
|
|
128
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
129
|
+
return workspaceDir;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function runPi({ artifactDir, timeoutMs }) {
|
|
133
|
+
const model = process.env.CURSOR_CLOUD_SMOKE_MODEL || "cursor/composer-2-5";
|
|
134
|
+
const workspaceDir = buildCloudSmokeWorkspace(artifactDir);
|
|
135
|
+
const sessionDir = join(artifactDir, "sessions");
|
|
136
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
137
|
+
const child = spawn(
|
|
138
|
+
findPiBin(),
|
|
139
|
+
[
|
|
140
|
+
"-e", root,
|
|
141
|
+
"--model", model,
|
|
142
|
+
"--session-dir", sessionDir,
|
|
143
|
+
"--session-id", `cloud-smoke-${Date.now()}`,
|
|
144
|
+
"-p", "Reply exactly: cloud-smoke-ok",
|
|
145
|
+
],
|
|
146
|
+
{
|
|
147
|
+
cwd: workspaceDir,
|
|
148
|
+
env: buildCloudSmokeEnv(artifactDir),
|
|
149
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
150
|
+
...CHILD_PROCESS_TREE_SPAWN_OPTIONS,
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
let stdout = "";
|
|
154
|
+
let stderr = "";
|
|
155
|
+
child.stdout.setEncoding("utf8");
|
|
156
|
+
child.stderr.setEncoding("utf8");
|
|
157
|
+
child.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
158
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
159
|
+
return new Promise((resolveRun, rejectRun) => {
|
|
160
|
+
let timedOut = false;
|
|
161
|
+
const timer = setTimeout(() => {
|
|
162
|
+
timedOut = true;
|
|
163
|
+
void terminateChild(child).then(
|
|
164
|
+
() => rejectRun(new Error(`pi cloud smoke timed out after ${timeoutMs}ms`)),
|
|
165
|
+
(error) => rejectRun(new Error(`pi cloud smoke timed out and process-tree cleanup failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error })),
|
|
166
|
+
);
|
|
167
|
+
}, timeoutMs);
|
|
168
|
+
child.once("error", (error) => {
|
|
169
|
+
if (timedOut) return;
|
|
170
|
+
clearTimeout(timer);
|
|
171
|
+
rejectRun(error);
|
|
172
|
+
});
|
|
173
|
+
child.once("close", (code, signal) => {
|
|
174
|
+
if (timedOut) return;
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
resolveRun({ code, signal, stdout, stderr });
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function metadataFiles(dir) {
|
|
182
|
+
const files = [];
|
|
183
|
+
const stack = [dir];
|
|
184
|
+
while (stack.length > 0) {
|
|
185
|
+
const current = stack.pop();
|
|
186
|
+
try {
|
|
187
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
188
|
+
const path = join(current, entry.name);
|
|
189
|
+
if (entry.isDirectory()) stack.push(path);
|
|
190
|
+
else if (entry.name === "metadata.json") files.push(path);
|
|
191
|
+
}
|
|
192
|
+
} catch {}
|
|
193
|
+
}
|
|
194
|
+
return files.sort();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function readLatestMetadataIfPresent(artifactDir) {
|
|
198
|
+
const files = metadataFiles(artifactDir);
|
|
199
|
+
if (files.length === 0) return undefined;
|
|
200
|
+
const metadataPath = files.at(-1);
|
|
201
|
+
return { metadataPath, metadata: JSON.parse(readFileSync(metadataPath, "utf8")) };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function addExactCloudAgentId(ids, value) {
|
|
205
|
+
if (typeof value === "string" && CLOUD_AGENT_ID_PATTERN.test(value)) ids.add(value);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function cloudAgentIdsFromMetadata(artifactDir) {
|
|
209
|
+
const ids = new Set();
|
|
210
|
+
for (const metadataPath of metadataFiles(artifactDir)) {
|
|
211
|
+
try {
|
|
212
|
+
const metadata = JSON.parse(readFileSync(metadataPath, "utf8"));
|
|
213
|
+
addExactCloudAgentId(ids, metadata.run?.agentId ?? metadata.providerMeta?.cloudAgentId);
|
|
214
|
+
} catch {
|
|
215
|
+
// One corrupt optional debug artifact must not hide IDs available from other evidence.
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return [...ids];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function lifecycleArtifactFiles(dir) {
|
|
222
|
+
const files = [];
|
|
223
|
+
const stack = [dir];
|
|
224
|
+
while (stack.length > 0) {
|
|
225
|
+
const current = stack.pop();
|
|
226
|
+
try {
|
|
227
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
228
|
+
const path = join(current, entry.name);
|
|
229
|
+
if (entry.isDirectory()) stack.push(path);
|
|
230
|
+
else if (entry.name.endsWith(".jsonl") || (entry.name.startsWith(`${CLOUD_LIFECYCLE_JOURNAL_PREFIX}-`) && entry.name.endsWith(".journal"))) files.push(path);
|
|
231
|
+
}
|
|
232
|
+
} catch {}
|
|
233
|
+
}
|
|
234
|
+
return files.sort();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function cloudAgentIdsFromLifecycleArtifacts(artifactDir) {
|
|
238
|
+
const ids = new Set();
|
|
239
|
+
for (const path of lifecycleArtifactFiles(artifactDir)) {
|
|
240
|
+
const journal = path.split(/[\\/]/).at(-1)?.startsWith(`${CLOUD_LIFECYCLE_JOURNAL_PREFIX}-`) === true;
|
|
241
|
+
let lines;
|
|
242
|
+
try {
|
|
243
|
+
lines = readFileSync(path, "utf8").split(/\r?\n/);
|
|
244
|
+
} catch {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
for (const line of lines) {
|
|
248
|
+
if (!line) continue;
|
|
249
|
+
try {
|
|
250
|
+
const entry = JSON.parse(line);
|
|
251
|
+
const data = journal
|
|
252
|
+
? entry
|
|
253
|
+
: entry?.type === "custom" && entry.customType === CLOUD_LIFECYCLE_ENTRY_TYPE
|
|
254
|
+
? entry.data
|
|
255
|
+
: undefined;
|
|
256
|
+
addExactCloudAgentId(ids, data?.agentId);
|
|
257
|
+
} catch {
|
|
258
|
+
// Journals are newline-framed; one partial line must not hide later IDs.
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return [...ids];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function resolveMetadataArtifactPath(metadataPath, artifactPath) {
|
|
266
|
+
if (!artifactPath) return undefined;
|
|
267
|
+
return resolve(artifactPath) === artifactPath ? artifactPath : join(dirname(metadataPath), artifactPath);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function readJsonlIfPresent(path) {
|
|
271
|
+
if (!path || !existsSync(path)) return [];
|
|
272
|
+
return readFileSync(path, "utf8")
|
|
273
|
+
.split(/\n+/)
|
|
274
|
+
.filter(Boolean)
|
|
275
|
+
.map((line) => JSON.parse(line));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function startRpc({ artifactDir, contextHandoff, sessionId }) {
|
|
279
|
+
const model = process.env.CURSOR_CLOUD_SMOKE_MODEL || "cursor/composer-2-5";
|
|
280
|
+
const sessionDir = join(artifactDir, "sessions");
|
|
281
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
282
|
+
const child = spawn(
|
|
283
|
+
findPiBin(),
|
|
284
|
+
["--mode", "rpc", "-e", root, "--model", model, "--approve", "--session-dir", sessionDir, "--session-id", sessionId],
|
|
285
|
+
{
|
|
286
|
+
cwd: buildCloudSmokeWorkspace(artifactDir),
|
|
287
|
+
env: buildCloudSmokeEnv(artifactDir, { contextHandoff }),
|
|
288
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
289
|
+
...CHILD_PROCESS_TREE_SPAWN_OPTIONS,
|
|
290
|
+
},
|
|
291
|
+
);
|
|
292
|
+
let stderr = "";
|
|
293
|
+
const events = [];
|
|
294
|
+
const pending = new Map();
|
|
295
|
+
let requestId = 0;
|
|
296
|
+
let stdoutBuffer = "";
|
|
297
|
+
child.stdout.setEncoding("utf8");
|
|
298
|
+
child.stderr.setEncoding("utf8");
|
|
299
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
300
|
+
child.stdout.on("data", (chunk) => {
|
|
301
|
+
stdoutBuffer += chunk;
|
|
302
|
+
let newlineIndex;
|
|
303
|
+
while ((newlineIndex = stdoutBuffer.indexOf("\n")) >= 0) {
|
|
304
|
+
const line = stdoutBuffer.slice(0, newlineIndex);
|
|
305
|
+
stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1);
|
|
306
|
+
if (!line.trim()) continue;
|
|
307
|
+
let message;
|
|
308
|
+
try {
|
|
309
|
+
message = JSON.parse(line);
|
|
310
|
+
} catch {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (message.type === "response" && pending.has(message.id)) {
|
|
314
|
+
const request = pending.get(message.id);
|
|
315
|
+
pending.delete(message.id);
|
|
316
|
+
clearTimeout(request.timer);
|
|
317
|
+
request.resolve(message);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
events.push(message);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
const send = (type, extra = {}, timeoutMs = 120000) => new Promise((resolveRequest, rejectRequest) => {
|
|
324
|
+
const id = `cloud_smoke_${++requestId}`;
|
|
325
|
+
const timer = setTimeout(() => {
|
|
326
|
+
pending.delete(id);
|
|
327
|
+
rejectRequest(new Error(`timeout waiting for ${type}. Stderr: ${stderr}`));
|
|
328
|
+
}, timeoutMs);
|
|
329
|
+
pending.set(id, { resolve: resolveRequest, reject: rejectRequest, timer });
|
|
330
|
+
child.stdin.write(`${JSON.stringify({ id, type, ...extra })}\n`);
|
|
331
|
+
});
|
|
332
|
+
const stop = async () => {
|
|
333
|
+
for (const request of pending.values()) clearTimeout(request.timer);
|
|
334
|
+
pending.clear();
|
|
335
|
+
await terminateChild(child);
|
|
336
|
+
};
|
|
337
|
+
return { events, send, stop, get stderr() { return stderr; } };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function waitForAgentEnd(rpc, fromIndex, timeoutMs) {
|
|
341
|
+
const started = Date.now();
|
|
342
|
+
while (Date.now() - started < timeoutMs) {
|
|
343
|
+
if (rpc.events.slice(fromIndex).some((event) => event.type === "agent_end")) return;
|
|
344
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 250));
|
|
345
|
+
}
|
|
346
|
+
throw new Error(`timeout waiting for agent_end. Stderr: ${rpc.stderr}`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function promptAndRead({ rpc, artifactDir, message, timeoutMs, expectedContextHandoff }) {
|
|
350
|
+
const fromIndex = rpc.events.length;
|
|
351
|
+
await rpc.send("prompt", { message }, timeoutMs);
|
|
352
|
+
await waitForAgentEnd(rpc, fromIndex, timeoutMs);
|
|
353
|
+
const textResponse = await rpc.send("get_last_assistant_text", {}, 120000);
|
|
354
|
+
if (!textResponse.success) fail("failed to read last assistant text", textResponse.error);
|
|
355
|
+
const latestMetadata = readLatestMetadataIfPresent(artifactDir);
|
|
356
|
+
if (!latestMetadata) fail("context smoke metadata missing", artifactDir);
|
|
357
|
+
assertCloudMetadata(latestMetadata.metadata, latestMetadata.metadataPath, { requireReport: false });
|
|
358
|
+
if (expectedContextHandoff && latestMetadata.metadata.providerMeta?.contextHandoff !== expectedContextHandoff) {
|
|
359
|
+
fail("context smoke metadata recorded wrong handoff", JSON.stringify({ expected: expectedContextHandoff, actual: latestMetadata.metadata.providerMeta?.contextHandoff }, null, 2));
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
text: typeof textResponse.data?.text === "string" ? textResponse.data.text : "",
|
|
363
|
+
agentId: latestMetadata.metadata.run?.agentId ?? latestMetadata.metadata.providerMeta?.cloudAgentId,
|
|
364
|
+
runId: latestMetadata.metadata.run?.runId,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function runContextScenario({ artifactRoot, contextHandoff, timeoutMs }) {
|
|
369
|
+
const artifactDir = join(artifactRoot, `context-${contextHandoff}`);
|
|
370
|
+
mkdirSync(artifactDir, { recursive: true });
|
|
371
|
+
const marker = `CLOUD_CONTEXT_${contextHandoff}_${Date.now()}`;
|
|
372
|
+
const rpc = startRpc({ artifactDir, contextHandoff, sessionId: `cloud-context-${contextHandoff}-${Date.now()}` });
|
|
373
|
+
try {
|
|
374
|
+
const first = await promptAndRead({
|
|
375
|
+
rpc,
|
|
376
|
+
artifactDir,
|
|
377
|
+
message: `Remember exact marker ${marker}. Reply exactly FIRST_OK.`,
|
|
378
|
+
timeoutMs,
|
|
379
|
+
expectedContextHandoff: contextHandoff,
|
|
380
|
+
});
|
|
381
|
+
if (firstNonEmptyLine(first.text) !== "FIRST_OK") fail(`cloud ${contextHandoff} setup turn did not return FIRST_OK`, first.text);
|
|
382
|
+
const second = await promptAndRead({
|
|
383
|
+
rpc,
|
|
384
|
+
artifactDir,
|
|
385
|
+
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.",
|
|
386
|
+
timeoutMs,
|
|
387
|
+
expectedContextHandoff: contextHandoff,
|
|
388
|
+
});
|
|
389
|
+
return { contextHandoff, marker, first, second };
|
|
390
|
+
} finally {
|
|
391
|
+
await rpc.stop();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function nonEmptyLines(text) {
|
|
396
|
+
return String(text ?? "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function firstNonEmptyLine(text) {
|
|
400
|
+
return nonEmptyLines(text).at(0) ?? "";
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function lastNonEmptyLine(text) {
|
|
404
|
+
return nonEmptyLines(text).at(-1) ?? "";
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function runContextMatrix({ artifactRoot, timeoutMs }) {
|
|
408
|
+
const fresh = await runContextScenario({ artifactRoot, contextHandoff: "fresh", timeoutMs });
|
|
409
|
+
if (fresh.second.text.includes(fresh.marker) || lastNonEmptyLine(fresh.second.text) !== "NO_MARKER") {
|
|
410
|
+
fail("fresh cloud context handoff leaked prior pi context", JSON.stringify({ expected: "NO_MARKER", actual: fresh.second.text, marker: fresh.marker }, null, 2));
|
|
411
|
+
}
|
|
412
|
+
const bootstrap = await runContextScenario({ artifactRoot, contextHandoff: "bootstrap", timeoutMs });
|
|
413
|
+
if (lastNonEmptyLine(bootstrap.second.text) !== `MARKER=${bootstrap.marker}`) {
|
|
414
|
+
fail("bootstrap cloud context handoff did not include prior pi context", JSON.stringify({ expected: `MARKER=${bootstrap.marker}`, actual: bootstrap.second.text }, null, 2));
|
|
415
|
+
}
|
|
416
|
+
return [fresh, bootstrap];
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async function archiveCloudAgent(agentId) {
|
|
420
|
+
if (!agentId) return;
|
|
421
|
+
const { Agent } = await import("@cursor/sdk");
|
|
422
|
+
await Agent.archive(agentId, {});
|
|
423
|
+
const archived = await Agent.get(agentId, {});
|
|
424
|
+
if (archived.archived !== true) {
|
|
425
|
+
fail(`cloud agent ${agentId} did not report archived after cleanup`, JSON.stringify(archived, null, 2));
|
|
426
|
+
}
|
|
427
|
+
console.error(scrubSmokeText(`[cloud-smoke] archived agent ${agentId}`));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function assertCloudMetadata(metadata, metadataPath, options = {}) {
|
|
431
|
+
if (metadata.providerMeta?.runtime !== "cloud") fail("provider metadata did not record cloud runtime", metadataPath);
|
|
432
|
+
if (metadata.send?.bridgeEnabled !== false) fail("cloud send unexpectedly enabled pi bridge", metadataPath);
|
|
433
|
+
if (metadata.send?.useNativeToolReplay !== false) fail("cloud send unexpectedly enabled native replay live-run mode", metadataPath);
|
|
434
|
+
if (metadata.send?.agentMode !== "agent") fail("cloud send did not use agent mode", metadataPath);
|
|
435
|
+
if (!metadata.run?.agentId?.startsWith?.("bc-")) fail("cloud run did not return a cloud agent id", metadataPath);
|
|
436
|
+
const providerEventsPath = resolveMetadataArtifactPath(metadataPath, metadata.artifacts?.providerEvents);
|
|
437
|
+
const providerEvents = readJsonlIfPresent(providerEventsPath);
|
|
438
|
+
const report = providerEvents.find((event) => event.phase === "cloud_run_report")?.payload;
|
|
439
|
+
if (!report && options.requireReport === false) return;
|
|
440
|
+
if (report?.agentId !== metadata.run.agentId) fail("cloud report did not include the cloud agent id", providerEventsPath ?? metadataPath);
|
|
441
|
+
if (report?.runId !== metadata.run.runId) fail("cloud report did not include the cloud run id", providerEventsPath ?? metadataPath);
|
|
442
|
+
if (!Array.isArray(report.branches)) fail("cloud report branches were not an array", providerEventsPath ?? metadataPath);
|
|
443
|
+
if (report.artifacts !== undefined && !Array.isArray(report.artifacts)) fail("cloud report artifacts were not an array", providerEventsPath ?? metadataPath);
|
|
444
|
+
if (report.usage !== undefined && (typeof report.usage !== "object" || report.usage === null)) fail("cloud report usage was not an object", providerEventsPath ?? metadataPath);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async function main() {
|
|
448
|
+
if (!process.env.CURSOR_API_KEY) fail("CURSOR_API_KEY is required for cloud smoke and cleanup");
|
|
449
|
+
const timeoutMs = Number(process.env.CURSOR_CLOUD_SMOKE_TIMEOUT_MS || 300000);
|
|
450
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) fail("CURSOR_CLOUD_SMOKE_TIMEOUT_MS must be a positive number");
|
|
451
|
+
|
|
452
|
+
const artifactRoot = mkdtempSync(join(tmpdir(), "pi-cursor-cloud-smoke-"));
|
|
453
|
+
let agentId;
|
|
454
|
+
let failure;
|
|
455
|
+
console.error(scrubSmokeText(`[cloud-smoke] artifacts: ${artifactRoot}`));
|
|
456
|
+
try {
|
|
457
|
+
if (args.has("--context-matrix")) {
|
|
458
|
+
await runContextMatrix({ artifactRoot, timeoutMs });
|
|
459
|
+
console.error(scrubSmokeText("[cloud-smoke] context matrix passed"));
|
|
460
|
+
} else {
|
|
461
|
+
const run = await runPi({ artifactDir: artifactRoot, timeoutMs });
|
|
462
|
+
const latestMetadata = readLatestMetadataIfPresent(artifactRoot);
|
|
463
|
+
agentId = latestMetadata?.metadata.run?.agentId ?? latestMetadata?.metadata.providerMeta?.cloudAgentId;
|
|
464
|
+
if (run.code !== 0) {
|
|
465
|
+
fail(`pi cloud smoke exited ${run.code}${run.signal ? ` (${run.signal})` : ""}`, `${run.stderr}\n${run.stdout}`.trim());
|
|
466
|
+
}
|
|
467
|
+
if (!/cloud-smoke-ok/i.test(run.stdout)) {
|
|
468
|
+
fail("cloud smoke output missing exact marker", `${run.stderr}\n${run.stdout}`.trim());
|
|
469
|
+
}
|
|
470
|
+
if (!latestMetadata) fail("no SDK event debug metadata was written", artifactRoot);
|
|
471
|
+
assertCloudMetadata(latestMetadata.metadata, latestMetadata.metadataPath);
|
|
472
|
+
}
|
|
473
|
+
} catch (error) {
|
|
474
|
+
failure = error;
|
|
475
|
+
}
|
|
476
|
+
for (const id of new Set([
|
|
477
|
+
agentId,
|
|
478
|
+
...cloudAgentIdsFromMetadata(artifactRoot),
|
|
479
|
+
...cloudAgentIdsFromLifecycleArtifacts(artifactRoot),
|
|
480
|
+
].filter((id) => typeof id === "string" && CLOUD_AGENT_ID_PATTERN.test(id)))) {
|
|
481
|
+
try {
|
|
482
|
+
await archiveCloudAgent(id);
|
|
483
|
+
} catch (error) {
|
|
484
|
+
console.error(scrubSmokeText(`[cloud-smoke] cleanup failed for ${id}: ${error instanceof Error ? error.message : String(error)}`));
|
|
485
|
+
failure ??= error;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (process.env.CURSOR_CLOUD_SMOKE_KEEP_ARTIFACTS !== "1" && !failure) {
|
|
489
|
+
rmSync(artifactRoot, { recursive: true, force: true });
|
|
490
|
+
} else {
|
|
491
|
+
console.error(scrubSmokeText(`[cloud-smoke] retained artifacts: ${artifactRoot}`));
|
|
492
|
+
}
|
|
493
|
+
if (failure) throw failure;
|
|
494
|
+
console.log("cloud-smoke-ok");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
498
|
+
main().catch((error) => {
|
|
499
|
+
reportFailure(error);
|
|
500
|
+
process.exit(1);
|
|
501
|
+
});
|
|
502
|
+
}
|
|
@@ -15,7 +15,12 @@ import {
|
|
|
15
15
|
parseArgv,
|
|
16
16
|
requireApiKey,
|
|
17
17
|
} from "./lib/cursor-cli-args.mjs";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
CHILD_PROCESS_TREE_SPAWN_OPTIONS,
|
|
20
|
+
parseJsonLines,
|
|
21
|
+
terminateChild,
|
|
22
|
+
waitForChildClose,
|
|
23
|
+
} from "./lib/cursor-child-process.mjs";
|
|
19
24
|
import { scrubSensitiveText } from "../shared/cursor-sensitive-text.mjs";
|
|
20
25
|
import { createScriptFail } from "./lib/cursor-script-fail.mjs";
|
|
21
26
|
import { serializeCursorSettingSources } from "../shared/cursor-setting-sources.mjs";
|
|
@@ -219,7 +224,7 @@ export async function runDebugProviderEvents(args, envInput = process.env) {
|
|
|
219
224
|
cwd: args.cwd,
|
|
220
225
|
env,
|
|
221
226
|
stdio: ["pipe", "pipe", "pipe"],
|
|
222
|
-
|
|
227
|
+
...CHILD_PROCESS_TREE_SPAWN_OPTIONS,
|
|
223
228
|
});
|
|
224
229
|
let closed = false;
|
|
225
230
|
let stdout = "";
|