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.
Files changed (88) hide show
  1. package/CHANGELOG.md +60 -2
  2. package/README.md +100 -28
  3. package/docs/cursor-live-smoke-checklist.md +7 -7
  4. package/docs/cursor-model-ux-spec.md +48 -41
  5. package/docs/cursor-native-tool-replay.md +4 -4
  6. package/docs/cursor-native-tool-visual-audit.md +1 -1
  7. package/docs/cursor-testing-lessons.md +20 -7
  8. package/docs/cursor-tool-surfaces.md +13 -2
  9. package/docs/platform-smoke-implementation.md +220 -0
  10. package/docs/platform-smoke.md +183 -247
  11. package/package.json +39 -7
  12. package/platform-smoke.config.mjs +5 -1
  13. package/scripts/cloud-runtime-smoke.d.mts +3 -0
  14. package/scripts/cloud-runtime-smoke.mjs +502 -0
  15. package/scripts/debug-provider-events.mjs +7 -2
  16. package/scripts/isolated-cursor-smoke.sh +4 -6
  17. package/scripts/lib/cursor-child-process.d.mts +1 -0
  18. package/scripts/lib/cursor-child-process.mjs +137 -7
  19. package/scripts/lib/local-resume-smoke-harness.mjs +543 -0
  20. package/scripts/local-resume-cleanup-smoke.mjs +108 -0
  21. package/scripts/local-resume-smoke.d.mts +1 -0
  22. package/scripts/local-resume-smoke.mjs +642 -0
  23. package/scripts/platform-smoke/artifact-anchored-extract.d.mts +10 -0
  24. package/scripts/platform-smoke/artifact-anchored-extract.mjs +111 -0
  25. package/scripts/platform-smoke/artifact-bundle-chunk.mjs +57 -0
  26. package/scripts/platform-smoke/artifact-bundle-contract.mjs +34 -0
  27. package/scripts/platform-smoke/artifact-fs-safety.mjs +311 -0
  28. package/scripts/platform-smoke/artifact-openat-extract.c +335 -0
  29. package/scripts/platform-smoke/artifact-secrets.mjs +155 -0
  30. package/scripts/platform-smoke/artifacts.mjs +293 -65
  31. package/scripts/platform-smoke/card-detect.mjs +16 -4
  32. package/scripts/platform-smoke/crabbox-runner.mjs +45 -3
  33. package/scripts/platform-smoke/doctor.mjs +20 -10
  34. package/scripts/platform-smoke/live-suite-runner.mjs +18 -57
  35. package/scripts/platform-smoke/local-resume-runner.mjs +252 -0
  36. package/scripts/platform-smoke/local-resume-suites.d.mts +15 -0
  37. package/scripts/platform-smoke/local-resume-suites.mjs +104 -0
  38. package/scripts/platform-smoke/scenarios.mjs +16 -2
  39. package/scripts/platform-smoke/target-runtime.mjs +206 -0
  40. package/scripts/platform-smoke/targets.mjs +33 -141
  41. package/scripts/platform-smoke/visual-evidence.mjs +6 -7
  42. package/scripts/platform-smoke/wrapped-line-match.mjs +9 -0
  43. package/scripts/platform-smoke.mjs +40 -27
  44. package/scripts/refresh-cursor-model-snapshots.mjs +18 -6
  45. package/scripts/steering-rpc-smoke.mjs +12 -2
  46. package/scripts/tmux-live-smoke.sh +3 -5
  47. package/shared/cursor-cloud-lifecycle-constants.d.mts +3 -0
  48. package/shared/cursor-cloud-lifecycle-constants.mjs +7 -0
  49. package/shared/cursor-sensitive-text.mjs +7 -1
  50. package/src/context.ts +5 -2
  51. package/src/cursor-agents-context-registration.ts +7 -0
  52. package/src/cursor-agents-context.ts +3 -1
  53. package/src/cursor-api-key.ts +15 -1
  54. package/src/cursor-bridge-contract.ts +3 -0
  55. package/src/cursor-cloud-lifecycle.ts +733 -0
  56. package/src/cursor-cloud-options.ts +206 -0
  57. package/src/cursor-cloud-reporting.ts +246 -0
  58. package/src/cursor-config.ts +659 -0
  59. package/src/cursor-display-only-trace.ts +14 -0
  60. package/src/cursor-display-text.ts +8 -2
  61. package/src/cursor-durable-fs.ts +49 -0
  62. package/src/cursor-fallback-models.generated.ts +2045 -485
  63. package/src/cursor-live-run-accounting.ts +7 -1
  64. package/src/cursor-live-run-coordinator.ts +1 -0
  65. package/src/cursor-pi-tool-bridge-run.ts +14 -4
  66. package/src/cursor-provider-errors.ts +31 -5
  67. package/src/cursor-provider-live-run-drain.ts +5 -0
  68. package/src/cursor-provider-run-finalizer.ts +37 -28
  69. package/src/cursor-provider-run-outcome.ts +7 -2
  70. package/src/cursor-provider-turn-finalize.ts +77 -7
  71. package/src/cursor-provider-turn-prepare.ts +228 -12
  72. package/src/cursor-provider-turn-runner.ts +42 -13
  73. package/src/cursor-provider-turn-send.ts +59 -16
  74. package/src/cursor-provider-turn-types.ts +44 -10
  75. package/src/cursor-runtime-state.ts +478 -0
  76. package/src/cursor-sdk-event-debug.ts +46 -6
  77. package/src/cursor-sdk-process-error-guard.ts +101 -30
  78. package/src/cursor-session-agent-cleanup.ts +328 -0
  79. package/src/cursor-session-agent-resume.ts +439 -0
  80. package/src/cursor-session-agent.ts +109 -13
  81. package/src/cursor-session-scope.ts +35 -2
  82. package/src/cursor-session-send-policy.ts +1 -1
  83. package/src/cursor-skill-tool.ts +30 -11
  84. package/src/cursor-state.ts +112 -69
  85. package/src/cursor-tool-manifest.ts +1 -1
  86. package/src/cursor-usage-accounting.ts +14 -4
  87. package/src/index.ts +11 -2
  88. package/src/model-discovery.ts +10 -56
