pi-cursor-sdk 0.1.57 → 0.1.59

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.59 - 2026-07-16
4
+
5
+ ### Changed
6
+
7
+ - Migrate stored Cursor API-key reads from the removed root `AuthStorage` export to the Pi 0.80.9 `readStoredCredential()` contract, accepting only stored `api_key` credentials before the existing environment fallback.
8
+ - Build extension test contexts through an isolated async `ModelRuntime` and `ModelRegistry` compatibility facade, and refresh exact Pi development dependencies to 0.80.9 while preserving wildcard runtime peers.
9
+
10
+ ## 0.1.58 - 2026-07-14
11
+
12
+ ### Changed
13
+
14
+ - Update the Pi development and validation baseline to 0.80.7, including Pi's 372k GPT-5.6 Codex metadata, and expose opt-in `max` thinking only when Cursor advertises a distinct `max` value.
15
+ - Wait for Pi's final-idle `agent_settled` RPC event in smoke/debug harnesses instead of stopping at a low-level `agent_end` that may precede retries or queued work.
16
+
3
17
  ## 0.1.57 - 2026-07-10
4
18
 
5
19
  ### Changed
@@ -39,11 +53,6 @@
39
53
  - Reject local resume handles that cross user messages already persisted at process startup, preventing a hard-crash restart from resending a prompt that the prior Cursor agent may already have accepted.
40
54
  - Reject non-HTTPS and credential/query/fragment-bearing Cursor Cloud repository URLs before `Agent.create()`, and redact URL/SCP-style userinfo from provider and maintainer output.
41
55
 
42
- ### Validation
43
-
44
- - Current offline evidence is limited to the checked-in focused tests, full unit suite, platform-smoke syntax/tooling checks, and TypeScript checks; final command results belong in the release artifacts/status.
45
- - Required and pending release gates are `npm publish --dry-run`, `npm run smoke:platform:all`, `npm run smoke:cloud`, and the applicable live release smokes. Their final results belong in the release artifacts/status, not this changelog.
46
-
47
56
  ## 0.1.56 - 2026-07-04
48
57
 
49
58
  ### Changed
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.80.5 or newer recommended; pi core peer metadata is intentionally unpinned so newer pi releases are not blocked
54
+ - pi 0.80.9 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.23`, so normal package installation brings in the SDK version this extension was built and tested against. Cursor SDK 1.0.23 declares its Node ConnectRPC transport dependency directly, so npm installs place `@connectrpc/connect-node` where the SDK can resolve it. The extension intentionally does not bundle `@cursor/sdk` or its platform packages, because packing from one maintainer OS can otherwise ship the wrong optional SDK binary for another OS. Cursor SDK 1.0.23 keeps the older `sqlite3 -> node-gyp@8` dependency chain out of the runtime tree, so deprecated install warnings for `inflight`, `rimraf`, `glob@7`, `npmlog`, `gauge`, `are-we-there-yet`, and `tar@6` from that chain are not expected. 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.80.5 plus Cursor SDK 1.0.23; older pi compatibility paths are best-effort and older Cursor SDK compatibility paths are not maintained.
57
+ No global `@cursor/sdk` install is required. This package depends on exact `@cursor/sdk@1.0.23`, so normal package installation brings in the SDK version this extension was built and tested against. Cursor SDK 1.0.23 declares its Node ConnectRPC transport dependency directly, so npm installs place `@connectrpc/connect-node` where the SDK can resolve it. The extension intentionally does not bundle `@cursor/sdk` or its platform packages, because packing from one maintainer OS can otherwise ship the wrong optional SDK binary for another OS. Cursor SDK 1.0.23 keeps the older `sqlite3 -> node-gyp@8` dependency chain out of the runtime tree, so deprecated install warnings for `inflight`, `rimraf`, `glob@7`, `npmlog`, `gauge`, `are-we-there-yet`, and `tar@6` from that chain are not expected. 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.80.9 plus Cursor SDK 1.0.23; older pi compatibility paths are best-effort and older Cursor SDK compatibility paths are not maintained.
58
58
 
59
59
  ## Install
60
60
 
@@ -178,7 +178,7 @@ How to read model IDs:
178
178
 
179
179
  - `cursor/...` is the Cursor provider registered by this extension
180
180
  - `@1m`, `@272k`, and `@300k` are context-window variants
181
- - `:medium`, `:high`, and `:xhigh` are pi thinking-level suffixes for models where the Cursor SDK exposes a pi-controllable thinking parameter
181
+ - `:medium`, `:high`, `:xhigh`, and `:max` are pi thinking-level suffixes for models where the Cursor SDK exposes the corresponding pi-controllable thinking parameter
182
182
  - unambiguous latest-style Cursor aliases returned by `Cursor.models.list()` are registered too, using the same context suffixes when the target model has context variants; aliases shared by multiple base models or colliding with a base model ID are skipped because their SDK resolution and displayed metadata can diverge
183
183
 
184
184
  Examples with pi thinking controls:
@@ -186,6 +186,7 @@ Examples with pi thinking controls:
186
186
  ```bash
