pi-cursor-sdk 0.2.0 → 0.3.7
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 +73 -0
- package/README.md +55 -51
- package/dist/bundled-context-windows.js +39 -0
- package/dist/context-window-cache.js +92 -0
- package/dist/context.js +353 -0
- package/dist/cursor-active-tools.js +3 -0
- package/dist/cursor-agent-message-web-tools.js +165 -0
- package/dist/cursor-agents-context-registration.js +17 -0
- package/dist/cursor-agents-context.js +127 -0
- package/dist/cursor-api-key.js +34 -0
- package/dist/cursor-bridge-contract.js +27 -0
- package/dist/cursor-cloud-lifecycle.js +650 -0
- package/dist/cursor-cloud-local-state.js +460 -0
- package/dist/cursor-cloud-options.js +145 -0
- package/dist/cursor-cloud-reporting.js +222 -0
- package/dist/cursor-compact-tool-summary.js +75 -0
- package/dist/cursor-config.js +613 -0
- package/dist/cursor-context-tools.js +6 -0
- package/dist/cursor-display-only-trace.js +11 -0
- package/dist/cursor-display-text.js +18 -0
- package/dist/cursor-durable-fs.js +53 -0
- package/dist/cursor-edit-diff.js +13 -0
- package/dist/cursor-env-boolean.js +19 -0
- package/dist/cursor-fallback-models.generated.js +7760 -0
- package/dist/cursor-fallback-warning.js +15 -0
- package/dist/cursor-http1.js +45 -0
- package/dist/cursor-incomplete-tool-visibility.js +83 -0
- package/dist/cursor-live-run-accounting.js +55 -0
- package/dist/cursor-live-run-coordinator.js +409 -0
- package/dist/cursor-mcp-timeout-override.js +116 -0
- package/dist/cursor-model-lifecycle.js +32 -0
- package/dist/cursor-model.js +5 -0
- package/dist/cursor-native-replay-routing.js +35 -0
- package/dist/cursor-native-replay-trace.js +28 -0
- package/dist/cursor-native-tool-display-registration.js +102 -0
- package/dist/cursor-native-tool-display-replay.js +428 -0
- package/dist/cursor-native-tool-display-state.js +70 -0
- package/dist/cursor-native-tool-display-tools.js +123 -0
- package/dist/cursor-native-tool-names.js +11 -0
- package/dist/cursor-partial-content-emitter.js +130 -0
- package/dist/cursor-pi-context.js +30 -0
- package/dist/cursor-pi-tool-bridge-abort.js +114 -0
- package/dist/cursor-pi-tool-bridge-constants.js +6 -0
- package/dist/cursor-pi-tool-bridge-diagnostics.js +125 -0
- package/dist/cursor-pi-tool-bridge-env.js +18 -0
- package/dist/cursor-pi-tool-bridge-mcp.js +100 -0
- package/dist/cursor-pi-tool-bridge-run.js +355 -0
- package/dist/cursor-pi-tool-bridge-server.js +163 -0
- package/dist/cursor-pi-tool-bridge-snapshot.js +63 -0
- package/dist/cursor-pi-tool-bridge-types.js +1 -0
- package/dist/cursor-pi-tool-bridge.js +123 -0
- package/dist/cursor-provider-errors.js +311 -0
- package/dist/cursor-provider-lazy.js +39 -0
- package/dist/cursor-provider-live-run-drain.js +420 -0
- package/dist/cursor-provider-overflow.js +90 -0
- package/dist/cursor-provider-run-finalizer.js +176 -0
- package/dist/cursor-provider-run-outcome.js +76 -0
- package/dist/cursor-provider-turn-coordinator.js +293 -0
- package/dist/cursor-provider-turn-display-router.js +158 -0
- package/dist/cursor-provider-turn-emit.js +41 -0
- package/dist/cursor-provider-turn-finalize.js +154 -0
- package/dist/cursor-provider-turn-lifecycle-emitter.js +126 -0
- package/dist/cursor-provider-turn-prepare.js +379 -0
- package/dist/cursor-provider-turn-runner.js +146 -0
- package/dist/cursor-provider-turn-sdk-normalizer.js +74 -0
- package/dist/cursor-provider-turn-send.js +138 -0
- package/dist/cursor-provider-turn-shell-output.js +117 -0
- package/dist/cursor-provider-turn-tool-ledger.js +99 -0
- package/dist/cursor-provider-turn-types.js +1 -0
- package/dist/cursor-provider.js +72 -0
- package/dist/cursor-question-tool.js +194 -0
- package/dist/cursor-record-utils.js +61 -0
- package/dist/cursor-replay-activity-builders.js +114 -0
- package/dist/cursor-replay-source-names.js +27 -0
- package/dist/cursor-replay-summary-args.js +105 -0
- package/dist/cursor-replay-tool-details.js +167 -0
- package/dist/cursor-ripgrep-path.js +27 -0
- package/dist/cursor-run-final-text.js +63 -0
- package/dist/cursor-runtime-state.js +380 -0
- package/dist/cursor-sdk-billed-usage.js +86 -0
- package/dist/cursor-sdk-event-debug-constants.js +33 -0
- package/dist/cursor-sdk-event-debug-session.js +107 -0
- package/dist/cursor-sdk-event-debug.js +461 -0
- package/dist/cursor-sdk-output-filter.js +1 -0
- package/dist/cursor-sdk-process-error-guard.js +221 -0
- package/dist/cursor-sdk-runtime.js +3 -0
- package/dist/cursor-sensitive-text.js +23 -0
- package/dist/cursor-session-agent-cleanup.js +311 -0
- package/dist/cursor-session-agent-lifecycle.js +32 -0
- package/dist/cursor-session-agent-lineage.js +103 -0
- package/dist/cursor-session-agent-resume.js +417 -0
- package/dist/cursor-session-agent.js +507 -0
- package/dist/cursor-session-compaction-prep.js +21 -0
- package/dist/cursor-session-scope.js +113 -0
- package/dist/cursor-session-send-policy.js +18 -0
- package/dist/cursor-session-store.js +105 -0
- package/dist/cursor-session-turn-queue.js +50 -0
- package/dist/cursor-setting-sources.js +15 -0
- package/dist/cursor-skill-tool.js +230 -0
- package/dist/cursor-state.js +519 -0
- package/dist/cursor-task-presentation.js +59 -0
- package/dist/cursor-tool-lifecycle.js +94 -0
- package/dist/cursor-tool-manifest.js +34 -0
- package/dist/cursor-tool-presentation-registry.js +305 -0
- package/dist/cursor-tool-result-display-readers.js +158 -0
- package/dist/cursor-tool-transcript.js +32 -0
- package/dist/cursor-tool-visibility.js +28 -0
- package/dist/cursor-transcript-tool-formatters.js +618 -0
- package/dist/cursor-transcript-tool-specs.js +284 -0
- package/dist/cursor-transcript-utils.js +170 -0
- package/dist/cursor-usage-accounting.js +166 -0
- package/dist/cursor-web-tool-activity.js +23 -0
- package/dist/cursor-web-tool-args.js +12 -0
- package/dist/index.js +82 -0
- package/dist/model-discovery.js +305 -0
- package/dist/model-list-cache.js +156 -0
- package/docs/cursor-dogfood-checklist.md +2 -1
- package/docs/cursor-live-smoke-checklist.md +13 -12
- package/docs/cursor-model-ux-spec.md +27 -6
- package/docs/cursor-native-tool-replay.md +13 -13
- package/docs/cursor-native-tool-visual-audit.md +6 -3
- package/docs/cursor-testing-lessons.md +14 -8
- package/docs/cursor-tool-surfaces.md +5 -5
- package/docs/platform-smoke.md +3 -3
- package/package.json +19 -7
- package/platform-smoke.config.mjs +1 -1
- package/scripts/build.mjs +126 -0
- package/scripts/cloud-runtime-smoke.mjs +3 -1
- package/scripts/debug-provider-events.mjs +3 -1
- package/scripts/debug-sdk-events.mjs +1 -1
- package/scripts/isolated-cursor-smoke.sh +5 -4
- package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +1 -1
- package/scripts/lib/cursor-visual-render.mjs +1 -1
- package/scripts/lib/ensure-built.mjs +16 -0
- package/scripts/lib/local-resume-smoke-harness.mjs +1 -1
- package/scripts/local-resume-smoke.mjs +3 -1
- package/scripts/platform-smoke/card-detect.mjs +1 -1
- package/scripts/platform-smoke/live-suite-runner.mjs +1 -1
- package/scripts/platform-smoke/render-ansi.mjs +1 -1
- package/scripts/platform-smoke/targets.mjs +2 -2
- package/scripts/prepare.mjs +102 -0
- package/scripts/steering-rpc-smoke.mjs +4 -2
- package/scripts/tmux-live-smoke.sh +11 -11
- package/scripts/visual-tui-smoke-self-test.mjs +4 -1
- package/scripts/visual-tui-smoke.mjs +51 -7
- package/src/bundled-context-windows.ts +4 -1
- package/src/context.ts +27 -4
- package/src/cursor-agents-context-registration.ts +2 -2
- package/src/cursor-agents-context.ts +24 -10
- package/src/cursor-api-key.ts +5 -4
- package/src/cursor-cloud-reporting.ts +23 -2
- package/src/cursor-context-tools.ts +3 -1
- package/src/cursor-fallback-models.generated.ts +592 -204
- package/src/cursor-live-run-coordinator.ts +11 -6
- package/src/cursor-native-tool-display-registration.ts +10 -11
- package/src/cursor-partial-content-emitter.ts +13 -0
- package/src/cursor-pi-context.ts +44 -0
- package/src/cursor-provider-errors.ts +1 -1
- package/src/cursor-provider-lazy.ts +5 -4
- package/src/cursor-provider-live-run-drain.ts +14 -9
- package/src/cursor-provider-run-finalizer.ts +1 -0
- package/src/cursor-provider-turn-coordinator.ts +15 -0
- package/src/cursor-provider-turn-emit.ts +6 -0
- package/src/cursor-provider-turn-finalize.ts +12 -0
- package/src/cursor-provider-turn-prepare.ts +10 -5
- package/src/cursor-provider-turn-types.ts +2 -0
- package/src/cursor-sdk-billed-usage.ts +107 -0
- package/src/cursor-sdk-process-error-guard.ts +6 -4
- package/src/cursor-session-agent-lifecycle.ts +8 -9
- package/src/cursor-session-agent-resume.ts +23 -0
- package/src/cursor-session-compaction-prep.ts +4 -0
- package/src/cursor-skill-tool.ts +1 -1
- package/src/cursor-tool-manifest.ts +1 -1
- package/src/cursor-usage-accounting.ts +70 -9
- package/src/index.ts +1 -1
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { parseOptionalEnvBoolean } from "./cursor-env-boolean.js";
|
|
6
|
+
import { asRecord } from "./cursor-record-utils.js";
|
|
7
|
+
export const CURSOR_SDK_CONFIG_FILE = "cursor-sdk.json";
|
|
8
|
+
export const CURSOR_RUNTIME_ENV = "PI_CURSOR_RUNTIME";
|
|
9
|
+
export const CURSOR_CLOUD_REPO_ENV = "PI_CURSOR_CLOUD_REPO";
|
|
10
|
+
export const CURSOR_CLOUD_BRANCH_ENV = "PI_CURSOR_CLOUD_BRANCH";
|
|
11
|
+
export const CURSOR_CLOUD_CONTEXT_ENV = "PI_CURSOR_CLOUD_CONTEXT";
|
|
12
|
+
export const CURSOR_CLOUD_DIRECT_PUSH_ENV = "PI_CURSOR_CLOUD_DIRECT_PUSH";
|
|
13
|
+
export const CURSOR_CLOUD_AUTO_CREATE_PR_ENV = "PI_CURSOR_CLOUD_AUTO_CREATE_PR";
|
|
14
|
+
export const CURSOR_CLOUD_SKIP_REVIEWER_REQUEST_ENV = "PI_CURSOR_CLOUD_SKIP_REVIEWER_REQUEST";
|
|
15
|
+
export const CURSOR_CLOUD_ALLOW_LOCAL_STATE_ENV = "PI_CURSOR_CLOUD_ALLOW_LOCAL_STATE";
|
|
16
|
+
export const CURSOR_CLOUD_ENV_ENV = "PI_CURSOR_CLOUD_ENV";
|
|
17
|
+
export const CURSOR_CLOUD_ENV_FROM_FILES_ENV = "PI_CURSOR_CLOUD_ENV_FROM_FILES";
|
|
18
|
+
export const CURSOR_CLOUD_ENV_TYPE_ENV = "PI_CURSOR_CLOUD_ENV_TYPE";
|
|
19
|
+
export const CURSOR_CLOUD_ENV_NAME_ENV = "PI_CURSOR_CLOUD_ENV_NAME";
|
|
20
|
+
export const CURSOR_CLOUD_ACK_ENV = "PI_CURSOR_CLOUD_ACK";
|
|
21
|
+
export const CURSOR_AUTO_REVIEW_ENV = "PI_CURSOR_AUTO_REVIEW";
|
|
22
|
+
export const CURSOR_SANDBOX_ENV = "PI_CURSOR_SANDBOX";
|
|
23
|
+
export const CURSOR_LOCAL_FORCE_ENV = "PI_CURSOR_LOCAL_FORCE";
|
|
24
|
+
export const CURSOR_LOCAL_RESUME_ENV = "PI_CURSOR_LOCAL_RESUME";
|
|
25
|
+
export const CURSOR_HTTP1_ENV = "PI_CURSOR_HTTP_1_1";
|
|
26
|
+
const TRUST_LEVELS = {
|
|
27
|
+
cli: "one-shot",
|
|
28
|
+
environment: "environment",
|
|
29
|
+
project: "trusted-project",
|
|
30
|
+
user: "user",
|
|
31
|
+
session: "session",
|
|
32
|
+
"model-alias": "model-catalog",
|
|
33
|
+
builtin: "builtin",
|
|
34
|
+
};
|
|
35
|
+
const BUILT_IN_CURSOR_CONFIG = {
|
|
36
|
+
runtime: "local",
|
|
37
|
+
cloud: {
|
|
38
|
+
repo: "",
|
|
39
|
+
branch: "",
|
|
40
|
+
contextHandoff: "fresh",
|
|
41
|
+
directPush: false,
|
|
42
|
+
autoCreatePR: false,
|
|
43
|
+
skipReviewerRequest: false,
|
|
44
|
+
allowLocalState: false,
|
|
45
|
+
envNames: [],
|
|
46
|
+
envFromFiles: false,
|
|
47
|
+
environment: {},
|
|
48
|
+
acknowledged: false,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
export function isCursorRuntime(value) {
|
|
52
|
+
return value === "local" || value === "cloud";
|
|
53
|
+
}
|
|
54
|
+
export function isCursorCloudContextHandoff(value) {
|
|
55
|
+
return value === "never" || value === "fresh" || value === "bootstrap";
|
|
56
|
+
}
|
|
57
|
+
function validateExplicitValue(raw, name, isValid, validValues) {
|
|
58
|
+
const value = raw?.trim();
|
|
59
|
+
if (!value)
|
|
60
|
+
return undefined;
|
|
61
|
+
if (!isValid(value))
|
|
62
|
+
throw new Error(`Invalid ${name} "${value}". Use ${validValues}.`);
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
export function isCursorCloudEnvironmentType(value) {
|
|
66
|
+
return value === "cloud" || value === "pool" || value === "machine";
|
|
67
|
+
}
|
|
68
|
+
function parseNonEmptyString(value) {
|
|
69
|
+
if (typeof value !== "string")
|
|
70
|
+
return undefined;
|
|
71
|
+
const trimmed = value.trim();
|
|
72
|
+
return trimmed ? trimmed : undefined;
|
|
73
|
+
}
|
|
74
|
+
function isAllowedEnvName(value) {
|
|
75
|
+
return typeof value === "string" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(value) && !value.startsWith("CURSOR_");
|
|
76
|
+
}
|
|
77
|
+
function parseEnvNames(value) {
|
|
78
|
+
const names = Array.isArray(value)
|
|
79
|
+
? value
|
|
80
|
+
: typeof value === "string"
|
|
81
|
+
? value.split(",")
|
|
82
|
+
: undefined;
|
|
83
|
+
if (!names)
|
|
84
|
+
return undefined;
|
|
85
|
+
const parsed = [...new Set(names.map((name) => (typeof name === "string" ? name.trim() : "")).filter(isAllowedEnvName))];
|
|
86
|
+
return parsed.length > 0 || (Array.isArray(value) && value.length === 0) ? parsed : undefined;
|
|
87
|
+
}
|
|
88
|
+
export function parseExplicitCursorCloudEnvNames(value, name) {
|
|
89
|
+
const request = parseNonEmptyString(value);
|
|
90
|
+
if (!request)
|
|
91
|
+
return undefined;
|
|
92
|
+
const parsed = parseEnvNames(request);
|
|
93
|
+
if (!parsed?.length)
|
|
94
|
+
throw new Error(`Invalid ${name}: no valid environment variable names were requested.`);
|
|
95
|
+
return parsed;
|
|
96
|
+
}
|
|
97
|
+
function parseCloudEnvironment(value) {
|
|
98
|
+
const environment = asRecord(value);
|
|
99
|
+
if (!environment)
|
|
100
|
+
return undefined;
|
|
101
|
+
const parsed = {};
|
|
102
|
+
const rawType = parseNonEmptyString(environment.type);
|
|
103
|
+
const name = parseNonEmptyString(environment.name);
|
|
104
|
+
if (rawType)
|
|
105
|
+
parsed.type = rawType;
|
|
106
|
+
if (name)
|
|
107
|
+
parsed.name = name;
|
|
108
|
+
return Object.keys(parsed).length > 0 ? parsed : undefined;
|
|
109
|
+
}
|
|
110
|
+
export function parseCursorSdkConfig(value) {
|
|
111
|
+
const record = asRecord(value);
|
|
112
|
+
if (!record)
|
|
113
|
+
return undefined;
|
|
114
|
+
const config = {};
|
|
115
|
+
if (isCursorRuntime(record.runtime))
|
|
116
|
+
config.runtime = record.runtime;
|
|
117
|
+
const fastDefaults = asRecord(record.fastDefaults);
|
|
118
|
+
if (fastDefaults) {
|
|
119
|
+
config.fastDefaults = Object.fromEntries(Object.entries(fastDefaults).filter((entry) => typeof entry[1] === "boolean"));
|
|
120
|
+
}
|
|
121
|
+
const cloud = asRecord(record.cloud);
|
|
122
|
+
if (cloud) {
|
|
123
|
+
const parsedCloud = {};
|
|
124
|
+
const repo = parseNonEmptyString(cloud.repo);
|
|
125
|
+
const branch = parseNonEmptyString(cloud.branch);
|
|
126
|
+
const envNames = parseEnvNames(cloud.envNames);
|
|
127
|
+
const environment = parseCloudEnvironment(cloud.environment);
|
|
128
|
+
if (repo)
|
|
129
|
+
parsedCloud.repo = repo;
|
|
130
|
+
if (branch)
|
|
131
|
+
parsedCloud.branch = branch;
|
|
132
|
+
if (isCursorCloudContextHandoff(cloud.contextHandoff))
|
|
133
|
+
parsedCloud.contextHandoff = cloud.contextHandoff;
|
|
134
|
+
if (typeof cloud.directPush === "boolean")
|
|
135
|
+
parsedCloud.directPush = cloud.directPush;
|
|
136
|
+
if (typeof cloud.autoCreatePR === "boolean")
|
|
137
|
+
parsedCloud.autoCreatePR = cloud.autoCreatePR;
|
|
138
|
+
if (typeof cloud.skipReviewerRequest === "boolean")
|
|
139
|
+
parsedCloud.skipReviewerRequest = cloud.skipReviewerRequest;
|
|
140
|
+
if (typeof cloud.allowLocalState === "boolean")
|
|
141
|
+
parsedCloud.allowLocalState = cloud.allowLocalState;
|
|
142
|
+
if (envNames)
|
|
143
|
+
parsedCloud.envNames = envNames;
|
|
144
|
+
if (typeof cloud.envFromFiles === "boolean")
|
|
145
|
+
parsedCloud.envFromFiles = cloud.envFromFiles;
|
|
146
|
+
if (environment)
|
|
147
|
+
parsedCloud.environment = environment;
|
|
148
|
+
if (typeof cloud.acknowledged === "boolean")
|
|
149
|
+
parsedCloud.acknowledged = cloud.acknowledged;
|
|
150
|
+
if (Object.keys(parsedCloud).length > 0)
|
|
151
|
+
config.cloud = parsedCloud;
|
|
152
|
+
}
|
|
153
|
+
const local = asRecord(record.local);
|
|
154
|
+
if (local) {
|
|
155
|
+
const parsedLocal = {};
|
|
156
|
+
if (typeof local.autoReview === "boolean")
|
|
157
|
+
parsedLocal.autoReview = local.autoReview;
|
|
158
|
+
if (typeof local.sandbox === "boolean")
|
|
159
|
+
parsedLocal.sandbox = local.sandbox;
|
|
160
|
+
if (typeof local.force === "boolean")
|
|
161
|
+
parsedLocal.force = local.force;
|
|
162
|
+
if (typeof local.resume === "boolean")
|
|
163
|
+
parsedLocal.resume = local.resume;
|
|
164
|
+
if (typeof local.useHttp1ForAgent === "boolean")
|
|
165
|
+
parsedLocal.useHttp1ForAgent = local.useHttp1ForAgent;
|
|
166
|
+
const sandboxOptions = asRecord(local.sandboxOptions);
|
|
167
|
+
if (typeof sandboxOptions?.enabled === "boolean")
|
|
168
|
+
parsedLocal.sandboxOptions = { enabled: sandboxOptions.enabled };
|
|
169
|
+
if (Object.keys(parsedLocal).length > 0)
|
|
170
|
+
config.local = parsedLocal;
|
|
171
|
+
}
|
|
172
|
+
return config;
|
|
173
|
+
}
|
|
174
|
+
export function getCursorSdkUserConfigPath(agentDir = getAgentDir()) {
|
|
175
|
+
return join(agentDir, CURSOR_SDK_CONFIG_FILE);
|
|
176
|
+
}
|
|
177
|
+
export function getCursorSdkProjectConfigPath(cwd, configDirName = CONFIG_DIR_NAME) {
|
|
178
|
+
return join(cwd, configDirName, CURSOR_SDK_CONFIG_FILE);
|
|
179
|
+
}
|
|
180
|
+
function readCursorSdkConfigFile(path) {
|
|
181
|
+
if (!existsSync(path))
|
|
182
|
+
return {};
|
|
183
|
+
try {
|
|
184
|
+
return parseCursorSdkConfig(JSON.parse(readFileSync(path, "utf-8"))) ?? {};
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return {};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export function loadCursorSdkConfigForUpdate(path) {
|
|
191
|
+
if (!existsSync(path))
|
|
192
|
+
return {};
|
|
193
|
+
let source;
|
|
194
|
+
try {
|
|
195
|
+
source = readFileSync(path, "utf-8");
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
throw new Error(`Failed to read Cursor SDK config ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
199
|
+
}
|
|
200
|
+
let parsed;
|
|
201
|
+
try {
|
|
202
|
+
parsed = JSON.parse(source);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
throw new Error(`Invalid JSON in Cursor SDK config ${path}`);
|
|
206
|
+
}
|
|
207
|
+
const record = asRecord(parsed);
|
|
208
|
+
if (!record)
|
|
209
|
+
throw new Error(`Invalid Cursor SDK config ${path}: expected a JSON object`);
|
|
210
|
+
return record;
|
|
211
|
+
}
|
|
212
|
+
export function loadCursorSdkUserConfig(path = getCursorSdkUserConfigPath()) {
|
|
213
|
+
return readCursorSdkConfigFile(path);
|
|
214
|
+
}
|
|
215
|
+
export function loadCursorSdkProjectConfig(cwd, projectTrusted) {
|
|
216
|
+
if (!projectTrusted)
|
|
217
|
+
return undefined;
|
|
218
|
+
const path = getCursorSdkProjectConfigPath(cwd);
|
|
219
|
+
return existsSync(path) ? readCursorSdkConfigFile(path) : undefined;
|
|
220
|
+
}
|
|
221
|
+
export function loadCursorSdkConfig(options = {}) {
|
|
222
|
+
const user = loadCursorSdkUserConfig(getCursorSdkUserConfigPath(options.agentDir));
|
|
223
|
+
const project = options.cwd ? loadCursorSdkProjectConfig(options.cwd, options.projectTrusted === true) : undefined;
|
|
224
|
+
return project ? { user, project } : { user };
|
|
225
|
+
}
|
|
226
|
+
const CONFIG_LOCK_RETRY_MS = 20;
|
|
227
|
+
const CONFIG_LOCK_TIMEOUT_MS = 5_000;
|
|
228
|
+
const configLockWaitBuffer = new Int32Array(new SharedArrayBuffer(4));
|
|
229
|
+
function sleepForConfigLock() {
|
|
230
|
+
Atomics.wait(configLockWaitBuffer, 0, 0, CONFIG_LOCK_RETRY_MS);
|
|
231
|
+
}
|
|
232
|
+
function acquireCursorSdkConfigLock(path) {
|
|
233
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
234
|
+
const lockPath = `${path}.lock`;
|
|
235
|
+
const deadline = Date.now() + CONFIG_LOCK_TIMEOUT_MS;
|
|
236
|
+
while (true) {
|
|
237
|
+
try {
|
|
238
|
+
const descriptor = openSync(lockPath, "wx", 0o600);
|
|
239
|
+
return () => {
|
|
240
|
+
try {
|
|
241
|
+
closeSync(descriptor);
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
rmSync(lockPath, { force: true });
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
if (error.code !== "EEXIST")
|
|
250
|
+
throw error;
|
|
251
|
+
if (Date.now() >= deadline) {
|
|
252
|
+
throw new Error(`Timed out waiting for Cursor SDK config lock ${lockPath}; remove it only if no pi process is writing this config`);
|
|
253
|
+
}
|
|
254
|
+
sleepForConfigLock();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function replaceJsonFile(path, config, mode) {
|
|
259
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
260
|
+
const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
261
|
+
let replaced = false;
|
|
262
|
+
try {
|
|
263
|
+
writeFileSync(tempPath, `${JSON.stringify(config, null, 2)}\n`, {
|
|
264
|
+
encoding: "utf8",
|
|
265
|
+
flag: "wx",
|
|
266
|
+
...(mode === undefined ? {} : { mode }),
|
|
267
|
+
});
|
|
268
|
+
if (mode !== undefined)
|
|
269
|
+
chmodSync(tempPath, mode);
|
|
270
|
+
renameSync(tempPath, path);
|
|
271
|
+
replaced = true;
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
if (!replaced) {
|
|
275
|
+
try {
|
|
276
|
+
rmSync(tempPath, { force: true });
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
// Keep the replacement failure; cleanup is best effort.
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function withCursorSdkConfigLock(path, operation) {
|
|
285
|
+
const release = acquireCursorSdkConfigLock(path);
|
|
286
|
+
try {
|
|
287
|
+
return operation();
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
release();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
export function updateCursorSdkConfig(path, update, options = {}) {
|
|
294
|
+
return withCursorSdkConfigLock(path, () => {
|
|
295
|
+
const updated = update(loadCursorSdkConfigForUpdate(path));
|
|
296
|
+
const mode = existsSync(path) ? statSync(path).mode & 0o777 : options.newFileMode;
|
|
297
|
+
replaceJsonFile(path, updated, mode);
|
|
298
|
+
return updated;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
export function saveCursorSdkUserConfig(config, path = getCursorSdkUserConfigPath()) {
|
|
302
|
+
updateCursorSdkConfig(path, () => ({ ...config }), { newFileMode: 0o600 });
|
|
303
|
+
}
|
|
304
|
+
export function saveCursorSdkProjectConfig(cwd, config, configDirName = CONFIG_DIR_NAME) {
|
|
305
|
+
const path = getCursorSdkProjectConfigPath(cwd, configDirName);
|
|
306
|
+
updateCursorSdkConfig(path, () => ({ ...config }));
|
|
307
|
+
}
|
|
308
|
+
export function mergeCursorSdkConfig(base, patch) {
|
|
309
|
+
return mergeCursorSdkConfigForUpdate({ ...base }, patch);
|
|
310
|
+
}
|
|
311
|
+
export function mergeCursorSdkConfigForUpdate(base, patch) {
|
|
312
|
+
const baseCloud = asRecord(base.cloud);
|
|
313
|
+
const baseLocal = asRecord(base.local);
|
|
314
|
+
const baseSandboxOptions = asRecord(baseLocal?.sandboxOptions);
|
|
315
|
+
return {
|
|
316
|
+
...base,
|
|
317
|
+
...patch,
|
|
318
|
+
...(baseCloud || patch.cloud
|
|
319
|
+
? { cloud: { ...baseCloud, ...patch.cloud } }
|
|
320
|
+
: {}),
|
|
321
|
+
...(baseLocal || patch.local
|
|
322
|
+
? {
|
|
323
|
+
local: {
|
|
324
|
+
...baseLocal,
|
|
325
|
+
...patch.local,
|
|
326
|
+
...(baseSandboxOptions || patch.local?.sandboxOptions
|
|
327
|
+
? { sandboxOptions: { ...baseSandboxOptions, ...patch.local?.sandboxOptions } }
|
|
328
|
+
: {}),
|
|
329
|
+
},
|
|
330
|
+
}
|
|
331
|
+
: {}),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
export function cursorFastDefaultsFromConfig(config) {
|
|
335
|
+
return new Map(Object.entries(config?.fastDefaults ?? {}));
|
|
336
|
+
}
|
|
337
|
+
export function withCursorFastDefaults(config, fastDefaults) {
|
|
338
|
+
return {
|
|
339
|
+
...config,
|
|
340
|
+
fastDefaults: Object.fromEntries([...fastDefaults.entries()].sort(([a], [b]) => a.localeCompare(b))),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function resolved(source, value, cappedBy) {
|
|
344
|
+
return { value, source, trustLevel: TRUST_LEVELS[source], ...(cappedBy ? { cappedBy } : {}) };
|
|
345
|
+
}
|
|
346
|
+
function valueFrom(source, value) {
|
|
347
|
+
return value === undefined ? undefined : resolved(source, value);
|
|
348
|
+
}
|
|
349
|
+
function resolveOrdinary(layers) {
|
|
350
|
+
const match = layers.find((layer) => layer !== undefined);
|
|
351
|
+
if (!match)
|
|
352
|
+
throw new Error("Cursor config resolver missing built-in default");
|
|
353
|
+
return match;
|
|
354
|
+
}
|
|
355
|
+
// One cap engine for every safety-capped field. `cap` receives the candidate cap layer's raw value and the
|
|
356
|
+
// resolved base setting; it returns the value to use once capped, or undefined to mean "don't cap". CLI is
|
|
357
|
+
// always uncapped (one-shot, explicit operator intent).
|
|
358
|
+
function resolveWithCap(baseLayers, capLayer, cap) {
|
|
359
|
+
const cli = baseLayers[0];
|
|
360
|
+
if (cli)
|
|
361
|
+
return cli;
|
|
362
|
+
const base = resolveOrdinary(baseLayers.slice(1));
|
|
363
|
+
if (!capLayer)
|
|
364
|
+
return base;
|
|
365
|
+
const cappedValue = cap(capLayer.value, base);
|
|
366
|
+
if (cappedValue === undefined)
|
|
367
|
+
return base;
|
|
368
|
+
return resolved(capLayer.source, cappedValue, {
|
|
369
|
+
source: capLayer.source,
|
|
370
|
+
trustLevel: capLayer.trustLevel,
|
|
371
|
+
value: cappedValue,
|
|
372
|
+
cappedSource: base.source,
|
|
373
|
+
cappedValue: base.value,
|
|
374
|
+
reason: "safer-source",
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
function scalarRiskCap(risk) {
|
|
378
|
+
return (candidate, base) => (risk(candidate) < risk(base.value) ? candidate : undefined);
|
|
379
|
+
}
|
|
380
|
+
function envNamesCap(candidate, base) {
|
|
381
|
+
if (base.source === "user")
|
|
382
|
+
return undefined;
|
|
383
|
+
const allowed = new Set(candidate);
|
|
384
|
+
const filtered = base.value.filter((name) => allowed.has(name));
|
|
385
|
+
return filtered.length === base.value.length ? undefined : filtered;
|
|
386
|
+
}
|
|
387
|
+
const RUNTIME_ORDER = ["cli", "environment", "session", "project", "user", "builtin"];
|
|
388
|
+
const CLOUD_ORDER = ["cli", "environment", "session", "user", "builtin"];
|
|
389
|
+
const LOCAL_ORDER = ["cli", "environment", "project", "user", "builtin"];
|
|
390
|
+
const LOCAL_FORCE_ORDER = ["cli", "environment", "builtin"];
|
|
391
|
+
const HTTP1_ORDER = ["session", "environment", "user", "builtin"];
|
|
392
|
+
function buildFieldLayers(order, values) {
|
|
393
|
+
return order.map((source) => (source === "builtin" ? resolved("builtin", values.builtin) : valueFrom(source, values[source])));
|
|
394
|
+
}
|
|
395
|
+
function resolveOrdinaryField(order, values) {
|
|
396
|
+
return resolveOrdinary(buildFieldLayers(order, values));
|
|
397
|
+
}
|
|
398
|
+
function resolveSafetyField(order, values, risk) {
|
|
399
|
+
return resolveWithCap(buildFieldLayers(order, values), valueFrom("user", values.user), scalarRiskCap(risk));
|
|
400
|
+
}
|
|
401
|
+
function resolveEnvNamesSafetyField(order, values) {
|
|
402
|
+
return resolveWithCap(buildFieldLayers(order, values), valueFrom("user", values.user), envNamesCap);
|
|
403
|
+
}
|
|
404
|
+
export function cursorSdkConfigFromEnv(env = process.env) {
|
|
405
|
+
const config = {};
|
|
406
|
+
const runtime = validateExplicitValue(env[CURSOR_RUNTIME_ENV], CURSOR_RUNTIME_ENV, isCursorRuntime, '"local" or "cloud"');
|
|
407
|
+
if (runtime)
|
|
408
|
+
config.runtime = runtime;
|
|
409
|
+
const repo = parseNonEmptyString(env[CURSOR_CLOUD_REPO_ENV]);
|
|
410
|
+
const branch = parseNonEmptyString(env[CURSOR_CLOUD_BRANCH_ENV]);
|
|
411
|
+
const contextHandoff = validateExplicitValue(env[CURSOR_CLOUD_CONTEXT_ENV], CURSOR_CLOUD_CONTEXT_ENV, isCursorCloudContextHandoff, '"never", "fresh", or "bootstrap"');
|
|
412
|
+
const directPush = parseOptionalEnvBoolean(env[CURSOR_CLOUD_DIRECT_PUSH_ENV]);
|
|
413
|
+
const autoCreatePR = parseOptionalEnvBoolean(env[CURSOR_CLOUD_AUTO_CREATE_PR_ENV]);
|
|
414
|
+
const skipReviewerRequest = parseOptionalEnvBoolean(env[CURSOR_CLOUD_SKIP_REVIEWER_REQUEST_ENV]);
|
|
415
|
+
const allowLocalState = parseOptionalEnvBoolean(env[CURSOR_CLOUD_ALLOW_LOCAL_STATE_ENV]);
|
|
416
|
+
const envNames = parseExplicitCursorCloudEnvNames(env[CURSOR_CLOUD_ENV_ENV], CURSOR_CLOUD_ENV_ENV);
|
|
417
|
+
const envFromFiles = parseOptionalEnvBoolean(env[CURSOR_CLOUD_ENV_FROM_FILES_ENV]);
|
|
418
|
+
const environment = parseCloudEnvironment({
|
|
419
|
+
type: env[CURSOR_CLOUD_ENV_TYPE_ENV],
|
|
420
|
+
name: env[CURSOR_CLOUD_ENV_NAME_ENV],
|
|
421
|
+
});
|
|
422
|
+
const acknowledged = parseOptionalEnvBoolean(env[CURSOR_CLOUD_ACK_ENV]);
|
|
423
|
+
if (repo !== undefined ||
|
|
424
|
+
branch !== undefined ||
|
|
425
|
+
isCursorCloudContextHandoff(contextHandoff) ||
|
|
426
|
+
directPush !== undefined ||
|
|
427
|
+
autoCreatePR !== undefined ||
|
|
428
|
+
skipReviewerRequest !== undefined ||
|
|
429
|
+
allowLocalState !== undefined ||
|
|
430
|
+
envNames !== undefined ||
|
|
431
|
+
envFromFiles !== undefined ||
|
|
432
|
+
environment !== undefined ||
|
|
433
|
+
acknowledged !== undefined) {
|
|
434
|
+
config.cloud = {
|
|
435
|
+
...(repo !== undefined ? { repo } : {}),
|
|
436
|
+
...(branch !== undefined ? { branch } : {}),
|
|
437
|
+
...(isCursorCloudContextHandoff(contextHandoff) ? { contextHandoff } : {}),
|
|
438
|
+
...(directPush !== undefined ? { directPush } : {}),
|
|
439
|
+
...(autoCreatePR !== undefined ? { autoCreatePR } : {}),
|
|
440
|
+
...(skipReviewerRequest !== undefined ? { skipReviewerRequest } : {}),
|
|
441
|
+
...(allowLocalState !== undefined ? { allowLocalState } : {}),
|
|
442
|
+
...(envNames !== undefined ? { envNames } : {}),
|
|
443
|
+
...(envFromFiles !== undefined ? { envFromFiles } : {}),
|
|
444
|
+
...(environment !== undefined ? { environment } : {}),
|
|
445
|
+
...(acknowledged !== undefined ? { acknowledged } : {}),
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
const autoReview = parseOptionalEnvBoolean(env[CURSOR_AUTO_REVIEW_ENV]);
|
|
449
|
+
const sandbox = parseOptionalEnvBoolean(env[CURSOR_SANDBOX_ENV]);
|
|
450
|
+
const force = parseOptionalEnvBoolean(env[CURSOR_LOCAL_FORCE_ENV]);
|
|
451
|
+
const resume = parseOptionalEnvBoolean(env[CURSOR_LOCAL_RESUME_ENV]);
|
|
452
|
+
const useHttp1ForAgent = parseOptionalEnvBoolean(env[CURSOR_HTTP1_ENV]);
|
|
453
|
+
if (autoReview !== undefined || sandbox !== undefined || force !== undefined || resume !== undefined || useHttp1ForAgent !== undefined) {
|
|
454
|
+
config.local = {
|
|
455
|
+
...(autoReview !== undefined ? { autoReview } : {}),
|
|
456
|
+
...(sandbox !== undefined ? { sandboxOptions: { enabled: sandbox } } : {}),
|
|
457
|
+
...(force !== undefined ? { force } : {}),
|
|
458
|
+
...(resume !== undefined ? { resume } : {}),
|
|
459
|
+
...(useHttp1ForAgent !== undefined ? { useHttp1ForAgent } : {}),
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
return config;
|
|
463
|
+
}
|
|
464
|
+
export function resolveCursorSdkConfig(options = {}) {
|
|
465
|
+
const cliRuntime = validateExplicitValue(options.cli?.runtime, "--cursor-runtime", isCursorRuntime, '"local" or "cloud"');
|
|
466
|
+
const cliContextHandoff = validateExplicitValue(options.cli?.cloud?.contextHandoff, "--cursor-cloud-context", isCursorCloudContextHandoff, '"never", "fresh", or "bootstrap"');
|
|
467
|
+
const env = cursorSdkConfigFromEnv(options.env);
|
|
468
|
+
const builtIn = {
|
|
469
|
+
...BUILT_IN_CURSOR_CONFIG,
|
|
470
|
+
...options.builtIn,
|
|
471
|
+
cloud: { ...BUILT_IN_CURSOR_CONFIG.cloud, ...options.builtIn?.cloud },
|
|
472
|
+
};
|
|
473
|
+
const cli = options.cli;
|
|
474
|
+
const session = options.session;
|
|
475
|
+
const project = options.project;
|
|
476
|
+
const user = options.user;
|
|
477
|
+
return {
|
|
478
|
+
runtime: resolveSafetyField(RUNTIME_ORDER, {
|
|
479
|
+
cli: cliRuntime,
|
|
480
|
+
environment: env.runtime,
|
|
481
|
+
session: session?.runtime,
|
|
482
|
+
project: project?.runtime,
|
|
483
|
+
user: user?.runtime,
|
|
484
|
+
builtin: builtIn.runtime,
|
|
485
|
+
}, (value) => (value === "cloud" ? 1 : 0)),
|
|
486
|
+
cloud: {
|
|
487
|
+
repo: resolveOrdinaryField(CLOUD_ORDER, {
|
|
488
|
+
cli: cli?.cloud?.repo,
|
|
489
|
+
environment: env.cloud?.repo,
|
|
490
|
+
session: session?.cloud?.repo,
|
|
491
|
+
user: user?.cloud?.repo,
|
|
492
|
+
builtin: undefined,
|
|
493
|
+
}),
|
|
494
|
+
branch: resolveOrdinaryField(CLOUD_ORDER, {
|
|
495
|
+
cli: cli?.cloud?.branch,
|
|
496
|
+
environment: env.cloud?.branch,
|
|
497
|
+
session: session?.cloud?.branch,
|
|
498
|
+
user: user?.cloud?.branch,
|
|
499
|
+
builtin: undefined,
|
|
500
|
+
}),
|
|
501
|
+
contextHandoff: resolveSafetyField(CLOUD_ORDER, {
|
|
502
|
+
cli: cliContextHandoff,
|
|
503
|
+
environment: env.cloud?.contextHandoff,
|
|
504
|
+
session: session?.cloud?.contextHandoff,
|
|
505
|
+
user: user?.cloud?.contextHandoff,
|
|
506
|
+
builtin: builtIn.cloud.contextHandoff,
|
|
507
|
+
}, (value) => ({ never: 0, fresh: 1, bootstrap: 2 })[value]),
|
|
508
|
+
directPush: resolveSafetyField(CLOUD_ORDER, {
|
|
509
|
+
cli: cli?.cloud?.directPush,
|
|
510
|
+
environment: env.cloud?.directPush,
|
|
511
|
+
session: session?.cloud?.directPush,
|
|
512
|
+
user: user?.cloud?.directPush,
|
|
513
|
+
builtin: builtIn.cloud.directPush,
|
|
514
|
+
}, (value) => (value ? 1 : 0)),
|
|
515
|
+
autoCreatePR: resolveSafetyField(CLOUD_ORDER, {
|
|
516
|
+
cli: cli?.cloud?.autoCreatePR,
|
|
517
|
+
environment: env.cloud?.autoCreatePR,
|
|
518
|
+
session: session?.cloud?.autoCreatePR,
|
|
519
|
+
user: user?.cloud?.autoCreatePR,
|
|
520
|
+
builtin: builtIn.cloud.autoCreatePR,
|
|
521
|
+
}, (value) => (value ? 1 : 0)),
|
|
522
|
+
skipReviewerRequest: resolveSafetyField(CLOUD_ORDER, {
|
|
523
|
+
cli: cli?.cloud?.skipReviewerRequest,
|
|
524
|
+
environment: env.cloud?.skipReviewerRequest,
|
|
525
|
+
session: session?.cloud?.skipReviewerRequest,
|
|
526
|
+
user: user?.cloud?.skipReviewerRequest,
|
|
527
|
+
builtin: builtIn.cloud.skipReviewerRequest,
|
|
528
|
+
}, (value) => (value ? 1 : 0)),
|
|
529
|
+
allowLocalState: resolveSafetyField(CLOUD_ORDER, {
|
|
530
|
+
cli: cli?.cloud?.allowLocalState,
|
|
531
|
+
environment: env.cloud?.allowLocalState,
|
|
532
|
+
session: session?.cloud?.allowLocalState,
|
|
533
|
+
user: user?.cloud?.allowLocalState,
|
|
534
|
+
builtin: builtIn.cloud.allowLocalState,
|
|
535
|
+
}, (value) => (value ? 1 : 0)),
|
|
536
|
+
envNames: resolveEnvNamesSafetyField(CLOUD_ORDER, {
|
|
537
|
+
cli: cli?.cloud?.envNames,
|
|
538
|
+
environment: env.cloud?.envNames,
|
|
539
|
+
session: session?.cloud?.envNames,
|
|
540
|
+
user: user?.cloud?.envNames,
|
|
541
|
+
builtin: builtIn.cloud.envNames,
|
|
542
|
+
}),
|
|
543
|
+
envFromFiles: resolveSafetyField(CLOUD_ORDER, {
|
|
544
|
+
cli: cli?.cloud?.envFromFiles,
|
|
545
|
+
environment: env.cloud?.envFromFiles,
|
|
546
|
+
session: session?.cloud?.envFromFiles,
|
|
547
|
+
user: user?.cloud?.envFromFiles,
|
|
548
|
+
builtin: builtIn.cloud.envFromFiles,
|
|
549
|
+
}, (value) => (value ? 1 : 0)),
|
|
550
|
+
environment: resolveOrdinaryField(CLOUD_ORDER, {
|
|
551
|
+
cli: cli?.cloud?.environment,
|
|
552
|
+
environment: env.cloud?.environment,
|
|
553
|
+
session: session?.cloud?.environment,
|
|
554
|
+
user: user?.cloud?.environment,
|
|
555
|
+
builtin: undefined,
|
|
556
|
+
}),
|
|
557
|
+
acknowledged: resolveOrdinaryField(CLOUD_ORDER, {
|
|
558
|
+
cli: cli?.cloud?.acknowledged,
|
|
559
|
+
environment: env.cloud?.acknowledged,
|
|
560
|
+
session: session?.cloud?.acknowledged,
|
|
561
|
+
user: user?.cloud?.acknowledged,
|
|
562
|
+
builtin: builtIn.cloud.acknowledged,
|
|
563
|
+
}),
|
|
564
|
+
},
|
|
565
|
+
local: {
|
|
566
|
+
autoReview: resolveOrdinaryField(LOCAL_ORDER, {
|
|
567
|
+
cli: cli?.local?.autoReview,
|
|
568
|
+
environment: env.local?.autoReview,
|
|
569
|
+
project: project?.local?.autoReview,
|
|
570
|
+
user: user?.local?.autoReview,
|
|
571
|
+
builtin: false,
|
|
572
|
+
}),
|
|
573
|
+
sandboxEnabled: resolveOrdinaryField(LOCAL_ORDER, {
|
|
574
|
+
cli: cli?.local?.sandboxOptions?.enabled ?? cli?.local?.sandbox,
|
|
575
|
+
environment: env.local?.sandboxOptions?.enabled ?? env.local?.sandbox,
|
|
576
|
+
project: project?.local?.sandboxOptions?.enabled ?? project?.local?.sandbox,
|
|
577
|
+
user: user?.local?.sandboxOptions?.enabled ?? user?.local?.sandbox,
|
|
578
|
+
builtin: false,
|
|
579
|
+
}),
|
|
580
|
+
force: resolveOrdinaryField(LOCAL_FORCE_ORDER, {
|
|
581
|
+
cli: cli?.local?.force,
|
|
582
|
+
environment: env.local?.force,
|
|
583
|
+
builtin: false,
|
|
584
|
+
}),
|
|
585
|
+
resume: resolveOrdinaryField(LOCAL_ORDER, {
|
|
586
|
+
cli: cli?.local?.resume,
|
|
587
|
+
environment: env.local?.resume,
|
|
588
|
+
project: project?.local?.resume,
|
|
589
|
+
user: user?.local?.resume,
|
|
590
|
+
builtin: true,
|
|
591
|
+
}),
|
|
592
|
+
useHttp1ForAgent: resolveOrdinaryField(HTTP1_ORDER, {
|
|
593
|
+
session: session?.local?.useHttp1ForAgent,
|
|
594
|
+
environment: env.local?.useHttp1ForAgent,
|
|
595
|
+
user: user?.local?.useHttp1ForAgent,
|
|
596
|
+
builtin: false,
|
|
597
|
+
}),
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
export function resolveCursorFastDefault(options) {
|
|
602
|
+
if (options.cliForceNoFast)
|
|
603
|
+
return resolved("cli", false);
|
|
604
|
+
if (options.cliForceFast)
|
|
605
|
+
return resolved("cli", true);
|
|
606
|
+
if (options.aliasOverride !== undefined)
|
|
607
|
+
return resolved("model-alias", options.aliasOverride);
|
|
608
|
+
if (options.sessionValue !== undefined)
|
|
609
|
+
return resolved("session", options.sessionValue);
|
|
610
|
+
if (options.userValue !== undefined)
|
|
611
|
+
return resolved("user", options.userValue);
|
|
612
|
+
return resolved("builtin", options.modelDefault);
|
|
613
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { resolveCursorPiContext } from "./cursor-pi-context.js";
|
|
2
|
+
/** Tool names from the provider context snapshot at stream start (not live pi.getActiveTools()). */
|
|
3
|
+
export function getActiveContextToolNames(context) {
|
|
4
|
+
const { tools } = resolveCursorPiContext(context);
|
|
5
|
+
return tools === undefined ? undefined : new Set(tools.map((tool) => tool.name));
|
|
6
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function withDisplayOnlyThinking(partial, thinking) {
|
|
2
|
+
return { ...partial, content: [...partial.content, { type: "thinking", thinking }] };
|
|
3
|
+
}
|
|
4
|
+
export function emitDisplayOnlyTraceBlock(stream, partial, text) {
|
|
5
|
+
const traceText = text.endsWith("\n") ? text : `${text}\n`;
|
|
6
|
+
const contentIndex = partial.content.length;
|
|
7
|
+
stream.push({ type: "thinking_start", contentIndex, partial: withDisplayOnlyThinking(partial, "") });
|
|
8
|
+
const displayPartial = withDisplayOnlyThinking(partial, traceText);
|
|
9
|
+
stream.push({ type: "thinking_delta", contentIndex, delta: traceText, partial: displayPartial });
|
|
10
|
+
stream.push({ type: "thinking_end", contentIndex, content: traceText, partial: displayPartial });
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Canonical single-line sanitization and truncation for Cursor replay/trace display. */
|
|
2
|
+
export function sanitizeCursorDisplayLine(value) {
|
|
3
|
+
return value
|
|
4
|
+
.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "�")
|
|
5
|
+
.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/g, " ")
|
|
6
|
+
.replace(/\s+/g, " ")
|
|
7
|
+
.trim();
|
|
8
|
+
}
|
|
9
|
+
export function truncateCursorDisplayLine(value, maxLength = 240) {
|
|
10
|
+
if (maxLength <= 0)
|
|
11
|
+
return "";
|
|
12
|
+
const sanitized = sanitizeCursorDisplayLine(value);
|
|
13
|
+
if (sanitized.length <= maxLength)
|
|
14
|
+
return sanitized;
|
|
15
|
+
if (maxLength === 1)
|
|
16
|
+
return "…";
|
|
17
|
+
return `${sanitized.slice(0, maxLength - 1).replace(/[\uD800-\uDBFF]$/, "")}…`;
|
|
18
|
+
}
|