@@ -1,6 +1,8 @@
1
1
  # Platform Smoke Gate
2
2
 
3
- Status: current release gate for Cursor provider/runtime changes. The Crabbox runner, packed-install platform-build suite, and real live PTY/ConPTY suite runner are implemented for macOS, Ubuntu, and Windows native targets with one-lease-per-target orchestration.
3
+ Status: current local-runtime release gate for Cursor provider/runtime changes. Cloud-runtime changes also require the opt-in `npm run smoke:cloud` lane. The Crabbox runner, packed-install platform-build suite, and real live PTY/ConPTY suite runner are implemented for macOS, Ubuntu, and Windows native targets with one-lease-per-target orchestration.
4
+
5
+ Detailed detector, registry, command-rendering, implementation-history, replacement, and portability reference: [Platform Smoke Implementation Reference](./platform-smoke-implementation.md).
4
6
 
5
7
  Branch introduced by: `feat/crabbox-platform-smoke`
6
8
 
@@ -10,7 +12,7 @@ Crabbox best-practice baseline applied from `~/Projects/crabbox`: Crabbox owns l
10
12
 
11
13
  ## Decision
12
14
 
13
- Crabbox is the required platform smoke runner for `pi-cursor-sdk` releases that touch Cursor provider/runtime behavior.
15
+ Crabbox is the required local platform smoke runner for `pi-cursor-sdk` releases that touch Cursor provider/runtime behavior. PRs that touch actual cloud runtime execution must also run `npm run smoke:cloud`.
14
16
 
15
17
  Inner-loop checks remain useful, but they are not release gates:
16
18
 
@@ -19,12 +21,18 @@ npm run verify
19
21
  npm pack --dry-run
20
22
  ```
21
23
 
22
- The required release gate is exactly:
24
+ The required local release gate is exactly:
23
25
 
24
26
  ```bash
25
27
  npm run smoke:platform:all
