pi-cursor-sdk 0.1.56 → 0.1.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/README.md +100 -28
  3. package/docs/cursor-live-smoke-checklist.md +7 -7
  4. package/docs/cursor-model-ux-spec.md +45 -38
  5. package/docs/cursor-native-tool-replay.md +2 -2
  6. package/docs/cursor-native-tool-visual-audit.md +1 -1
  7. package/docs/cursor-testing-lessons.md +20 -7
  8. package/docs/cursor-tool-surfaces.md +13 -2
  9. package/docs/platform-smoke-implementation.md +220 -0
  10. package/docs/platform-smoke.md +183 -247
  11. package/package.json +38 -6
  12. package/platform-smoke.config.mjs +5 -1
  13. package/scripts/cloud-runtime-smoke.d.mts +3 -0
  14. package/scripts/cloud-runtime-smoke.mjs +502 -0
  15. package/scripts/debug-provider-events.mjs +7 -2
  16. package/scripts/isolated-cursor-smoke.sh +4 -6
  17. package/scripts/lib/cursor-child-process.d.mts +1 -0
  18. package/scripts/lib/cursor-child-process.mjs +137 -7
  19. package/scripts/lib/local-resume-smoke-harness.mjs +543 -0
  20. package/scripts/local-resume-cleanup-smoke.mjs +108 -0
  21. package/scripts/local-resume-smoke.d.mts +1 -0
  22. package/scripts/local-resume-smoke.mjs +642 -0
  23. package/scripts/platform-smoke/artifact-anchored-extract.d.mts +10 -0
  24. package/scripts/platform-smoke/artifact-anchored-extract.mjs +111 -0
  25. package/scripts/platform-smoke/artifact-bundle-chunk.mjs +57 -0
  26. package/scripts/platform-smoke/artifact-bundle-contract.mjs +34 -0
  27. package/scripts/platform-smoke/artifact-fs-safety.mjs +311 -0
  28. package/scripts/platform-smoke/artifact-openat-extract.c +335 -0
  29. package/scripts/platform-smoke/artifact-secrets.mjs +155 -0
  30. package/scripts/platform-smoke/artifacts.mjs +293 -65
  31. package/scripts/platform-smoke/card-detect.mjs +16 -4
  32. package/scripts/platform-smoke/crabbox-runner.mjs +45 -3
  33. package/scripts/platform-smoke/doctor.mjs +20 -10
  34. package/scripts/platform-smoke/live-suite-runner.mjs +18 -57
  35. package/scripts/platform-smoke/local-resume-runner.mjs +252 -0
  36. package/scripts/platform-smoke/local-resume-suites.d.mts +15 -0
  37. package/scripts/platform-smoke/local-resume-suites.mjs +104 -0
  38. package/scripts/platform-smoke/scenarios.mjs +16 -2
  39. package/scripts/platform-smoke/target-runtime.mjs +206 -0
  40. package/scripts/platform-smoke/targets.mjs +33 -141
  41. package/scripts/platform-smoke/visual-evidence.mjs +6 -7
  42. package/scripts/platform-smoke/wrapped-line-match.mjs +9 -0
  43. package/scripts/platform-smoke.mjs +40 -27
  44. package/scripts/refresh-cursor-model-snapshots.mjs +18 -6
  45. package/scripts/steering-rpc-smoke.mjs +12 -2
  46. package/scripts/tmux-live-smoke.sh +3 -5
  47. package/shared/cursor-cloud-lifecycle-constants.d.mts +3 -0
  48. package/shared/cursor-cloud-lifecycle-constants.mjs +7 -0
  49. package/shared/cursor-sensitive-text.mjs +7 -1
  50. package/src/context.ts +5 -2
  51. package/src/cursor-agents-context-registration.ts +7 -0
  52. package/src/cursor-agents-context.ts +3 -1
  53. package/src/cursor-api-key.ts +15 -1
  54. package/src/cursor-bridge-contract.ts +3 -0
  55. package/src/cursor-cloud-lifecycle.ts +733 -0
  56. package/src/cursor-cloud-options.ts +206 -0
  57. package/src/cursor-cloud-reporting.ts +246 -0
  58. package/src/cursor-config.ts +659 -0
  59. package/src/cursor-display-only-trace.ts +14 -0
  60. package/src/cursor-display-text.ts +8 -2
  61. package/src/cursor-durable-fs.ts +49 -0
  62. package/src/cursor-fallback-models.generated.ts +2045 -485
  63. package/src/cursor-live-run-accounting.ts +7 -1
  64. package/src/cursor-live-run-coordinator.ts +1 -0
  65. package/src/cursor-pi-tool-bridge-run.ts +14 -4
  66. package/src/cursor-provider-live-run-drain.ts +5 -0
  67. package/src/cursor-provider-run-finalizer.ts +36 -28
  68. package/src/cursor-provider-turn-finalize.ts +72 -6
  69. package/src/cursor-provider-turn-prepare.ts +228 -12
  70. package/src/cursor-provider-turn-runner.ts +41 -13
  71. package/src/cursor-provider-turn-send.ts +59 -16
  72. package/src/cursor-provider-turn-types.ts +44 -10
  73. package/src/cursor-runtime-state.ts +478 -0
  74. package/src/cursor-sdk-event-debug.ts +46 -6
  75. package/src/cursor-sdk-process-error-guard.ts +101 -30
  76. package/src/cursor-session-agent-cleanup.ts +328 -0
  77. package/src/cursor-session-agent-resume.ts +439 -0
  78. package/src/cursor-session-agent.ts +109 -13
  79. package/src/cursor-session-scope.ts +35 -2
  80. package/src/cursor-session-send-policy.ts +1 -1
  81. package/src/cursor-skill-tool.ts +30 -11
  82. package/src/cursor-state.ts +112 -69
  83. package/src/cursor-usage-accounting.ts +14 -4
  84. package/src/index.ts +11 -2
  85. package/src/model-discovery.ts +10 -56