187
187
  pi --model cursor/gpt-5.5@1m:medium
188
188
  pi --model cursor/gpt-5.5@272k:xhigh
189
+ pi --model cursor/claude-opus-4-7@1m:max
189
190
  pi --model cursor/gpt-5.5@1m --thinking medium
190
191
  ```
191
192
 
@@ -201,6 +202,8 @@ For models where Cursor exposes `reasoning`, `effort`, or boolean `thinking` par
201
202
  - `effort=low|medium|high|xhigh|max`
202
203
  - `thinking=false|true` for boolean thinking models
203
204
 
205
+ Pi `xhigh` maps to Cursor `xhigh` or `extra-high`; Pi `max` maps only to a distinct Cursor `max` value.
206
+
204
207
  For Claude models with both `thinking` and `effort`, pi thinking `off` sends `thinking=false` and omits `effort`.
205
208
 
206
209
  ### Why some Cursor models show `thinking=no`
@@ -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.80.5 for this cutover baseline.
71
- - `npm ls` shows `@cursor/sdk@1.0.23` and local `@earendil-works/*@0.80.5` packages.
70
+ - `pi --version` reports pi 0.80.9 for this cutover baseline.
71
+ - `npm ls` shows `@cursor/sdk@1.0.23` and local `@earendil-works/*@0.80.9` 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.
@@ -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 show `cursor:local · fast:off`; ignore unrelated status text from other extensions.
128
- - The run uses pi 0.80.5 `--session-id` successfully.
128
+ - The run uses pi 0.80.9 `--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.80.5, `@cursor/sdk@1.0.23`, 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`.
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.80.9, `@cursor/sdk@1.0.23`, 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)"
@@ -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.23` is a package dependency of this extension; users should not need a global SDK install. pi 0.80.5 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.
18
+ - Exact `@cursor/sdk@1.0.23` is a package dependency of this extension; users should not need a global SDK install. pi 0.80.9 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
  - Startup discovery does not duplicate Pi CLI parsing: it uses stored `~/.pi/agent/auth.json` API-key auth from `/login`, then `CURSOR_API_KEY`, otherwise it registers the bundled fallback catalog. Provider turns keep Pi's resolved `options.apiKey`. `/cursor-refresh-models` and `/cursor-cloud` mutations resolve provider `cursor` through the command context's Pi ModelRegistry, then normalize placeholders through `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults.
20
20
  - Cursor Cloud repository overrides accept only HTTPS repository URLs without userinfo, query parameters, or fragments. Invalid values fail during preflight before `Agent.create()`, messages never echo the supplied URL, and shared provider/maintainer scrubbing removes URL/SCP-style userinfo defensively.
21
21
  - Cursor Cloud requires a persisted pi session. Immediately after remote `Agent.create()` returns, before debug work or abort checks, the provider appends a branch-local pi lifecycle entry, fsyncs the existing Pi session JSONL anchor through a read-write descriptor, and then fsyncs a newline-framed sidecar keyed by the stable pi session ID (POSIX mode `0600`; Windows inherits the user session directory ACL) in the session directory. Journal creation is exclusive, and existing append/read opens reject symlinks and require matching regular-file descriptor/path identity before using the descriptor. Existing session files use the exact lifecycle entry ID as anchor; fileless first turns use an orphan marker that a same-session-ID restart durably claims onto exactly one matching or replacement branch, surviving the timestamped path change and later JSONL creation without granting sibling access; returned run IDs are fsynced before abort/wait handling, readers skip malformed/truncated lines independently, and branch-only history events are reduced after deduplicated sidecar history, and tombstones are tracked before records exist. A durable sidecar result remains authoritative if its optional Pi mirror append fails; if the sidecar result itself fails, the prior durable intent remains pending and blocks retry rather than claiming success. `--no-session` and durable-ledger failures fail closed before send, while post-send ledger failure requests bounded cancellation. `/cursor-cloud` always validates the embedded session ID, accepts only null anchors while truly fileless, and reconciles each orphan to one branch before listing or mutation. Successive record events merge run/branch metadata monotonically even when mirrored sources arrive out of order. Archive/delete require resolved Cursor auth, fsync a durable intent before the SDK call, and fsync a durable success result afterward; unresolved intent blocks repeat mutation and requires dashboard inspection.
@@ -83,7 +83,7 @@ Cursor.models.list({ apiKey });
83
83
 
84
84
  Startup discovery resolves `apiKey` in this order:
85
85
 
86
- 1. Stored pi auth for provider `cursor` from `AuthStorage.create().getApiKey("cursor", { includeFallback: false })`.
86
+ 1. Stored pi auth for provider `cursor` from `readStoredCredential("cursor")`, accepting only an `api_key` credential.
87
87
  2. `CURSOR_API_KEY`.
88
88
 
89
89
  Startup never parses `process.argv`; Pi remains the sole owner of CLI model/provider/key parsing. Provider turns keep Pi's resolved `options.apiKey`. Users can persist the stored key through `/login` -> `Use an API key` -> `Cursor`. If auth is added after startup, fallback models can run once Pi resolves the saved key for provider requests, and `/cursor-refresh-models` asks the command context's ModelRegistry for provider `cursor` and passes that normalized key explicitly to a forced live refresh.
@@ -276,7 +276,7 @@ Do not mark a model `reasoning: true` only because it can think. That would make
276
276
  Pi levels:
277
277
 
278
278
  ```text
279
- off, minimal, low, medium, high, xhigh
279
+ off, minimal, low, medium, high, xhigh, max
280
280
  ```
281
281
 
282
282
  Cursor values vary by model. Build `thinkingLevelMap` from the values Cursor exposes.
@@ -290,18 +290,19 @@ Mapping rules:
290
290
  | `low` | `low` |
291
291
  | `medium` | `medium` |
292
292
  | `high` | `high`, else `true` for boolean-only thinking |
293
- | `xhigh` | `xhigh`, else `max`, else `extra-high` |
293
+ | `xhigh` | `xhigh`, else `extra-high` |
294
+ | `max` | `max` |
294
295
 
295
296
  Important details:
296
297
 
297
298
  - Use `null` for unsupported pi levels so pi hides/skips/clamps them natively.
298
- - Include `xhigh` only when Cursor exposes a real value for it.
299
- - Prefer exact `xhigh` over `max`. Cursor currently exposes both on some Claude models, and exact `xhigh` is the closer native mapping.
299
+ - Include `xhigh` and `max` only when Cursor exposes real values for them.
300
+ - Keep `xhigh` and `max` distinct. Cursor exposes both on some models, while `extra-high` remains an `xhigh` alias.
300
301
  - If Cursor exposes `reasoning=none`, map pi `off` to `none`.
301
302
  - If Cursor exposes `thinking=false`, map pi `off` to `false`.
302
303
  - `thinkingLevelMap` does not create Cursor SDK params by itself. It only controls pi-native behavior. The Cursor stream implementation must use the active pi thinking level plus the extension's discovered Cursor metadata to build `ModelSelection.params` for `Agent.create()`.
303
304
 
304
- For boolean-only `thinking`, unsupported pi levels must be explicit `null`; otherwise pi treats omitted non-`xhigh` levels as supported. Use this shape unless Cursor exposes richer values:
305
+ For boolean-only `thinking`, unsupported pi levels must be explicit `null`; otherwise pi treats omitted non-`xhigh`/`max` levels as supported. Use this shape unless Cursor exposes richer values:
305
306
 
306
307
  ```ts
307
308
  {
@@ -311,6 +312,7 @@ For boolean-only `thinking`, unsupported pi levels must be explicit `null`; othe
311
312
  medium: null,
312
313
  high: "true",
313
314
  xhigh: null,
315
+ max: null,
314
316
  }
315
317
  ```
316
318
 
@@ -701,7 +703,8 @@ Before calling done:
701
703
  - `reasoning` mapping
702
704
  - `effort` mapping
703
705
  - boolean `thinking` maps to pi `off` / enabled levels
704
- - pi `xhigh` preference order: `xhigh`, then `max`, then `extra-high`
706
+ - pi `xhigh` preference order: `xhigh`, then `extra-high`
707
+ - pi `max` maps only to Cursor `max`
705
708
  - session restore for Cursor-only state
706
709
  - global default state for Cursor-only state
707
710
  - unsupported no-op notifications
@@ -722,6 +725,7 @@ Before calling done:
722
725
  3. Print mode:
723
726
  - `pi --model cursor/gpt-5.5@1m:medium -p "Say ok only"`
724
727
  - `pi --model cursor/gpt-5.5@272k --thinking xhigh -p "Say ok only"`
728
+ - `pi --model cursor/claude-opus-4-7@1m --thinking max -p "Say ok only"`
725
729
  - `pi --model cursor/gpt-5.5@1m --cursor-fast -p "Say ok only"`
726
730
  - `pi --model cursor/gpt-5.5@1m --cursor-mode plan -p "Say ok only"`
727
731
  - confirm requests use selected context, pi thinking, fast flag state, and SDK-native mode
@@ -6,7 +6,7 @@ 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.80.5, exact `@cursor/sdk@1.0.23`, local validation packages `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` at 0.80.5. 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.
9
+ Current validation baseline: pi 0.80.9, exact `@cursor/sdk@1.0.23`, local validation packages `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` at 0.80.9. 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
11
  ## Cursor SDK 1.0.17 / pi 0.79.0 cutover visual record
12
12
 
@@ -256,7 +256,7 @@ The script writes timestamped artifacts under `--out` (default `/tmp/pi-cursor-s
256
256
 
257
257
  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.
258
258
 
259
- 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.23` and pi 0.80.5 local packages.
259
+ 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.23` and pi 0.80.9 local packages.
260
260
 
261
261
  ## Pi provider SDK event capture
262
262
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cursor-sdk",
3
- "version": "0.1.57",
3
+ "version": "0.1.59",
4
4
  "description": "pi provider extension backed by @cursor/sdk local and cloud agents",
5
5
  "author": "Mitch Fultz (https://github.com/fitchmultz)",
6
6
  "license": "MIT",
@@ -151,9 +151,9 @@
151
151
  "typebox": "*"
152
152
  },
153
153
  "devDependencies": {
154
- "@earendil-works/pi-ai": "0.80.5",
155
- "@earendil-works/pi-coding-agent": "0.80.5",
156
- "@earendil-works/pi-tui": "0.80.5",
154
+ "@earendil-works/pi-ai": "0.80.9",
155
+ "@earendil-works/pi-coding-agent": "0.80.9",
156
+ "@earendil-works/pi-tui": "0.80.9",
157
157
  "@xterm/xterm": "^6.0.0",
158
158
  "node-pty": "^1.1.0",
159
159
  "playwright": "^1.60.0",
@@ -337,19 +337,19 @@ function startRpc({ artifactDir, contextHandoff, sessionId }) {
337
337
  return { events, send, stop, get stderr() { return stderr; } };
338
338
  }
339
339
 
340
- async function waitForAgentEnd(rpc, fromIndex, timeoutMs) {
340
+ async function waitForAgentSettled(rpc, fromIndex, timeoutMs) {
341
341
  const started = Date.now();
342
342
  while (Date.now() - started < timeoutMs) {
343
- if (rpc.events.slice(fromIndex).some((event) => event.type === "agent_end")) return;
343
+ if (rpc.events.slice(fromIndex).some((event) => event.type === "agent_settled")) return;
344
344
  await new Promise((resolveWait) => setTimeout(resolveWait, 250));
345
345
  }
346
- throw new Error(`timeout waiting for agent_end. Stderr: ${rpc.stderr}`);
346
+ throw new Error(`timeout waiting for agent_settled. Stderr: ${rpc.stderr}`);
347
347
  }
348
348
 
349
349
  async function promptAndRead({ rpc, artifactDir, message, timeoutMs, expectedContextHandoff }) {
350
350
  const fromIndex = rpc.events.length;
351
351
  await rpc.send("prompt", { message }, timeoutMs);
352
- await waitForAgentEnd(rpc, fromIndex, timeoutMs);
352
+ await waitForAgentSettled(rpc, fromIndex, timeoutMs);
353
353
  const textResponse = await rpc.send("get_last_assistant_text", {}, 120000);
354
354
  if (!textResponse.success) fail("failed to read last assistant text", textResponse.error);
355
355
  const latestMetadata = readLatestMetadataIfPresent(artifactDir);
@@ -248,7 +248,7 @@ export async function runDebugProviderEvents(args, envInput = process.env) {
248
248
  const start = Date.now();
249
249
  const tick = () => {
250
250
  const events = parseJsonLines(stdout);
251
- if (events.some((event) => event.type === "agent_end")) {
251
+ if (events.some((event) => event.type === "agent_settled")) {
252
252
  resolve(events);
253
253
  return;
254
254
  }
@@ -268,22 +268,22 @@ export async function withRpc(options, run) {
268
268
  }
269
269
  }
270
270
 
271
- async function waitForAgentEnd(rpc, fromIndex, timeoutMs) {
271
+ async function waitForAgentSettled(rpc, fromIndex, timeoutMs) {
272
272
  const started = Date.now();
273
273
  while (Date.now() - started < timeoutMs) {
274
- if (rpc.events.slice(fromIndex).some((event) => event.type === "agent_end"))
274
+ if (rpc.events.slice(fromIndex).some((event) => event.type === "agent_settled"))
275
275
  return;
276
276
  await new Promise((resolveWait) => setTimeout(resolveWait, 250));
277
277
  }
278
- throw new Error(`timeout waiting for agent_end. Stderr: ${rpc.stderr}`);
278
+ throw new Error(`timeout waiting for agent_settled. Stderr: ${rpc.stderr}`);
279
279
  }
280
280
 
281
281
  async function waitForFile(path, timeoutMs, rpc) {
282
282
  const started = Date.now();
283
283
  while (Date.now() - started < timeoutMs) {
284
284
  if (existsSync(path)) return;
285
- if (rpc?.events.some((event) => event.type === "agent_end"))
286
- fail(`agent ended before ${path} existed`, rpc.stderr);
285
+ if (rpc?.events.some((event) => event.type === "agent_settled"))
286
+ fail(`agent settled before ${path} existed`, rpc.stderr);
287
287
  await new Promise((resolveWait) => setTimeout(resolveWait, 250));
288
288
  }
289
289
  fail(`timeout waiting for ${path}`, rpc?.stderr ?? "");
@@ -443,7 +443,7 @@ export async function promptAndRead({
443
443
  }) {
444
444
  const eventStart = rpc.events.length;
445
445
  await rpc.send("prompt", { message }, timeoutMs);
446
- await waitForAgentEnd(rpc, eventStart, timeoutMs);
446
+ await waitForAgentSettled(rpc, eventStart, timeoutMs);
447
447
  const text = await readLastAssistantText(rpc);
448
448
  return {
449
449
  text,
@@ -464,7 +464,7 @@ export async function promptAbortAndRead({
464
464
  await rpc.send("prompt", { message }, timeoutMs);
465
465
  await waitForFile(markerPath, timeoutMs, rpc);
466
466
  await rpcData(rpc, "abort", {}, 120000);
467
- await waitForAgentEnd(rpc, eventStart, timeoutMs);
467
+ await waitForAgentSettled(rpc, eventStart, timeoutMs);
468
468
  return takeLatestMetadata(artifactDir, seenMetadata);
469
469
  }
470
470
 
@@ -71,7 +71,7 @@ async function waitForChildCloseWithTimeout(child, timeoutMs, outputSummary = ()
71
71
  new Promise((_, reject) => {
72
72
  timeout = setTimeout(() => {
73
73
  const summary = outputSummary();
74
- reject(new Error(`pi did not exit within ${timeoutMs}ms after agent_end${summary ? `\n${summary}` : ""}`));
74
+ reject(new Error(`pi did not exit within ${timeoutMs}ms after agent_settled${summary ? `\n${summary}` : ""}`));
75
75
  }, timeoutMs);
76
76
  }),
77
77
  ]);
@@ -217,7 +217,7 @@ async function runPiRpcSmoke(sessionDir, piBin) {
217
217
  () => stdout,
218
218
  (events) => {
219
219
  const text = assistantText(events);
220
- return text.includes("STEER_OK=yes") && text.includes("STEER_CHAIN=ok") && events.some((event) => event.type === "agent_end");
220
+ return text.includes("STEER_OK=yes") && text.includes("STEER_CHAIN=ok") && events.some((event) => event.type === "agent_settled");
221
221
  },
222
222
  );
223
223
 
@@ -312,9 +312,9 @@ async function runSelfTest() {
312
312
  await waitForChildCloseWithTimeout(hangingChild, 10, () => smokeOutputTail("STEER_OK=yes", ""));
313
313
  } catch (error) {
314
314
  const message = error instanceof Error ? error.message : String(error);
315
- closeTimedOut = message.includes("pi did not exit within 10ms after agent_end") && message.includes("stdoutTail=STEER_OK=yes");
315
+ closeTimedOut = message.includes("pi did not exit within 10ms after agent_settled") && message.includes("stdoutTail=STEER_OK=yes");
316
316
  }
317
- if (!closeTimedOut) fail("self-test failed: post-agent_end child close wait should be bounded and report output tail");
317
+ if (!closeTimedOut) fail("self-test failed: post-agent_settled child close wait should be bounded and report output tail");
318
318
  } finally {
319
319
  if (originalPiBin === undefined) delete process.env.PI_BIN;
320
320
  else process.env.PI_BIN = originalPiBin;
@@ -23,8 +23,9 @@ export function resolveCursorApiKey(apiKey?: string): string | undefined {
23
23
 
24
24
  async function getStoredCursorApiKey(): Promise<string | undefined> {
25
25
  try {
26
- const { AuthStorage } = await import("@earendil-works/pi-coding-agent");
27
- return resolveCursorApiKey(await AuthStorage.create().getApiKey(CURSOR_PROVIDER_ID, { includeFallback: false }));
26
+ const { readStoredCredential } = await import("@earendil-works/pi-coding-agent");
27
+ const credential = readStoredCredential(CURSOR_PROVIDER_ID);
28
+ return resolveCursorApiKey(credential?.type === "api_key" ? credential.key : undefined);
28
29
  } catch {
29
30
  return undefined;
30
31
  }
@@ -104,7 +104,7 @@ function mapComparableLevel(
104
104
  level: Exclude<ModelThinkingLevel, "off">,
105
105
  ): string | null {
106
106
  if (level === "xhigh") {
107
- return getPreferredParameterValue(parameter, ["xhigh", "max", "extra-high"]);
107
+ return getPreferredParameterValue(parameter, ["xhigh", "extra-high"]);
108
108
  }
109
109
  return getParameterValue(parameter, level);
110
110
  }
@@ -124,6 +124,7 @@ function getThinkingLevelMap(item: ModelListItem): ThinkingLevelMap | undefined
124
124
  medium: null,
125
125
  high: getParameterValue(valueParameter, "true"),
126
126
  xhigh: null,
127
+ max: null,
127
128
  };
128
129
  }
129
130
 
@@ -137,6 +138,7 @@ function getThinkingLevelMap(item: ModelListItem): ThinkingLevelMap | undefined
137
138
  medium: mapComparableLevel(valueParameter, "medium"),
138
139
  high: mapComparableLevel(valueParameter, "high"),
139
140
  xhigh: mapComparableLevel(valueParameter, "xhigh"),
141
+ max: mapComparableLevel(valueParameter, "max"),
140
142
  };
141
143
  }
142
144