26
28
  ```
27
29
 
30
+ Cloud-runtime changes additionally require:
31
+
32
+ ```bash
33
+ npm run smoke:cloud
34
+ ```
35
+
28
36
  `smoke:platform:all` runs `smoke:platform:doctor` first and only starts the target matrix after doctor passes. Maintainers may still run `npm run smoke:platform:doctor` by itself for setup diagnosis.
29
37
 
30
38
 
@@ -35,7 +43,7 @@ No partial adoption exists. The release evidence must include macOS, Ubuntu, and
35
43
  ## Non-negotiable constraints
36
44
 
37
45
  - No GitHub Actions dependency.
38
- - No cloud provider dependency.
46
+ - No cloud provider dependency in the default local platform gate; cloud-runtime changes use the separate opt-in cloud lane.
39
47
  - No Crabbox broker/coordinator dependency.
40
48
  - No release gate that runs on only one operating system.
41
49
  - No release gate that proves command behavior but not TUI visual behavior.
@@ -110,6 +118,16 @@ start target session
110
118
  run cursor-native-visual-matrix
111
119
  run cursor-bridge-visual-matrix
112
120
  run cursor-abort-cleanup
121
+ run cursor-local-resume-restart
122
+ run cursor-local-resume-safety
123
+ run cursor-local-resume-tool-surface
124
+ run cursor-local-resume-abort
125
+ run cursor-local-resume-tree
126
+ run cursor-local-resume-copy-switch
127
+ run cursor-local-resume-fallback
128
+ run cursor-local-resume-compaction
129
+ run cursor-local-resume-default-dry-run
130
+ run cursor-local-resume-cleanup
113
131
  download artifacts after every suite
114
132
  stop target
115
133
  write lease-cleanup stop evidence
@@ -123,7 +141,7 @@ Runtime budget is part of the contract:
123
141
  - `smoke:platform:doctor` never calls Cursor.
124
142
  - `platform-build` runs once per target and is the only suite that performs the full local CI/build/typecheck/package gate.
125
143
  - Live suites reuse the target checkout and prepared `node_modules` when run after `platform-build`; they do not repeat `npm ci` in a target-session release run.
126
- - Live suites share one target-local packed-install prep directory per target-session release run. The first live suite runs `npm pack` and `npm install --no-save <tarball>` once, then each suite still performs its own `pi install --approve -l <packed package path>`, `pi list --approve`, fresh `--session-dir`, suite `PI_CODING_AGENT_DIR`, workspace fixture, JSONL, visual, bridge, and abort assertions.
144
+ - Live and local-resume suites share one target-local packed-install prep directory per target-session release run. The first such suite runs `npm pack` and `npm install --no-save <tarball>` once. Visual/abort suites install that packed path with `pi install --approve -l`; local-resume lanes pass the same packed package path to their source-tree smoke harness instead of loading the checkout extension.
127
145
  - Visual coverage is batched into one native prompt, one bridge prompt, and one abort/cleanup prompt per target. Do not split these into one prompt per card.
128
146
  - The gate is fail-fast by target to avoid burning Cursor calls after a platform has already failed.
129
147
 
@@ -137,6 +155,60 @@ Runtime budget is part of the contract:
137
155
 
138
156
  Ubuntu is covered as its own local-container target, and Windows native remains a full visual TUI target.
139
157
 
158
+ ## Opt-in cloud smoke lane
159
+
160
+ Cloud validation is intentionally separate from `smoke:platform:all`. The required release gate keeps **no cloud provider dependency**; cloud runtime PRs must also run the opt-in cloud lane when they touch actual cloud execution.
161
+
162
+ Run:
163
+
164
+ ```bash
165
+ npm run smoke:cloud
166
+ ```
167
+
168
+ The default lane is intentionally minimal: it starts one non-interactive cloud run in a fresh persisted pi session (Cloud rejects `--no-session` so lifecycle intent can be fsynced before remote work), with explicit acknowledgement, fresh context, no pi bridge, no env forwarding, SDK event-debug contract checks, bounded stream-only cloud report shape checks, and cloud-agent archival cleanup with post-archive `archived: true` verification. Raw usage/artifact presence is account-dependent, so display-only accounting and artifact formatting contracts stay covered by unit/provider tests. It must fail closed when requested but unavailable:
169
+
170
+ - missing cloud-capable credentials, repo access, or cloud entitlement → report **blocked**, not skipped-ready;
171
+ - no non-interactive prompts; every needed cloud choice must come from CLI/env/config;
172
+ - do not expose inline cloud MCP in the first cloud runtime lane;
173
+ - do not forward local env values;
174
+ - harvest exact throwaway agent IDs from optional debug metadata plus canonical session JSONL/lifecycle journals, archive the full union, and verify archived metadata before passing;
175
+ - retain the entire artifact root whenever the run or any cleanup fails (successful runs remove it unless `CURSOR_CLOUD_SMOKE_KEEP_ARTIFACTS=1`).
176
+
177
+ Focused optional proof: `npm run smoke:cloud:context` runs a separate sessionful context-handoff matrix. Use it when changing cloud context-handoff behavior or gathering default-on/cloud-resume evidence. `fresh` must answer `NO_MARKER` for a prior-marker recall prompt, while `bootstrap` must recall the marker. It archives and verifies every exact cloud agent ID observed in debug metadata or canonical lifecycle artifacts.
178
+
179
+ Future expanded cloud smoke work should add throwaway repo/branch coverage, dirty/unpushed warning assertions, branch/PR behavior, explicit direct-push opt-in, missing-branch failure, cancel/delete cleanup, and account-backed artifact/raw-usage fixtures when those contracts need live proof beyond the current report-shape and context-handoff smokes.
180
+
181
+ This lane is a cloud-runtime release gate, not a substitute for the local macOS/Ubuntu/Windows `smoke:platform:all` gate.
182
+
183
+ ## Focused local resume smoke
184
+
185
+ The platform matrix includes the required local-resume lanes: restart, safety, tool-surface, abort, tree, copy/switch, fallback, compaction, default/opt-out proof, and recorded-ID-only cleanup. Platform lanes run those scripts against the target's shared packed package path, then copy each lane's session JSONL, Cursor SDK debug metadata, runtime-launch record, and other bounded smoke artifacts into its canonical platform suite directory. The same scripts still load the source checkout by default when run directly as focused host-local inner-loop checks. Windows uses the intentionally short target-side evidence component `lr` so the Cursor SDK's derived SQLite path remains below legacy `MAX_PATH`; every suite removes and verifies that directory before use, failing closed on stale or locked evidence.
186
+
187
+ The smoke starts one sessionful local Cursor run with local resume enabled by default, records the SDK agent id from provider debug metadata, restarts pi against the same session, asks for the remembered marker, and verifies:
188
+
189
+ - the first run records `localResume: true` and `resumedAgent: false`;
190
+ - the second run records `localResume: true`, `resumedAgent: true`, and a one-time bootstrap send plan;
191
+ - both runs use the same local SDK `agent-*` id;
192
+ - the remembered marker survives the process restart from the bootstrapped current pi transcript.
193
+
194
+ The safety lane verifies an original session resumes the same local agent after restart, then proves cloned-session copied resume entries and a fork before a future-marker prompt both create a new local `agent-*`. The forked earlier branch must not reveal the future marker.
195
+
196
+ The tool-surface lane verifies same-session restart reuses the original local agent with the same bridge/tool surface, then enables the builtin pi tool bridge surface and verifies the old resume handle is rejected, a bridge run is created, a new local `agent-*` is used, and a new resume pool key is persisted.
197
+
198
+ The abort lane verifies a completed bridge-enabled turn persists a local resume handle, an interrupted long-running bridge turn starts from that handle but does not append a new one, and the next same-surface restart uses a new local `agent-*` instead of resuming the pre-abort agent.
199
+
200
+ The tree lane verifies both realistic navigation to an earlier assistant entry and direct navigation to an earlier `cursor-sdk-agent-resume` custom entry reject the future-seeing SDK agent and do not reveal the future-only marker.
201
+
202
+ The copy/switch lane copies a session file containing resume custom entries, switches to that copied file, and verifies the copied handle is rejected while transcript bootstrap still recalls the marker.
203
+
204
+ The fallback lane rewrites a persisted handle to a missing local SDK `agent-*`, verifies create+bootstrap fallback, and asserts the continuity notice is emitted in `pi-stream-events.jsonl`.
205
+
206
+ The compaction lane uses an isolated temp pi settings file with `compaction.keepRecentTokens: 1` to force manual compaction without huge dummy prompts. It verifies the pre-compaction SDK agent is not reused, the new handle records `compactionGeneration: 1`, and restart resumes the post-compaction agent.
207
+
208
+ The default/opt-out lane verifies the built-in local resume default resumes, then verifies `PI_CURSOR_LOCAL_RESUME=0` opts out and creates a new agent while bootstrapping the transcript.
209
+
210
+ The cleanup lane verifies `/cursor-local-resume-cleanup --dry-run` reports only recorded superseded local `agent-*` IDs, `/cursor-local-resume-cleanup --yes` verifies and fsyncs intent before deleting exactly the old recorded ID and fsyncs the result afterward, the current recorded agent still resumes, and tree navigation to the old handle falls back instead of resuming the deleted agent.
211
+
140
212
  ## Files and scripts
141
213
 
142
214
  Files:
@@ -144,6 +216,10 @@ Files:
144
216
  ```text
145
217
  platform-smoke.config.mjs
146
218
  scripts/platform-smoke.mjs
219
+ scripts/platform-smoke/artifact-bundle-chunk.mjs
220
+ scripts/platform-smoke/artifact-bundle-contract.mjs
221
+ scripts/platform-smoke/artifact-fs-safety.mjs
222
+ scripts/platform-smoke/artifact-secrets.mjs
147
223
  scripts/platform-smoke/assertions.mjs
148
224
  scripts/platform-smoke/artifacts.mjs
