pi-cursor-sdk 0.1.38 → 0.1.40
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 +18 -0
- package/README.md +7 -7
- package/docs/cursor-dogfood-checklist.md +1 -1
- package/docs/cursor-live-smoke-checklist.md +13 -13
- package/docs/cursor-model-ux-spec.md +4 -4
- package/docs/cursor-native-tool-replay.md +2 -2
- package/docs/cursor-native-tool-visual-audit.md +4 -4
- package/docs/cursor-testing-lessons.md +5 -5
- package/docs/platform-smoke.md +13 -6
- package/package.json +4 -4
- package/platform-smoke.config.mjs +5 -0
- package/scripts/debug-provider-events.mjs +1 -0
- package/scripts/isolated-cursor-smoke.sh +7 -7
- package/scripts/platform-smoke/artifacts.mjs +79 -1
- package/scripts/platform-smoke/live-suite-runner.mjs +7 -6
- package/scripts/platform-smoke/platform-build-windows.ps1 +2 -2
- package/scripts/platform-smoke/targets.mjs +2 -2
- package/scripts/platform-smoke.mjs +7 -0
- package/scripts/steering-rpc-smoke.mjs +1 -1
- package/scripts/tmux-live-smoke.sh +1 -1
- package/scripts/visual-tui-smoke.mjs +1 -0
- package/shared/cursor-setting-sources.d.mts +1 -0
- package/shared/cursor-setting-sources.mjs +2 -1
- package/src/cursor-provider-errors.ts +11 -4
- package/src/cursor-provider-turn-coordinator.ts +12 -1
- package/src/cursor-provider-turn-shell-output.ts +38 -3
- package/src/cursor-setting-sources.ts +2 -0
- package/src/cursor-state.ts +2 -1
- package/src/cursor-tool-lifecycle.ts +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.40 - 2026-06-08
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Update the local pi validation baseline to `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` `0.79.0` after reviewing current Pi extension, package, SDK/RPC, model/provider, and project-trust docs. Runtime Cursor setting-source defaults remain risk-on and unchanged: unset `PI_CURSOR_SETTING_SOURCES` still loads `all` Cursor setting sources.
|
|
10
|
+
- Make maintainer smoke/debug scripts pass Pi 0.79 `--approve` explicitly when they must load project-local package settings, extensions, or instructions in noninteractive release automation.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Prune old local platform-smoke artifact run directories before new matrix runs so `.artifacts/platform-smoke` does not grow without bound while preserving recent and manual evidence directories.
|
|
15
|
+
|
|
16
|
+
## 0.1.39 - 2026-06-08
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Surface Cursor shell command starts with scrubbed command previews, including path-bearing commands, and stream bounded `shell-output-delta` stdout/stderr progress before completion so users do not stare at only pi's generic `Working...` state.
|
|
21
|
+
- Mark generic Cursor SDK run failures and Cursor SDK network failures with pi-native retry classifier phrases so pi's existing auto-retry/backoff flow can recover transient failures automatically instead of requiring a manual follow-up message.
|
|
22
|
+
|
|
5
23
|
## 0.1.38 - 2026-06-08
|
|
6
24
|
|
|
7
25
|
### Added
|
package/README.md
CHANGED
|
@@ -51,10 +51,10 @@ If pi started without a key, run `/cursor-refresh-models` after `/login` to refr
|
|
|
51
51
|
## Requirements
|
|
52
52
|
|
|
53
53
|
- Node.js 22.19+
|
|
54
|
-
- pi 0.
|
|
54
|
+
- pi 0.79.0 or newer recommended; pi core peer metadata is intentionally unpinned so newer pi releases are not blocked
|
|
55
55
|
- a Cursor SDK API key saved through `/login`, available as `CURSOR_API_KEY`, or passed with pi's `--api-key`
|
|
56
56
|
|
|
57
|
-
No global `@cursor/sdk` install is required. This package depends on exact `@cursor/sdk@1.0.17`, so normal package installation brings in the SDK version this extension was built and tested against. The Cursor SDK currently depends on `sqlite3@^5.1.7`, whose install path can print deprecated transitive `node-gyp@8` dependency warnings such as `inflight`, `rimraf`, `glob`, `npmlog`, `gauge`, `are-we-there-yet`, and `tar@6`. Those warnings are non-fatal and come from the closed-source Cursor SDK dependency boundary; this package cannot force npm overrides into consumer projects. If you install from a root `package.json` you control, you may choose a root-level override such as `"overrides": { "sqlite3": "6.0.1" }`; pi package installs will still follow npm's normal transitive dependency rules. This package follows pi package guidance by declaring pi core package peers with `"*"` ranges, so users who update pi before this extension is republished are not blocked by peer metadata. The current recommended and validated pi baseline is 0.
|
|
57
|
+
No global `@cursor/sdk` install is required. This package depends on exact `@cursor/sdk@1.0.17`, so normal package installation brings in the SDK version this extension was built and tested against. The Cursor SDK currently depends on `sqlite3@^5.1.7`, whose install path can print deprecated transitive `node-gyp@8` dependency warnings such as `inflight`, `rimraf`, `glob`, `npmlog`, `gauge`, `are-we-there-yet`, and `tar@6`. Those warnings are non-fatal and come from the closed-source Cursor SDK dependency boundary; this package cannot force npm overrides into consumer projects. If you install from a root `package.json` you control, you may choose a root-level override such as `"overrides": { "sqlite3": "6.0.1" }`; pi package installs will still follow npm's normal transitive dependency rules. This package follows pi package guidance by declaring pi core package peers with `"*"` ranges, so users who update pi before this extension is republished are not blocked by peer metadata. The current recommended and validated pi baseline is 0.79.0 plus Cursor SDK 1.0.17; older pi compatibility paths are best-effort and older Cursor SDK compatibility paths are not maintained.
|
|
58
58
|
|
|
59
59
|
## Install
|
|
60
60
|
|
|
@@ -84,7 +84,7 @@ For development from this repository:
|
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
86
|
npm install
|
|
87
|
-
pi -e . --model cursor/composer-2-5
|
|
87
|
+
pi --approve -e . --model cursor/composer-2-5
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
## Configure your Cursor SDK API key
|
|
@@ -324,7 +324,7 @@ On bootstrap sends, a compact **callable tool surfaces** block is injected into
|
|
|
324
324
|
|
|
325
325
|
For Cursor provider/runtime changes, the canonical release and pre-commit gate is the local platform smoke gate in [Platform smoke](docs/platform-smoke.md): run `npm run smoke:platform:all`, which runs doctor before the target matrix. The gate validates macOS, Ubuntu, and Windows native through Crabbox using packed installs, PTY/ConPTY ANSI capture, host-rendered xterm/PNG evidence, JSONL assertions, bridge diagnostics, usage/cache checks, abort cleanup, artifact manifests, and redaction scans. Do not mark a release ready with optional, deferred, mostly-passing, or unobserved platform smoke checks outstanding.
|
|
326
326
|
|
|
327
|
-
The older live smoke helpers remain useful for inner-loop debugging and focused visual audits, not as the release gate. Use [Cursor live smoke checklist](docs/cursor-live-smoke-checklist.md), `npm run smoke:visual`, `npm run smoke:live`, or direct `pi -e . --cursor-no-fast --model cursor/composer-2-5` runs when iterating on a specific TUI/card/runtime issue before the full platform gate. `npm run smoke:visual` captures an offscreen PTY rendered through browser/xterm and saved as PNG screenshots with Playwright, or with `agent_browser` from the generated HTML when available. Its default matrix is native replay only: native replay registration is forced on, Cursor setting sources are disabled, the pi bridge is off, overlapping built-in pi tools are not exposed, and inherited Cursor SDK event-debug artifact env is cleared; `--event-debug` writes to a deterministic debug directory under the visual output directory. The visible TUI/output, rendered screenshots, scrubbed diagnostics, and persisted JSONL must agree. See [Cursor testing lessons](docs/cursor-testing-lessons.md) for auth.json seeding, isolated `/tmp` harness layout, JSONL replay-error scans, and other regression traps.
|
|
327
|
+
The older live smoke helpers remain useful for inner-loop debugging and focused visual audits, not as the release gate. Use [Cursor live smoke checklist](docs/cursor-live-smoke-checklist.md), `npm run smoke:visual`, `npm run smoke:live`, or direct `pi --approve -e . --cursor-no-fast --model cursor/composer-2-5` runs when iterating on a specific TUI/card/runtime issue before the full platform gate. `npm run smoke:visual` captures an offscreen PTY rendered through browser/xterm and saved as PNG screenshots with Playwright, or with `agent_browser` from the generated HTML when available. Its default matrix is native replay only: native replay registration is forced on, Cursor setting sources are disabled, the pi bridge is off, overlapping built-in pi tools are not exposed, and inherited Cursor SDK event-debug artifact env is cleared; `--event-debug` writes to a deterministic debug directory under the visual output directory. The visible TUI/output, rendered screenshots, scrubbed diagnostics, and persisted JSONL must agree. See [Cursor testing lessons](docs/cursor-testing-lessons.md) for auth.json seeding, isolated `/tmp` harness layout, JSONL replay-error scans, and other regression traps.
|
|
328
328
|
|
|
329
329
|
### Maintainer Cursor SDK event capture
|
|
330
330
|
|
|
@@ -361,11 +361,11 @@ Actual Cursor runs still need a key from `/login`, `CURSOR_API_KEY`, or `--api-k
|
|
|
361
361
|
|
|
362
362
|
You may be seeing fallback startup models or a missing/invalid Cursor SDK API key. Cursor Agent CLI/Desktop login is not reused by this extension. In interactive pi, run `/login`, choose `Use an API key`, choose `Cursor`, paste the key, then run `/cursor-refresh-models`.
|
|
363
363
|
|
|
364
|
-
When a Cursor run fails after auth is configured, pi now surfaces scrubbed provider detail instead of only `Cursor SDK run failed`. Generic SDK failures include safe run metadata such as model id, a short run id prefix, and duration when available
|
|
364
|
+
When a Cursor run fails after auth is configured, pi now surfaces scrubbed provider detail instead of only `Cursor SDK run failed`. Generic SDK failures include safe run metadata such as model id, a short run id prefix, and duration when available, and are phrased as pi retryable provider errors so automatic retry/backoff can recover transient SDK failures.
|
|
365
365
|
|
|
366
366
|
Aborted runs now include a likely cause when determinable, for example `Cancelled: prompt interrupted.` for user cancel or `Cancelled: Cursor SDK run was cancelled.` for SDK-side cancellation.
|
|
367
367
|
|
|
368
|
-
Network failures from the Cursor SDK connect layer (for example `ConnectError: read ETIMEDOUT` or `ConnectError: [aborted] read ECONNRESET`) surface as
|
|
368
|
+
Network failures from the Cursor SDK connect layer (for example `ConnectError: read ETIMEDOUT` or `ConnectError: [aborted] read ECONNRESET`) surface as scrubbed `Network error` messages instead of crashing pi, matching pi's native auto-retry classifier. Persistent failures may indicate a transient Cursor service or network issue.
|
|
369
369
|
|
|
370
370
|
You can also restart pi with a key in the same shell or launcher that starts pi:
|
|
371
371
|
|
|
@@ -508,7 +508,7 @@ Local development run:
|
|
|
508
508
|
|
|
509
509
|
```bash
|
|
510
510
|
npm install
|
|
511
|
-
CURSOR_API_KEY="your-key" pi -e . --model cursor/composer-2-5
|
|
511
|
+
CURSOR_API_KEY="your-key" pi --approve -e . --model cursor/composer-2-5
|
|
512
512
|
```
|
|
513
513
|
|
|
514
514
|
Maintainer design notes live in [`docs/cursor-model-ux-spec.md`](docs/cursor-model-ux-spec.md).
|
|
@@ -4,7 +4,7 @@ Short maintainer checklist for **minimal-surface** validation after prompt, brid
|
|
|
4
4
|
|
|
5
5
|
## Minimal environment
|
|
6
6
|
|
|
7
|
-
- Extension only: `pi -e . --cursor-no-fast --model cursor/composer-2-5`
|
|
7
|
+
- Extension only: `pi --approve -e . --cursor-no-fast --model cursor/composer-2-5`
|
|
8
8
|
- Fresh session dir: `--session-dir /tmp/pi-cursor-dogfood-<id>`
|
|
9
9
|
- Baseline surface (no ambient Cursor MCP/rules):
|
|
10
10
|
- `PI_CURSOR_SETTING_SOURCES=none`, **or**
|
|
@@ -9,7 +9,7 @@ Use this manual checklist during development and debugging of Cursor provider/ru
|
|
|
9
9
|
## Inner-loop rule
|
|
10
10
|
|
|
11
11
|
- Run from a clean working tree except for the intended branch diff.
|
|
12
|
-
- Use the local extension under test: `pi -e . --cursor-no-fast --model cursor/composer-2-5`.
|
|
12
|
+
- Use the local extension under test: `pi --approve -e . --cursor-no-fast --model cursor/composer-2-5`.
|
|
13
13
|
- Use a temporary `--session-dir` for every run.
|
|
14
14
|
- Do not paste or commit Cursor API keys, raw session contents with secrets, endpoint URLs, or local private paths.
|
|
15
15
|
- If an inner-loop check fails, stop and fix or use [docs/platform-smoke.md](./platform-smoke.md) as the release-blocking source of truth. Do not treat this checklist as a narrower replacement for the platform gate.
|
|
@@ -23,7 +23,7 @@ export SMOKE_DIR="/tmp/pi-cursor-sdk-live-smoke-$(date +%Y%m%dT%H%M%S)"
|
|
|
23
23
|
mkdir -p "$SMOKE_DIR"
|
|
24
24
|
pi --version
|
|
25
25
|
npm ls @cursor/sdk @earendil-works/pi-coding-agent @earendil-works/pi-ai @earendil-works/pi-tui
|
|
26
|
-
pi -e . --list-models cursor
|
|
26
|
+
pi --approve -e . --list-models cursor
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Live pi runs resolve provider auth from **`~/.pi/agent/auth.json`**, not only shell env. Isolated smoke copies that file into a clean temporary `HOME`. Ensure `auth.json` includes a `cursor` provider entry, or export `CURSOR_API_KEY` as a fallback.
|
|
@@ -67,8 +67,8 @@ The replay scan flags only error `toolResult` / error assistant messages with `T
|
|
|
67
67
|
|
|
68
68
|
Pass criteria:
|
|
69
69
|
|
|
70
|
-
- `pi --version` reports pi 0.
|
|
71
|
-
- `npm ls` shows `@cursor/sdk@1.0.17` and local `@earendil-works/*@0.
|
|
70
|
+
- `pi --version` reports pi 0.79.0 for this cutover baseline.
|
|
71
|
+
- `npm ls` shows `@cursor/sdk@1.0.17` and local `@earendil-works/*@0.79.0` packages.
|
|
72
72
|
- `cursor/composer-2-5` appears in the model list.
|
|
73
73
|
- No Cursor key or auth token is printed.
|
|
74
74
|
- If neither `~/.pi/agent/auth.json` cursor auth nor `CURSOR_API_KEY` is available, stop and report the live smoke as blocked.
|
|
@@ -77,7 +77,7 @@ Pass criteria:
|
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
79
|
PI_CURSOR_SETTING_SOURCES=none \
|
|
80
|
-
pi -e . --cursor-no-fast --model cursor/composer-2-5 \
|
|
80
|
+
pi --approve -e . --cursor-no-fast --model cursor/composer-2-5 \
|
|
81
81
|
--session-dir "$SMOKE_DIR/basic" \
|
|
82
82
|
--no-tools \
|
|
83
83
|
-p 'Live smoke. Reply exactly: PI_CURSOR_SMOKE_OK' \
|
|
@@ -95,7 +95,7 @@ Pass criteria:
|
|
|
95
95
|
## 2. Default setting-source startup noise check
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
|
-
pi -e . --cursor-no-fast --model cursor/composer-2-5 \
|
|
98
|
+
pi --approve -e . --cursor-no-fast --model cursor/composer-2-5 \
|
|
99
99
|
--session-dir "$SMOKE_DIR/default-settings" \
|
|
100
100
|
--no-tools \
|
|
101
101
|
-p 'Default settings smoke. Include PRODUCT=42 in the final answer.' \
|
|
@@ -117,7 +117,7 @@ Run a real interactive session under tmux:
|
|
|
117
117
|
```bash
|
|
118
118
|
SESSION="pi-cursor-sdk-smoke-$(date +%s)"
|
|
119
119
|
tmux new-session -d -s "$SESSION" -x 120 -y 40 -- zsh -lc \
|
|
120
|
-
"cd '$PWD' && PI_CURSOR_SETTING_SOURCES=none pi -e . --cursor-no-fast --model cursor/composer-2-5 --session-dir '$SMOKE_DIR/tui' --session-id cursor-sdk-1016-tui --no-tools 'TUI smoke. Compute 19 + 23. Reply only with SUM=<number>.'"
|
|
120
|
+
"cd '$PWD' && PI_CURSOR_SETTING_SOURCES=none pi --approve -e . --cursor-no-fast --model cursor/composer-2-5 --session-dir '$SMOKE_DIR/tui' --session-id cursor-sdk-1016-tui --no-tools 'TUI smoke. Compute 19 + 23. Reply only with SUM=<number>.'"
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
Observe with `tmux capture-pane -pt "$SESSION"` or attach manually.
|
|
@@ -125,7 +125,7 @@ Observe with `tmux capture-pane -pt "$SESSION"` or attach manually.
|
|
|
125
125
|
Pass criteria:
|
|
126
126
|
|
|
127
127
|
- Footer shows `(cursor) composer-2-5`. With `--cursor-no-fast`, Cursor fast mode is off and the Cursor extension status should not show `cursor fast`; ignore unrelated status text from other extensions.
|
|
128
|
-
- The run uses pi 0.
|
|
128
|
+
- The run uses pi 0.79.0 `--session-id` successfully.
|
|
129
129
|
- Assistant answer appears correctly.
|
|
130
130
|
- `/session` shows one user and one assistant message for the simple run.
|
|
131
131
|
- Persisted JSONL has one assistant message. If the screen appears duplicated, inspect JSONL before deciding whether it is a rendering bug.
|
|
@@ -133,7 +133,7 @@ Pass criteria:
|
|
|
133
133
|
|
|
134
134
|
## 4. Focused visual card/color rendering check
|
|
135
135
|
|
|
136
|
-
This is the canonical inner-loop visual debug path for Cursor provider/runtime changes. It requires offscreen TUI visual inspection, not only JSONL or code review. Use pi 0.
|
|
136
|
+
This is the canonical inner-loop visual debug path for Cursor provider/runtime changes. It requires offscreen TUI visual inspection, not only JSONL or code review. Use pi 0.79.0, `@cursor/sdk@1.0.17`, a fresh temporary session dir, Cursor SDK `plan` mode, native replay enabled, and the checked-in visual runner. The runner resolves `pi` by directly walking the parent `PATH`, uses `process.execPath` for Node, and prepends that Node directory for both prereq checks and tmux launches so `#!/usr/bin/env node` shims use the validated Node. The default matrix is native replay only: native replay registration is forced on, settings sources are `none`, the pi bridge is off, overlapping built-in pi tools are not exposed, and inherited Cursor SDK event-debug artifact env is cleared. With `--event-debug`, debug capture writes to a deterministic directory under `VISUAL_DIR`.
|
|
137
137
|
|
|
138
138
|
```bash
|
|
139
139
|
VISUAL_DIR="$(mktemp -d /tmp/pi-cursor-sdk-1016-visual.XXXXXX)"
|
|
@@ -204,7 +204,7 @@ Pass criteria:
|
|
|
204
204
|
|
|
205
205
|
```bash
|
|
206
206
|
PI_CURSOR_SETTING_SOURCES=none \
|
|
207
|
-
pi -e . --cursor-no-fast --cursor-mode plan --model cursor/composer-2-5 \
|
|
207
|
+
pi --approve -e . --cursor-no-fast --cursor-mode plan --model cursor/composer-2-5 \
|
|
208
208
|
--session-dir "$SMOKE_DIR/cursor-mode-plan" \
|
|
209
209
|
--session-id cursor-sdk-1016-plan \
|
|
210
210
|
--no-tools \
|
|
@@ -226,7 +226,7 @@ Pass criteria:
|
|
|
226
226
|
PI_CURSOR_SETTING_SOURCES=none \
|
|
227
227
|
PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 \
|
|
228
228
|
PI_CURSOR_PI_TOOL_BRIDGE_DEBUG=1 \
|
|
229
|
-
pi -e . --cursor-no-fast --model cursor/composer-2-5 \
|
|
229
|
+
pi --approve -e . --cursor-no-fast --model cursor/composer-2-5 \
|
|
230
230
|
--session-dir "$SMOKE_DIR/bridge" \
|
|
231
231
|
-p 'Bridge smoke. Do exactly two tool calls before answering: first call pi__read on ./package.json; second call pi__read on ./definitely-missing-pi-cursor-sdk-smoke-file.txt. Then answer: OK_NAME=<package name>; MISSING_RESULT=<error or success>. Do not use shell.' \
|
|
232
232
|
> "$SMOKE_DIR/bridge.stdout.txt" \
|
|
@@ -247,7 +247,7 @@ Pass criteria:
|
|
|
247
247
|
PI_CURSOR_SETTING_SOURCES=none \
|
|
248
248
|
PI_CURSOR_PI_TOOL_BRIDGE=0 \
|
|
249
249
|
PI_CURSOR_NATIVE_TOOL_DISPLAY=1 \
|
|
250
|
-
pi -e . --cursor-no-fast --model cursor/composer-2-5 \
|
|
250
|
+
pi --approve -e . --cursor-no-fast --model cursor/composer-2-5 \
|
|
251
251
|
--session-dir "$SMOKE_DIR/native-replay" \
|
|
252
252
|
-p 'Native replay smoke. Use your Cursor file-reading capability to read ./README.md, then answer README_SEEN=yes if it contains pi-cursor-sdk.' \
|
|
253
253
|
> "$SMOKE_DIR/native-replay.stdout.txt" \
|
|
@@ -321,7 +321,7 @@ Use a harmless long-running command and interrupt it after the bridge request is
|
|
|
321
321
|
PI_CURSOR_SETTING_SOURCES=none \
|
|
322
322
|
PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 \
|
|
323
323
|
PI_CURSOR_PI_TOOL_BRIDGE_DEBUG=1 \
|
|
324
|
-
pi -e . --cursor-no-fast --model cursor/composer-2-5 \
|
|
324
|
+
pi --approve -e . --cursor-no-fast --model cursor/composer-2-5 \
|
|
325
325
|
--session-dir "$SMOKE_DIR/abort" \
|
|
326
326
|
-p 'Abort smoke. Call pi__bash with command: sleep 30 && echo SHOULD_NOT_PRINT. Do not answer until the tool completes.'
|
|
327
327
|
```
|
|
@@ -15,7 +15,7 @@ Current implementation notes:
|
|
|
15
15
|
- Cursor status uses one coordinated `ctx.ui.setStatus("cursor", ...)` value for fast and non-default plan mode; the default pi footer remains intact.
|
|
16
16
|
- Installed `@cursor/sdk` user messages accept images, and Cursor models are treated as image-capable; registered input metadata is `text` plus `image`.
|
|
17
17
|
- Image payload forwarding sends images only from the latest user message. If the latest user turn is plain text after an earlier image turn, the transcript keeps an `[image omitted from transcript]` placeholder but no image bytes are sent to Cursor. The prompt explicitly tells Cursor that prior image bytes are unavailable and to ask the user to reattach or describe a prior image when needed. Carrying images forward across turns remains a future product decision because it affects token cost, privacy, stale visual context, and expected multimodal follow-up behavior.
|
|
18
|
-
- Exact `@cursor/sdk@1.0.17` is a package dependency of this extension; users should not need a global SDK install. pi 0.
|
|
18
|
+
- Exact `@cursor/sdk@1.0.17` is a package dependency of this extension; users should not need a global SDK install. pi 0.79.0 is the current recommended validation baseline, while published pi core peer dependencies use `"*"` ranges per current pi package guidance. Newer pi versions are allowed to attempt loading this extension before a matching extension release exists; compatibility is best-effort until validated.
|
|
19
19
|
- Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults.
|
|
20
20
|
- Local agents pass `settingSources: ["all"]` by default so Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities are available. Users can narrow loading with a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`, or disable ambient setting sources with `PI_CURSOR_SETTING_SOURCES=none`. The provider suppresses direct Cursor SDK bootstrap stdout/stderr/console noise (including late first-send workspace loading such as hook compatibility warnings) so it does not pollute pi's TUI.
|
|
21
21
|
- On `cursor/*` models, pi-cursor-sdk removes only pi-generated `<project_instructions>` blocks that overlap the effective Cursor `settingSources`: `user` for `~/.pi/agent/AGENTS.md`; `project` for discovered repo/parent `AGENTS.md` and `CLAUDE.md` (verified Cursor behavior: local agents load project `AGENTS.md` and `CLAUDE.md`). `~/.pi/agent/CLAUDE.md` is not removed (Cursor user layer uses `~/.claude/CLAUDE.md`). Blocks are removed by exact pi serialization match from structured `contextFiles` via the `before_agent_start` hook, not in `buildCursorPrompt` sanitization. Suppression is skipped with `-nc`, `PI_CURSOR_SETTING_SOURCES=none`, narrowed sources such as `plugins` that omit the matching layer, or `PI_CURSOR_PRESERVE_PI_AGENTS_MD=1`. Switching away from a Cursor model restores pi's full context block on the next user message.
|
|
@@ -29,12 +29,12 @@ Current implementation notes:
|
|
|
29
29
|
- Cursor SDK MCP tool calls use a guarded timeout override because installed `@cursor/sdk` 1.0.17 has a 60-second MCP request default with no public per-server timeout option. The extension extends the verified Cursor SDK MCP `callTool` timeout path to 3600 seconds by default and shortens the verified first-send MCP initialize/listTools timeout paths to 10 seconds by default so unavailable configured MCP servers do not block the first reply for a full minute; unknown MCP protocol timeout stacks keep the SDK default. Users can override tool-call timeouts with `PI_CURSOR_MCP_TOOL_TIMEOUT_MS` or `PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS`, and initialize/listTools timeouts with `PI_CURSOR_MCP_CONNECT_TIMEOUT_MS` or `PI_CURSOR_MCP_CONNECT_TIMEOUT_SECONDS`.
|
|
30
30
|
- Bridge diagnostics are opt-in only: `PI_CURSOR_PI_TOOL_BRIDGE_DEBUG=1` writes typed, allowlisted, scrubbed single-line JSONL records to `process.stderr` with prefix `[pi-cursor-sdk:bridge]`. Diagnostics are scrubbed operational logs, not anonymous telemetry. They intentionally include tool names, safe correlation IDs, run lifecycle, exposed pi↔MCP name pairs, queued requests, result resolution, rejection, cancellation, and pending counts. Correlation IDs are generated independently from the tokenized endpoint path, and Cursor MCP call IDs are hashed before serialization. Diagnostics must not include endpoint paths/URLs/path components/tokens, API keys, bearer tokens, cookies, session credentials, raw args/results, stdout/stderr payloads, file contents, Cursor settings output, or local private session paths in tracked docs, and they must not call pi UI status, notification, or footer APIs. If tool names themselves are unacceptable for a release target, bridge debug diagnostics are not safe for shared logs under the current contract.
|
|
31
31
|
- This repo does not provide a generic desktop-automation, browser-driver, or CDP recipe. Provider docs should describe pi-cursor-sdk's Cursor provider/bridge contract only.
|
|
32
|
-
- Cursor internal tool activity is recorded from SDK events and scrubbed. Maintainer reference for all 16 `@cursor/sdk@1.0.17` `ToolType` values, runtime alias normalization, and intentional mapping/fallback rules: [Cursor native tool replay — SDK ToolType replay matrix](./cursor-native-tool-replay.md#sdk-tooltype-replay-matrix) (official SDK docs: https://cursor.com/docs/sdk/typescript). In interactive TTY sessions, supported completed `read`, `bash`, `grep`, `find`, `ls`, `edit`, `write`, diagnostics, delete, todo/plan, task, image generation, MCP, semantic search, and screen recording activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native-looking cards without rerunning Cursor's reads/shell commands/file edits. Cursor `glob` activity is replayed through native `find` cards. Cursor write activity is replayed through native-looking `write` cards, and Cursor StrReplace/edit activity uses native-looking `edit` only when recorded arguments truthfully satisfy pi's `edit` schema; path-only Cursor edit and notebook edit replay falls back to neutral Cursor activity before pi validation. Diagnostics, delete, todos/plans, task, image, and MCP activity use neutral Cursor activity cards with pi's default success/error shell. Neutral Cursor activity calls include `activityTitle` and, when available, `activitySummary` so partial/collapsed cards preserve identity such as `Cursor plan`, `Cursor todos`, `Cursor MCP`, or `Cursor edit`. For long-running or externally meaningful Cursor tools (`task`, `shell`, `mcp`, `generateImage`, `recordScreen`, `semSearch`, web search/fetch, plan/todo), the provider may surface one low-noise deferred in-progress thinking line such as `Cursor MCP: external_search` from bounded, scrubbed SDK args; fast local tools (`read`, `grep`, `glob`, and similar) skip lifecycle lines when completion follows immediately, and pi bridge MCP calls are excluded because pi already shows real pi tool execution ([lifecycle visibility](./cursor-native-tool-replay.md#low-noise-tool-lifecycle-visibility)). Replay-only tools display recorded Cursor results, normalize workspace-local paths/diff headers for display, use pi diff colors for edit previews and path-inferred syntax highlighting for write previews, and fail closed if called without a recorded result. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. Cursor workflow tools such as mode/task/todo/plan activity are not pi workflow controls; reported todo/plan events are displayed as Cursor activity only. Plan/todo replay cards can be followed by Cursor's final plan text, selected from `run.wait().result` when Cursor provides one and trimmed against already-emitted text. Started Cursor SDK tool calls that never receive a completion event are surfaced with bounded user-visible labels/traces (neutral activity cards when native replay routing allows, otherwise the same inactive or transcript trace fallbacks used for completed replay) instead of being silently discarded when the run failed/aborted, produced no assistant text, or involved external/side-effectful tools; incomplete fast local discovery starts (`read`, `grep`, `glob`, `ls`) remain maintainer-debug-only after successful text-producing runs so stale SDK start events do not create red post-answer cards. Explicit failures remain visible when Cursor reports them through completed tool calls or step results. Pi bridge MCP starts remain excluded from duplicate incomplete Cursor cards because pi already shows real pi tool execution. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When bridge or native replay cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK activity arrives: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later tool batches as further `toolUse` turns, then Cursor's final assistant answer. For shell replay, completed `stdout` / `stderr` are primary; unambiguous `shell-output-delta` data is
|
|
32
|
+
- Cursor internal tool activity is recorded from SDK events and scrubbed. Maintainer reference for all 16 `@cursor/sdk@1.0.17` `ToolType` values, runtime alias normalization, and intentional mapping/fallback rules: [Cursor native tool replay — SDK ToolType replay matrix](./cursor-native-tool-replay.md#sdk-tooltype-replay-matrix) (official SDK docs: https://cursor.com/docs/sdk/typescript). In interactive TTY sessions, supported completed `read`, `bash`, `grep`, `find`, `ls`, `edit`, `write`, diagnostics, delete, todo/plan, task, image generation, MCP, semantic search, and screen recording activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native-looking cards without rerunning Cursor's reads/shell commands/file edits. Cursor `glob` activity is replayed through native `find` cards. Cursor write activity is replayed through native-looking `write` cards, and Cursor StrReplace/edit activity uses native-looking `edit` only when recorded arguments truthfully satisfy pi's `edit` schema; path-only Cursor edit and notebook edit replay falls back to neutral Cursor activity before pi validation. Diagnostics, delete, todos/plans, task, image, and MCP activity use neutral Cursor activity cards with pi's default success/error shell. Neutral Cursor activity calls include `activityTitle` and, when available, `activitySummary` so partial/collapsed cards preserve identity such as `Cursor plan`, `Cursor todos`, `Cursor MCP`, or `Cursor edit`. For long-running or externally meaningful Cursor tools (`task`, `shell`, `mcp`, `generateImage`, `recordScreen`, `semSearch`, web search/fetch, plan/todo), the provider may surface one low-noise deferred in-progress thinking line such as `Cursor MCP: external_search` from bounded, scrubbed SDK args; fast local tools (`read`, `grep`, `glob`, and similar) skip lifecycle lines when completion follows immediately, and pi bridge MCP calls are excluded because pi already shows real pi tool execution ([lifecycle visibility](./cursor-native-tool-replay.md#low-noise-tool-lifecycle-visibility)). Replay-only tools display recorded Cursor results, normalize workspace-local paths/diff headers for display, use pi diff colors for edit previews and path-inferred syntax highlighting for write previews, and fail closed if called without a recorded result. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. Cursor workflow tools such as mode/task/todo/plan activity are not pi workflow controls; reported todo/plan events are displayed as Cursor activity only. Plan/todo replay cards can be followed by Cursor's final plan text, selected from `run.wait().result` when Cursor provides one and trimmed against already-emitted text. Started Cursor SDK tool calls that never receive a completion event are surfaced with bounded user-visible labels/traces (neutral activity cards when native replay routing allows, otherwise the same inactive or transcript trace fallbacks used for completed replay) instead of being silently discarded when the run failed/aborted, produced no assistant text, or involved external/side-effectful tools; incomplete fast local discovery starts (`read`, `grep`, `glob`, `ls`) remain maintainer-debug-only after successful text-producing runs so stale SDK start events do not create red post-answer cards. Explicit failures remain visible when Cursor reports them through completed tool calls or step results. Pi bridge MCP starts remain excluded from duplicate incomplete Cursor cards because pi already shows real pi tool execution. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When bridge or native replay cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK activity arrives: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later tool batches as further `toolUse` turns, then Cursor's final assistant answer. For shell replay, completed `stdout` / `stderr` are primary; unambiguous `shell-output-delta` data is also shown as bounded live progress while one shell call is active and used as display-only fallback for empty successful shell completions, while overlapping shell calls drop ambiguous deltas instead of guessing. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when no live-run turn split is active.
|
|
33
33
|
- Cursor native replay uses one neutral replay tool name, `cursor`, plus native-compatible card names when renderer-compatible (`read`, `bash`, `grep`, `find`, `ls`, `edit`, `write`). Neutral replay identity lives in `activityTitle`, `activitySummary`, and typed replay details, not in extra registered tool names. Bridge MCP names such as `pi__sem_reindex` are MCP-only; pi session output uses real pi tool names.
|
|
34
34
|
- Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension does not copy raw Cursor SDK usage into pi usage or compaction. For Cursor assistant messages, `usage.input`/`usage.output` are approximate pi session activity components: initial Cursor prompt input is counted once, consumed split-run tool results are counted as deduped input on the following assistant turn, and assistant output includes visible text/thinking/tool-call content. `usage.totalTokens` is the replayable Cursor prompt/context estimate derived from the same `buildCursorPrompt()` path used for `Agent.send`; it may differ from `input + output` and is the context-safe value for display/compaction. `src/cursor-usage-accounting.ts` owns this usage policy, and `src/cursor-live-run-accounting.ts` owns prompt-once and consumed-tool-result accounting so provider usage and bridge result resolution share the same matched tool-result boundary.
|
|
35
35
|
- Audit observation, 2026-05-19, superseded by the 2026-05-21 replay pass and #68 incomplete visibility, then narrowed by the 2026-05-26 fast-local suppression: a missing-file read with Composer 2.5 emitted `tool-call-started` for Cursor `read`, then streamed final text `Error: File not found`, but did not emit `tool-call-completed` or an `onStep` `toolCall` error result. Leftover external/side-effectful started calls are surfaced at run completion through the same native replay routing as completed tools (activity cards when allowed, otherwise inactive/transcript traces), while fast local discovery starts are debug-only after a successful text-producing run. Cursor-reported completed/step errors remain visible.
|
|
36
36
|
- Maintainer visual verification for replay-card changes should follow [Cursor Native Tool Visual Audit Workflow](./cursor-native-tool-visual-audit.md): offscreen PTY-driven pi run, xterm.js/Playwright screenshot rendering, and JSONL inspection before accepting commits or PRs.
|
|
37
|
-
- Cursor provider/runtime releases must pass the [Platform Smoke Gate](./platform-smoke.md): `npm run smoke:platform:doctor && npm run smoke:platform:all`. Use [Cursor Live Smoke Checklist](./cursor-live-smoke-checklist.md) only for focused inner-loop/debug runs with real `pi -e . --cursor-no-fast --model cursor/composer-2-5` invocations, manual observation, temporary session dirs, diagnostics scans, and persisted JSONL inspection. See [Cursor testing lessons](./cursor-testing-lessons.md) for auth.json seeding, isolated smoke harnesses, and replay JSONL scans. Assume every runtime surface is in scope.
|
|
37
|
+
- Cursor provider/runtime releases must pass the [Platform Smoke Gate](./platform-smoke.md): `npm run smoke:platform:doctor && npm run smoke:platform:all`. Use [Cursor Live Smoke Checklist](./cursor-live-smoke-checklist.md) only for focused inner-loop/debug runs with real `pi --approve -e . --cursor-no-fast --model cursor/composer-2-5` invocations, manual observation, temporary session dirs, diagnostics scans, and persisted JSONL inspection. See [Cursor testing lessons](./cursor-testing-lessons.md) for auth.json seeding, isolated smoke harnesses, and replay JSONL scans. Assume every runtime surface is in scope.
|
|
38
38
|
- For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup.
|
|
39
39
|
- Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.17 documentation says the SDK may enable Max Mode automatically when a selected model requires it, but the public local-agent `ModelSelection` path still does not expose a manual Max Mode selector. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends.
|
|
40
40
|
- The installed `@cursor/sdk` exposes latest-style `ModelListItem.aliases`. The extension registers only unambiguous aliases as pi model IDs (with the same context suffixes when applicable) and sends the alias back in `ModelSelection.id`. Cursor-only fast preferences are keyed by the selected SDK model ID/alias, with read fallback for older preferences keyed by the underlying catalog `id`. Aliases shared by multiple base models, such as generic family aliases, are skipped because the pi row metadata would otherwise imply one base model while Cursor may resolve the alias to another.
|
|
@@ -544,7 +544,7 @@ pi --model cursor/gpt-5.5@1m --cursor-fast -p "Say ok only"
|
|
|
544
544
|
|
|
545
545
|
## Discovered Model Capability Examples
|
|
546
546
|
|
|
547
|
-
These examples document the capability shapes the extension handles, not an exhaustive live catalog. The exact Cursor catalog changes over time; use `pi -e . --list-models cursor` or `Cursor.models.list()` for the current model surface. When the SDK reports aliases, only unambiguous aliases are registered; shared generic aliases are skipped.
|
|
547
|
+
These examples document the capability shapes the extension handles, not an exhaustive live catalog. The exact Cursor catalog changes over time; use `pi --approve -e . --list-models cursor` or `Cursor.models.list()` for the current model surface. When the SDK reports aliases, only unambiguous aliases are registered; shared generic aliases are skipped.
|
|
548
548
|
|
|
549
549
|
| Example model shape | Cursor controls | Pi representation |
|
|
550
550
|
|---|---|---|
|
|
@@ -169,7 +169,7 @@ Most Cursor tool visibility is completion-based: the completed replay card (or b
|
|
|
169
169
|
Lifecycle rules:
|
|
170
170
|
|
|
171
171
|
- Eligible tools include `task`, `shell`, `mcp`, `generateImage`, `recordScreen`, `semSearch`, web search/fetch activity, and plan/todo activity. Fast local tools such as `read`, `grep`, and `glob` do not get lifecycle lines in normal cases.
|
|
172
|
-
- Lifecycle text is emitted as a single bounded, scrubbed thinking line such as `Cursor MCP: external_search` or `Cursor shell:
|
|
172
|
+
- Lifecycle text is emitted as a single bounded, scrubbed thinking line such as `Cursor MCP: external_search` or `Cursor shell: npm test`. Shell pending labels show a scrubbed/truncated command preview, matching pi's native bash UX; the completed replay card remains the source of truth for recorded shell results. Lifecycle lines are not separate permanent replay cards and do not rerun tools.
|
|
173
173
|
- A short defer window coalesces fast start+complete pairs: if a tool completes before the defer elapses, only the completed replay card/trace is shown.
|
|
174
174
|
- pi bridge MCP calls (`pi__*`) are excluded because pi already shows the real pi tool execution path.
|
|
175
175
|
- Implementation: `src/cursor-tool-lifecycle.ts` (eligibility/labels) and `src/cursor-provider-turn-coordinator.ts` (defer, emit, bridge exclusion).
|
|
@@ -180,7 +180,7 @@ As Cursor SDK tool completions arrive, the extension mirrors native Codex orderi
|
|
|
180
180
|
|
|
181
181
|
Bridged pi tool calls follow the same visible pi `toolUse` turn shape, but they are real pi tool executions rather than replayed Cursor results. Split-run usage accounting keeps Cursor SDK internal counters out of pi usage: each live Cursor prompt is counted once, replay/bridge tool-call turns include visible assistant activity in output estimates, consumed tool results are counted once as input on the following assistant turn, and `usage.totalTokens` remains the replayable Cursor prompt/context estimate.
|
|
182
182
|
|
|
183
|
-
For shell replay, completed `stdout` / `stderr` remain the primary source.
|
|
183
|
+
For shell replay, completed `stdout` / `stderr` remain the primary source. While exactly one shell call is active, the provider also emits a bounded scrubbed preview of the first few `shell-output-delta` stdout/stderr chunks so long-running commands show visible progress before completion. If a successful completed shell result is empty, the replay card uses unambiguous buffered delta data as display-only fallback data. Overlapping shell calls make delta attribution ambiguous, so those fallback/progress deltas are dropped rather than guessed. `(no output)` is kept only when no completed output or safe delta fallback is available.
|
|
184
184
|
|
|
185
185
|
Non-interactive and session consumers still receive bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text.
|
|
186
186
|
|
|
@@ -6,16 +6,16 @@ This workflow is the canonical repo path for verifying Cursor SDK tool replay th
|
|
|
6
6
|
|
|
7
7
|
Use it before accepting replay-card commits or PRs, and for every Cursor provider/runtime release where TUI card/color behavior could regress. Text logs and JSONL are necessary, but they are not enough when the claim is visual parity: always keep PNGs for the exact prompt, and keep before/after PNGs when reviewing a rendering change.
|
|
8
8
|
|
|
9
|
-
Current validation baseline: pi 0.
|
|
9
|
+
Current validation baseline: pi 0.79.0, exact `@cursor/sdk@1.0.17`, local validation packages `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` at 0.79.0. Published pi core peer dependencies use `"*"` ranges per current pi package guidance, so newer pi installs can try the extension before a matching validation release exists.
|
|
10
10
|
|
|
11
|
-
## Cursor SDK 1.0.17 / pi 0.
|
|
11
|
+
## Cursor SDK 1.0.17 / pi 0.79.0 cutover visual record
|
|
12
12
|
|
|
13
13
|
Record the required cutover validation here or in the final release handoff. The default matrix is native replay only: the runner forces native replay registration on, forces Cursor setting sources off, disables the pi bridge, disables overlapping built-in pi tool exposure, and clears inherited Cursor SDK event-debug artifact env. With `--event-debug`, debug capture writes to a deterministic directory under the visual output directory. Do not commit raw ANSI logs, screenshots, terminal recordings, debug artifacts, or `.debug/visual-smoke` scratch files.
|
|
14
14
|
|
|
15
15
|
| Field | Required value / evidence |
|
|
16
16
|
| --- | --- |
|
|
17
17
|
| Command/session used | `npm run smoke:visual -- --ext "$PWD" --cwd "$PWD" --mode plan --out-dir <fresh /tmp dir> --label <matrix label> --prompt <matrix prompt>` with default native-replay isolation |
|
|
18
|
-
| Baseline versions | `pi --version` = 0.
|
|
18
|
+
| Baseline versions | `pi --version` = 0.79.0; `npm ls` = `@cursor/sdk@1.0.17` and local `@earendil-works/*@0.79.0` |
|
|
19
19
|
| Card categories checked | Claim only categories proven by both PNG and JSONL. Required cutover categories are read, grep/search, find/glob, shell success, write, edit/diff, and true read failure. Direct `ls`/list is tracked as excluded from the current one-prompt platform matrix because composer-2-5 does not route it through native `ls` reliably; source-enumeration coverage is gated through find/glob. Neutral Cursor plan/todo/task/mode activity is optional/opportunistic and only counts when JSONL contains a completed Cursor workflow event. |
|
|
20
20
|
| Observed status/card colors | Confirm native-looking cards use native pi styling; neutral Cursor activity is not red; true errors are distinct; diff previews show red/green; plan status is readable |
|
|
21
21
|
| Screenshot/ANSI evidence location | External path only, for example `/tmp/pi-cursor-sdk-1016-visual.*/read-package.{ansi,txt,html,png,jsonl.path}` |
|
|
@@ -82,7 +82,7 @@ npx playwright install chromium
|
|
|
82
82
|
|
|
83
83
|
`scripts/visual-tui-smoke.mjs` is the durable source of truth for this workflow. It must keep supporting:
|
|
84
84
|
|
|
85
|
-
- fixed-size tmux PTY execution of the parent-resolved `pi -e <extension-dir> --model cursor/composer-2-5`
|
|
85
|
+
- fixed-size tmux PTY execution of the parent-resolved `pi --approve -e <extension-dir> --model cursor/composer-2-5`
|
|
86
86
|
- parent-resolved `pi` and `tmux` command paths reused in tmux-launched runs, with `process.execPath`'s directory prepended for prereq checks and tmux launches so Node shims use the validated Node
|
|
87
87
|
- `PI_CURSOR_NATIVE_TOOL_DISPLAY=1`
|
|
88
88
|
- `PI_CURSOR_REGISTER_NATIVE_TOOLS=1` by default
|
|
@@ -82,7 +82,7 @@ Never commit, log, or paste `auth.json` contents, API keys, or session JSONL wit
|
|
|
82
82
|
|
|
83
83
|
Use isolated `/tmp` trees when validating:
|
|
84
84
|
|
|
85
|
-
- packed tarball install (`npm pack` → extract → `pi install -l`)
|
|
85
|
+
- packed tarball install (`npm pack` → extract → `pi install --approve -l`)
|
|
86
86
|
- clean `HOME` with no inherited shell profile state
|
|
87
87
|
- plan-mode-style tool stripping via a shim extension
|
|
88
88
|
- JSONL replay-error scans independent of stdout
|
|
@@ -178,7 +178,7 @@ Simulate plan-mode execute stripping with the repo fixture:
|
|
|
178
178
|
It sets active tools to `read`, `bash`, `edit`, `write` on each `turn_start`. Run pi with:
|
|
179
179
|
|
|
180
180
|
```bash
|
|
181
|
-
pi -e scripts/fixtures/plan-strip-shim --cursor-no-fast --model cursor/composer-2-5 \
|
|
181
|
+
pi --approve -e scripts/fixtures/plan-strip-shim --cursor-no-fast --model cursor/composer-2-5 \
|
|
182
182
|
--session-dir "$SMOKE_DIR/plan-strip" \
|
|
183
183
|
-p 'After reset, read README.md and answer PLAN_STRIP_OK=yes.'
|
|
184
184
|
```
|
|
@@ -243,7 +243,7 @@ The script writes timestamped artifacts under `--out` (default `/tmp/pi-cursor-s
|
|
|
243
243
|
|
|
244
244
|
Stdout prints artifact paths and summary counts only. Raw payloads stay on disk and may contain local paths, project text, tool args/results, or secrets — do not commit or share them.
|
|
245
245
|
|
|
246
|
-
Hard repo rule: Cursor SDK behavior claims must come from the installed `@cursor/sdk` package and/or https://cursor.com/docs/sdk/typescript, not from memory or ad-hoc probes alone. Current cutover validation targets exact `@cursor/sdk@1.0.17` and pi 0.
|
|
246
|
+
Hard repo rule: Cursor SDK behavior claims must come from the installed `@cursor/sdk` package and/or https://cursor.com/docs/sdk/typescript, not from memory or ad-hoc probes alone. Current cutover validation targets exact `@cursor/sdk@1.0.17` and pi 0.79.0 local packages.
|
|
247
247
|
|
|
248
248
|
## Pi provider SDK event capture
|
|
249
249
|
|
|
@@ -294,7 +294,7 @@ Artifacts under `--out` (default `.debug/cursor-sdk-events/<timestamp>/` under `
|
|
|
294
294
|
During any normal pi session you can also opt in with:
|
|
295
295
|
|
|
296
296
|
```bash
|
|
297
|
-
PI_CURSOR_SDK_EVENT_DEBUG=1 pi -e . --model cursor/composer-2-5
|
|
297
|
+
PI_CURSOR_SDK_EVENT_DEBUG=1 pi --approve -e . --model cursor/composer-2-5
|
|
298
298
|
```
|
|
299
299
|
|
|
300
300
|
Multi-turn sessions group automatically by pi session file:
|
|
@@ -366,7 +366,7 @@ chmod 600 "$SMOKE_DIR/home/.pi/agent/auth.json"
|
|
|
366
366
|
env -i HOME="$SMOKE_DIR/home" PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin" \
|
|
367
367
|
MISE_DISABLE=1 \
|
|
368
368
|
PI_CURSOR_PI_TOOL_BRIDGE_DEBUG=1 \
|
|
369
|
-
pi -e . --cursor-no-fast --model cursor/composer-2-5 \
|
|
369
|
+
pi --approve -e . --cursor-no-fast --model cursor/composer-2-5 \
|
|
370
370
|
--session-dir "$SMOKE_DIR/session" \
|
|
371
371
|
-p '<exact reporter prompt>'
|
|
372
372
|
```
|
package/docs/platform-smoke.md
CHANGED
|
@@ -123,7 +123,7 @@ Runtime budget is part of the contract:
|
|
|
123
123
|
- `smoke:platform:doctor` never calls Cursor.
|
|
124
124
|
- `platform-build` runs once per target and is the only suite that performs the full local CI/build/typecheck/package gate.
|
|
125
125
|
- 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 -l <packed package path>`, `pi list`, fresh `--session-dir`, suite `PI_CODING_AGENT_DIR`, workspace fixture, JSONL, visual, bridge, and abort assertions.
|
|
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.
|
|
127
127
|
- 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
128
|
- The gate is fail-fast by target to avoid burning Cursor calls after a platform has already failed.
|
|
129
129
|
|
|
@@ -186,6 +186,11 @@ export default {
|
|
|
186
186
|
packageName: "pi-cursor-sdk",
|
|
187
187
|
cursorModel: "cursor/composer-2-5",
|
|
188
188
|
artifactRoot: ".artifacts/platform-smoke",
|
|
189
|
+
artifactRetention: {
|
|
190
|
+
maxRunDirs: 18,
|
|
191
|
+
maxAgeDays: 14,
|
|
192
|
+
preserveRecentHours: 24,
|
|
193
|
+
},
|
|
189
194
|
requiredTargets: ["macos", "ubuntu", "windows-native"],
|
|
190
195
|
requiredSuites: [
|
|
191
196
|
"platform-build",
|
|
@@ -211,6 +216,8 @@ export default {
|
|
|
211
216
|
|
|
212
217
|
`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
218
|
|
|
219
|
+
`artifactRetention` bounds local host evidence growth under `artifactRoot`. `smoke:platform:run` prunes only top-level directories named `run-<timestamp>-<suffix>` before starting a new matrix; it leaves non-run/manual directories untouched and preserves directories newer than `preserveRecentHours` to avoid deleting evidence from active or very recent runs. Doctor is read-only and does not prune artifacts.
|
|
220
|
+
|
|
214
221
|
## Required local environment
|
|
215
222
|
|
|
216
223
|
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.
|
|
@@ -261,12 +268,12 @@ Definitions:
|
|
|
261
268
|
- `piProjectRoot`: target-local pi project where platform-build proves packed install.
|
|
262
269
|
- `livePrepRoot`: target-local shared live-suite prep where the first live suite installs the packed tarball once for reuse by later live suites in the same target session.
|
|
263
270
|
|
|
264
|
-
Live suites run in a suite-local `testWorkspaceRoot`. The extension loaded by pi is the packed tarball package path from `livePrepRoot`, installed into that suite-local workspace with `pi install -l`; no live suite uses `pi -e .`.
|
|
271
|
+
Live suites run in a suite-local `testWorkspaceRoot`. The extension loaded by pi is the packed tarball package path from `livePrepRoot`, installed into that suite-local workspace with `pi install --approve -l`; no live suite uses `pi -e .`.
|
|
265
272
|
|
|
266
273
|
The runner must prove this by recording:
|
|
267
274
|
|
|
268
275
|
- packed tarball path;
|
|
269
|
-
- `pi list` output from the suite-local project after `pi install -l <packed package path>`;
|
|
276
|
+
- `pi list --approve` output from the suite-local project after `pi install --approve -l <packed package path>`;
|
|
270
277
|
- command line showing no `-e .`;
|
|
271
278
|
- live suite cwd as `testWorkspaceRoot`.
|
|
272
279
|
|
|
@@ -386,8 +393,8 @@ Per target, `platform-build` must:
|
|
|
386
393
|
6. Run `npm pack`.
|
|
387
394
|
7. Create `testWorkspaceRoot` with deterministic fixture files copied from the repo.
|
|
388
395
|
8. Create `piProjectRoot`.
|
|
389
|
-
9. Install the packed tarball into `piProjectRoot` with `pi install -l <tarball>`.
|
|
390
|
-
10. Run `pi list` and assert the installed package points at the packed tarball/install, not `-e .`.
|
|
396
|
+
9. Install the packed tarball into `piProjectRoot` with `pi install --approve -l <tarball>`.
|
|
397
|
+
10. Run `pi list --approve` and assert the installed package points at the packed tarball/install, not `-e .`.
|
|
391
398
|
|
|
392
399
|
## Required suites
|
|
393
400
|
|
|
@@ -401,7 +408,7 @@ Purpose:
|
|
|
401
408
|
- fail before spending Cursor tokens;
|
|
402
409
|
- produce the packed extension used by later suites.
|
|
403
410
|
|
|
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.
|
|
411
|
+
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.
|
|
405
412
|
|
|
406
413
|
### `cursor-native-visual-matrix`
|
|
407
414
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cursor-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
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",
|
|
@@ -116,9 +116,9 @@
|
|
|
116
116
|
"typebox": "*"
|
|
117
117
|
},
|
|
118
118
|
"devDependencies": {
|
|
119
|
-
"@earendil-works/pi-ai": "0.
|
|
120
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
121
|
-
"@earendil-works/pi-tui": "0.
|
|
119
|
+
"@earendil-works/pi-ai": "0.79.0",
|
|
120
|
+
"@earendil-works/pi-coding-agent": "0.79.0",
|
|
121
|
+
"@earendil-works/pi-tui": "0.79.0",
|
|
122
122
|
"@xterm/xterm": "^6.0.0",
|
|
123
123
|
"node-pty": "^1.1.0",
|
|
124
124
|
"playwright": "^1.60.0",
|
|
@@ -5,6 +5,11 @@ export default {
|
|
|
5
5
|
packageName: "pi-cursor-sdk",
|
|
6
6
|
cursorModel: "cursor/composer-2-5",
|
|
7
7
|
artifactRoot: ".artifacts/platform-smoke",
|
|
8
|
+
artifactRetention: {
|
|
9
|
+
maxRunDirs: 18,
|
|
10
|
+
maxAgeDays: 14,
|
|
11
|
+
preserveRecentHours: 24,
|
|
12
|
+
},
|
|
8
13
|
requiredTargets: ["macos", "ubuntu", "windows-native"],
|
|
9
14
|
requiredSuites: [
|
|
10
15
|
"platform-build",
|
|
@@ -344,12 +344,12 @@ fi
|
|
|
344
344
|
log "npm install packed extension deps"
|
|
345
345
|
run_in_dir_capture_combined "npm install --omit=dev" 120 "$EXTRACT_DIR/package" "$ISOLATED/npm-install.log" "${TOOL_ENV[@]}" "$NPM_BIN" install --omit=dev
|
|
346
346
|
|
|
347
|
-
log "pi install -l (clean HOME)"
|
|
347
|
+
log "pi install --approve -l (clean HOME)"
|
|
348
348
|
cp "$REPO/README.md" "$PROJECT_DIR/README.md"
|
|
349
|
-
run_in_dir_capture_combined "pi install" 30 "$PROJECT_DIR" "$ISOLATED/pi-install.log" "${PI_DEFAULT_ENV[@]}" "$PI_BIN" install -l "$EXTRACT_DIR/package"
|
|
349
|
+
run_in_dir_capture_combined "pi install" 30 "$PROJECT_DIR" "$ISOLATED/pi-install.log" "${PI_DEFAULT_ENV[@]}" "$PI_BIN" install --approve -l "$EXTRACT_DIR/package"
|
|
350
350
|
|
|
351
351
|
PI_LIST_OUT="$ISOLATED/pi-list.txt"
|
|
352
|
-
run_in_dir_capture_combined "pi list" 15 "$PROJECT_DIR" "$PI_LIST_OUT" "${PI_DEFAULT_ENV[@]}" "$PI_BIN" list
|
|
352
|
+
run_in_dir_capture_combined "pi list" 15 "$PROJECT_DIR" "$PI_LIST_OUT" "${PI_DEFAULT_ENV[@]}" "$PI_BIN" list --approve
|
|
353
353
|
"$RG_BIN" -q "extract/package" "$PI_LIST_OUT" || fail "packed extension not installed"
|
|
354
354
|
|
|
355
355
|
PI_CURSOR_ENV=( "${PI_NONE_ENV[@]}" )
|
|
@@ -360,14 +360,14 @@ fi
|
|
|
360
360
|
log "check: list-models"
|
|
361
361
|
LIST_OUT="$ISOLATED/list-models.txt"
|
|
362
362
|
run_in_dir_capture_combined "list-models" 30 "$PROJECT_DIR" "$LIST_OUT" "${PI_CURSOR_ENV[@]}" \
|
|
363
|
-
"$PI_BIN" --cursor-no-fast --list-models cursor
|
|
363
|
+
"$PI_BIN" --approve --cursor-no-fast --list-models cursor
|
|
364
364
|
"$RG_BIN" -q "composer-2\\.5|composer-2-5" "$LIST_OUT" || fail "composer-2-5 not listed (see $LIST_OUT)"
|
|
365
365
|
|
|
366
366
|
log "check: basic provider prompt"
|
|
367
367
|
BASIC_DIR="$SESSION_ROOT/basic"
|
|
368
368
|
mkdir -p "$BASIC_DIR"
|
|
369
369
|
run_in_dir_capture_split "basic prompt" "$PI_LIVE_TIMEOUT" "$PROJECT_DIR" "$ISOLATED/basic.stdout.txt" "$ISOLATED/basic.stderr.txt" "${PI_CURSOR_ENV[@]}" \
|
|
370
|
-
"$PI_BIN" --cursor-no-fast --model cursor/composer-2-5 --session-dir "$BASIC_DIR" --no-tools -p 'Reply exactly: PI_CURSOR_ISOLATED_OK'
|
|
370
|
+
"$PI_BIN" --approve --cursor-no-fast --model cursor/composer-2-5 --session-dir "$BASIC_DIR" --no-tools -p 'Reply exactly: PI_CURSOR_ISOLATED_OK'
|
|
371
371
|
"$RG_BIN" -q "PI_CURSOR_ISOLATED_OK" "$ISOLATED/basic.stdout.txt" || fail "basic prompt missing PI_CURSOR_ISOLATED_OK"
|
|
372
372
|
validate_replay_jsonl "$BASIC_DIR"
|
|
373
373
|
|
|
@@ -375,14 +375,14 @@ log "check: native replay"
|
|
|
375
375
|
REPLAY_DIR="$SESSION_ROOT/native-replay"
|
|
376
376
|
mkdir -p "$REPLAY_DIR"
|
|
377
377
|
run_in_dir_capture_split "native replay" "$PI_LIVE_TIMEOUT" "$PROJECT_DIR" "$ISOLATED/replay.stdout.txt" "$ISOLATED/replay.stderr.txt" "${PI_CURSOR_ENV[@]}" PI_CURSOR_NATIVE_TOOL_DISPLAY=1 \
|
|
378
|
-
"$PI_BIN" --cursor-no-fast --model cursor/composer-2-5 --session-dir "$REPLAY_DIR" -p 'Read ./README.md briefly, then answer README_SEEN=yes if it mentions pi-cursor-sdk.'
|
|
378
|
+
"$PI_BIN" --approve --cursor-no-fast --model cursor/composer-2-5 --session-dir "$REPLAY_DIR" -p 'Read ./README.md briefly, then answer README_SEEN=yes if it mentions pi-cursor-sdk.'
|
|
379
379
|
validate_replay_jsonl "$REPLAY_DIR"
|
|
380
380
|
|
|
381
381
|
log "check: plan-strip shim (plan-mode execute reset)"
|
|
382
382
|
PLAN_DIR="$SESSION_ROOT/plan-strip"
|
|
383
383
|
mkdir -p "$PLAN_DIR"
|
|
384
384
|
run_in_dir_capture_split "plan-strip replay" "$PI_LIVE_TIMEOUT" "$PROJECT_DIR" "$ISOLATED/plan.stdout.txt" "$ISOLATED/plan.stderr.txt" "${PI_CURSOR_ENV[@]}" PI_CURSOR_NATIVE_TOOL_DISPLAY=1 \
|
|
385
|
-
"$PI_BIN" -e "$SHIM_DIR" --cursor-no-fast --model cursor/composer-2-5 --session-dir "$PLAN_DIR" -p 'After reset, read README.md and answer PLAN_STRIP_OK=yes.'
|
|
385
|
+
"$PI_BIN" --approve -e "$SHIM_DIR" --cursor-no-fast --model cursor/composer-2-5 --session-dir "$PLAN_DIR" -p 'After reset, read README.md and answer PLAN_STRIP_OK=yes.'
|
|
386
386
|
validate_replay_jsonl "$PLAN_DIR"
|
|
387
387
|
|
|
388
388
|
log "PASS isolated install smoke: $ISOLATED"
|
|
@@ -2,9 +2,87 @@
|
|
|
2
2
|
* Artifact management — directory layout, manifest, redaction scanning, packaging.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { mkdirSync, writeFileSync, readFileSync, readdirSync,
|
|
5
|
+
import { mkdirSync, writeFileSync, readFileSync, readdirSync, existsSync, rmSync } from "node:fs";
|
|
6
6
|
import { resolve, relative, basename } from "node:path";
|
|
7
7
|
|
|
8
|
+
const PLATFORM_SMOKE_RUN_DIR_PATTERN = /^run-(\d+)-[a-z0-9]+$/i;
|
|
9
|
+
const HOURS_TO_MS = 60 * 60 * 1000;
|
|
10
|
+
const DAYS_TO_MS = 24 * HOURS_TO_MS;
|
|
11
|
+
|
|
12
|
+
function finiteNonNegativeNumber(value) {
|
|
13
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function finiteNonNegativeInteger(value) {
|
|
17
|
+
return Number.isInteger(value) && value >= 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Prune old top-level platform-smoke run artifact directories. */
|
|
21
|
+
export function prunePlatformSmokeArtifacts(artifactRoot, retention = {}, options = {}) {
|
|
22
|
+
const root = resolve(process.cwd(), artifactRoot);
|
|
23
|
+
const maxRunDirs = finiteNonNegativeInteger(retention.maxRunDirs) ? retention.maxRunDirs : undefined;
|
|
24
|
+
const maxAgeDays = finiteNonNegativeNumber(retention.maxAgeDays) ? retention.maxAgeDays : undefined;
|
|
25
|
+
const preserveRecentHours = finiteNonNegativeNumber(retention.preserveRecentHours) ? retention.preserveRecentHours : 24;
|
|
26
|
+
const enabled = retention.enabled !== false && (maxRunDirs !== undefined || maxAgeDays !== undefined);
|
|
27
|
+
const result = { root, enabled, removed: [], kept: [], ignored: [] };
|
|
28
|
+
if (!enabled || !existsSync(root)) return result;
|
|
29
|
+
|
|
30
|
+
const nowMs = finiteNonNegativeNumber(options.nowMs) ? options.nowMs : Date.now();
|
|
31
|
+
const preserveRecentMs = preserveRecentHours * HOURS_TO_MS;
|
|
32
|
+
const maxAgeMs = maxAgeDays === undefined ? undefined : maxAgeDays * DAYS_TO_MS;
|
|
33
|
+
const runDirs = [];
|
|
34
|
+
|
|
35
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
36
|
+
if (!entry.isDirectory()) {
|
|
37
|
+
result.ignored.push(entry.name);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const match = PLATFORM_SMOKE_RUN_DIR_PATTERN.exec(entry.name);
|
|
41
|
+
if (!match) {
|
|
42
|
+
result.ignored.push(entry.name);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
runDirs.push({ name: entry.name, path: resolve(root, entry.name), timestampMs: Number(match[1]) });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const recentCutoffMs = nowMs - preserveRecentMs;
|
|
49
|
+
const protectedRecent = new Set(runDirs.filter((dir) => dir.timestampMs > recentCutoffMs).map((dir) => dir.name));
|
|
50
|
+
const removeNames = new Set();
|
|
51
|
+
|
|
52
|
+
if (maxAgeMs !== undefined) {
|
|
53
|
+
const staleCutoffMs = nowMs - maxAgeMs;
|
|
54
|
+
for (const dir of runDirs) {
|
|
55
|
+
if (dir.timestampMs < staleCutoffMs) removeNames.add(dir.name);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (maxRunDirs !== undefined && runDirs.length > maxRunDirs) {
|
|
60
|
+
const sortedNewestFirst = [...runDirs].sort((a, b) => b.timestampMs - a.timestampMs);
|
|
61
|
+
let remainingKeepSlots = maxRunDirs - protectedRecent.size;
|
|
62
|
+
for (const dir of sortedNewestFirst) {
|
|
63
|
+
if (protectedRecent.has(dir.name)) continue;
|
|
64
|
+
if (remainingKeepSlots > 0) {
|
|
65
|
+
remainingKeepSlots--;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
removeNames.add(dir.name);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const dir of runDirs) {
|
|
73
|
+
if (!removeNames.has(dir.name)) {
|
|
74
|
+
result.kept.push(dir.name);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
rmSync(dir.path, { recursive: true, force: true });
|
|
78
|
+
result.removed.push(dir.name);
|
|
79
|
+
}
|
|
80
|
+
result.kept.sort();
|
|
81
|
+
result.removed.sort();
|
|
82
|
+
result.ignored.sort();
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
8
86
|
/** Create a suite artifact directory. */
|
|
9
87
|
export function createSuiteDir(artifactRoot, runId, targetName, suiteName) {
|
|
10
88
|
const dir = resolve(process.cwd(), artifactRoot, runId, targetName, suiteName);
|
|
@@ -603,10 +603,10 @@ async function main() {
|
|
|
603
603
|
const npmInstallPacked = runLogged(logDir, "workspace-npm-install-packed", commandName("npm"), ["install", "--no-save", tarballPath], { cwd: workspaceDir, timeout: 180_000 });
|
|
604
604
|
requireOk(npmInstallPacked, "workspace npm install packed tarball");
|
|
605
605
|
}
|
|
606
|
-
const install = runLogged(logDir, "pi-install", piCli, ["install", "-l", installPath], { cwd: workspaceDir, env: piEnv, timeout: 120_000 });
|
|
607
|
-
requireOk(install, "pi install packed package directory");
|
|
608
|
-
const list = runLogged(logDir, "pi-list", piCli, ["list"], { cwd: workspaceDir, env: piEnv, timeout: 60_000 });
|
|
609
|
-
requireOk(list, "pi list");
|
|
606
|
+
const install = runLogged(logDir, "pi-install", piCli, ["install", "--approve", "-l", installPath], { cwd: workspaceDir, env: piEnv, timeout: 120_000 });
|
|
607
|
+
requireOk(install, "pi install --approve packed package directory");
|
|
608
|
+
const list = runLogged(logDir, "pi-list", piCli, ["list", "--approve"], { cwd: workspaceDir, env: piEnv, timeout: 60_000 });
|
|
609
|
+
requireOk(list, "pi list --approve");
|
|
610
610
|
|
|
611
611
|
const suiteEnv = {
|
|
612
612
|
...process.env,
|
|
@@ -620,16 +620,17 @@ async function main() {
|
|
|
620
620
|
if (args.suite === "cursor-abort-cleanup") writeProcessSnapshot(logDir, "process-before", platform);
|
|
621
621
|
const prompt = renderPrompt(scenario, platform);
|
|
622
622
|
writeFileSync(join(artifactDir, "prompt.txt"), prompt);
|
|
623
|
+
const piArgs = ["--approve", "--cursor-no-fast", "--cursor-mode", "agent", "--model", args.model, "--session-dir", sessionDir, "--session-id", `platform-${args.suite}-${Date.now()}`];
|
|
623
624
|
writeFileSync(join(artifactDir, "pi-command.json"), JSON.stringify({
|
|
624
625
|
piCli,
|
|
625
|
-
args:
|
|
626
|
+
args: piArgs,
|
|
626
627
|
cwd: workspaceDir,
|
|
627
628
|
env: Object.fromEntries(Object.entries(suiteEnv).filter(([key]) => key.startsWith("PI_CURSOR_") || key === "PI_CODING_AGENT_DIR" || key === "TERM")),
|
|
628
629
|
}, null, 2));
|
|
629
630
|
const ptyResult = await runPtyPi({
|
|
630
631
|
artifactDir,
|
|
631
632
|
piCli,
|
|
632
|
-
piArgs
|
|
633
|
+
piArgs,
|
|
633
634
|
env: suiteEnv,
|
|
634
635
|
cwd: workspaceDir,
|
|
635
636
|
sessionDir,
|
|
@@ -137,7 +137,7 @@ if ($PackTarball -and $PiCli -and (Test-Path -LiteralPath $TarballPath)) {
|
|
|
137
137
|
if ($PACKED_NODE_INSTALL_EXIT -eq 0) {
|
|
138
138
|
$PreviousPiOffline = $env:PI_OFFLINE
|
|
139
139
|
$env:PI_OFFLINE = "1"
|
|
140
|
-
& $PiCli install -l (Join-Path ".\node_modules" $PackageName) 1> $PiInstallOut 2> $PiInstallErr
|
|
140
|
+
& $PiCli install --approve -l (Join-Path ".\node_modules" $PackageName) 1> $PiInstallOut 2> $PiInstallErr
|
|
141
141
|
$PI_INSTALL_EXIT = Exit-CodeFromLastCommand
|
|
142
142
|
if ($null -eq $PreviousPiOffline) { Remove-Item Env:\PI_OFFLINE -ErrorAction SilentlyContinue } else { $env:PI_OFFLINE = $PreviousPiOffline }
|
|
143
143
|
} else {
|
|
@@ -163,7 +163,7 @@ if ($PiCli) {
|
|
|
163
163
|
Push-Location $PiProject
|
|
164
164
|
$PreviousPiOffline = $env:PI_OFFLINE
|
|
165
165
|
$env:PI_OFFLINE = "1"
|
|
166
|
-
& $PiCli list 1> $PiListOut 2> $PiListErr
|
|
166
|
+
& $PiCli list --approve 1> $PiListOut 2> $PiListErr
|
|
167
167
|
$PI_LIST_EXIT = Exit-CodeFromLastCommand
|
|
168
168
|
if ($null -eq $PreviousPiOffline) { Remove-Item Env:\PI_OFFLINE -ErrorAction SilentlyContinue } else { $env:PI_OFFLINE = $PreviousPiOffline }
|
|
169
169
|
Pop-Location
|
|
@@ -469,13 +469,13 @@ export function buildPlatformBuildCommand(targetName, packageName = "pi-cursor-s
|
|
|
469
469
|
lines.push('echo "PLATFORM_PACKED_NODE_INSTALL_EXIT=$PACKED_NODE_INSTALL_EXIT"');
|
|
470
470
|
lines.push(...posixSection("PACKED_NODE_INSTALL_STDOUT", 'cat "$PACK_DIR/packed-node-install.stdout.txt" 2>/dev/null || true'));
|
|
471
471
|
lines.push(...posixSection("PACKED_NODE_INSTALL_STDERR", 'cat "$PACK_DIR/packed-node-install.stderr.txt" 2>/dev/null || true'));
|
|
472
|
-
lines.push(`if [ "$PACKED_NODE_INSTALL_EXIT" -eq 0 ] && [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" install -l ./node_modules/${packageName} >"$PACK_DIR/pi-install.stdout.txt" 2>"$PACK_DIR/pi-install.stderr.txt"); PI_INSTALL_EXIT=$?; else echo "packed npm install failed or missing pi cli" >"$PACK_DIR/pi-install.stderr.txt"; PI_INSTALL_EXIT=1; fi`);
|
|
472
|
+
lines.push(`if [ "$PACKED_NODE_INSTALL_EXIT" -eq 0 ] && [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" install --approve -l ./node_modules/${packageName} >"$PACK_DIR/pi-install.stdout.txt" 2>"$PACK_DIR/pi-install.stderr.txt"); PI_INSTALL_EXIT=$?; else echo "packed npm install failed or missing pi cli" >"$PACK_DIR/pi-install.stderr.txt"; PI_INSTALL_EXIT=1; fi`);
|
|
473
473
|
lines.push('echo "PLATFORM_PI_INSTALL_EXIT=$PI_INSTALL_EXIT"');
|
|
474
474
|
lines.push(...posixSection("PI_INSTALL_STDOUT", 'cat "$PACK_DIR/pi-install.stdout.txt" 2>/dev/null || true'));
|
|
475
475
|
lines.push(...posixSection("PI_INSTALL_STDERR", 'cat "$PACK_DIR/pi-install.stderr.txt" 2>/dev/null || true'));
|
|
476
476
|
lines.push("");
|
|
477
477
|
lines.push('echo "=== pi list ==="');
|
|
478
|
-
lines.push('if [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" list >"$PACK_DIR/pi-list.stdout.txt" 2>"$PACK_DIR/pi-list.stderr.txt"); PI_LIST_EXIT=$?; else echo "missing pi cli" >"$PACK_DIR/pi-list.stderr.txt"; PI_LIST_EXIT=1; fi');
|
|
478
|
+
lines.push('if [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" list --approve >"$PACK_DIR/pi-list.stdout.txt" 2>"$PACK_DIR/pi-list.stderr.txt"); PI_LIST_EXIT=$?; else echo "missing pi cli" >"$PACK_DIR/pi-list.stderr.txt"; PI_LIST_EXIT=1; fi');
|
|
479
479
|
lines.push('echo "PLATFORM_PI_LIST_EXIT=$PI_LIST_EXIT"');
|
|
480
480
|
lines.push(...posixSection("PI_LIST_STDOUT", 'cat "$PACK_DIR/pi-list.stdout.txt" 2>/dev/null || true'));
|
|
481
481
|
lines.push(...posixSection("PI_LIST_STDERR", 'cat "$PACK_DIR/pi-list.stderr.txt" 2>/dev/null || true'));
|
|
@@ -5,6 +5,8 @@ import { resolve, dirname } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { accessSync, constants } from "node:fs";
|
|
7
7
|
|
|
8
|
+
import { prunePlatformSmokeArtifacts } from "./platform-smoke/artifacts.mjs";
|
|
9
|
+
|
|
8
10
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
9
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
12
|
const __dirname = dirname(__filename);
|
|
@@ -172,6 +174,11 @@ async function main() {
|
|
|
172
174
|
process.exit(2);
|
|
173
175
|
}
|
|
174
176
|
|
|
177
|
+
const pruneResult = prunePlatformSmokeArtifacts(config.artifactRoot, config.artifactRetention);
|
|
178
|
+
if (pruneResult.removed.length > 0) {
|
|
179
|
+
console.log(`Pruned ${pruneResult.removed.length} old platform smoke artifact run(s) from ${pruneResult.root}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
175
182
|
const targetRuns = targets.map(async (targetName) => {
|
|
176
183
|
console.log(`\n=== Target: ${targetName} ===`);
|
|
177
184
|
const result = args.suite
|
|
@@ -165,7 +165,7 @@ function buildPiRpcEnv(baseEnv = process.env, nodePath = process.execPath) {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
async function runPiRpcSmoke(sessionDir, piBin) {
|
|
168
|
-
const args = ["-e", root, "--cursor-no-fast", "--model", "cursor/composer-2-5", "--mode", "rpc", "--session-dir", sessionDir];
|
|
168
|
+
const args = ["--approve", "-e", root, "--cursor-no-fast", "--model", "cursor/composer-2-5", "--mode", "rpc", "--session-dir", sessionDir];
|
|
169
169
|
const env = buildPiRpcEnv();
|
|
170
170
|
|
|
171
171
|
const child = spawn(piBin, args, { cwd: root, env, stdio: ["pipe", "pipe", "pipe"], detached: process.platform !== "win32" });
|
|
@@ -363,6 +363,7 @@ function buildLaunchPlan(options, commands, shell) {
|
|
|
363
363
|
...envAssignments.map(([name, value]) => `${name}=${shellQuote(value)}`),
|
|
364
364
|
"exec",
|
|
365
365
|
shellQuote(commands.pi),
|
|
366
|
+
"--approve",
|
|
366
367
|
"-e", shellQuote(options.ext),
|
|
367
368
|
"--cursor-no-fast",
|
|
368
369
|
"--cursor-mode", shellQuote(options.mode),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/** Canonical Cursor settingSources parsing (parity-tested by provider runtime and maintainer scripts). */
|
|
2
2
|
export const CURSOR_SETTING_SOURCES_ENV = "PI_CURSOR_SETTING_SOURCES";
|
|
3
|
+
export const DEFAULT_CURSOR_SETTING_SOURCES = Object.freeze(["all"]);
|
|
3
4
|
|
|
4
5
|
export function resolveCursorSettingSources(raw) {
|
|
5
6
|
const trimmed = raw?.trim();
|
|
6
|
-
if (!trimmed) return [
|
|
7
|
+
if (!trimmed) return [...DEFAULT_CURSOR_SETTING_SOURCES];
|
|
7
8
|
const normalized = trimmed.toLowerCase();
|
|
8
9
|
if (["0", "false", "off", "none", "omit", "disabled"].includes(normalized)) return undefined;
|
|
9
10
|
if (["1", "true", "on", "all"].includes(normalized)) return ["all"];
|
|
@@ -8,10 +8,12 @@ const GENERIC_CURSOR_SDK_ERROR_MESSAGE =
|
|
|
8
8
|
"Cursor SDK request failed. The Cursor SDK API key may be missing, invalid, or unauthorized. Cursor Agent CLI/Desktop login is not reused. Run /login -> Use an API key -> Cursor, verify CURSOR_API_KEY, or pass --api-key, then retry.";
|
|
9
9
|
const AUTH_CURSOR_SDK_ERROR_MESSAGE =
|
|
10
10
|
"Cursor SDK request failed because the Cursor SDK API key may be invalid or unauthorized. Cursor Agent CLI/Desktop login is not reused. Run /login -> Use an API key -> Cursor, verify CURSOR_API_KEY, or pass --api-key, then retry.";
|
|
11
|
+
// Keep "Network error" aligned with pi's agent-level retry classifier.
|
|
11
12
|
const NETWORK_CURSOR_SDK_ERROR_MESSAGE =
|
|
12
|
-
"Cursor SDK request failed during network or service I/O. Check your connection
|
|
13
|
+
"Network error: Cursor SDK request failed during network or service I/O. Check your connection; pi will retry automatically when auto-retry is enabled.";
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
// Keep this phrase aligned with pi's agent-level retry classifier (`provider.?returned.?error`).
|
|
16
|
+
const RETRYABLE_CURSOR_RUN_FAILURE_PREFIX = "Provider returned error: Cursor SDK run failed";
|
|
15
17
|
|
|
16
18
|
export type CursorSdkRunFailureSource = Pick<RunResult, "id" | "status" | "durationMs" | "model" | "result">;
|
|
17
19
|
|
|
@@ -20,9 +22,13 @@ function isGenericErrorMessage(message: string): boolean {
|
|
|
20
22
|
return normalized === "" || normalized === "error" || normalized === "unknown error";
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
function isGenericCursorRunFailureMessage(message: string): boolean {
|
|
26
|
+
return /^cursor sdk run failed\.?$/i.test(message.trim());
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
function isKnownGenericRunFailureText(message: string): boolean {
|
|
24
30
|
const normalized = message.trim().toLowerCase();
|
|
25
|
-
return normalized === "" ||
|
|
31
|
+
return normalized === "" || isGenericCursorRunFailureMessage(message) || isGenericErrorMessage(normalized);
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
function isLikelyAuthError(message: string): boolean {
|
|
@@ -150,7 +156,7 @@ export function formatCursorSdkRunFailureDetail(result: CursorSdkRunFailureSourc
|
|
|
150
156
|
return fromRun;
|
|
151
157
|
}
|
|
152
158
|
|
|
153
|
-
const parts = [
|
|
159
|
+
const parts = [RETRYABLE_CURSOR_RUN_FAILURE_PREFIX];
|
|
154
160
|
if (result.model?.id) parts.push(`model ${result.model.id}`);
|
|
155
161
|
parts.push(`run ${shortRunId(result.id)}`);
|
|
156
162
|
if (typeof result.durationMs === "number") parts.push(`${result.durationMs}ms`);
|
|
@@ -190,6 +196,7 @@ export function sanitizeCursorProviderError(error: unknown, apiKey?: string): st
|
|
|
190
196
|
const connectClassification = classifyCursorConnectError(error);
|
|
191
197
|
if (connectClassification?.kind === "unauthenticated" || isLikelyAuthError(scrubbed)) return AUTH_CURSOR_SDK_ERROR_MESSAGE;
|
|
192
198
|
if (connectClassification?.kind === "network" || isLikelyNetworkTimeout(scrubbed)) return NETWORK_CURSOR_SDK_ERROR_MESSAGE;
|
|
199
|
+
if (isGenericCursorRunFailureMessage(scrubbed)) return RETRYABLE_CURSOR_RUN_FAILURE_PREFIX;
|
|
193
200
|
if (isGenericErrorMessage(scrubbed)) return GENERIC_CURSOR_SDK_ERROR_MESSAGE;
|
|
194
201
|
return scrubbed || GENERIC_CURSOR_SDK_ERROR_MESSAGE;
|
|
195
202
|
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { resolveCursorToolCompletion } from "./cursor-provider-turn-sdk-normalizer.js";
|
|
25
25
|
import {
|
|
26
26
|
CursorShellOutputTracker,
|
|
27
|
+
formatCursorShellOutputProgressText,
|
|
27
28
|
getCursorShellOutputDelta,
|
|
28
29
|
isCursorShellToolCall,
|
|
29
30
|
} from "./cursor-provider-turn-shell-output.js";
|
|
@@ -210,7 +211,17 @@ export class CursorSdkTurnCoordinator {
|
|
|
210
211
|
}
|
|
211
212
|
if (update.type === "shell-output-delta") {
|
|
212
213
|
const delta = getCursorShellOutputDelta(update);
|
|
213
|
-
if (delta)
|
|
214
|
+
if (delta) {
|
|
215
|
+
const progress = this.shellOutput.appendShellOutputDelta(delta);
|
|
216
|
+
const progressText = progress ? formatCursorShellOutputProgressText(progress, this.resolvedApiKey) : undefined;
|
|
217
|
+
if (progressText) {
|
|
218
|
+
if (this.liveRun) {
|
|
219
|
+
cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: progressText });
|
|
220
|
+
} else {
|
|
221
|
+
this.contentEmitter.appendThinkingDelta(progressText);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
214
225
|
return;
|
|
215
226
|
}
|
|
216
227
|
if (update.type === "summary") {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { InteractionUpdate } from "@cursor/sdk";
|
|
2
2
|
import { asRecord, getField, hasUsableText } from "./cursor-record-utils.js";
|
|
3
|
+
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
4
|
+
import { truncateCursorDisplayLine } from "./cursor-display-text.js";
|
|
3
5
|
import { classifyCursorToolVisibility } from "./cursor-tool-visibility.js";
|
|
4
6
|
|
|
5
7
|
export interface CursorShellOutputDelta {
|
|
@@ -12,6 +14,12 @@ export interface CursorShellOutputDeltas {
|
|
|
12
14
|
stderr: string[];
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
export interface CursorShellOutputProgressDelta extends CursorShellOutputDelta {
|
|
18
|
+
callId: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SHELL_OUTPUT_PROGRESS_MAX_DELTAS_PER_CALL = 3;
|
|
22
|
+
|
|
15
23
|
export function isCursorShellToolCall(toolCall: unknown): boolean {
|
|
16
24
|
return classifyCursorToolVisibility(toolCall).normalizedKey === "shell";
|
|
17
25
|
}
|
|
@@ -27,6 +35,22 @@ export function getCursorShellOutputDelta(update: InteractionUpdate): CursorShel
|
|
|
27
35
|
return { stream: eventCase, data };
|
|
28
36
|
}
|
|
29
37
|
|
|
38
|
+
function getCursorShellOutputProgressPreview(data: string): string | undefined {
|
|
39
|
+
return data
|
|
40
|
+
.split(/\r?\n/)
|
|
41
|
+
.map((line) => line.trim())
|
|
42
|
+
.find((line) => line.length > 0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function formatCursorShellOutputProgressText(
|
|
46
|
+
progress: CursorShellOutputProgressDelta,
|
|
47
|
+
apiKey?: string,
|
|
48
|
+
): string | undefined {
|
|
49
|
+
const preview = getCursorShellOutputProgressPreview(progress.data);
|
|
50
|
+
if (!preview) return undefined;
|
|
51
|
+
return `Cursor shell ${progress.stream}: ${truncateCursorDisplayLine(scrubSensitiveText(preview, apiKey), 160)}\n`;
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
export function mergeShellOutputDeltasIntoCursorToolCall(
|
|
31
55
|
toolCall: unknown,
|
|
32
56
|
deltas: CursorShellOutputDeltas | undefined,
|
|
@@ -65,6 +89,7 @@ export class CursorShellOutputTracker {
|
|
|
65
89
|
private readonly activeShellCallIds = new Set<string>();
|
|
66
90
|
private readonly ambiguousShellOutputCallIds = new Set<string>();
|
|
67
91
|
private readonly shellOutputDeltasByCallId = new Map<string, CursorShellOutputDeltas>();
|
|
92
|
+
private readonly shellOutputProgressCountsByCallId = new Map<string, number>();
|
|
68
93
|
|
|
69
94
|
onShellToolStarted(callId: string): void {
|
|
70
95
|
this.activeShellCallIds.add(callId);
|
|
@@ -73,29 +98,38 @@ export class CursorShellOutputTracker {
|
|
|
73
98
|
onShellToolCleared(callId: string): void {
|
|
74
99
|
this.activeShellCallIds.delete(callId);
|
|
75
100
|
this.ambiguousShellOutputCallIds.delete(callId);
|
|
101
|
+
this.shellOutputProgressCountsByCallId.delete(callId);
|
|
76
102
|
}
|
|
77
103
|
|
|
78
|
-
appendShellOutputDelta(delta: CursorShellOutputDelta):
|
|
104
|
+
appendShellOutputDelta(delta: CursorShellOutputDelta): CursorShellOutputProgressDelta | undefined {
|
|
79
105
|
if (this.activeShellCallIds.size !== 1) {
|
|
80
106
|
for (const activeCallId of this.activeShellCallIds) {
|
|
81
107
|
this.ambiguousShellOutputCallIds.add(activeCallId);
|
|
82
108
|
this.shellOutputDeltasByCallId.delete(activeCallId);
|
|
109
|
+
this.shellOutputProgressCountsByCallId.delete(activeCallId);
|
|
83
110
|
}
|
|
84
|
-
return;
|
|
111
|
+
return undefined;
|
|
85
112
|
}
|
|
86
113
|
const [callId] = this.activeShellCallIds;
|
|
87
|
-
if (!callId || this.ambiguousShellOutputCallIds.has(callId)) return;
|
|
114
|
+
if (!callId || this.ambiguousShellOutputCallIds.has(callId)) return undefined;
|
|
88
115
|
let deltas = this.shellOutputDeltasByCallId.get(callId);
|
|
89
116
|
if (!deltas) {
|
|
90
117
|
deltas = { stdout: [], stderr: [] };
|
|
91
118
|
this.shellOutputDeltasByCallId.set(callId, deltas);
|
|
92
119
|
}
|
|
93
120
|
deltas[delta.stream].push(delta.data);
|
|
121
|
+
|
|
122
|
+
if (!getCursorShellOutputProgressPreview(delta.data)) return undefined;
|
|
123
|
+
const progressCount = this.shellOutputProgressCountsByCallId.get(callId) ?? 0;
|
|
124
|
+
if (progressCount >= SHELL_OUTPUT_PROGRESS_MAX_DELTAS_PER_CALL) return undefined;
|
|
125
|
+
this.shellOutputProgressCountsByCallId.set(callId, progressCount + 1);
|
|
126
|
+
return { ...delta, callId };
|
|
94
127
|
}
|
|
95
128
|
|
|
96
129
|
takeDeltasForCall(callId: string): CursorShellOutputDeltas | undefined {
|
|
97
130
|
const deltas = this.shellOutputDeltasByCallId.get(callId);
|
|
98
131
|
this.shellOutputDeltasByCallId.delete(callId);
|
|
132
|
+
this.shellOutputProgressCountsByCallId.delete(callId);
|
|
99
133
|
return deltas;
|
|
100
134
|
}
|
|
101
135
|
|
|
@@ -103,5 +137,6 @@ export class CursorShellOutputTracker {
|
|
|
103
137
|
this.activeShellCallIds.clear();
|
|
104
138
|
this.ambiguousShellOutputCallIds.clear();
|
|
105
139
|
this.shellOutputDeltasByCallId.clear();
|
|
140
|
+
this.shellOutputProgressCountsByCallId.clear();
|
|
106
141
|
}
|
|
107
142
|
}
|
|
@@ -2,10 +2,12 @@ import type { SettingSource } from "@cursor/sdk";
|
|
|
2
2
|
/** Provider-facing wrapper; canonical parsing lives in shared/cursor-setting-sources.mjs. */
|
|
3
3
|
import {
|
|
4
4
|
CURSOR_SETTING_SOURCES_ENV as CURSOR_SETTING_SOURCES_ENV_JS,
|
|
5
|
+
DEFAULT_CURSOR_SETTING_SOURCES as DEFAULT_CURSOR_SETTING_SOURCES_JS,
|
|
5
6
|
resolveCursorSettingSources as resolveCursorSettingSourcesJs,
|
|
6
7
|
} from "../shared/cursor-setting-sources.mjs";
|
|
7
8
|
|
|
8
9
|
export const CURSOR_SETTING_SOURCES_ENV = CURSOR_SETTING_SOURCES_ENV_JS;
|
|
10
|
+
export const DEFAULT_CURSOR_SETTING_SOURCES = DEFAULT_CURSOR_SETTING_SOURCES_JS as readonly SettingSource[];
|
|
9
11
|
|
|
10
12
|
export function resolveCursorSettingSources(raw?: string): SettingSource[] | undefined {
|
|
11
13
|
return resolveCursorSettingSourcesJs(raw) as SettingSource[] | undefined;
|
package/src/cursor-state.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "./cursor-pi-tool-bridge-snapshot.js";
|
|
16
16
|
import {
|
|
17
17
|
CURSOR_SETTING_SOURCES_ENV,
|
|
18
|
+
DEFAULT_CURSOR_SETTING_SOURCES,
|
|
18
19
|
resolveCursorSettingSources,
|
|
19
20
|
} from "./cursor-setting-sources.js";
|
|
20
21
|
import { isCursorModel } from "./cursor-model.js";
|
|
@@ -298,7 +299,7 @@ function notifyInvalidCursorModeIfCursorActive(ctx: Pick<ExtensionContext, "hasU
|
|
|
298
299
|
function formatEffectiveCursorSettingSourcesLabel(raw: string | undefined = process.env[CURSOR_SETTING_SOURCES_ENV]): string {
|
|
299
300
|
const effective = resolveCursorSettingSources(raw);
|
|
300
301
|
const effectiveLabel = effective === undefined ? "none" : effective.join(",");
|
|
301
|
-
const rawLabel = raw?.trim() ? raw.trim() :
|
|
302
|
+
const rawLabel = raw?.trim() ? raw.trim() : `(unset → ${DEFAULT_CURSOR_SETTING_SOURCES.join(",")})`;
|
|
302
303
|
return `${rawLabel} (effective: ${effectiveLabel})`;
|
|
303
304
|
}
|
|
304
305
|
|
|
@@ -36,6 +36,11 @@ function scrubLifecycleDetail(value: string | undefined, apiKey?: string): strin
|
|
|
36
36
|
return scrubbed;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function scrubShellLifecycleDetail(value: string | undefined, apiKey?: string): string | undefined {
|
|
40
|
+
if (!value?.trim()) return undefined;
|
|
41
|
+
return truncateCursorDisplayLine(scrubSensitiveText(value, apiKey));
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
function buildCursorToolLifecycleLabelFromVisibility(
|
|
40
45
|
visibility: CursorToolVisibility,
|
|
41
46
|
apiKey?: string,
|
|
@@ -47,7 +52,7 @@ function buildCursorToolLifecycleLabelFromVisibility(
|
|
|
47
52
|
return scrubLifecycleDetail(getString(args, "description"), apiKey) ?? "task";
|
|
48
53
|
}
|
|
49
54
|
case "shell": {
|
|
50
|
-
return
|
|
55
|
+
return scrubShellLifecycleDetail(getString(args, "command") ?? getString(args, "cmd"), apiKey);
|
|
51
56
|
}
|
|
52
57
|
case "mcp": {
|
|
53
58
|
return scrubLifecycleDetail(getString(args, "toolName"), apiKey) ?? "mcp";
|