pi-cursor-sdk 0.1.32 → 0.1.33
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 +7 -0
- package/README.md +21 -1
- package/docs/cursor-testing-lessons.md +1 -1
- package/docs/platform-smoke.md +36 -14
- package/package.json +2 -3
- package/platform-smoke.config.mjs +6 -1
- package/scripts/platform-smoke/crabbox-runner.mjs +5 -4
- package/scripts/platform-smoke/doctor.mjs +36 -17
- package/scripts/platform-smoke/platform-build-windows.ps1 +0 -4
- package/scripts/platform-smoke/targets.mjs +2 -2
- package/scripts/platform-smoke.mjs +24 -19
- package/src/cursor-live-run-coordinator.ts +13 -1
- package/src/cursor-provider-errors.ts +3 -1
- package/src/cursor-sdk-process-error-guard.ts +4 -2
- package/docs/crabbox-platform-testing-lessons.md +0 -508
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.33 - 2026-06-04
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Prevent connect-node-only Cursor SDK network resets such as `ConnectError: [aborted] read ECONNRESET` from escaping as process-level uncaught exceptions during active Cursor turns, while keeping provenance-free generic ConnectRPC errors unsuppressed (#121).
|
|
10
|
+
- Suppress expected Cursor SDK abort `ConnectError` / `AbortError` shapes during abandoned live-run cancellation so idle-resume and interrupt cleanup paths keep pi alive for later prompts (#120).
|
|
11
|
+
|
|
5
12
|
## 0.1.32 - 2026-06-02
|
|
6
13
|
|
|
7
14
|
### Added
|
package/README.md
CHANGED
|
@@ -2,7 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
A pi provider extension that lets pi use Cursor models through the local `@cursor/sdk` agent runtime.
|
|
4
4
|
|
|
5
|
-
Use this extension if you want Cursor's
|
|
5
|
+
Use this extension if you primarily use Cursor models inside pi and want Cursor's local SDK agent loop preserved while pi adds native model selection, auth, thinking/context controls, session behavior, replay UI, and optional pi tool bridging.
|
|
6
|
+
|
|
7
|
+
## Why use this instead of an OpenAI-compatible Cursor endpoint?
|
|
8
|
+
|
|
9
|
+
Use `pi-cursor-sdk` when you primarily want to use Cursor models **inside pi**.
|
|
10
|
+
|
|
11
|
+
This extension runs Cursor models through the local `@cursor/sdk` agent runtime and keeps Cursor's agent loop intact. pi integrates around that loop: model discovery, model selection, context-window variants, thinking controls where Cursor exposes them, fast/slow aliases, Cursor mode, session handling, native replay cards, and the optional pi tool bridge.
|
|
12
|
+
|
|
13
|
+
OpenAI-compatible Cursor proxies are useful when you want a generic `/v1/chat/completions` or `/v1/responses` endpoint for many clients such as curl, the OpenAI SDK, OpenCode, or other tools. That compatibility comes from translating Cursor behavior into OpenAI-shaped requests, responses, and tool calls.
|
|
14
|
+
|
|
15
|
+
For pi users, that translation is usually the wrong abstraction. `pi-cursor-sdk` is pi-specific on purpose: it lets Cursor remain Cursor while making it feel native in pi.
|
|
16
|
+
|
|
17
|
+
| If you want... | Prefer |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| First-class Cursor usage inside pi | `pi-cursor-sdk` |
|
|
20
|
+
| Cursor's local SDK agent loop preserved, not replaced by an OpenAI-shaped adapter | `pi-cursor-sdk` |
|
|
21
|
+
| pi model picker, `/login`, `/model`, sessions, context display, footer/status UX | `pi-cursor-sdk` |
|
|
22
|
+
| Cursor SDK local-agent tools, settings, MCP, and native replay surfaced in pi | `pi-cursor-sdk` |
|
|
23
|
+
| pi extension tools exposed to Cursor through a local MCP bridge | `pi-cursor-sdk` |
|
|
24
|
+
| A generic OpenAI-compatible localhost `/v1` API for non-pi clients | An OpenAI-compatible Cursor proxy |
|
|
25
|
+
| One Cursor-ish endpoint shared across several unrelated tools | An OpenAI-compatible Cursor proxy |
|
|
6
26
|
|
|
7
27
|
## Quick start
|
|
8
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Cursor Testing Lessons
|
|
2
2
|
|
|
3
|
-
> **Platform Smoke (new):** The required cross-platform release gate is `npm run smoke:platform:doctor && npm run smoke:platform:all`. See [docs/platform-smoke.md](./platform-smoke.md). For portable lessons other pi extension projects can adapt without sharing repo-specific state, see
|
|
3
|
+
> **Platform Smoke (new):** The required cross-platform release gate is `npm run smoke:platform:doctor && npm run smoke:platform:all`. See [docs/platform-smoke.md](./platform-smoke.md). For portable lessons other pi extension projects can adapt without sharing repo-specific state, see the generic Crabbox platform testing guide at `/Users/mitchfultz/Projects/crabbox/docs/pi-extension-platform-testing.md`. The live smoke checklist remains useful for inner-loop development but is not the release gate.
|
|
4
4
|
|
|
5
5
|
## Purpose
|
|
6
6
|
|
package/docs/platform-smoke.md
CHANGED
|
@@ -6,6 +6,8 @@ Branch introduced by: `feat/crabbox-platform-smoke`
|
|
|
6
6
|
|
|
7
7
|
Oracle review incorporated: this gate resolves the packed-install workspace conflict, Cursor budget contradiction, Windows shell drift, artifact-on-failure gap, render-location ambiguity, provider-debug ambiguity, and registry-classification gap called out during review.
|
|
8
8
|
|
|
9
|
+
Crabbox best-practice baseline applied from `~/Projects/crabbox`: Crabbox owns lease, sync, run, evidence transport, and cleanup; this repo owns target policy, package setup, scenario meaning, assertions, artifacts, auth forwarding, redaction, and release criteria.
|
|
10
|
+
|
|
9
11
|
## Decision
|
|
10
12
|
|
|
11
13
|
Crabbox is the required platform smoke runner for `pi-cursor-sdk` releases that touch Cursor provider/runtime behavior.
|
|
@@ -53,7 +55,7 @@ Current baseline:
|
|
|
53
55
|
|
|
54
56
|
```text
|
|
55
57
|
install: brew install openclaw/tap/crabbox
|
|
56
|
-
version: 0.
|
|
58
|
+
version: 0.26.0 or newer
|
|
57
59
|
binary: Homebrew `crabbox` on PATH (`/opt/homebrew/bin/crabbox` on Apple Silicon Homebrew installs)
|
|
58
60
|
```
|
|
59
61
|
|
|
@@ -75,6 +77,8 @@ scenario + target capability + artifact contract
|
|
|
75
77
|
|
|
76
78
|
not a one-off shell script.
|
|
77
79
|
|
|
80
|
+
Crabbox is deliberately kept as the transport/lifecycle layer. It must not be treated as proof that the pi extension behavior passed; every suite still fails or passes from project-owned assertions and artifact manifests.
|
|
81
|
+
|
|
78
82
|
High-level flow:
|
|
79
83
|
|
|
80
84
|
```text
|
|
@@ -145,19 +149,21 @@ scripts/platform-smoke/artifacts.mjs
|
|
|
145
149
|
scripts/platform-smoke/card-detect.mjs
|
|
146
150
|
scripts/platform-smoke/crabbox-runner.mjs
|
|
147
151
|
scripts/platform-smoke/doctor.mjs
|
|
152
|
+
scripts/platform-smoke/jsonl-text.mjs
|
|
148
153
|
scripts/platform-smoke/live-suite-runner.mjs
|
|
149
154
|
scripts/platform-smoke/platform-build-windows.ps1
|
|
150
155
|
scripts/platform-smoke/pty-capture.mjs
|
|
151
156
|
scripts/platform-smoke/render-ansi.mjs
|
|
152
157
|
scripts/platform-smoke/scenarios.mjs
|
|
153
158
|
scripts/platform-smoke/targets.mjs
|
|
159
|
+
scripts/platform-smoke/visual-evidence.mjs
|
|
154
160
|
```
|
|
155
161
|
|
|
156
162
|
Package scripts:
|
|
157
163
|
|
|
158
164
|
```json
|
|
159
165
|
{
|
|
160
|
-
"check:platform-smoke": "node --check <platform smoke scripts> && vitest run test/smoke-tooling.test.ts",
|
|
166
|
+
"check:platform-smoke": "node --check platform-smoke.config.mjs && node --check <platform smoke scripts> && vitest run test/smoke-tooling.test.ts",
|
|
161
167
|
"smoke:platform": "node scripts/platform-smoke.mjs",
|
|
162
168
|
"smoke:platform:doctor": "node scripts/platform-smoke.mjs doctor",
|
|
163
169
|
"smoke:platform:macos": "node scripts/platform-smoke.mjs run --target macos",
|
|
@@ -167,7 +173,7 @@ Package scripts:
|
|
|
167
173
|
}
|
|
168
174
|
```
|
|
169
175
|
|
|
170
|
-
Add `.artifacts/`, `.crabbox/`, and `.platform-smoke-runs/` to `.gitignore`.
|
|
176
|
+
Add `.artifacts/`, `.crabbox/`, `.debug/`, and `.platform-smoke-runs/` to `.gitignore`.
|
|
171
177
|
|
|
172
178
|
## Configuration source
|
|
173
179
|
|
|
@@ -189,18 +195,25 @@ export default {
|
|
|
189
195
|
],
|
|
190
196
|
requiredCrabbox: {
|
|
191
197
|
install: "Homebrew package or PLATFORM_SMOKE_CRABBOX override",
|
|
192
|
-
minVersion: "0.
|
|
198
|
+
minVersion: "0.26.0",
|
|
193
199
|
},
|
|
194
200
|
ubuntuContainerImage: "cimg/node:24.16",
|
|
195
201
|
nodeValidationMajor: 24,
|
|
202
|
+
windowsParallels: {
|
|
203
|
+
sourceVm: "pi-extension-windows-template",
|
|
204
|
+
snapshot: "crabbox-ready",
|
|
205
|
+
workRoot: "C:\\crabbox\\pi-cursor-sdk",
|
|
206
|
+
},
|
|
196
207
|
};
|
|
197
208
|
```
|
|
198
209
|
|
|
199
210
|
`ubuntuContainerImage` defaults the local-container Ubuntu target to an Ubuntu 24.04 Node 24 image with a current glibc baseline for native test dependencies; Crabbox still bootstraps SSH/Git/rsync/curl as needed. `nodeValidationMajor: 24` is the release-smoke validation baseline. It does not change the package engine by itself. A separate compatibility lane can test Node 22.19 later; this required gate validates Node 24 on every target.
|
|
200
211
|
|
|
212
|
+
`windowsParallels` records this repo's default shared Windows template contract. Environment overrides may point at a temporary candidate template during infrastructure work, but release runs should use the shared `pi-extension-windows-template` / `crabbox-ready` baseline unless this document is updated.
|
|
213
|
+
|
|
201
214
|
## Required local environment
|
|
202
215
|
|
|
203
|
-
The doctor fails if
|
|
216
|
+
The config owns reusable defaults. Environment variables are local-machine knobs and one-off overrides, not a second source of truth. The doctor fails if required auth or target readiness is missing.
|
|
204
217
|
|
|
205
218
|
```bash
|
|
206
219
|
# Optional override; by default the gate uses Homebrew `crabbox` from PATH.
|
|
@@ -211,11 +224,13 @@ PLATFORM_SMOKE_MAC_USER="$USER"
|
|
|
211
224
|
PLATFORM_SMOKE_MAC_WORK_ROOT="/Users/$USER/crabbox/pi-cursor-sdk"
|
|
212
225
|
PLATFORM_SMOKE_UBUNTU_IMAGE="cimg/node:24.16"
|
|
213
226
|
|
|
214
|
-
|
|
227
|
+
# Optional Parallels overrides; defaults come from platform-smoke.config.mjs.
|
|
228
|
+
PLATFORM_SMOKE_WINDOWS_VM="pi-extension-windows-template"
|
|
215
229
|
PLATFORM_SMOKE_WINDOWS_SNAPSHOT="crabbox-ready"
|
|
216
230
|
PLATFORM_SMOKE_WINDOWS_USER="<windows-ssh-user>"
|
|
217
231
|
PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT="C:\\crabbox\\pi-cursor-sdk"
|
|
218
232
|
|
|
233
|
+
# Required for live suites; doctor fails before spending Cursor tokens if absent.
|
|
219
234
|
CURSOR_API_KEY="..."
|
|
220
235
|
```
|
|
221
236
|
|
|
@@ -278,7 +293,13 @@ Required:
|
|
|
278
293
|
|
|
279
294
|
### Windows template VM
|
|
280
295
|
|
|
281
|
-
The user's daily Windows VM is not the long-term test target.
|
|
296
|
+
The user's daily Windows VM is not the long-term test target. Use the shared pi-extension Parallels template unless this project documents a replacement with equal evidence:
|
|
297
|
+
|
|
298
|
+
```text
|
|
299
|
+
source VM: pi-extension-windows-template
|
|
300
|
+
snapshot: crabbox-ready
|
|
301
|
+
work root: C:\\crabbox\\pi-cursor-sdk
|
|
302
|
+
```
|
|
282
303
|
|
|
283
304
|
Template requirements:
|
|
284
305
|
|
|
@@ -293,8 +314,9 @@ Template requirements:
|
|
|
293
314
|
- `node-pty` self-test passes in native Windows.
|
|
294
315
|
- Source VM is powered off.
|
|
295
316
|
- Snapshot named `crabbox-ready` exists.
|
|
317
|
+
- The template contains reusable platform tools only; no repo checkout, `.pi` state, Cursor API key, browser auth, smoke artifacts, or temp files.
|
|
296
318
|
|
|
297
|
-
Crabbox Parallels creates linked clones from the powered-off snapshot. The source template VM is never used directly for smoke runs.
|
|
319
|
+
Crabbox Parallels creates linked clones from the powered-off snapshot. The source template VM is never used directly for smoke runs. If a run has to install a missing global tool or browser on every Windows clone, treat that as template drift and refresh the shared template instead of making the per-run fallback normal.
|
|
298
320
|
|
|
299
321
|
### Windows native
|
|
300
322
|
|
|
@@ -313,13 +335,13 @@ tar --version
|
|
|
313
335
|
|
|
314
336
|
Doctor checks:
|
|
315
337
|
|
|
316
|
-
1. Required
|
|
338
|
+
1. Required auth is present and optional target overrides resolve against config defaults.
|
|
317
339
|
2. Homebrew `crabbox` is available on PATH, or `PLATFORM_SMOKE_CRABBOX` points at an executable override.
|
|
318
340
|
3. Crabbox build matches the configured baseline.
|
|
319
341
|
4. Crabbox provider registry includes `local-container`, `ssh`, and `parallels`.
|
|
320
|
-
5. `crabbox doctor --provider local-container --json` passes.
|
|
342
|
+
5. `crabbox doctor --provider local-container --target linux --json` passes.
|
|
321
343
|
6. Docker runtime is active.
|
|
322
|
-
7. macOS SSH
|
|
344
|
+
7. Crabbox macOS static SSH doctor with `--doctor-probe-ssh` passes, and the localhost SSH probe sees Node, npm, Git, rsync, and tar.
|
|
323
345
|
8. `prlctl` exists.
|
|
324
346
|
9. Windows source VM exists.
|
|
325
347
|
10. Windows source snapshot exists.
|
|
@@ -358,7 +380,7 @@ Per target, `platform-build` must:
|
|
|
358
380
|
|
|
359
381
|
1. Record `node --version` and assert the target Node major is at least `nodeValidationMajor`.
|
|
360
382
|
2. Run `npm ci` in `extensionSourceRoot`.
|
|
361
|
-
3. Run `npm run check:platform-smoke` on the target so smoke harness syntax and invariant tests fail before live Cursor calls
|
|
383
|
+
3. Run `npm run check:platform-smoke` on the target so config syntax, smoke harness syntax, invalid target/suite guards, and invariant tests fail before live Cursor calls.
|
|
362
384
|
4. Run `npm test` on the target with the same target-local release-tag guard bypass.
|
|
363
385
|
5. Run `npm run typecheck`.
|
|
364
386
|
6. Run `npm pack`.
|
|
@@ -379,7 +401,7 @@ Purpose:
|
|
|
379
401
|
- fail before spending Cursor tokens;
|
|
380
402
|
- produce the packed extension used by later suites.
|
|
381
403
|
|
|
382
|
-
The host `smoke:platform:all` entrypoint enforces doctor first
|
|
404
|
+
The host `smoke:platform:all` entrypoint enforces doctor first before running targets. Required artifacts include `node-version.txt`, `npm-version.txt`, stdout/stderr for `npm ci`, `npm run check:platform-smoke`, `npm test`, `npm run typecheck`, `npm pack`, packed npm install, `pi install`, and `pi list`, plus `packed-tarball.txt`, `summary.json`, `artifact-manifest.json`, `assertions.json`, and `failures.md` on failed assertions.
|
|
383
405
|
|
|
384
406
|
### `cursor-native-visual-matrix`
|
|
385
407
|
|
|
@@ -596,7 +618,7 @@ cursor-abort-cleanup: 1
|
|
|
596
618
|
|
|
597
619
|
Maximum per target: `3` Cursor invocations.
|
|
598
620
|
|
|
599
|
-
Maximum full gate: `
|
|
621
|
+
Maximum full gate: `9` Cursor invocations.
|
|
600
622
|
|
|
601
623
|
The merge gate is `npm run smoke:platform:all`; that script runs doctor first and then the matrix to preserve this budget. No suite adds a new Cursor invocation without updating this plan and `platform-smoke.config.mjs`.
|
|
602
624
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cursor-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"description": "pi provider extension backed by @cursor/sdk local agents",
|
|
5
5
|
"author": "Mitch Fultz (https://github.com/fitchmultz)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -69,7 +69,6 @@
|
|
|
69
69
|
"docs/cursor-tool-surfaces.md",
|
|
70
70
|
"docs/cursor-live-smoke-checklist.md",
|
|
71
71
|
"docs/cursor-testing-lessons.md",
|
|
72
|
-
"docs/crabbox-platform-testing-lessons.md",
|
|
73
72
|
"docs/cursor-dogfood-checklist.md",
|
|
74
73
|
"docs/cursor-native-tool-replay.md",
|
|
75
74
|
"docs/cursor-native-tool-visual-audit.md",
|
|
@@ -98,7 +97,7 @@
|
|
|
98
97
|
"debug:sdk-events": "node scripts/debug-sdk-events.mjs",
|
|
99
98
|
"debug:provider-events": "node scripts/debug-provider-events.mjs",
|
|
100
99
|
"debug:mcp-coldstart": "node scripts/probe-mcp-coldstart.mjs",
|
|
101
|
-
"check:platform-smoke": "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",
|
|
100
|
+
"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",
|
|
102
101
|
"smoke:platform": "node scripts/platform-smoke.mjs",
|
|
103
102
|
"smoke:platform:doctor": "node scripts/platform-smoke.mjs doctor",
|
|
104
103
|
"smoke:platform:macos": "node scripts/platform-smoke.mjs run --target macos",
|
|
@@ -14,8 +14,13 @@ export default {
|
|
|
14
14
|
],
|
|
15
15
|
requiredCrabbox: {
|
|
16
16
|
install: "Homebrew package or PLATFORM_SMOKE_CRABBOX override",
|
|
17
|
-
minVersion: "0.
|
|
17
|
+
minVersion: "0.26.0",
|
|
18
18
|
},
|
|
19
19
|
ubuntuContainerImage: "cimg/node:24.16",
|
|
20
20
|
nodeValidationMajor: 24,
|
|
21
|
+
windowsParallels: {
|
|
22
|
+
sourceVm: "pi-extension-windows-template",
|
|
23
|
+
snapshot: "crabbox-ready",
|
|
24
|
+
workRoot: "C:\\crabbox\\pi-cursor-sdk",
|
|
25
|
+
},
|
|
21
26
|
};
|
|
@@ -98,10 +98,11 @@ export function buildTargetBaseArgs(targetName, config = {}) {
|
|
|
98
98
|
];
|
|
99
99
|
}
|
|
100
100
|
case "windows-native": {
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const
|
|
101
|
+
const windows = config.windowsParallels ?? {};
|
|
102
|
+
const vm = env("PLATFORM_SMOKE_WINDOWS_VM") || windows.sourceVm || "pi-extension-windows-template";
|
|
103
|
+
const snap = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || windows.snapshot || "crabbox-ready";
|
|
104
|
+
const user = env("PLATFORM_SMOKE_WINDOWS_USER") || windows.user || env("USER");
|
|
105
|
+
const workRoot = env("PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT") || windows.workRoot || "C:\\crabbox\\pi-cursor-sdk";
|
|
105
106
|
return [
|
|
106
107
|
"--provider", "parallels",
|
|
107
108
|
"--target", "windows",
|
|
@@ -55,11 +55,22 @@ function parseLeaseId(output) {
|
|
|
55
55
|
?? null;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
function windowsParallelsDefaults(config = {}) {
|
|
59
|
+
const windows = config?.windowsParallels ?? {};
|
|
60
|
+
return {
|
|
61
|
+
vm: windows.sourceVm || "pi-extension-windows-template",
|
|
62
|
+
snapshot: windows.snapshot || "crabbox-ready",
|
|
63
|
+
user: windows.user || env("USER"),
|
|
64
|
+
workRoot: windows.workRoot || "C:\\crabbox\\pi-cursor-sdk",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function windowsCrabboxBaseArgs(config = {}) {
|
|
69
|
+
const defaults = windowsParallelsDefaults(config);
|
|
70
|
+
const vm = env("PLATFORM_SMOKE_WINDOWS_VM") || defaults.vm;
|
|
71
|
+
const snap = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || defaults.snapshot;
|
|
72
|
+
const user = env("PLATFORM_SMOKE_WINDOWS_USER") || defaults.user;
|
|
73
|
+
const workRoot = env("PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT") || defaults.workRoot;
|
|
63
74
|
return [
|
|
64
75
|
"--provider", "parallels",
|
|
65
76
|
"--target", "windows",
|
|
@@ -91,9 +102,9 @@ function crabbox(cbox, args, timeout = 300_000) {
|
|
|
91
102
|
}
|
|
92
103
|
}
|
|
93
104
|
|
|
94
|
-
function disposableWindowsSshProbe(cbox) {
|
|
105
|
+
function disposableWindowsSshProbe(cbox, config = {}) {
|
|
95
106
|
const slug = "pi-cursor-sdk-doctor-windows";
|
|
96
|
-
const baseArgs = windowsCrabboxBaseArgs();
|
|
107
|
+
const baseArgs = windowsCrabboxBaseArgs(config);
|
|
97
108
|
const warm = crabbox(cbox, ["warmup", ...baseArgs, "--slug", slug, "--keep", "--reclaim"], 300_000);
|
|
98
109
|
const leaseId = parseLeaseId(warm.stdout) ?? parseLeaseId(warm.stderr) ?? slug;
|
|
99
110
|
try {
|
|
@@ -131,10 +142,6 @@ function runChecks(config) {
|
|
|
131
142
|
console.log("\n── Environment variables ──");
|
|
132
143
|
const requiredVars = [
|
|
133
144
|
"CURSOR_API_KEY",
|
|
134
|
-
"PLATFORM_SMOKE_WINDOWS_VM",
|
|
135
|
-
"PLATFORM_SMOKE_WINDOWS_SNAPSHOT",
|
|
136
|
-
"PLATFORM_SMOKE_WINDOWS_USER",
|
|
137
|
-
"PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT",
|
|
138
145
|
];
|
|
139
146
|
const optionalVars = [
|
|
140
147
|
"PLATFORM_SMOKE_CRABBOX",
|
|
@@ -142,15 +149,27 @@ function runChecks(config) {
|
|
|
142
149
|
"PLATFORM_SMOKE_MAC_USER",
|
|
143
150
|
"PLATFORM_SMOKE_MAC_WORK_ROOT",
|
|
144
151
|
"PLATFORM_SMOKE_UBUNTU_IMAGE",
|
|
152
|
+
"PLATFORM_SMOKE_WINDOWS_VM",
|
|
153
|
+
"PLATFORM_SMOKE_WINDOWS_SNAPSHOT",
|
|
154
|
+
"PLATFORM_SMOKE_WINDOWS_USER",
|
|
155
|
+
"PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT",
|
|
145
156
|
];
|
|
146
157
|
for (const name of requiredVars) {
|
|
147
158
|
const v = env(name);
|
|
148
159
|
v ? ok(`${name} = ${name === "CURSOR_API_KEY" ? "(present, redacted)" : (v.length > 50 ? v.slice(0, 50) + "..." : v)}`)
|
|
149
160
|
: fail(`${name} missing`);
|
|
150
161
|
}
|
|
162
|
+
const windowsDefaults = windowsParallelsDefaults(config);
|
|
163
|
+
const optionalDefaults = {
|
|
164
|
+
PLATFORM_SMOKE_WINDOWS_VM: windowsDefaults.vm,
|
|
165
|
+
PLATFORM_SMOKE_WINDOWS_SNAPSHOT: windowsDefaults.snapshot,
|
|
166
|
+
PLATFORM_SMOKE_WINDOWS_USER: windowsDefaults.user,
|
|
167
|
+
PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT: windowsDefaults.workRoot,
|
|
168
|
+
};
|
|
151
169
|
for (const name of optionalVars) {
|
|
152
170
|
const v = env(name);
|
|
153
|
-
|
|
171
|
+
const fallback = optionalDefaults[name] ? `(default: ${optionalDefaults[name]})` : "(default)";
|
|
172
|
+
ok(`${name} = ${v || fallback}`);
|
|
154
173
|
}
|
|
155
174
|
|
|
156
175
|
// ── Phase 2: Crabbox binary ──
|
|
@@ -205,7 +224,7 @@ function runChecks(config) {
|
|
|
205
224
|
fail("crabbox providers failed");
|
|
206
225
|
}
|
|
207
226
|
const ubuntuImage = env("PLATFORM_SMOKE_UBUNTU_IMAGE") || config?.ubuntuContainerImage || "cimg/node:24.16";
|
|
208
|
-
const lcDoc = silent(cbox, ["doctor", "--provider", "local-container", "--local-container-image", ubuntuImage, "--json"]);
|
|
227
|
+
const lcDoc = silent(cbox, ["doctor", "--provider", "local-container", "--target", "linux", "--local-container-image", ubuntuImage, "--json"]);
|
|
209
228
|
if (lcDoc) {
|
|
210
229
|
try {
|
|
211
230
|
const d = JSON.parse(lcDoc);
|
|
@@ -223,7 +242,7 @@ function runChecks(config) {
|
|
|
223
242
|
"doctor", "--provider", "ssh", "--target", "macos",
|
|
224
243
|
"--static-host", sshHost, "--static-user", sshUser,
|
|
225
244
|
"--static-port", "22", "--static-work-root", sshRoot,
|
|
226
|
-
"--json",
|
|
245
|
+
"--doctor-probe-ssh", "--json",
|
|
227
246
|
]);
|
|
228
247
|
if (sshDoc) {
|
|
229
248
|
try {
|
|
@@ -265,7 +284,7 @@ function runChecks(config) {
|
|
|
265
284
|
fail("prlctl not found");
|
|
266
285
|
} else {
|
|
267
286
|
ok("prlctl found");
|
|
268
|
-
const vmName = env("PLATFORM_SMOKE_WINDOWS_VM") ||
|
|
287
|
+
const vmName = env("PLATFORM_SMOKE_WINDOWS_VM") || windowsParallelsDefaults(config).vm;
|
|
269
288
|
const list = shell("prlctl list -a --no-header 2>/dev/null");
|
|
270
289
|
if (list) {
|
|
271
290
|
const vms = list.split("\n").filter(Boolean);
|
|
@@ -279,7 +298,7 @@ function runChecks(config) {
|
|
|
279
298
|
fail(`VM "${vmName}" state: ${status} — source VM must be stopped for linked clones`);
|
|
280
299
|
}
|
|
281
300
|
|
|
282
|
-
const snapName = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") ||
|
|
301
|
+
const snapName = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || windowsParallelsDefaults(config).snapshot;
|
|
283
302
|
const snapsJson = shell(`prlctl snapshot-list "${vmName}" -j 2>/dev/null`);
|
|
284
303
|
let snapshotFound = false;
|
|
285
304
|
let snapshotPowerOff = false;
|
|
@@ -323,7 +342,7 @@ function runChecks(config) {
|
|
|
323
342
|
} else {
|
|
324
343
|
ok(`template "${vmName}" has no IP; verifying Windows SSH/tools through a disposable Crabbox clone`);
|
|
325
344
|
if (cbox && snapshotFound && snapshotPowerOff) {
|
|
326
|
-
const probe = disposableWindowsSshProbe(cbox);
|
|
345
|
+
const probe = disposableWindowsSshProbe(cbox, config);
|
|
327
346
|
probe.ok ? ok(`disposable Windows clone SSH/tool probe OK: ${probe.message}`) : fail(probe.message);
|
|
328
347
|
} else {
|
|
329
348
|
fail(`Windows SSH probe could not run because "${vmName}" has no IP and no verified snapshot was available`);
|
|
@@ -62,19 +62,15 @@ Write-SectionFile "NPM_CI_STDOUT" $NpmCiOut
|
|
|
62
62
|
Write-SectionFile "NPM_CI_STDERR" $NpmCiErr
|
|
63
63
|
|
|
64
64
|
Write-Output "=== check:platform-smoke ==="
|
|
65
|
-
$env:PI_CURSOR_SKIP_RELEASE_VERSION_GUARD = "1"
|
|
66
65
|
& npm.cmd run check:platform-smoke 1> $CheckPlatformSmokeOut 2> $CheckPlatformSmokeErr
|
|
67
66
|
$CHECK_PLATFORM_SMOKE_EXIT = Exit-CodeFromLastCommand
|
|
68
|
-
Remove-Item Env:\PI_CURSOR_SKIP_RELEASE_VERSION_GUARD -ErrorAction SilentlyContinue
|
|
69
67
|
Write-Output "PLATFORM_CHECK_PLATFORM_SMOKE_EXIT=$CHECK_PLATFORM_SMOKE_EXIT"
|
|
70
68
|
Write-SectionFile "CHECK_PLATFORM_SMOKE_STDOUT" $CheckPlatformSmokeOut
|
|
71
69
|
Write-SectionFile "CHECK_PLATFORM_SMOKE_STDERR" $CheckPlatformSmokeErr
|
|
72
70
|
|
|
73
71
|
Write-Output "=== npm test ==="
|
|
74
|
-
$env:PI_CURSOR_SKIP_RELEASE_VERSION_GUARD = "1"
|
|
75
72
|
& npm.cmd test 1> $NpmTestOut 2> $NpmTestErr
|
|
76
73
|
$TEST_EXIT = Exit-CodeFromLastCommand
|
|
77
|
-
Remove-Item Env:\PI_CURSOR_SKIP_RELEASE_VERSION_GUARD -ErrorAction SilentlyContinue
|
|
78
74
|
Write-Output "PLATFORM_NPM_TEST_EXIT=$TEST_EXIT"
|
|
79
75
|
Write-SectionFile "NPM_TEST_STDOUT" $NpmTestOut
|
|
80
76
|
Write-SectionFile "NPM_TEST_STDERR" $NpmTestErr
|
|
@@ -422,14 +422,14 @@ export function buildPlatformBuildCommand(targetName, packageName = "pi-cursor-s
|
|
|
422
422
|
lines.push(...posixSection("NPM_CI_STDERR", 'cat "$PACK_DIR/npm-ci.stderr.txt" 2>/dev/null || true'));
|
|
423
423
|
lines.push("");
|
|
424
424
|
lines.push('echo "=== check:platform-smoke ==="');
|
|
425
|
-
lines.push('
|
|
425
|
+
lines.push('npm run check:platform-smoke >"$PACK_DIR/check-platform-smoke.stdout.txt" 2>"$PACK_DIR/check-platform-smoke.stderr.txt"');
|
|
426
426
|
lines.push("CHECK_PLATFORM_SMOKE_EXIT=$?");
|
|
427
427
|
lines.push('echo "PLATFORM_CHECK_PLATFORM_SMOKE_EXIT=$CHECK_PLATFORM_SMOKE_EXIT"');
|
|
428
428
|
lines.push(...posixSection("CHECK_PLATFORM_SMOKE_STDOUT", 'cat "$PACK_DIR/check-platform-smoke.stdout.txt" 2>/dev/null || true'));
|
|
429
429
|
lines.push(...posixSection("CHECK_PLATFORM_SMOKE_STDERR", 'cat "$PACK_DIR/check-platform-smoke.stderr.txt" 2>/dev/null || true'));
|
|
430
430
|
lines.push("");
|
|
431
431
|
lines.push('echo "=== npm test ==="');
|
|
432
|
-
lines.push('
|
|
432
|
+
lines.push('npm test >"$PACK_DIR/npm-test.stdout.txt" 2>"$PACK_DIR/npm-test.stderr.txt"');
|
|
433
433
|
lines.push("TEST_EXIT=$?");
|
|
434
434
|
lines.push('echo "PLATFORM_NPM_TEST_EXIT=$TEST_EXIT"');
|
|
435
435
|
lines.push(...posixSection("NPM_TEST_STDOUT", 'cat "$PACK_DIR/npm-test.stdout.txt" 2>/dev/null || true'));
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { resolve, dirname } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { accessSync, constants
|
|
7
|
-
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { accessSync, constants } from "node:fs";
|
|
8
7
|
|
|
9
8
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
10
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -47,11 +46,11 @@ Environment:
|
|
|
47
46
|
PLATFORM_SMOKE_MAC_HOST macOS SSH host (default: localhost)
|
|
48
47
|
PLATFORM_SMOKE_MAC_USER macOS SSH user (default: \$USER)
|
|
49
48
|
PLATFORM_SMOKE_MAC_WORK_ROOT macOS work root
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT Windows native work root
|
|
49
|
+
PLATFORM_SMOKE_UBUNTU_IMAGE Ubuntu container image
|
|
50
|
+
PLATFORM_SMOKE_WINDOWS_VM Parallels source VM override (default from config)
|
|
51
|
+
PLATFORM_SMOKE_WINDOWS_SNAPSHOT Snapshot override (default from config)
|
|
52
|
+
PLATFORM_SMOKE_WINDOWS_USER Windows SSH user override (default: \$USER)
|
|
53
|
+
PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT Windows native work root override (default from config)
|
|
55
54
|
`);
|
|
56
55
|
}
|
|
57
56
|
|
|
@@ -90,17 +89,17 @@ function parseArgs(argv) {
|
|
|
90
89
|
return args;
|
|
91
90
|
}
|
|
92
91
|
|
|
93
|
-
function
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
92
|
+
function validateSelections(targets, suites) {
|
|
93
|
+
const allowedTargets = new Set(config.requiredTargets ?? []);
|
|
94
|
+
const allowedSuites = new Set(config.requiredSuites ?? []);
|
|
95
|
+
const badTargets = targets.filter((target) => !allowedTargets.has(target));
|
|
96
|
+
const badSuites = suites.filter((suite) => !allowedSuites.has(suite));
|
|
97
|
+
if (badTargets.length > 0) {
|
|
98
|
+
throw new Error(`unknown target(s): ${badTargets.join(", ")}; allowed: ${[...allowedTargets].join(", ")}`);
|
|
99
|
+
}
|
|
100
|
+
if (badSuites.length > 0) {
|
|
101
|
+
throw new Error(`unknown suite(s): ${badSuites.join(", ")}; allowed: ${[...allowedSuites].join(", ")}`);
|
|
102
|
+
}
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
// ── commands ───────────────────────────────────────────────────────────────
|
|
@@ -158,7 +157,6 @@ async function main() {
|
|
|
158
157
|
}
|
|
159
158
|
|
|
160
159
|
if (args.command === "run") {
|
|
161
|
-
assertHostReleaseVersionGuard();
|
|
162
160
|
const targets = args.target
|
|
163
161
|
? args.target.split(",").map((s) => s.trim()).filter(Boolean)
|
|
164
162
|
: config.requiredTargets;
|
|
@@ -167,6 +165,13 @@ async function main() {
|
|
|
167
165
|
? [args.suite]
|
|
168
166
|
: config.requiredSuites;
|
|
169
167
|
|
|
168
|
+
try {
|
|
169
|
+
validateSelections(targets, suites);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(err.message);
|
|
172
|
+
process.exit(2);
|
|
173
|
+
}
|
|
174
|
+
|
|
170
175
|
const targetRuns = targets.map(async (targetName) => {
|
|
171
176
|
console.log(`\n=== Target: ${targetName} ===`);
|
|
172
177
|
const result = args.suite
|
|
@@ -11,6 +11,7 @@ import type { CursorNativeToolDisplayItem } from "./cursor-native-tool-display.j
|
|
|
11
11
|
import type { CursorPiBridgeToolRequest, CursorPiToolBridgeRun } from "./cursor-pi-tool-bridge.js";
|
|
12
12
|
import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
13
13
|
import type { CursorSdkEventDebugRecorder } from "./cursor-sdk-event-debug.js";
|
|
14
|
+
import { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
|
|
14
15
|
|
|
15
16
|
export class CursorLiveRunAbortError extends Error {
|
|
16
17
|
constructor() {
|
|
@@ -118,6 +119,17 @@ interface LeaseWaiter {
|
|
|
118
119
|
onAbort?: () => void;
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
async function cancelCursorLiveSdkRun(run: CursorLiveRun): Promise<void> {
|
|
123
|
+
if (!run.sdkRun) return;
|
|
124
|
+
const guard = installCursorSdkProcessErrorGuard();
|
|
125
|
+
guard.suppressAbortErrors();
|
|
126
|
+
try {
|
|
127
|
+
await run.sdkRun.cancel();
|
|
128
|
+
} finally {
|
|
129
|
+
guard.dispose();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
121
133
|
interface CursorLiveRunPrivateState {
|
|
122
134
|
waiters: Set<ProgressWaiter>;
|
|
123
135
|
idleDisposeTimer?: ReturnType<typeof setTimeout>;
|
|
@@ -474,7 +486,7 @@ export function createCursorLiveRunCoordinator(deps: CursorLiveRunCoordinatorDep
|
|
|
474
486
|
if (abandoned) {
|
|
475
487
|
if (!run.done) {
|
|
476
488
|
try {
|
|
477
|
-
await run
|
|
489
|
+
await cancelCursorLiveSdkRun(run);
|
|
478
490
|
} catch {
|
|
479
491
|
// cancellation failure should not block session-agent abandonment
|
|
480
492
|
}
|
|
@@ -60,13 +60,15 @@ function getCursorConnectSource(error: unknown, record: Record<string, unknown>
|
|
|
60
60
|
const type = getErrorStringField(asRecord(detail), "type");
|
|
61
61
|
return typeof type === "string" && type.startsWith("aiserver.");
|
|
62
62
|
});
|
|
63
|
-
|
|
63
|
+
if (hasCursorBackendDetails) return "cursor-backend-details";
|
|
64
|
+
return stack.includes("@connectrpc/connect-node") ? "connect-node-stack" : "generic-connect";
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
export type CursorConnectErrorSource =
|
|
67
68
|
| "cursor-sdk-stack"
|
|
68
69
|
| "cursor-extension-connect-stack"
|
|
69
70
|
| "cursor-backend-details"
|
|
71
|
+
| "connect-node-stack"
|
|
70
72
|
| "generic-connect";
|
|
71
73
|
|
|
72
74
|
export type CursorConnectErrorClassification =
|
|
@@ -13,7 +13,7 @@ type GenericProcessEmit = (event: string | symbol, ...args: unknown[]) => boolea
|
|
|
13
13
|
|
|
14
14
|
// The local Cursor SDK can surface some ConnectRPC failures as process-level
|
|
15
15
|
// uncaught exceptions/unhandled rejections even when run.wait()/run.cancel() is awaited.
|
|
16
|
-
// Keep suppression scoped to active Cursor provider turns and tightly matched
|
|
16
|
+
// Keep suppression scoped to active Cursor provider turns and tightly matched ConnectRPC shapes.
|
|
17
17
|
const activeProviderTurns = new Set<CursorSdkProcessErrorGuardToken>();
|
|
18
18
|
let originalProcessEmit: GenericProcessEmit | undefined;
|
|
19
19
|
let captureCallbackInstalled = false;
|
|
@@ -35,7 +35,9 @@ function shouldSuppressProcessError(event: string | symbol, args: readonly unkno
|
|
|
35
35
|
const classification = classifyCursorConnectError(error);
|
|
36
36
|
if (!classification) return false;
|
|
37
37
|
if (classification.kind === "abort") return hasActiveAbortSuppression();
|
|
38
|
-
|
|
38
|
+
if (activeProviderTurns.size === 0) return false;
|
|
39
|
+
if (classification.kind === "network") return isCursorProvenance(classification.source) || classification.source === "connect-node-stack";
|
|
40
|
+
return isCursorProvenance(classification.source);
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
function installProcessEmitPatch(): void {
|
|
@@ -1,508 +0,0 @@
|
|
|
1
|
-
# Crabbox Local Platform Testing Guide for pi Extensions
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
This is the reusable field guide for adding **local Crabbox platform testing** to pi extension repositories.
|
|
6
|
-
|
|
7
|
-
Current scope:
|
|
8
|
-
|
|
9
|
-
- pi extension packages only;
|
|
10
|
-
- local maintainer machine is macOS;
|
|
11
|
-
- required target matrix is macOS, Ubuntu Linux, and native Windows;
|
|
12
|
-
- Windows runs through the local Parallels template `pi-extension-windows-template` and snapshot `crabbox-ready` unless a project documents a different source of truth.
|
|
13
|
-
|
|
14
|
-
This guide is generic on purpose. Do not copy another project's model IDs, package names, API keys, VM clones, artifact folders, prompts, or release decisions. Copy the architecture and conventions, then make the target project own its config, docs, assertions, and release gate.
|
|
15
|
-
|
|
16
|
-
Official references:
|
|
17
|
-
|
|
18
|
-
- [Crabbox docs](https://crabbox.sh/)
|
|
19
|
-
- [openclaw/crabbox](https://github.com/openclaw/crabbox)
|
|
20
|
-
- Crabbox source docs worth reading before changing a harness: `docs/commands/run.md`, `docs/commands/warmup.md`, `docs/commands/stop.md`, `docs/features/doctor.md`, `docs/features/sync.md`, `docs/features/env-forwarding.md`, `docs/providers/ssh.md`, `docs/providers/local-container.md`, and `docs/providers/parallels.md`.
|
|
21
|
-
|
|
22
|
-
Local reference implementations reviewed for this guide:
|
|
23
|
-
|
|
24
|
-
- `~/Projects/AI/pi-cursor-sdk` — full provider/runtime gate with live model, visual TUI, JSONL, bridge, usage/cache, and abort-cleanup assertions.
|
|
25
|
-
- `~/Projects/AI/pi-oracle` — package/build platform gate plus project-specific real smoke, with a project-specific env prefix.
|
|
26
|
-
- `~/Projects/AI/pi-codex-goal` — compact reusable harness with platform build plus real pi runtime smoke on all targets.
|
|
27
|
-
|
|
28
|
-
## Standard local matrix
|
|
29
|
-
|
|
30
|
-
| Harness target | Crabbox provider | Local purpose | Shell contract | Default work root |
|
|
31
|
-
| --- | --- | --- | --- | --- |
|
|
32
|
-
| `macos` | `ssh` static localhost | Current host macOS | POSIX shell over SSH | `/Users/$USER/crabbox/<project>` |
|
|
33
|
-
| `ubuntu` | `local-container` | Linux smoke without cloud | POSIX shell in a Docker-compatible container | provider default `/work/crabbox` |
|
|
34
|
-
| `windows-native` | `parallels` | Real native Windows behavior | PowerShell/OpenSSH, `--windows-mode normal` | `C:\crabbox\<project>` |
|
|
35
|
-
|
|
36
|
-
Use this matrix by default for pi extensions. If a project does not need all three targets, that project's docs must say which target is non-required and why. A missing required target is a blocked local setup, not a skipped pass.
|
|
37
|
-
|
|
38
|
-
## What Crabbox owns vs. what the project owns
|
|
39
|
-
|
|
40
|
-
Crabbox owns the lease/sync/run loop:
|
|
41
|
-
|
|
42
|
-
1. lease or claim a target;
|
|
43
|
-
2. sync tracked plus nonignored local files;
|
|
44
|
-
3. run a command remotely;
|
|
45
|
-
4. stream output;
|
|
46
|
-
5. expose timing, logs, failure bundles, and cleanup commands;
|
|
47
|
-
6. stop or expire the lease.
|
|
48
|
-
|
|
49
|
-
The project owns the test contract:
|
|
50
|
-
|
|
51
|
-
- which targets and suites are required;
|
|
52
|
-
- target setup and runtime versions;
|
|
53
|
-
- package install semantics;
|
|
54
|
-
- pi commands and prompts;
|
|
55
|
-
- assertions over stdout, JSONL, visual evidence, artifacts, cleanup, and redaction;
|
|
56
|
-
- docs and release criteria.
|
|
57
|
-
|
|
58
|
-
Do not treat Crabbox as a runtime installer. If a target needs Node, npm, Git, `tar`, `rsync`, `zstd`, `ffmpeg`, a browser renderer, or another reusable tool, put that setup in the target image/template or in a documented project setup step.
|
|
59
|
-
|
|
60
|
-
## Recommended repository shape
|
|
61
|
-
|
|
62
|
-
Use names that fit the project, but keep this shape unless the project already has a better source of truth.
|
|
63
|
-
|
|
64
|
-
```text
|
|
65
|
-
platform-smoke.config.mjs
|
|
66
|
-
scripts/platform-smoke.mjs
|
|
67
|
-
scripts/platform-smoke/doctor.mjs
|
|
68
|
-
scripts/platform-smoke/crabbox-runner.mjs
|
|
69
|
-
scripts/platform-smoke/targets.mjs
|
|
70
|
-
scripts/platform-smoke/artifacts.mjs
|
|
71
|
-
scripts/platform-smoke/platform-build-windows.ps1
|
|
72
|
-
scripts/platform-smoke/<runtime-suite>.mjs # optional real pi/model smoke
|
|
73
|
-
scripts/platform-smoke/pty-capture.mjs # optional TUI/PTY suites
|
|
74
|
-
scripts/platform-smoke/render-ansi.mjs # optional host-side visual renderer
|
|
75
|
-
docs/platform-smoke.md # project source of truth
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Gitignored local state:
|
|
79
|
-
|
|
80
|
-
```text
|
|
81
|
-
.artifacts/
|
|
82
|
-
.crabbox/
|
|
83
|
-
.debug/
|
|
84
|
-
.platform-smoke-runs/
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Package scripts:
|
|
88
|
-
|
|
89
|
-
```json
|
|
90
|
-
{
|
|
91
|
-
"scripts": {
|
|
92
|
-
"check:platform-smoke": "node --check scripts/platform-smoke.mjs && node --check scripts/platform-smoke/doctor.mjs && node --check scripts/platform-smoke/crabbox-runner.mjs && node --check scripts/platform-smoke/targets.mjs",
|
|
93
|
-
"smoke:platform": "node scripts/platform-smoke.mjs",
|
|
94
|
-
"smoke:platform:doctor": "node scripts/platform-smoke.mjs doctor",
|
|
95
|
-
"smoke:platform:macos": "node scripts/platform-smoke.mjs run --target macos",
|
|
96
|
-
"smoke:platform:ubuntu": "node scripts/platform-smoke.mjs run --target ubuntu",
|
|
97
|
-
"smoke:platform:windows-native": "node scripts/platform-smoke.mjs run --target windows-native",
|
|
98
|
-
"smoke:platform:all": "node scripts/platform-smoke.mjs run --target macos,ubuntu,windows-native"
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Add tests for cheap harness invariants: syntax, help text, target/suite validation, package-file inclusion or exclusion, packed-install command rendering, artifact-manifest failure behavior, cleanup-failure behavior, path traversal rejection, and secret redaction.
|
|
104
|
-
|
|
105
|
-
## Host Crabbox install
|
|
106
|
-
|
|
107
|
-
Install the Crabbox CLI on the macOS host and keep the harness explicit about the binary it uses:
|
|
108
|
-
|
|
109
|
-
```sh
|
|
110
|
-
brew install openclaw/tap/crabbox
|
|
111
|
-
crabbox --version
|
|
112
|
-
crabbox providers
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
If Homebrew already has the OpenClaw tap configured, `brew install crabbox` may resolve to the same formula. Use `PLATFORM_SMOKE_CRABBOX=/path/to/crabbox` only when testing a non-default binary or a locally built Crabbox.
|
|
116
|
-
|
|
117
|
-
## Configuration conventions
|
|
118
|
-
|
|
119
|
-
Keep project-specific defaults in `platform-smoke.config.mjs`:
|
|
120
|
-
|
|
121
|
-
```js
|
|
122
|
-
export default {
|
|
123
|
-
packageName: "pi-example-extension",
|
|
124
|
-
artifactRoot: ".artifacts/platform-smoke",
|
|
125
|
-
requiredTargets: ["macos", "ubuntu", "windows-native"],
|
|
126
|
-
requiredSuites: ["platform-build"],
|
|
127
|
-
requiredCrabbox: { minVersion: "0.24.0" },
|
|
128
|
-
ubuntuContainerImage: "cimg/node:24.16",
|
|
129
|
-
nodeValidationMajor: 24,
|
|
130
|
-
};
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Use the config as the harness source of truth. Crabbox itself resolves config as `flags > env > repo .crabbox.yaml/crabbox.yaml > user config > defaults`; local smoke harnesses should still pass critical provider/work-root flags explicitly so the gate does not depend on hidden user config.
|
|
134
|
-
|
|
135
|
-
Environment conventions:
|
|
136
|
-
|
|
137
|
-
- Prefer defaults derived from `config.packageName` for project-specific work roots and slugs.
|
|
138
|
-
- Do not export project-specific work-root overrides globally.
|
|
139
|
-
- Use `PLATFORM_SMOKE_*` for reusable harness knobs when scripts are shared across projects.
|
|
140
|
-
- Use a project-specific prefix such as `PI_ORACLE_SMOKE_*` only when a repo needs to coexist with another harness or already has established project-specific environment names.
|
|
141
|
-
- Keep auth variable names in config, not auth values.
|
|
142
|
-
|
|
143
|
-
Useful standard variables:
|
|
144
|
-
|
|
145
|
-
```text
|
|
146
|
-
PLATFORM_SMOKE_CRABBOX=/opt/homebrew/bin/crabbox
|
|
147
|
-
PLATFORM_SMOKE_MAC_HOST=localhost
|
|
148
|
-
PLATFORM_SMOKE_MAC_USER=$USER
|
|
149
|
-
PLATFORM_SMOKE_MAC_WORK_ROOT=/Users/$USER/crabbox/<project>
|
|
150
|
-
PLATFORM_SMOKE_UBUNTU_IMAGE=cimg/node:24.16
|
|
151
|
-
PLATFORM_SMOKE_WINDOWS_VM=pi-extension-windows-template
|
|
152
|
-
PLATFORM_SMOKE_WINDOWS_SNAPSHOT=crabbox-ready
|
|
153
|
-
PLATFORM_SMOKE_WINDOWS_USER=<windows-ssh-user>
|
|
154
|
-
PLATFORM_SMOKE_WINDOWS_WORK_ROOT=C:\crabbox\<project>
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
Pin Crabbox deliberately. Exact pins are best for release-critical harnesses whose parsing depends on CLI output. Minimum version checks are fine for simpler gates. Current local baseline is Crabbox `0.24.0` or newer from the Homebrew package.
|
|
158
|
-
|
|
159
|
-
## Target setup best practices
|
|
160
|
-
|
|
161
|
-
### macOS: static SSH to the current host
|
|
162
|
-
|
|
163
|
-
Use the `ssh` provider for current macOS. It is a static provider: Crabbox does not create or clean up the host.
|
|
164
|
-
|
|
165
|
-
Required setup:
|
|
166
|
-
|
|
167
|
-
- macOS Remote Login enabled.
|
|
168
|
-
- Noninteractive SSH works: `ssh -o BatchMode=yes $USER@localhost 'whoami'`.
|
|
169
|
-
- Target user has a writable work root such as `/Users/$USER/crabbox/<project>`.
|
|
170
|
-
- `node`, `npm`, `git`, `rsync`, `tar`, and project-specific native tools are on the remote SSH path.
|
|
171
|
-
|
|
172
|
-
Base args:
|
|
173
|
-
|
|
174
|
-
```text
|
|
175
|
-
--provider ssh
|
|
176
|
-
--target macos
|
|
177
|
-
--static-host localhost
|
|
178
|
-
--static-user $USER
|
|
179
|
-
--static-port 22
|
|
180
|
-
--static-work-root /Users/$USER/crabbox/<project>
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
Notes:
|
|
184
|
-
|
|
185
|
-
- Static `stop` removes Crabbox's local claim only; it does not clean the Mac.
|
|
186
|
-
- Use `--reclaim` intentionally when multiple repos reuse localhost and a previous claim blocks the run.
|
|
187
|
-
- Because this is the real host, avoid tests that mutate global user state unless the project explicitly owns cleanup.
|
|
188
|
-
|
|
189
|
-
### Ubuntu: local-container provider
|
|
190
|
-
|
|
191
|
-
Use `local-container` for the Linux target. It runs through Docker Desktop, OrbStack, Colima, or another Docker-compatible runtime on the local machine. There is no broker or cloud dependency.
|
|
192
|
-
|
|
193
|
-
Required setup:
|
|
194
|
-
|
|
195
|
-
- `docker info` passes.
|
|
196
|
-
- The chosen image supports the project's Node/npm baseline or can bootstrap quickly.
|
|
197
|
-
- Default image for current pi extension smokes: `cimg/node:24.16`.
|
|
198
|
-
|
|
199
|
-
Base args:
|
|
200
|
-
|
|
201
|
-
```text
|
|
202
|
-
--provider local-container
|
|
203
|
-
--target linux
|
|
204
|
-
--local-container-image cimg/node:24.16
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
Notes:
|
|
208
|
-
|
|
209
|
-
- Use a prebuilt image when first-start package bootstrapping becomes a bottleneck.
|
|
210
|
-
- Do not mount the host Docker socket unless the suite actually needs nested Docker; that grants the container access to the host daemon.
|
|
211
|
-
- Treat container cache volumes as local mutable state. Name them per project and clean obsolete keys manually.
|
|
212
|
-
|
|
213
|
-
### Windows: Parallels native Windows template
|
|
214
|
-
|
|
215
|
-
Use the `parallels` provider for native Windows. The default reusable template is:
|
|
216
|
-
|
|
217
|
-
```text
|
|
218
|
-
source VM: pi-extension-windows-template
|
|
219
|
-
snapshot: crabbox-ready
|
|
220
|
-
mode: windows normal
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
Base args:
|
|
224
|
-
|
|
225
|
-
```text
|
|
226
|
-
--provider parallels
|
|
227
|
-
--target windows
|
|
228
|
-
--windows-mode normal
|
|
229
|
-
--parallels-source pi-extension-windows-template
|
|
230
|
-
--parallels-source-snapshot crabbox-ready
|
|
231
|
-
--parallels-user <windows-ssh-user>
|
|
232
|
-
--parallels-work-root C:\crabbox\<project>
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
Template requirements:
|
|
236
|
-
|
|
237
|
-
- Parallels Tools installed.
|
|
238
|
-
- A stable SSH user.
|
|
239
|
-
- OpenSSH Server enabled and reachable on port `22`.
|
|
240
|
-
- PowerShell available.
|
|
241
|
-
- Git for Windows installed.
|
|
242
|
-
- `tar` available for archive sync.
|
|
243
|
-
- Node/npm at the project validation baseline.
|
|
244
|
-
- Writable `C:\crabbox` work root.
|
|
245
|
-
- The source VM is not used as a normal work machine.
|
|
246
|
-
- `crabbox-ready` is a known-good power-off snapshot. Linked clones depend on that snapshot, so do not delete or replace it casually.
|
|
247
|
-
|
|
248
|
-
PowerShell rules:
|
|
249
|
-
|
|
250
|
-
- Use a checked-in `.ps1` script for long Windows suites.
|
|
251
|
-
- Run with `powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\scripts\platform-smoke\platform-build-windows.ps1`.
|
|
252
|
-
- Avoid one giant quoted `--shell` string for Windows unless it is a small probe.
|
|
253
|
-
- If installing Git or tools changes PATH, restart `sshd` or validate in a fresh SSH session.
|
|
254
|
-
|
|
255
|
-
### Windows template image policy
|
|
256
|
-
|
|
257
|
-
Agents should reuse `pi-extension-windows-template` instead of creating one-off Windows VMs for each project.
|
|
258
|
-
|
|
259
|
-
Add a tool to the template when all are true:
|
|
260
|
-
|
|
261
|
-
- more than one pi extension is likely to need it;
|
|
262
|
-
- installing it every run is slow, flaky, or network-dependent;
|
|
263
|
-
- it is safe to have globally on Windows test machines;
|
|
264
|
-
- it has no project secrets, local user auth, or repo-specific config.
|
|
265
|
-
|
|
266
|
-
Keep project-specific tools in repo scripts when they are truly one-off.
|
|
267
|
-
|
|
268
|
-
Template update runbook:
|
|
269
|
-
|
|
270
|
-
1. Prefer updating `pi-extension-windows-template` over adding per-project/per-run installers when a tool is reusable across pi extensions.
|
|
271
|
-
2. Boot the source VM, not a Crabbox clone.
|
|
272
|
-
3. Install or update the globally useful tool.
|
|
273
|
-
4. Verify from a fresh SSH session: `node --version`, `npm --version`, `git --version`, `tar --version`, and the new tool's `--version` or equivalent.
|
|
274
|
-
5. Remove caches, downloads, auth files, local checkouts, `.pi` state, `.artifacts`, `.debug`, and secrets.
|
|
275
|
-
6. Shut down the VM cleanly.
|
|
276
|
-
7. Create a new known-good power-off snapshot. Prefer a dated snapshot for trial adoption; promote it to `crabbox-ready` only after at least one project passes the Windows smoke.
|
|
277
|
-
8. Update project docs/config if the snapshot name changes.
|
|
278
|
-
9. Stop or clean stale clones after the template update so future runs do not reuse pre-update state.
|
|
279
|
-
|
|
280
|
-
Never bake API keys, browser sessions, user project checkouts, generated artifacts, or repo-specific `.env` files into the template.
|
|
281
|
-
|
|
282
|
-
## Doctor is mandatory
|
|
283
|
-
|
|
284
|
-
`npm run smoke:platform:doctor` should fail before any expensive, token-spending, or long-running suite starts. The release entrypoint should enforce this, either by making `smoke:platform:all` run doctor first or by making the canonical release command run `smoke:platform:doctor && smoke:platform:all`.
|
|
285
|
-
|
|
286
|
-
Doctor should check:
|
|
287
|
-
|
|
288
|
-
- Crabbox binary path and version/minimum version.
|
|
289
|
-
- `crabbox providers` includes `ssh`, `local-container`, and `parallels`.
|
|
290
|
-
- `crabbox doctor --provider local-container --json` passes for the configured image.
|
|
291
|
-
- `crabbox doctor --provider ssh --target macos --json` passes or reports a concrete host setup failure.
|
|
292
|
-
- Docker is running for Ubuntu.
|
|
293
|
-
- macOS SSH probe reaches the host and sees Node/npm/Git.
|
|
294
|
-
- `prlctl` exists.
|
|
295
|
-
- The Windows source VM and snapshot exist.
|
|
296
|
-
- The Windows snapshot is forkable/power-off; if the template has no live IP because it is stopped, a disposable Crabbox clone probe is acceptable.
|
|
297
|
-
- Host `node`, `npm`, `git`, `tar`, and any host-side renderer tools exist.
|
|
298
|
-
- Required auth variables for live suites are present, reported as redacted presence only.
|
|
299
|
-
- Artifact root is writable.
|
|
300
|
-
- Repo status is visible.
|
|
301
|
-
- Forbidden files such as `.env`, `.env.*`, local package tarballs, `.artifacts`, `.crabbox`, and `.debug` are not in the package or source archive.
|
|
302
|
-
|
|
303
|
-
Do not downgrade a missing required target to a warning. A release gate with missing Windows, Docker, SSH, auth, or Crabbox setup is blocked.
|
|
304
|
-
|
|
305
|
-
## Lease and run strategy
|
|
306
|
-
|
|
307
|
-
Use target sessions, not one fresh lease per suite.
|
|
308
|
-
|
|
309
|
-
Recommended shape:
|
|
310
|
-
|
|
311
|
-
```text
|
|
312
|
-
for each target in parallel:
|
|
313
|
-
warmup once with slug <project>-<target>
|
|
314
|
-
run suites serially on that lease
|
|
315
|
-
stop lease in finally
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
Targets can run concurrently when the host can handle Docker, localhost SSH, and Parallels together. Suites should stay serial within a target unless the project has proven its ports, sessions, workspaces, and artifacts are isolated.
|
|
319
|
-
|
|
320
|
-
Use stable slugs:
|
|
321
|
-
|
|
322
|
-
```text
|
|
323
|
-
<project>-macos
|
|
324
|
-
<project>-ubuntu
|
|
325
|
-
<project>-windows-native
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
Sync rules:
|
|
329
|
-
|
|
330
|
-
- Start with `crabbox sync-plan` when first onboarding a repo or when sync is unexpectedly large.
|
|
331
|
-
- Use `.gitignore`, `.crabboxignore`, or Crabbox `sync.exclude` for generated state.
|
|
332
|
-
- Use `--fresh-sync` when a target workspace may be stale or a previous suite mutated the checkout.
|
|
333
|
-
- Use `--no-sync` only after a deliberate shared prep step on the same lease.
|
|
334
|
-
- If a private/local repo cannot use remote Git seeding reliably, set `CRABBOX_SYNC_GIT_SEED=false` in the harness and document why.
|
|
335
|
-
- Do not use `--force-sync-large` unless the large transfer is understood and intentional.
|
|
336
|
-
|
|
337
|
-
Always record:
|
|
338
|
-
|
|
339
|
-
```text
|
|
340
|
-
crabbox.stdout.txt
|
|
341
|
-
crabbox.stderr.txt
|
|
342
|
-
crabbox.timing.json
|
|
343
|
-
crabbox.stop.stdout.txt
|
|
344
|
-
crabbox.stop.stderr.txt
|
|
345
|
-
crabbox.stop.exit-code.txt
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
A `stop` failure is a test result. Preserve the original suite result and add a failing `lease-cleanup` result or mark the owning suite failed.
|
|
349
|
-
|
|
350
|
-
## pi extension release contract
|
|
351
|
-
|
|
352
|
-
For pi extensions, the baseline `platform-build` suite should prove package installation, not only source-tree execution.
|
|
353
|
-
|
|
354
|
-
On every required target:
|
|
355
|
-
|
|
356
|
-
1. Check Node major version against `nodeValidationMajor`.
|
|
357
|
-
2. Run `npm ci`.
|
|
358
|
-
3. Run the repo's local verification command, usually `npm run verify` or the repo-specific equivalent.
|
|
359
|
-
4. Run `npm pack`.
|
|
360
|
-
5. Create a fresh target-local pi project/workspace.
|
|
361
|
-
6. Run `npm install --no-save <packed tarball>`.
|
|
362
|
-
7. Run `pi install -l ./node_modules/<package>`.
|
|
363
|
-
8. Run `pi list`.
|
|
364
|
-
9. Assert the installed package came from the packed install path.
|
|
365
|
-
10. Assert the release proof did not use `pi -e .` or `pi --extension .`.
|
|
366
|
-
|
|
367
|
-
`pi -e .` is inner-loop debug only. It is not release proof because it bypasses package contents, `files`, install layout, and publish-time mistakes.
|
|
368
|
-
|
|
369
|
-
Add a real pi runtime suite when the extension's user contract depends on runtime behavior that unit tests cannot prove. Keep it deterministic:
|
|
370
|
-
|
|
371
|
-
- install the packed package into a clean project;
|
|
372
|
-
- use a fixed model/provider unless the project config overrides it;
|
|
373
|
-
- forward only named auth env vars;
|
|
374
|
-
- write session JSONL and target-local result files;
|
|
375
|
-
- assert final assistant text, tool calls/results, extension state, and persisted files structurally, not by broad substring scans.
|
|
376
|
-
|
|
377
|
-
Add visual/TUI suites only when the extension has user-facing terminal UI. The portable visual contract is:
|
|
378
|
-
|
|
379
|
-
```text
|
|
380
|
-
target captures PTY/ConPTY ANSI
|
|
381
|
-
host renders ANSI through one xterm/Playwright renderer
|
|
382
|
-
host writes HTML + PNG evidence
|
|
383
|
-
assert rendered output, not prompt text
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
Do not make tmux the cross-platform visual source of truth when native Windows is required. Use PTY on POSIX targets and ConPTY on Windows.
|
|
387
|
-
|
|
388
|
-
## Artifact contract
|
|
389
|
-
|
|
390
|
-
Every suite should write a self-contained directory:
|
|
391
|
-
|
|
392
|
-
```text
|
|
393
|
-
.artifacts/platform-smoke/<run-id>/<target>/<suite>/
|
|
394
|
-
summary.json
|
|
395
|
-
artifact-manifest.json
|
|
396
|
-
target.json
|
|
397
|
-
suite.json
|
|
398
|
-
command.txt
|
|
399
|
-
exit-code.txt
|
|
400
|
-
crabbox.stdout.txt
|
|
401
|
-
crabbox.stderr.txt
|
|
402
|
-
crabbox.timing.json
|
|
403
|
-
assertions.json
|
|
404
|
-
failures.md # only when assertions fail
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
Add suite-specific evidence, for example:
|
|
408
|
-
|
|
409
|
-
```text
|
|
410
|
-
node-version.txt
|
|
411
|
-
npm-ci.stdout.txt
|
|
412
|
-
npm-ci.stderr.txt
|
|
413
|
-
npm-test.stdout.txt
|
|
414
|
-
npm-test.stderr.txt
|
|
415
|
-
packed-tarball.txt
|
|
416
|
-
packed-node-install.stdout.txt
|
|
417
|
-
packed-node-install.stderr.txt
|
|
418
|
-
pi-install.stdout.txt
|
|
419
|
-
pi-install.stderr.txt
|
|
420
|
-
pi-list.stdout.txt
|
|
421
|
-
pi-list.stderr.txt
|
|
422
|
-
session.jsonl
|
|
423
|
-
terminal.ansi
|
|
424
|
-
terminal.html
|
|
425
|
-
terminal.png
|
|
426
|
-
redaction-scan.json
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
Pass/fail invariant:
|
|
430
|
-
|
|
431
|
-
```text
|
|
432
|
-
summary.ok === assertions.ok
|
|
433
|
-
artifact-manifest.missing.length === 0 for any passing suite
|
|
434
|
-
missing required artifact => assertion failure + summary.ok=false
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
Do not rely on Crabbox `--artifact-glob` for this matrix. Crabbox's SSH artifact collector is useful on Linux, but native Windows and macOS targets reject that collector. A portable harness should write host-side artifact files from captured stdout/stderr, explicit target output markers, session paths, or a safe target-produced bundle whose paths are validated before unpacking.
|
|
438
|
-
|
|
439
|
-
## Secrets and environment forwarding
|
|
440
|
-
|
|
441
|
-
Crabbox intentionally does not forward your whole environment to the remote target. By default it forwards only narrow built-ins such as `CI` and `NODE_OPTIONS`. Live pi suites must opt in to exactly the variables they need.
|
|
442
|
-
|
|
443
|
-
Local forwarding of secrets is acceptable for these maintainer-owned smoke gates when the suite needs real provider/model auth. The hard line is persistence and sharing: secrets must never be committed, baked into templates, written to artifacts, printed in docs/PRs, or posted in chat.
|
|
444
|
-
|
|
445
|
-
Best practices:
|
|
446
|
-
|
|
447
|
-
- Keep auth values out of docs, configs, shell commands, artifact names, and template images.
|
|
448
|
-
- Store auth variable names in config, e.g. `defaultAuthEnv: ["ZAI_API_KEY"]`.
|
|
449
|
-
- Forward only named auth variables with `--allow-env NAME` or `--env-from-profile <file> --allow-env NAME`.
|
|
450
|
-
- Do not pass API keys as command-line arguments.
|
|
451
|
-
- Preserve normal local process environment such as `PATH`, `HOME`, and tool configuration, but do not dump the full environment into artifacts.
|
|
452
|
-
- Redact stdout, stderr, JSONL, HTML, ANSI, debug files, and failure bundles before committing, publishing, posting, or sharing them.
|
|
453
|
-
- Fail if a redaction scan finds API keys, bearer tokens, cookies, auth headers, or raw `.env` contents in persisted artifacts.
|
|
454
|
-
|
|
455
|
-
Docs may say `CURSOR_API_KEY=(present, redacted)` or `ZAI_API_KEY=(present, redacted)`. They must never include values.
|
|
456
|
-
|
|
457
|
-
## Make false green states impossible
|
|
458
|
-
|
|
459
|
-
The main guardrails:
|
|
460
|
-
|
|
461
|
-
- `doctor` is required before `all`.
|
|
462
|
-
- Required targets do not skip green.
|
|
463
|
-
- Release proof uses packed install, not `pi -e .`.
|
|
464
|
-
- A suite cannot pass with missing required artifacts.
|
|
465
|
-
- Cleanup failures fail the target result.
|
|
466
|
-
- Visual assertions inspect rendered output, not only prompt text.
|
|
467
|
-
- JSONL assertions inspect specific message fields, not all-file substrings.
|
|
468
|
-
- Auth is forwarded to targets by explicit allowlist only.
|
|
469
|
-
- Secrets can be used locally, but artifacts/docs/comments never expose them.
|
|
470
|
-
- Target-specific assumptions live in `docs/platform-smoke.md`, not in chat.
|
|
471
|
-
|
|
472
|
-
## Adoption procedure for a new pi extension
|
|
473
|
-
|
|
474
|
-
1. Identify the package name and pi install path.
|
|
475
|
-
2. Define required targets: default to `macos`, `ubuntu`, and `windows-native`.
|
|
476
|
-
3. Define required suites: always start with `platform-build`; add runtime or visual suites only for real user contracts.
|
|
477
|
-
4. Add `platform-smoke.config.mjs` with package name, targets, suites, Crabbox version, Ubuntu image, and Node baseline.
|
|
478
|
-
5. Add `scripts/platform-smoke.mjs` with `doctor`, per-target, and `all` commands.
|
|
479
|
-
6. Add a thin `crabbox-runner.mjs` that owns target base args, warmup, run, timeout, env allowlist, and stop.
|
|
480
|
-
7. Add target command builders in `targets.mjs`; keep POSIX and PowerShell paths explicit.
|
|
481
|
-
8. Add `platform-build-windows.ps1` for the Windows suite body.
|
|
482
|
-
9. Add `artifacts.mjs` and make missing artifacts fail.
|
|
483
|
-
10. Add `doctor.mjs`; all required local prerequisites fail hard.
|
|
484
|
-
11. Add cheap tests for harness syntax, help, target selection, packed-install command rendering, manifest failure, cleanup failure, and package inclusion/exclusion.
|
|
485
|
-
12. Add `docs/platform-smoke.md` as the project-specific source of truth.
|
|
486
|
-
13. Add a short pointer in `AGENTS.md` and README if the platform gate is release-blocking.
|
|
487
|
-
14. Run `npm run check:platform-smoke`, then `npm run smoke:platform:doctor`, then a single target, then `npm run smoke:platform:all`.
|
|
488
|
-
|
|
489
|
-
## Project adoption checklist
|
|
490
|
-
|
|
491
|
-
Before declaring a project integrated, answer these in that project's docs:
|
|
492
|
-
|
|
493
|
-
1. What package/install path must release prove?
|
|
494
|
-
2. Which OS targets are release-blocking?
|
|
495
|
-
3. What exact Crabbox version or minimum version is supported?
|
|
496
|
-
4. Which Ubuntu image is used?
|
|
497
|
-
5. Which Parallels template and snapshot are used?
|
|
498
|
-
6. What target tools are expected globally, especially on Windows?
|
|
499
|
-
7. What suite proves packed pi install?
|
|
500
|
-
8. What suite, if any, proves real pi runtime behavior?
|
|
501
|
-
9. What visual evidence, if any, is required?
|
|
502
|
-
10. What auth env names are allowed to cross into targets?
|
|
503
|
-
11. What artifacts must exist for a pass?
|
|
504
|
-
12. What redaction scans run before sharing evidence?
|
|
505
|
-
13. How are lease cleanup failures surfaced?
|
|
506
|
-
14. Which docs, package scripts, and tests are the source of truth?
|
|
507
|
-
|
|
508
|
-
The standard is not "copy every file from pi-cursor-sdk." The standard is: define the platform failure modes that matter for the extension, then make the local Crabbox gate produce durable evidence for them on macOS, Ubuntu, and native Windows without sharing state between repositories.
|