149
225
  scripts/platform-smoke/card-detect.mjs
@@ -151,25 +227,48 @@ scripts/platform-smoke/crabbox-runner.mjs
151
227
  scripts/platform-smoke/doctor.mjs
152
228
  scripts/platform-smoke/jsonl-text.mjs
153
229
  scripts/platform-smoke/live-suite-runner.mjs
230
+ scripts/platform-smoke/local-resume-runner.mjs
231
+ scripts/platform-smoke/local-resume-suites.mjs
154
232
  scripts/platform-smoke/platform-build-windows.ps1
155
233
  scripts/platform-smoke/pty-capture.mjs
156
234
  scripts/platform-smoke/render-ansi.mjs
157
235
  scripts/platform-smoke/scenarios.mjs
236
+ scripts/platform-smoke/target-runtime.mjs
158
237
  scripts/platform-smoke/targets.mjs
159
238
  scripts/platform-smoke/visual-evidence.mjs
239
+ scripts/platform-smoke/wrapped-line-match.mjs
160
240
  ```
161
241
 
242
+ `artifact-bundle-contract.mjs` owns bundle transport markers/path/size caps and the canonical
243
+ exact-size base64 decoder. `artifact-secrets.mjs` owns redaction patterns and scanning.
244
+ `artifact-fs-safety.mjs` owns no-follow traversal/identity primitives, bounded reads, safe
245
+ extraction writes/cleanup, and the exclusive bundle spill writer. `artifacts.mjs` retains
246
+ retention/manifest/index bookkeeping and bundle build/format/extract orchestration on top of
247
+ those three modules, re-exporting their public names for backward compatibility.
248
+
162
249
  Package scripts:
163
250
 
164
251
  ```json
165
252
  {
166
- "check:platform-smoke": "node --check platform-smoke.config.mjs && node --check <platform smoke scripts> && vitest run test/smoke-tooling.test.ts",
253
+ "check:platform-smoke": "node --check platform-smoke.config.mjs && node --check <platform smoke scripts> && 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",
167
254
  "smoke:platform": "node scripts/platform-smoke.mjs",
168
255
  "smoke:platform:doctor": "node scripts/platform-smoke.mjs doctor",
169
256
  "smoke:platform:macos": "node scripts/platform-smoke.mjs run --target macos",
170
257
  "smoke:platform:ubuntu": "node scripts/platform-smoke.mjs run --target ubuntu",
171
258
  "smoke:platform:windows-native": "node scripts/platform-smoke.mjs run --target windows-native",
172
- "smoke:platform:all": "npm run smoke:platform:doctor && node scripts/platform-smoke.mjs run --target macos,ubuntu,windows-native"
259
+ "smoke:platform:all": "npm run smoke:platform:doctor && node scripts/platform-smoke.mjs run --target macos,ubuntu,windows-native",
260
+ "smoke:cloud": "node scripts/cloud-runtime-smoke.mjs",
261
+ "smoke:cloud:context": "node scripts/cloud-runtime-smoke.mjs --context-matrix",
262
+ "smoke:local-resume": "node scripts/local-resume-smoke.mjs",
263
+ "smoke:local-resume:safety": "node scripts/local-resume-smoke.mjs --safety",
264
+ "smoke:local-resume:tool-surface": "node scripts/local-resume-smoke.mjs --tool-surface",
265
+ "smoke:local-resume:abort": "node scripts/local-resume-smoke.mjs --abort",
266
+ "smoke:local-resume:tree": "node scripts/local-resume-smoke.mjs --tree",
267
+ "smoke:local-resume:copy-switch": "node scripts/local-resume-smoke.mjs --copy-switch",
268
+ "smoke:local-resume:fallback": "node scripts/local-resume-smoke.mjs --fallback",
269
+ "smoke:local-resume:compaction": "node scripts/local-resume-smoke.mjs --compaction",
270
+ "smoke:local-resume:default-dry-run": "node scripts/local-resume-smoke.mjs --default-dry-run",
271
+ "smoke:local-resume:cleanup": "node scripts/local-resume-smoke.mjs --cleanup"
173
272
  }
174
273
  ```
175
274
 
@@ -182,6 +281,8 @@ All repo-specific behavior lives in `platform-smoke.config.mjs` so the framework
182
281
  Required config fields:
183
282
 
184
283
  ```js