@@ -8,7 +8,12 @@ import { accessSync, chmodSync, constants, mkdirSync, mkdtempSync, rmSync, write
8
8
  import { tmpdir } from "node:os";
9
9
  import { delimiter, dirname, join, resolve } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
- import { parseJsonLines, terminateChild, waitForChildClose } from "./lib/cursor-child-process.mjs";
11
+ import {
12
+ CHILD_PROCESS_TREE_SPAWN_OPTIONS,
13
+ parseJsonLines,
14
+ terminateChild,
15
+ waitForChildClose,
16
+ } from "./lib/cursor-child-process.mjs";
12
17
  import { apiKeySecretsFromProcess } from "./lib/cursor-cli-args.mjs";
13
18
  import { buildCursorSmokeEnv, CURSOR_SDK_EVENT_DEBUG_ENV_NAMES } from "./lib/cursor-smoke-env.mjs";
14
19
  import { scrubSensitiveText } from "../shared/cursor-sensitive-text.mjs";
@@ -168,7 +173,12 @@ async function runPiRpcSmoke(sessionDir, piBin) {
168
173
  const args = ["--approve", "-e", root, "--cursor-no-fast", "--model", "cursor/composer-2-5", "--mode", "rpc", "--session-dir", sessionDir];
169
174
  const env = buildPiRpcEnv();
170
175
 
171
- const child = spawn(piBin, args, { cwd: root, env, stdio: ["pipe", "pipe", "pipe"], detached: process.platform !== "win32" });
176
+ const child = spawn(piBin, args, {
177
+ cwd: root,
178
+ env,
179
+ stdio: ["pipe", "pipe", "pipe"],
180
+ ...CHILD_PROCESS_TREE_SPAWN_OPTIONS,
181
+ });
172
182
  let closed = false;
173
183
  let stdout = "";
174
184
  let stderr = "";
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bash
1
+ #!/bin/bash
2
2
  set -euo pipefail
3
3
 
4
4
  ROOT="$(cd "$(dirname "$0")/.." && pwd)"
@@ -33,8 +33,7 @@ cleanup() {
33
33
  trap cleanup EXIT
34
34
 
35
35
  print_help() {
36
- cat <<EOF
37
- Partial live smoke runner for pi-cursor-sdk (subset of docs/cursor-live-smoke-checklist.md).
36
+ printf '%s\n' 'Partial live smoke runner for pi-cursor-sdk (subset of docs/cursor-live-smoke-checklist.md).
38
37
 
39
38
  Usage:
40
39
  ./scripts/tmux-live-smoke.sh
@@ -73,8 +72,7 @@ Options:
73
72
 
74
73
  Exit codes:
75
74
  0 all partial checks passed
76
- 1 prerequisite, smoke, safety, or JSONL validation failure
77
- EOF
75
+ 1 prerequisite, smoke, safety, or JSONL validation failure'
78
76
  }
79
77
 
80
78
  log() { smoke_log "$@"; }
@@ -0,0 +1,3 @@
1
+ export declare const CLOUD_AGENT_ID_PATTERN: RegExp;
2
+ export declare const CLOUD_LIFECYCLE_ENTRY_TYPE: "cursor-cloud-lifecycle";
3
+ export declare const CLOUD_LIFECYCLE_JOURNAL_PREFIX: ".cursor-cloud-lifecycle";
@@ -0,0 +1,7 @@
1
+ /** Canonical Cursor Cloud lifecycle contract shared by the runtime and maintainer scripts. */
2
+
3
+ export const CLOUD_AGENT_ID_PATTERN = /^bc-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
+
5
+ export const CLOUD_LIFECYCLE_ENTRY_TYPE = "cursor-cloud-lifecycle";
6
+
7
+ export const CLOUD_LIFECYCLE_JOURNAL_PREFIX = ".cursor-cloud-lifecycle";
@@ -9,6 +9,12 @@ const BRIDGE_ENDPOINT_TOKEN_PATTERN = "[^/\\s\"'<>]+";
9
9
  const BRIDGE_LOOPBACK_HOST_PATTERN = "127\\.0\\.0\\.1(?::\\d+)?";
10
10
  const BRIDGE_ENDPOINT_PATH_PATTERN = `${escapeRegExp(BRIDGE_ENDPOINT_ROOT)}/${BRIDGE_ENDPOINT_TOKEN_PATTERN}/mcp`;
11
11
 
12
+ function scrubUrlUserInfo(text) {
13
+ return text
14
+ .replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^\s/?#]*@(?=[^\s/?#]+)/gi, "$1[redacted]@")
15
+ .replace(/\b[^\s/:@]+:[^\s/]+@(?=[A-Za-z0-9.-]+(?::|\/|\s|$))/g, "[redacted]@");
16
+ }
17
+
12
18
  function scrubBridgeEndpointMaterial(text) {
13
19
  return text
14
20
  .replace(
@@ -29,7 +35,7 @@ export function scrubSensitiveText(text, apiKey) {
29
35
  scrubbed = scrubbed.replace(new RegExp(escapeRegExp(trimmedKey), "g"), "[redacted]");
30
36
  }
31
37
  return scrubBridgeEndpointMaterial(
32
- scrubbed
38
+ scrubUrlUserInfo(scrubbed)
33
39
  .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]")
34
40
  .replace(/((?:^|[\s,{])cookie["']?\s*[:=]\s*["']?)[^\n]+/gi, "$1[redacted]")
35
41
  .replace(
package/src/context.ts CHANGED
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import type { Context, Message, ToolCall } from "@earendil-works/pi-ai/compat";
3
3
  import { convertToLlm } from "@earendil-works/pi-coding-agent";
4
4
  import type { AgentModeOption, SDKImage } from "@cursor/sdk";
5
+ import { CURSOR_PI_BRIDGE_PREFERENCE_TEXT } from "./cursor-bridge-contract.js";
5
6
  import { getCursorReplayPromptLabel } from "./cursor-tool-presentation-registry.js";
6
7
 
7
8
  export interface CursorPrompt {
@@ -48,6 +49,7 @@ export function getCursorToolTailGuardText(
48
49
  : getCursorPlanModeToolGuidanceText(options.agentMode, { includePiBridgeGuidance: options.includePiBridgeGuidance }),
49
50
  "Exact-output requests: output exactly the requested text; no preamble or checks unless asked.",
50
51
  "Tools: call available Cursor SDK/MCP tools; never print tool cards as assistant text.",
52
+ options.includePiBridgeGuidance === false ? undefined : CURSOR_PI_BRIDGE_PREFERENCE_TEXT,
51
53
  ].filter((line): line is string => line !== undefined).join("\n");
52
54
  }
53
55
 
@@ -73,7 +75,9 @@ function getCursorToolBoundaryText(
73
75
  return lines.join("\n");
74
76
  }
75
77
 
76
- function getCursorBootstrapTailSections(options: Pick<CursorPromptOptions, "agentMode"> = {}): string[] {
78
+ function getCursorBootstrapTailSections(
79
+ options: Pick<CursorPromptOptions, "agentMode" | "includePiBridgeGuidance"> = {},
80
+ ): string[] {
77
81
  return [
78
82
  "Answer the latest user request above using the instructions and Cursor SDK capabilities available in this run.",
79
83
  getCursorToolTailGuardText({ ...options, includePlanModeGuidance: false }),
@@ -438,6 +442,5 @@ export function buildCursorPrompt(context: Context, options: CursorPromptOptions
438
442
  );
439
443
  const text = parts.join(SECTION_SEPARATOR);
440
444
 
441
-
442
445
  return { text, images };
443
446
  }
@@ -1,15 +1,22 @@
1
+ import { isCursorModel } from "./cursor-model.js";
1
2
  import { registerCursorModelLifecycle, type CursorModelLifecycleExtensionApi } from "./cursor-model-lifecycle.js";
3
+ import { resolveEffectiveCursorConfigForContext } from "./cursor-runtime-state.js";
2
4
 
3
5
  export type CursorAgentsContextExtensionApi = CursorModelLifecycleExtensionApi;
4
6
 
5
7
  export function registerCursorAgentsContextDedup(pi: CursorAgentsContextExtensionApi): void {
6
8
  registerCursorModelLifecycle(pi, {
7
9
  beforeAgentStart: async (event, ctx) => {
10
+ if (!isCursorModel(ctx.model)) return undefined;
8
11
  const { resolveCursorFacingSystemPrompt } = await import("./cursor-agents-context.js");
12
+ const runtime = resolveEffectiveCursorConfigForContext(ctx).runtime.value;
9
13
  const resolved = resolveCursorFacingSystemPrompt(
10
14
  event.systemPrompt,
11
15
  ctx.model,
12
16
  event.systemPromptOptions,
17
+ undefined,
18
+ undefined,
19
+ runtime,
13
20
  );
14
21
  if (resolved === event.systemPrompt) return undefined;
15
22
  return { systemPrompt: resolved };
@@ -11,6 +11,7 @@ import {
11
11
  resolveCursorSettingSources,
12
12
  } from "./cursor-setting-sources.js";
13
13
  import type { SettingSource } from "@cursor/sdk";
14
+ import type { CursorRuntime } from "./cursor-config.js";
14
15
  export { registerCursorAgentsContextDedup, type CursorAgentsContextExtensionApi } from "./cursor-agents-context-registration.js";
15
16
 
16
17
  export const CURSOR_PRESERVE_PI_AGENTS_MD_ENV = "PI_CURSOR_PRESERVE_PI_AGENTS_MD";
@@ -153,8 +154,9 @@ export function resolveCursorFacingSystemPrompt(
153
154
  systemPromptOptions?: BuildSystemPromptOptions,
154
155
  settingSourcesRaw?: string,
155
156
  agentDir?: string,
157
+ runtime: CursorRuntime = "local",
156
158
  ): string {
157
- if (!systemPromptOptions) return systemPrompt;
159
+ if (runtime === "cloud" || !systemPromptOptions) return systemPrompt;
158
160
  const contextFiles = systemPromptOptions.contextFiles ?? [];
159
161
  const settingSources =
160
162
  settingSourcesRaw === undefined
@@ -1,9 +1,10 @@
1
1
  export const CURSOR_API_KEY_ENV_VAR = "CURSOR_API_KEY";
2
+ const CURSOR_PROVIDER_ID = "cursor";
2
3
 
3
4
  // Non-secret literal sentinel for pi's provider registry. Pi 0.77 treats `$ENV_VAR`
4
5
  // values as unconfigured when the env var is absent, which hides fallback models
5
6
  // before `/login`. Keep the provider available and resolve the real key in the
6
- // Cursor provider turn path from pi auth, --api-key, or CURSOR_API_KEY.
7
+ // Cursor provider turn path from pi auth or CURSOR_API_KEY.
7
8
  export const CURSOR_API_KEY_CONFIG_VALUE = "pi-cursor-sdk-cursor-api-key-placeholder";
8
9
 
9
10
  const CURSOR_API_KEY_PLACEHOLDERS = new Set([
@@ -19,3 +20,16 @@ export function resolveCursorApiKey(apiKey?: string): string | undefined {
19
20
  if (CURSOR_API_KEY_PLACEHOLDERS.has(trimmed)) return process.env.CURSOR_API_KEY?.trim() || undefined;
20
21
  return trimmed;
21
22
  }
23
+
24
+ async function getStoredCursorApiKey(): Promise<string | undefined> {
25
+ try {
26
+ const { AuthStorage } = await import("@earendil-works/pi-coding-agent");
27
+ return resolveCursorApiKey(await AuthStorage.create().getApiKey(CURSOR_PROVIDER_ID, { includeFallback: false }));
28
+ } catch {
29
+ return undefined;
30
+ }
31
+ }
32
+
33
+ export async function resolveCursorRuntimeApiKey(): Promise<string | undefined> {
34
+ return (await getStoredCursorApiKey()) ?? resolveCursorApiKey(process.env.CURSOR_API_KEY);
35
+ }
@@ -1,9 +1,12 @@
1
1
  export const CURSOR_PI_BRIDGE_MCP_TOOL_PREFIX = "pi__";
2
+ export const CURSOR_PI_BRIDGE_PREFERENCE_TEXT =
3
+ "When exposed, prefer pi__mcp for MCP work and pi__subagent for delegation; use Cursor-configured MCP or Cursor-native subagents only when the matching pi__ tool is not exposed or unavailable.";
2
4
 
3
5
  const CURSOR_PI_BRIDGE_CONTRACT_LINES = [
4
6
  "Pi bridge contract:",
5
7
  `${CURSOR_PI_BRIDGE_MCP_TOOL_PREFIX}* names are live Cursor MCP bridge tool names only when exposed in the current run.`,
6
8
  `Call the ${CURSOR_PI_BRIDGE_MCP_TOOL_PREFIX}* MCP tool name, not the real pi tool name shown in pi history or transcripts.`,
9
+ CURSOR_PI_BRIDGE_PREFERENCE_TEXT,
7
10
  "Bridged calls execute through normal pi tool flow, so pi shows the real pi tool name and returns a normal pi tool result.",
8
11
  "Replay IDs, replay labels, and transcript tool names are display-only/context-only, not callable tools.",
9
12
  "Cursor-native host tools, settings, plugins, and configured MCP servers are separate from the pi bridge.",