284
+ import { LOCAL_RESUME_SUITE_NAMES } from "./scripts/platform-smoke/local-resume-suites.mjs";
285
+
185
286
  export default {
186
287
  packageName: "pi-cursor-sdk",
187
288
  cursorModel: "cursor/composer-2-5",
@@ -197,12 +298,14 @@ export default {
197
298
  "cursor-native-visual-matrix",
198
299
  "cursor-bridge-visual-matrix",
199
300
  "cursor-abort-cleanup",
301
+ ...LOCAL_RESUME_SUITE_NAMES,
200
302
  ],
201
303
  requiredCrabbox: {
202
304
  install: "Homebrew package or PLATFORM_SMOKE_CRABBOX override",
203
305
  minVersion: "0.26.0",
204
306
  },
205
- ubuntuContainerImage: "cimg/node:24.16",
307
+ ubuntuContainerImage: "pi-cursor-sdk-platform-node:24.16-root",
308
+ ubuntuContainerBaseImage: "cimg/node:24.16",
206
309
  nodeValidationMajor: 24,
207
310
  windowsParallels: {
208
311
  sourceVm: "pi-extension-windows-template",
@@ -212,7 +315,7 @@ export default {
212
315
  };
213
316
  ```
214
317
 
215
- `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.
318
+ `ubuntuContainerBaseImage` is the Ubuntu 24.04 Node 24 base with the current glibc baseline for native test dependencies. The runner builds the local `ubuntuContainerImage` wrapper with only `USER root` changed before warmup because Crabbox 0.36.0 must install SSH/Git/rsync/curl during bootstrap and `cimg/node` defaults to an unprivileged user. An explicit `PLATFORM_SMOKE_UBUNTU_IMAGE` bypasses that build and must already support Crabbox bootstrap. `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.
216
319
 
217
320
  `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.
218
321
 
@@ -229,7 +332,8 @@ PLATFORM_SMOKE_CRABBOX=/opt/homebrew/bin/crabbox
229
332
  PLATFORM_SMOKE_MAC_HOST=localhost
230
333
  PLATFORM_SMOKE_MAC_USER="$USER"
231
334
  PLATFORM_SMOKE_MAC_WORK_ROOT="/Users/$USER/crabbox/pi-cursor-sdk"
232
- PLATFORM_SMOKE_UBUNTU_IMAGE="cimg/node:24.16"
335
+ # Optional prebuilt replacement; bypasses the configured local root-wrapper build.
336
+ PLATFORM_SMOKE_UBUNTU_IMAGE="registry.example.com/ubuntu-node24-crabbox:latest"
233
337
 
234
338
  # Optional Parallels overrides; defaults come from platform-smoke.config.mjs.
235
339
  PLATFORM_SMOKE_WINDOWS_VM="pi-extension-windows-template"
@@ -410,6 +514,34 @@ Purpose:
410
514
 
411
515
  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 --approve`, and `pi list --approve`, plus `packed-tarball.txt`, `summary.json`, `artifact-manifest.json`, `assertions.json`, and `failures.md` on failed assertions.
412
516
 
517
+ ### `cursor-local-resume-restart`
518
+
519
+ Cursor calls: `2`.
520
+
521
+ Purpose:
522
+
523
+ - prove guarded local resume default-on behavior across a pi process restart on each required OS;
524
+ - assert the first turn creates a local `agent-*` and the second turn resumes the same `agent-*`;
525
+ - force local runtime and clear cloud env knobs so ambient cloud settings cannot satisfy this suite.
526
+
527
+ The suite prepares or reuses the target's packed npm install, runs `npm run smoke:local-resume` with that packed extension path, and asserts the `local-resume-smoke-ok` marker plus the resumed local agent id line. It also requires extracted session, debug, and runtime-launch evidence under `local-resume-evidence/`; checkout `pi -e <repo-root>` is reserved for the standalone inner-loop command.
528
+
529
+ ### `cursor-local-resume-*` focused proof lanes
530
+
531
+ The remaining local-resume platform suites run the matching focused package script against the shared packed extension on each target, retain the same evidence categories, and assert its success marker plus stderr evidence line:
532
+
533
+ | Suite | Package script | Purpose |
534
+ | --- | --- | --- |
535
+ | `cursor-local-resume-safety` | `npm run smoke:local-resume:safety` | clone rejection and fork-before-future no-leak |
536
+ | `cursor-local-resume-tool-surface` | `npm run smoke:local-resume:tool-surface` | stale handle rejection after bridge/tool-surface change |
537
+ | `cursor-local-resume-abort` | `npm run smoke:local-resume:abort` | interrupted bridge turn does not persist/reuse stale handle |
538
+ | `cursor-local-resume-tree` | `npm run smoke:local-resume:tree` | earlier assistant and resume-entry tree targets reject future-seeing agent |
539
+ | `cursor-local-resume-copy-switch` | `npm run smoke:local-resume:copy-switch` | copied session file rejects copied resume handle |
540
+ | `cursor-local-resume-fallback` | `npm run smoke:local-resume:fallback` | missing local agent falls back with continuity notice |
541
+ | `cursor-local-resume-compaction` | `npm run smoke:local-resume:compaction` | compaction boundary creates/resumes post-compaction generation |
542
+ | `cursor-local-resume-default-dry-run` | `npm run smoke:local-resume:default-dry-run` | built-in default resumes and env opt-out wins |
543
+ | `cursor-local-resume-cleanup` | `npm run smoke:local-resume:cleanup` | recorded-ID-only cleanup deletes old agent and preserves current agent |
544
+
413
545
  ### `cursor-native-visual-matrix`
414
546
 
415
547
  Cursor calls: `1`.
@@ -619,13 +751,23 @@ Per target maximum live Cursor invocations:
619
751
  cursor-native-visual-matrix: 1
620
752
  cursor-bridge-visual-matrix: 1
621
753
  cursor-abort-cleanup: 1
754
+ cursor-local-resume-restart: 2
755
+ cursor-local-resume-safety: 5
756
+ cursor-local-resume-tool-surface: 3
757
+ cursor-local-resume-abort: 3
758
+ cursor-local-resume-tree: 4
759
+ cursor-local-resume-copy-switch: 2
760
+ cursor-local-resume-fallback: 2
761
+ cursor-local-resume-compaction: 5
762
+ cursor-local-resume-default-dry-run: 3
763
+ cursor-local-resume-cleanup: 4
622
764
  ```
623
765
 
624
- Maximum per target: `3` Cursor invocations.
766
+ Maximum per target: `36` Cursor invocations.
625
767
 
626
- Maximum full gate: `9` Cursor invocations.
768
+ Maximum full gate: `108` Cursor invocations.
627
769
 
628
- 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`.
770
+ 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 the scenario source of truth (`scripts/platform-smoke/scenarios.mjs`, plus `scripts/platform-smoke/local-resume-suites.mjs` for local-resume lanes).
629
771
 
630
772
  ## Artifact contract
631
773
 
@@ -647,7 +789,7 @@ After each `smoke:platform run` invocation, the host writes an atomic latest art
647
789
  .artifacts/platform-smoke/latest.json
648
790
  ```
649
791
 
650
- `latest.json` records the invocation timestamps, command selection, PID, run id(s), target/suite artifact directories, paths to suite summaries/assertions/failures when present, rendered terminal HTML/PNG paths, visual evidence, session JSONL, JSONL tool-result summaries, and capped Cursor SDK/provider debug artifact paths. The per-suite artifact directories remain the source of truth; `latest.json` is only a discoverability pointer.
792
+ `latest.json` records the invocation timestamps, command selection, PID, run id(s), target/suite artifact directories, paths to suite summaries/assertions/failures when present, rendered terminal HTML/PNG paths, visual evidence, session JSONL, JSONL tool-result summaries, capped Cursor SDK/provider debug artifact paths, and local-resume evidence roots/indexes/runtime-launch records. The per-suite artifact directories remain the source of truth; `latest.json` is only a discoverability pointer.
651
793
 
652
794
  Common required artifacts:
653
795
 
@@ -730,6 +872,17 @@ cursor-sdk-events/
730
872
  sessions/**/<turn-artifact>.json or .jsonl
731
873
  ```
732
874
 
875
+ Required local-resume artifacts:
876
+
877
+ ```text
878
+ local-resume-evidence.json # counts retained files by evidence category
879
+ local-resume-evidence/
880
+ runtime-launches.jsonl # proves the packed extension path used by each pi process
881
+ sessions/**/*.jsonl
882
+ debug/**/* # Cursor SDK/provider turn metadata
883
+ agent/**/* # bounded non-secret runtime state when written
884
+ ```
885
+
733
886
  Required abort artifacts:
734
887
 
735
888
  ```text
@@ -743,27 +896,18 @@ Provider debug artifacts are required for every live suite through `PI_CURSOR_SD
743
896
 
744
897
  ## Artifact collection on failure
745
898
 
746
- Crabbox success-path download is not sufficient. The target-side suite wrapper must always package artifacts before returning to the host.
899
+ The target-side live and local-resume runners encode only canonical bounded text evidence as gzip-compressed JSON/base64. Unknown extensions are secret-scanned and then omitted when benign; a secret finding still fails the run. Binary content under a transport-eligible text extension fails with bounded `binary-content` evidence, and direct bundles containing either unknown extensions or binary content are never extracted. The inner bundle schema is exactly `{files}`, and each entry is exactly `{path, contentBase64, size}`: source-root and per-file metadata are rejected, canonical paths and decoded content are secret-scanned, secret-bearing paths are rejected, and filenames in findings are redacted. The caller-selected artifact root is a trust boundary. Its final component must be a real directory, and its real path must equal the same relative path mapped beneath the canonical real CWD or temp base. This permits expected platform base aliases such as macOS `/var` → `/private/var` but rejects user-created intermediate root links. Every traversed directory is secured with lstat/open/fstat/post-lstat identity checks (POSIX opens use `O_DIRECTORY|O_NOFOLLOW`), its descriptor stays open while descendants are visited, and all current/ancestor identities plus the original ctime, mtime, size, link count, and mode are rechecked around directory reads and bounded file reads. These immutable metadata checks apply only to traversal guards; extraction uses identity-only guards because creating destination entries legitimately mutates parent metadata. Every regular artifact file, regardless of name or extension, is binary-safe scanned from a descriptor-bounded snapshot before transport eligibility is checked. Binary scans retain exact `CURSOR_API_KEY`, generic auth assignments, and structured bearer/cookie/bridge/JSON signatures in UTF-8/ASCII, UTF-16LE/BE, and UTF-32LE/BE at any byte alignment; the intentionally broad generic credential-URL and SCP-style heuristics run only on valid UTF-8 text so random compressed/executable bytes cannot create false failures. Static child symlinks fail as non-regular entries; a nested rename/symlink ABA invalidates the whole traversal, discarding collected findings and bytes in favor of bounded failure evidence. Sensitive files (`.env*`, `auth.json`, `id_rsa`, `id_ed25519`, `*.pem`, and `*.key`) therefore fail the run when they contain secrets but are never transported. `node_modules/` and `.git/` are non-artifact infrastructure and are pruned before recursion. File size is checked before allocation or reading. An unreadable, changed, or oversized file fails closed; oversized files are not read and produce bounded limit evidence, while the host scanner reports an oversized or otherwise unscannable artifact as a finding so the suite fails. Bundles allow at most 512 files, 5 MiB per file, 40 MiB aggregate decoded content, 4,096 UTF-8 bytes per path, 4,096 total path components, 64 MiB inflated JSON, and 20 MiB compressed transport. The writer and extractor enforce the same limits; limit overflow emits only a bounded `bundle-limit-exceeded.json` failure artifact. Before host filesystem mutation, extraction rejects duplicate and file-prefix-conflicting paths, symlink/non-directory destination components, and pre-existing final paths. On POSIX controllers it sends the already prevalidated bounded files to the packaged `artifact-openat-extract.c` helper, compiled once into a private temporary directory. The helper independently validates the complete frame before mutation, requires its opened root identity to match the caller's pinned root, and uses descriptor-relative `mkdirat`/`openat` with `O_NOFOLLOW` one canonical component at a time. Substituted symlinks and pre-open path swaps therefore fail without following them, final files use `O_EXCL`, and detected identity drift rolls created files back through retained parent descriptors even when a directory was moved. This is race-safe extraction, not a sandbox against a hostile same-owner process: such a process can transiently relocate an already-open directory or the pinned root itself, although it cannot make extraction overwrite an existing destination. Windows controllers reject every nonempty extraction bundle before mutation because Node exposes no handle-relative Windows creation API; Windows remains a supported release target when the matrix is orchestrated from the macOS/POSIX controller. Small bundles stay between `PLATFORM_LIVE_BUNDLE_JSON_START` and `PLATFORM_LIVE_BUNDLE_JSON_END`; larger bundles exclusively create and descriptor-write the exact CWD-relative `.platform-artifact-bundle.gz` final component (`O_NOFOLLOW` on POSIX), emit that exact marker path, and allow chunk reads only for that one-component path. No intermediate spill path is accepted. Windows uses lstat identity checks for directories and lstat/fstat identity checks for regular files and the spill, without POSIX-only flags. The host retrieves exact 32 KiB chunks through no-sync Crabbox runs before releasing the lease. Compact control output is collected through Crabbox's local `--capture-stdout` file. This works on scenario failure and preserves the original command exit code; chunk transport success is not substituted for suite success, and no tar/zip side channel exists.
747
900
 
748
- Required target wrapper behavior:
901
+ Required fail-through behavior:
749
902
 
750
- 1. Run the scenario.
751
- 2. Capture real scenario exit/assertion state in `exit-code.txt` and `assertions.json`.
752
- 3. Write `failures.md` when assertions fail.
753
- 4. Package the suite artifact directory.
754
- 5. Exit `0` for Crabbox transport so the archive downloads.
755
- 6. Let the host runner fail after unpacking and reading `assertions.json`.
756
-
757
- Crabbox command exit means transport status. Suite pass/fail comes from `assertions.json`.
758
-
759
- Archive names:
760
-
761
- ```text
762
- <target>-<suite>-artifacts.tar.gz # macOS, Ubuntu
763
- <target>-<suite>-artifacts.zip # Windows native
764
- ```
903
+ 1. Run the scenario and write target-side session/debug/runtime evidence.
904
+ 2. Encode the artifact bundle in a `finally` path after applying path, secret, file-size, file-count, aggregate, inflated-JSON, and compressed-size limits; emit it inline or write the marked gzip file. Any bound overflow or traversal invalidation must emit the bounded limit-failure artifact instead of partial evidence, prior findings, or bytes.
905
+ 3. Preserve the real scenario process exit code in Crabbox output.
906
+ 4. Before lease release, have the host fetch any marked chunks and call `extractPlatformArtifactBundle()` even for a nonzero result.
907
+ 5. Write host-side `exit-code.txt`, `assertions.json`, and `failures.md` from the process result plus extracted evidence.
908
+ 6. Verify the canonical `artifact-manifest.json`; missing or unsafe bundle content fails the suite.
765
909
 
766
- The host unpacks into the canonical artifact directory and verifies `artifact-manifest.json`.
910
+ There is no tar/zip side channel. Crabbox stdout carries the inline bundle or its authenticated retrieval marker/chunks, while the canonical host suite directory remains the durable source of truth.
767
911
 
768
912
  ## Assertion contract
769
913
 
@@ -791,81 +935,9 @@ Failures produce `failures.md` with:
791
935
  - command summary;
792
936
  - next diagnostic command.
793
937
 
794
- ## Visual evidence detector
795
-
796
- The detector operates on host-rendered terminal HTML and PNG evidence. It must not pass from prompt text alone.
797
-
798
- Required behavior:
799
-
800
- - render ANSI with xterm/Playwright and assert the terminal DOM/theme is present, styled, non-empty, and screenshotted;
801
- - search the rendered xterm buffer for suite-owned evidence patterns that correspond to actual tool output/results, not instructions in the prompt;
802
- - scroll to each evidence line and write `cards/<evidence-id>.png` screenshots plus `visual-evidence.json`;
803
- - write `cards.json` for the legacy rendered-evidence inventory;
804
- - fail when required visual evidence is missing;
805
- - fail when a card/evidence item has the wrong success/error state;
806
- - fail when footer/status is missing or unreadable.
807
-
808
- Meaningful gap closed: earlier card assertions could pass when the prompt mentioned `pi__read` or a missing-file path even if the actual tool card/result never rendered. The gate now requires JSONL result evidence and per-evidence rendered screenshots for native read, native shell success/failure, native edit diffs, bridge read success/failure, and bridge shell success.
809
-
810
- ## Registry visual classification
811
-
812
- The implementation must classify every `CURSOR_TOOL_PRESENTATION_SPECS` entry from `src/cursor-tool-presentation-registry.ts` as required or excluded for the release visual gate. A validation check fails when a registry entry lacks classification.
813
-
814
- Required deterministic cards:
815
-
816
- - `read`
817
- - `grep`
818
- - `glob` / find
819
- - `shell`
820
- - `write`
821
- - `edit`
822
- - failed `read`
823
-
824
- Excluded from release visual matrix with required rationale:
825
-
826
- - `delete`: destructive and redundant with file mutation card coverage.
827
- - `readLints`: dependent on target diagnostics state.
828
- - `updateTodos`: model workflow dependent.
829
- - `createPlan`: model workflow dependent.
830
- - `task`: model/task orchestration dependent.
831
- - `generateImage`: external image generation surface.
832
- - `mcp`: separate MCP integration surface beyond built-in bridge smoke.
833
- - `semSearch`: semantic index state dependent.
834
- - `recordScreen`: desktop capture dependency outside terminal smoke.
835
- - `webSearch`: network/search dependent.
836
- - `webFetch`: network dependent.
837
-
838
- Adding a registry entry requires adding it to the required or excluded list with rationale. `ls` is currently excluded from the required one-prompt matrix because composer-2-5 does not route the deterministic source-enumeration step through the native `ls` surface reliably; the suite instead gates that behavior through a successful native `find` result for `src/cursor-provider.ts`.
839
-
840
- ## Platform command rendering
841
-
842
- Scenario commands are not raw shell strings. The runner renders commands per target:
843
-
844
- - `posix` for macOS and Ubuntu.
845
- - `powershell` for Windows native.
846
-
847
- Scenario shape:
848
-
849
- ```js
850
- {
851
- id: "cursor-native-visual-matrix",
852
- requires: ["cursor-auth", "pty", "packed-install"],
853
- promptTemplate: "... <platform-command:shellSmoke> ...",
854
- commands: {
855
- shellSmoke: {
856
- posix: "printf 'cursor visual smoke\\n'",
857
- powershell: "Write-Output 'cursor visual smoke'",
858
- },
859
- },
860
- assertions: ["final-marker", "required-cards", "jsonl-tools"],
861
- }
862
- ```
863
-
864
- The renderer owns quoting, path normalization, environment assignment, and archive packaging.
865
-
866
938
  ## Security and redaction
867
939
 
868
- The runner must scan every artifact and fail on:
940
+ The runner must binary-safe scan every bounded regular artifact file, including transport-excluded sensitive names and unknown extensions, and fail closed when a file exceeds the scan cap, cannot be scanned, or is non-regular. It never follows non-regular entries or transports sensitive filenames. Non-artifact infrastructure (`node_modules/` and `.git/`) is pruned. The workspace-root `.platform-artifact-bundle.gz` spill is outside scenario artifact roots and is ignored by Git. It fails on:
869
941
 
870
942
  - the literal `CURSOR_API_KEY` value;
871
943
  - bearer tokens;
@@ -877,154 +949,18 @@ The runner must scan every artifact and fail on:
877
949
 
878
950
  Bridge diagnostics may include safe tool names and correlation IDs only.
879
951
 
880
- ## Implementation phases
881
-
882
- ### Phase 0: plan-only branch state
883
-
884
- Create this plan on `feat/crabbox-platform-smoke`. Do not implement code in this phase.
885
-
886
- ### Phase 1: dependency spike
887
-
888
- Verify `node-pty` and ConPTY on every target before committing the dependency.
889
-
890
- Exit criteria:
891
-
892
- - node-pty self-test passes on macOS;
893
- - node-pty self-test passes on Ubuntu local-container;
894
- - node-pty self-test passes on Windows native Node 24.
895
-
896
- ### Phase 2: config and doctor
897
-
898
- Add config, CLI skeleton, doctor, and npm scripts.
899
-
900
- Exit criteria:
901
-
902
- ```bash
903
- npm run smoke:platform:doctor
904
- ```
905
-
906
- passes only when all required local setup exists.
907
-
908
- ### Phase 3: target session manager
909
-
910
- Implement Crabbox target lifecycle for all three targets.
911
-
912
- Exit criteria:
913
-
914
- - each target can acquire/warm;
915
- - each target can sync;
916
- - each target can run `node --version`;
917
- - each target can package/download a trivial artifact;
918
- - each target can stop/cleanup;
919
- - one lease per target session.
920
-
921
- ### Phase 4: `platform-build`
922
-
923
- Implement build/package/install suite.
924
-
925
- Exit criteria: `platform-build` passes on all targets through `smoke:platform:all -- --suite platform-build` without live Cursor calls.
926
-
927
- ### Phase 5: PTY capture and host render
928
-
929
- Implement PTY/ConPTY capture and host-side xterm/Playwright render.
930
-
931
- Exit criteria:
932
-
933
- - ANSI capture works on all targets;
934
- - host render writes HTML, full PNG, and final viewport PNG;
935
- - visual evidence detector can capture fixture evidence screenshots.
936
-
937
- ### Phase 6: native visual matrix
938
-
939
- Implement one-call native matrix.
940
-
941
- Exit criteria:
942
-
943
- - all required native visual evidence screenshots are captured on every target;
944
- - JSONL assertions pass on every target;
945
- - Cursor call budget remains one call per target.
946
-
947
- ### Phase 7: bridge visual matrix
948
-
949
- Implement one-call bridge matrix.
950
-
951
- Exit criteria:
952
-
953
- - all required bridge visual evidence screenshots are captured on every target;
954
- - bridge diagnostics assertions pass on every target;
955
- - JSONL assertions pass on every target.
956
-
957
- ### Phase 8: abort cleanup
958
-
959
- Implement interrupted bridge run.
960
-
961
- Exit criteria:
962
-
963
- - no leftovers on any target;
964
- - no false success in JSONL;
965
- - target session stops cleanly.
966
-
967
- ### Phase 9: docs and legacy cleanup
968
-
969
- Update:
970
-
971
- - `README.md`
972
- - `docs/cursor-live-smoke-checklist.md`
973
- - `docs/cursor-testing-lessons.md`
974
- - `docs/cursor-native-tool-visual-audit.md`
975
-
976
- They must state:
977
-
978
- - required release gate is `npm run smoke:platform:all`;
979
- - legacy smoke scripts are inner-loop/debug helpers;
980
- - `tmux` visual smoke is not the canonical cross-platform gate.
981
-
982
952
  ## Release bar
983
953
 
984
- A provider/runtime release is ready only after this exact command passes on the maintainer machine:
954
+ A local provider/runtime release is ready only after this exact command passes on the maintainer machine:
985
955
 
986
956
  ```bash
987
957
  npm run smoke:platform:all
988
958
  ```
989
959
 
990
- The command runs doctor first and then all required targets and suites in one full gate execution.
991
-
992
- ## Gate replacement criteria
993
-
994
- Replace or redesign this platform runner if any of these become true:
995
-
996
- - Parallels Windows linked clones are unreliable.
997
- - Windows native cannot run the required ConPTY visual matrix.
998
- - macOS static SSH localhost cannot run the required PTY visual matrix.
999
- - Ubuntu local-container cannot run the required PTY visual matrix.
1000
- - Packed install cannot be tested uniformly across all targets.
1001
- - Artifact transfer cannot be made uniform across success and failure.
1002
- - The visual card detector cannot reliably identify required deterministic cards.
1003
- - The full gate exceeds the fixed Cursor invocation budget.
1004
- - Node 24 + `node-pty` cannot be made reliable on Windows native.
960
+ Cloud-runtime releases additionally require:
1005
961
 
1006
- If the gate is replaced, document the new cross-platform release process before removing this one. Existing local smoke scripts remain inner-loop/debug helpers, not release gates.
1007
-
1008
- ## Portability to other pi extensions
1009
-
1010
- Repo-specific pieces:
1011
-
1012
- - `platform-smoke.config.mjs`
1013
- - expected package name
1014
- - model IDs
1015
- - scenario prompts
1016
- - required visual card matrix
1017
- - final markers
1018
-
1019
- Reusable pieces:
1020
-
1021
- - Crabbox target session manager
1022
- - PTY/ConPTY capture
1023
- - host-side ANSI render
1024
- - artifact manifest writer
1025
- - JSONL parser
1026
- - visual evidence detector
1027
- - process cleanup checker
1028
- - target doctor
962
+ ```bash
963
+ npm run smoke:cloud
964
+ ```
1029
965
 
1030
- The framework is successful when another pi extension can copy the runner and change only its config plus scenarios.
966
+ `smoke:platform:all` runs doctor first and then all required local targets and suites in one full gate execution.