pi-cursor-sdk 0.5.3 → 0.5.4
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 +40 -1
- package/README.md +62 -816
- package/dist/context.js +11 -5
- package/dist/cursor-agents-context.js +6 -6
- package/dist/cursor-api-key.js +1 -1
- package/dist/cursor-bridge-contract.js +1 -13
- package/dist/cursor-cloud-local-state.js +0 -1
- package/dist/cursor-config.js +1 -17
- package/dist/cursor-fallback-models.generated.js +264 -1
- package/dist/cursor-live-run-coordinator.js +1 -0
- package/dist/cursor-mcp-timeout-override.js +6 -17
- package/dist/cursor-native-tool-display-replay.js +5 -5
- package/dist/cursor-native-tools.js +11 -0
- package/dist/cursor-pi-tool-bridge-env.js +25 -2
- package/dist/cursor-pi-tool-bridge-server.js +98 -48
- package/dist/cursor-pi-tool-bridge-snapshot.js +1 -1
- package/dist/cursor-pi-tool-bridge.js +4 -12
- package/dist/cursor-provider-errors.js +0 -6
- package/dist/cursor-provider-live-run-drain.js +8 -3
- package/dist/cursor-provider-overflow.js +7 -21
- package/dist/cursor-provider-turn-prepare.js +8 -1
- package/dist/cursor-provider-turn-runner.js +17 -6
- package/dist/cursor-provider.js +1 -4
- package/dist/cursor-runtime-state.js +3 -3
- package/dist/cursor-sdk-event-debug.js +2 -2
- package/dist/cursor-sdk-output-filter.js +1 -1
- package/dist/cursor-sdk-process-error-guard.js +1 -2
- package/dist/cursor-session-agent.js +6 -8
- package/dist/cursor-session-scope.js +1 -7
- package/dist/cursor-session-store.js +1 -1
- package/dist/cursor-state.js +9 -23
- package/dist/cursor-tool-lifecycle.js +0 -3
- package/dist/cursor-tool-manifest.js +3 -1
- package/dist/cursor-tool-presentation-registry.js +2 -4
- package/dist/cursor-tool-visibility.js +0 -3
- package/dist/cursor-transcript-tool-specs.js +1 -2
- package/dist/index.js +21 -0
- package/dist/model-discovery.js +0 -1
- package/dist/model-list-cache.js +4 -9
- package/docs/cursor-live-smoke-checklist.md +3 -3
- package/docs/cursor-model-ux-spec.md +16 -8
- package/docs/cursor-native-tool-replay.md +2 -2
- package/docs/cursor-native-tool-visual-audit.md +1 -1
- package/docs/cursor-testing-lessons.md +15 -3
- package/docs/cursor-tool-surfaces.md +32 -3
- package/docs/development.md +93 -0
- package/docs/platform-smoke-implementation.md +3 -2
- package/docs/reference.md +806 -0
- package/package.json +6 -4
- package/scripts/platform-smoke/artifact-secrets.mjs +13 -4
- package/scripts/platform-smoke/local-resume-runner.mjs +0 -1
- package/scripts/platform-smoke/target-runtime.mjs +4 -2
- package/scripts/platform-smoke/targets.mjs +0 -1
- package/shared/cursor-sdk-output-filter.d.mts +2 -0
- package/shared/cursor-sdk-output-filter.mjs +92 -28
- package/shared/cursor-sensitive-text.d.mts +1 -0
- package/shared/cursor-sensitive-text.mjs +7 -0
- package/src/context.ts +16 -9
- package/src/cursor-agents-context.ts +6 -6
- package/src/cursor-api-key.ts +1 -1
- package/src/cursor-bridge-contract.ts +1 -15
- package/src/cursor-cloud-local-state.ts +0 -2
- package/src/cursor-config.ts +1 -24
- package/src/cursor-fallback-models.generated.ts +264 -1
- package/src/cursor-live-run-coordinator.ts +5 -0
- package/src/cursor-mcp-timeout-override.ts +6 -30
- package/src/cursor-native-tool-display-replay.ts +4 -6
- package/src/cursor-native-tools.ts +14 -0
- package/src/cursor-pi-tool-bridge-env.ts +31 -2
- package/src/cursor-pi-tool-bridge-server.ts +89 -45
- package/src/cursor-pi-tool-bridge-snapshot.ts +0 -1
- package/src/cursor-pi-tool-bridge.ts +2 -18
- package/src/cursor-provider-errors.ts +0 -8
- package/src/cursor-provider-live-run-drain.ts +8 -2
- package/src/cursor-provider-overflow.ts +6 -24
- package/src/cursor-provider-turn-prepare.ts +10 -1
- package/src/cursor-provider-turn-runner.ts +17 -6
- package/src/cursor-provider.ts +0 -5
- package/src/cursor-runtime-state.ts +3 -3
- package/src/cursor-sdk-event-debug.ts +2 -2
- package/src/cursor-sdk-output-filter.ts +2 -1
- package/src/cursor-sdk-process-error-guard.ts +1 -3
- package/src/cursor-session-agent.ts +8 -8
- package/src/cursor-session-scope.ts +1 -9
- package/src/cursor-session-store.ts +1 -1
- package/src/cursor-state.ts +9 -36
- package/src/cursor-tool-lifecycle.ts +0 -4
- package/src/cursor-tool-manifest.ts +4 -1
- package/src/cursor-tool-presentation-registry.ts +3 -5
- package/src/cursor-tool-visibility.ts +0 -4
- package/src/cursor-transcript-tool-specs.ts +0 -3
- package/src/index.ts +18 -0
- package/src/model-discovery.ts +0 -1
- package/src/model-list-cache.ts +4 -10
|
@@ -1,15 +1,57 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
+
import { resolveCursorPiToolBridgePortRange, resolveCursorPiToolBridgeUrlHost, } from "./cursor-pi-tool-bridge-env.js";
|
|
2
3
|
import { asRecord } from "./cursor-record-utils.js";
|
|
3
4
|
import { CursorPiToolBridgeRunImpl } from "./cursor-pi-tool-bridge-run.js";
|
|
4
5
|
import { buildCursorPiToolBridgeSnapshot, buildCursorPiToolBridgeSurfaceSignature, createEmptySnapshot, resolveCursorPiToolBridgeBuiltinsEnabled, resolveCursorPiToolBridgeEnabled, } from "./cursor-pi-tool-bridge-snapshot.js";
|
|
5
|
-
|
|
6
|
+
const LOOPBACK_HOST = "127.0.0.1";
|
|
6
7
|
const HTTP_SERVER_CLOSE_GRACE_MS = 250;
|
|
8
|
+
const EPHEMERAL_BIND_ATTEMPTS = 10;
|
|
9
|
+
function listenOnce(server, port, host = LOOPBACK_HOST) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const onError = (error) => {
|
|
12
|
+
server.off("listening", onListening);
|
|
13
|
+
reject(error);
|
|
14
|
+
};
|
|
15
|
+
const onListening = () => {
|
|
16
|
+
server.off("error", onError);
|
|
17
|
+
resolve();
|
|
18
|
+
};
|
|
19
|
+
server.once("error", onError);
|
|
20
|
+
server.once("listening", onListening);
|
|
21
|
+
server.listen({ port, host, ipv6Only: host === "::1" });
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async function closeServers(servers) {
|
|
25
|
+
await Promise.all(servers.map(async (server) => {
|
|
26
|
+
if (!server.listening)
|
|
27
|
+
return;
|
|
28
|
+
await new Promise((resolve, reject) => {
|
|
29
|
+
let settled = false;
|
|
30
|
+
const settle = (error) => {
|
|
31
|
+
if (settled)
|
|
32
|
+
return;
|
|
33
|
+
settled = true;
|
|
34
|
+
clearTimeout(closeTimer);
|
|
35
|
+
if (error)
|
|
36
|
+
reject(error);
|
|
37
|
+
else
|
|
38
|
+
resolve();
|
|
39
|
+
};
|
|
40
|
+
const closeTimer = setTimeout(() => settle(), HTTP_SERVER_CLOSE_GRACE_MS);
|
|
41
|
+
closeTimer.unref?.();
|
|
42
|
+
server.close((error) => settle(error ?? undefined));
|
|
43
|
+
server.closeIdleConnections();
|
|
44
|
+
server.closeAllConnections();
|
|
45
|
+
});
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
7
48
|
export class CursorPiToolBridgeRegistry {
|
|
8
49
|
pi;
|
|
9
50
|
env;
|
|
10
51
|
runs = new Set();
|
|
11
52
|
routes = new Map();
|
|
12
|
-
|
|
53
|
+
httpServers = [];
|
|
54
|
+
advertisedHost;
|
|
13
55
|
listenPromise;
|
|
14
56
|
serverLifecycle = Promise.resolve();
|
|
15
57
|
constructor(pi, env = process.env) {
|
|
@@ -36,7 +78,17 @@ export class CursorPiToolBridgeRegistry {
|
|
|
36
78
|
: createEmptySnapshot();
|
|
37
79
|
const run = new CursorPiToolBridgeRunImpl(this, this.env, snapshot, bridgeEnabled && snapshot.tools.length > 0, options);
|
|
38
80
|
this.runs.add(run);
|
|
39
|
-
|
|
81
|
+
try {
|
|
82
|
+
await run.start();
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
try {
|
|
86
|
+
await run.dispose();
|
|
87
|
+
}
|
|
88
|
+
catch { /* Preserve the original startup failure, not a secondary cleanup error. */ }
|
|
89
|
+
this.runs.delete(run);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
40
92
|
run.emitStartDiagnostics(bridgeEnabled);
|
|
41
93
|
return run;
|
|
42
94
|
}
|
|
@@ -51,9 +103,9 @@ export class CursorPiToolBridgeRegistry {
|
|
|
51
103
|
await this.ensureHttpServer();
|
|
52
104
|
this.routes.set(pathname, run);
|
|
53
105
|
const address = this.getHttpServerAddress();
|
|
54
|
-
if (!address)
|
|
106
|
+
if (!address || !this.advertisedHost)
|
|
55
107
|
throw new Error("Cursor pi tool bridge HTTP server is not listening");
|
|
56
|
-
return `http://${
|
|
108
|
+
return `http://${this.advertisedHost}:${address.port}${pathname}`;
|
|
57
109
|
});
|
|
58
110
|
}
|
|
59
111
|
async unregisterRun(pathname, run) {
|
|
@@ -71,7 +123,7 @@ export class CursorPiToolBridgeRegistry {
|
|
|
71
123
|
return result;
|
|
72
124
|
}
|
|
73
125
|
getHttpServerAddress() {
|
|
74
|
-
const address = asRecord(this.
|
|
126
|
+
const address = asRecord(this.httpServers[0]?.address());
|
|
75
127
|
if (typeof address?.port !== "number")
|
|
76
128
|
return undefined;
|
|
77
129
|
return {
|
|
@@ -99,13 +151,17 @@ export class CursorPiToolBridgeRegistry {
|
|
|
99
151
|
}
|
|
100
152
|
async ensureHttpServer() {
|
|
101
153
|
this.listenPromise ??= this.startHttpServer().catch((error) => {
|
|
102
|
-
this.
|
|
154
|
+
this.httpServers = [];
|
|
155
|
+
this.advertisedHost = undefined;
|
|
103
156
|
this.listenPromise = undefined;
|
|
104
157
|
throw error;
|
|
105
158
|
});
|
|
106
159
|
await this.listenPromise;
|
|
107
160
|
}
|
|
108
161
|
async startHttpServer() {
|
|
162
|
+
// A hostname is a listener policy, not a mutable per-run URL preference.
|
|
163
|
+
const advertisedHost = resolveCursorPiToolBridgeUrlHost(this.env);
|
|
164
|
+
const range = resolveCursorPiToolBridgePortRange(this.env);
|
|
109
165
|
const [{ getRequestListener }, { createMcpHonoApp }] = await Promise.all([
|
|
110
166
|
import("@hono/node-server"),
|
|
111
167
|
import("@modelcontextprotocol/hono"),
|
|
@@ -115,51 +171,45 @@ export class CursorPiToolBridgeRegistry {
|
|
|
115
171
|
const parsedBody = context.get("parsedBody");
|
|
116
172
|
return this.handleHttpRequest(context.req.raw, parsedBody);
|
|
117
173
|
});
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
174
|
+
const listener = getRequestListener(app.fetch, { overrideGlobalObjects: false });
|
|
175
|
+
const attempts = range ? range.last - range.first + 1 : advertisedHost === "localhost" ? EPHEMERAL_BIND_ATTEMPTS : 1;
|
|
176
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
177
|
+
const servers = [createServer(listener)];
|
|
178
|
+
try {
|
|
179
|
+
await listenOnce(servers[0], range ? range.first + attempt : 0);
|
|
180
|
+
if (advertisedHost === "localhost") {
|
|
181
|
+
const port = servers[0].address().port;
|
|
182
|
+
const ipv6 = createServer(listener);
|
|
183
|
+
servers.push(ipv6);
|
|
184
|
+
// Own both explicit loopbacks before publishing any tokenized URL.
|
|
185
|
+
await listenOnce(ipv6, port, "::1");
|
|
186
|
+
}
|
|
187
|
+
this.httpServers = servers;
|
|
188
|
+
this.advertisedHost = advertisedHost;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
try {
|
|
193
|
+
await closeServers(servers);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
throw error;
|
|
197
|
+
} // Never retry or replace the bind failure when rollback fails.
|
|
198
|
+
if (error.code !== "EADDRINUSE" || (!range && advertisedHost !== "localhost"))
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (range)
|
|
203
|
+
throw new Error(`Cursor pi tool bridge could not listen on any port in ${range.first}-${range.last} (all in use)`);
|
|
204
|
+
throw new Error("Cursor pi tool bridge could not own both localhost loopback listeners on an ephemeral port");
|
|
133
205
|
}
|
|
134
206
|
async closeHttpServer() {
|
|
135
207
|
await this.listenPromise?.catch(() => undefined);
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
this.httpServer = undefined;
|
|
208
|
+
const servers = this.httpServers;
|
|
209
|
+
this.httpServers = [];
|
|
210
|
+
this.advertisedHost = undefined;
|
|
140
211
|
this.listenPromise = undefined;
|
|
141
|
-
await
|
|
142
|
-
let settled = false;
|
|
143
|
-
let closeTimer;
|
|
144
|
-
const settle = (error) => {
|
|
145
|
-
if (settled)
|
|
146
|
-
return;
|
|
147
|
-
settled = true;
|
|
148
|
-
if (closeTimer)
|
|
149
|
-
clearTimeout(closeTimer);
|
|
150
|
-
if (error)
|
|
151
|
-
reject(error);
|
|
152
|
-
else
|
|
153
|
-
resolve();
|
|
154
|
-
};
|
|
155
|
-
closeTimer = setTimeout(() => settle(), HTTP_SERVER_CLOSE_GRACE_MS);
|
|
156
|
-
closeTimer.unref?.();
|
|
157
|
-
server.close((error) => {
|
|
158
|
-
settle(error ?? undefined);
|
|
159
|
-
});
|
|
160
|
-
server.closeIdleConnections();
|
|
161
|
-
server.closeAllConnections();
|
|
162
|
-
});
|
|
212
|
+
await closeServers(servers);
|
|
163
213
|
}
|
|
164
214
|
async handleHttpRequest(request, parsedBody) {
|
|
165
215
|
const url = new URL(request.url);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createMcpToolName, normalizeMcpInputSchema, stableNameHash } from "./cursor-pi-tool-bridge-mcp.js";
|
|
2
|
-
export {
|
|
2
|
+
export { CURSOR_PI_TOOL_BRIDGE_ENV, resolveCursorPiToolBridgeBuiltinsEnabled, resolveCursorPiToolBridgeEnabled, } from "./cursor-pi-tool-bridge-env.js";
|
|
3
3
|
import { isRegisteredCursorNativeToolName } from "./cursor-native-tool-display-state.js";
|
|
4
4
|
import { isExcludedFromCursorBridgeExposure } from "./cursor-tool-presentation-registry.js";
|
|
5
5
|
const OVERLAPPING_CURSOR_NATIVE_PI_BUILTIN_TOOL_NAMES = new Set(["read", "bash", "write", "edit", "grep", "find", "ls"]);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import {
|
|
3
|
-
import { CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV, CURSOR_PI_TOOL_BRIDGE_CALL_TIMEOUT_MS_ENV, CURSOR_PI_TOOL_BRIDGE_ENV, } from "./cursor-pi-tool-bridge-env.js";
|
|
2
|
+
import { CURSOR_PI_TOOL_BRIDGE_DIAGNOSTIC_PREFIX, serializeCursorPiToolBridgeDiagnostic, } from "./cursor-pi-tool-bridge-diagnostics.js";
|
|
4
3
|
import { bridgeToolExecutionAbortTracker } from "./cursor-pi-tool-bridge-abort.js";
|
|
5
|
-
import { isCursorPiBridgeToolCallId
|
|
6
|
-
import {
|
|
4
|
+
import { isCursorPiBridgeToolCallId } from "./cursor-pi-tool-bridge-constants.js";
|
|
5
|
+
import { CursorPiToolBridgeRegistry } from "./cursor-pi-tool-bridge-server.js";
|
|
7
6
|
export { resolveCursorPiToolBridgeDebugEnabled } from "./cursor-pi-tool-bridge-diagnostics.js";
|
|
8
|
-
export {
|
|
7
|
+
export { resolveCursorPiToolBridgeBuiltinsEnabled, resolveCursorPiToolBridgeCallTimeoutMs, resolveCursorPiToolBridgeEnabled, } from "./cursor-pi-tool-bridge-env.js";
|
|
9
8
|
export { buildCursorPiToolBridgeSnapshot, buildCursorPiToolBridgeSurfaceSignature, } from "./cursor-pi-tool-bridge-snapshot.js";
|
|
10
9
|
const bridges = new Map();
|
|
11
10
|
const activeCallsByPi = new Map();
|
|
@@ -110,13 +109,7 @@ export function getRegisteredCursorPiToolBridge(pi) {
|
|
|
110
109
|
return pi ? bridges.get(pi) : registeredCursorPiToolBridge;
|
|
111
110
|
}
|
|
112
111
|
export const __testUtils = {
|
|
113
|
-
CURSOR_PI_TOOL_BRIDGE_ENV,
|
|
114
|
-
CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV,
|
|
115
|
-
CURSOR_PI_TOOL_BRIDGE_CALL_TIMEOUT_MS_ENV,
|
|
116
|
-
CURSOR_PI_TOOL_BRIDGE_DEBUG_ENV,
|
|
117
112
|
CURSOR_PI_TOOL_BRIDGE_DIAGNOSTIC_PREFIX,
|
|
118
|
-
LOOPBACK_HOST,
|
|
119
|
-
MCP_SERVER_NAME,
|
|
120
113
|
createRegistry(pi, env = process.env) {
|
|
121
114
|
return new CursorPiToolBridgeRegistry(pi, env);
|
|
122
115
|
},
|
|
@@ -130,7 +123,6 @@ export const __testUtils = {
|
|
|
130
123
|
return bridgeToolExecutionAbortTracker.getActiveCount();
|
|
131
124
|
},
|
|
132
125
|
buildWindowsBridgeBashAbortCommandForTests: buildWindowsBridgeBashAbortCommand,
|
|
133
|
-
installWindowsBridgeBashAbortMarkerForTests: installWindowsBridgeBashAbortMarker,
|
|
134
126
|
emitBridgeToolExecutionProcessAbortSignalForTests(signal) {
|
|
135
127
|
bridgeToolExecutionAbortTracker.emitProcessAbortSignalForTests(signal);
|
|
136
128
|
},
|
|
@@ -214,12 +214,6 @@ export function classifyCursorConnectError(error) {
|
|
|
214
214
|
}
|
|
215
215
|
return undefined;
|
|
216
216
|
}
|
|
217
|
-
export function isCursorSdkAbortConnectError(error) {
|
|
218
|
-
return classifyCursorConnectError(error)?.kind === "abort";
|
|
219
|
-
}
|
|
220
|
-
export function isUnauthenticatedConnectError(error) {
|
|
221
|
-
return classifyCursorConnectError(error)?.kind === "unauthenticated";
|
|
222
|
-
}
|
|
223
217
|
function isLikelyNetworkTimeout(message) {
|
|
224
218
|
return (/\b(ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENETUNREACH|EAI_AGAIN|NGHTTP2_ENHANCE_YOUR_CALM|ERR_HTTP2_STREAM_ERROR|ERR_HTTP2_SESSION_ERROR)\b/i.test(message) ||
|
|
225
219
|
/\bConnectError\b.*\b(unavailable|deadline|timeout|timed out)\b/i.test(message) ||
|
|
@@ -9,7 +9,7 @@ import { trimCurrentTurnAlreadyEmittedCursorText } from "./cursor-run-final-text
|
|
|
9
9
|
import { formatCursorSdkAbortMessage, resolveCursorSdkAbortCause } from "./cursor-provider-errors.js";
|
|
10
10
|
import { formatInactiveCursorReplayTrace } from "./cursor-native-replay-trace.js";
|
|
11
11
|
import { partitionNativeToolsByActiveContext } from "./cursor-native-replay-routing.js";
|
|
12
|
-
|
|
12
|
+
const DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS = 5 * 60 * 1000;
|
|
13
13
|
function parseCursorNativeReplayIdleDisposeMs(raw) {
|
|
14
14
|
if (!raw || /[^0-9]/.test(raw))
|
|
15
15
|
return DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS;
|
|
@@ -36,7 +36,7 @@ export function createCursorNativeReplayId() {
|
|
|
36
36
|
function getCursorNativeReplayIdFromToolCallId(toolCallId) {
|
|
37
37
|
return CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN.exec(toolCallId)?.[1];
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
function getPendingCursorLiveRun(context, scopeKey) {
|
|
40
40
|
return cursorLiveRuns.getPendingFromContext(context, getCursorNativeReplayIdFromToolCallId, scopeKey);
|
|
41
41
|
}
|
|
42
42
|
export function getActiveCursorLiveRunForCurrentScope(scopeKey) {
|
|
@@ -357,7 +357,7 @@ export async function drainCursorLiveRunTurn(stream, partial, model, context, ru
|
|
|
357
357
|
});
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
|
-
export async function drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, signal, turnDebugRecorder, scopeKey, occupancyFloor) {
|
|
360
|
+
export async function drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, signal, turnDebugRecorder, scopeKey, occupancyFloor, nativeToolsEnabled = true) {
|
|
361
361
|
turnDebugRecorder?.recordDrainEvent("pre_send_start", {});
|
|
362
362
|
while (true) {
|
|
363
363
|
const run = getPendingCursorLiveRun(context, scopeKey) ?? getActiveCursorLiveRunForCurrentScope(scopeKey);
|
|
@@ -369,6 +369,11 @@ export async function drainExistingCursorLiveRunBeforeSend(stream, partial, mode
|
|
|
369
369
|
const outcome = await cursorLiveRuns.withRunLease(run, signal, async () => {
|
|
370
370
|
if (run.disposed)
|
|
371
371
|
return "continue_send";
|
|
372
|
+
// Check under the drain lease, before consuming/delivering Pi results:
|
|
373
|
+
// resolving a bridge waiter can resume the SDK's old tool surface.
|
|
374
|
+
if ((run.nativeToolsEnabled !== false) !== nativeToolsEnabled) {
|
|
375
|
+
throw new Error("Cursor native tool policy changed while a local run is pending; restore the prior policy to finish it or run /cursor-abort before retrying. Already-executed work is not undone.");
|
|
376
|
+
}
|
|
372
377
|
const consumed = cursorLiveRuns.consumeToolResults(run, context, getCursorNativeReplayIdFromToolCallId);
|
|
373
378
|
await run.bridgeRun?.resolveToolResults(consumed.toolResults);
|
|
374
379
|
const shouldChainUserInput = run.chainUserInputAfterCompletion || hasTrailingUserMessagesAfterToolResults(context);
|
|
@@ -12,7 +12,7 @@ import { CURSOR_PROVIDER } from "./cursor-model.js";
|
|
|
12
12
|
*
|
|
13
13
|
* See pi `docs/custom-provider.md` -> "Context Overflow Errors".
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
const CURSOR_OVERFLOW_MARKER = "context_length_exceeded";
|
|
16
16
|
/**
|
|
17
17
|
* Context-overflow phrases. These overlap pi's own `OVERFLOW_PATTERNS` so the
|
|
18
18
|
* false-positive surface matches pi's built-in detection for other providers.
|
|
@@ -37,9 +37,9 @@ const CURSOR_THROTTLE_PATTERN = /too many requests|rate.?limit|throttl|retry.?af
|
|
|
37
37
|
* Map a finalized Cursor assistant `errorMessage` to pi's overflow form.
|
|
38
38
|
* Returns `undefined` when the message is not a Cursor context-overflow failure.
|
|
39
39
|
*
|
|
40
|
-
*
|
|
40
|
+
* Idempotent so repeated message delivery does not add another prefix.
|
|
41
41
|
*/
|
|
42
|
-
|
|
42
|
+
function normalizeCursorOverflowErrorMessage(errorMessage) {
|
|
43
43
|
const message = errorMessage?.trim();
|
|
44
44
|
if (!message)
|
|
45
45
|
return undefined;
|
|
@@ -53,22 +53,6 @@ export function normalizeCursorOverflowErrorMessage(errorMessage) {
|
|
|
53
53
|
}
|
|
54
54
|
return undefined;
|
|
55
55
|
}
|
|
56
|
-
/**
|
|
57
|
-
* Rewrite a finalized assistant message into pi's overflow form when it is a
|
|
58
|
-
* Cursor context-overflow failure. Returns the replacement message (for the
|
|
59
|
-
* `message_end` result) or `undefined` to leave it unchanged.
|
|
60
|
-
*
|
|
61
|
-
* `isCursorProvider` carries the provider scoping decision out of the handler so
|
|
62
|
-
* the full logic is unit-testable without a pi event harness.
|
|
63
|
-
*/
|
|
64
|
-
export function rewriteCursorOverflowAssistantMessage(message, isCursorProvider) {
|
|
65
|
-
if (!isCursorProvider || message.stopReason !== "error")
|
|
66
|
-
return undefined;
|
|
67
|
-
const rewritten = normalizeCursorOverflowErrorMessage(message.errorMessage);
|
|
68
|
-
if (!rewritten)
|
|
69
|
-
return undefined;
|
|
70
|
-
return { ...message, errorMessage: rewritten };
|
|
71
|
-
}
|
|
72
56
|
/**
|
|
73
57
|
* Register a `message_end` handler that rewrites Cursor context-overflow
|
|
74
58
|
* failures into the `context_length_exceeded` form pi auto-compacts on.
|
|
@@ -82,9 +66,11 @@ export function registerCursorOverflowNormalization(pi) {
|
|
|
82
66
|
if (message.role !== "assistant")
|
|
83
67
|
return undefined;
|
|
84
68
|
const isCursorProvider = message.provider === CURSOR_PROVIDER || ctx.model?.provider === CURSOR_PROVIDER;
|
|
85
|
-
|
|
69
|
+
if (!isCursorProvider || message.stopReason !== "error")
|
|
70
|
+
return undefined;
|
|
71
|
+
const rewritten = normalizeCursorOverflowErrorMessage(message.errorMessage);
|
|
86
72
|
if (!rewritten)
|
|
87
73
|
return undefined;
|
|
88
|
-
return { message: rewritten };
|
|
74
|
+
return { message: { ...message, errorMessage: rewritten } };
|
|
89
75
|
});
|
|
90
76
|
}
|
|
@@ -24,6 +24,7 @@ import { MISSING_CURSOR_API_KEY_MESSAGE } from "./cursor-provider-errors.js";
|
|
|
24
24
|
import { CursorSdkTurnCoordinator } from "./cursor-provider-turn-coordinator.js";
|
|
25
25
|
import { resolveCursorApiKey } from "./cursor-api-key.js";
|
|
26
26
|
import { loadCursorSdk } from "./cursor-sdk-runtime.js";
|
|
27
|
+
import { assertCursorNativeToolsRuntime } from "./cursor-native-tools.js";
|
|
27
28
|
import { prepareCursorSummaryProviderTurn } from "./cursor-provider-turn-summary.js";
|
|
28
29
|
function buildCursorCloudPromptContext(context, handoff, nativeSourceRoles) {
|
|
29
30
|
if (handoff === "bootstrap")
|
|
@@ -182,7 +183,7 @@ async function prepareCursorCloudProviderTurn(prepareParams) {
|
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
185
|
-
const { params, cwd, resolvedApiKey, sdkEventDebug, throwIfAborted, resolvedConfig, agentMode, selection, fastEnabled, agents, customSubagentNames } = prepareParams;
|
|
186
|
+
const { params, cwd, resolvedApiKey, sdkEventDebug, throwIfAborted, resolvedConfig, agentMode, selection, fastEnabled, agents, customSubagentNames, nativeToolsEnabled } = prepareParams;
|
|
186
187
|
const { model, context, options } = params;
|
|
187
188
|
let restoreCursorSdkOutputFilter;
|
|
188
189
|
let sessionAgentScopeKey;
|
|
@@ -212,6 +213,7 @@ async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
|
212
213
|
settingSources,
|
|
213
214
|
localSafety,
|
|
214
215
|
agents,
|
|
216
|
+
nativeToolsEnabled,
|
|
215
217
|
localResume: resolvedConfig.local.resume.value,
|
|
216
218
|
storeRoot: resolvedConfig.local.storeRoot.value,
|
|
217
219
|
useHttp1ForAgent,
|
|
@@ -236,6 +238,7 @@ async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
|
236
238
|
...getCursorPromptOptions(model),
|
|
237
239
|
nativeSourceRoles: params.request.nativeSourceRoles,
|
|
238
240
|
agentMode,
|
|
241
|
+
nativeToolsEnabled,
|
|
239
242
|
includePiBridgeGuidance,
|
|
240
243
|
includePiAskQuestionGuidance: false,
|
|
241
244
|
};
|
|
@@ -245,6 +248,7 @@ async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
|
245
248
|
return {
|
|
246
249
|
...promptOptions,
|
|
247
250
|
toolManifest: buildCursorToolManifestText({
|
|
251
|
+
nativeToolsEnabled,
|
|
248
252
|
bridgeSnapshot: sessionAgentLease.bridgeRun?.snapshot,
|
|
249
253
|
piBridgeEnabled: resolveCursorPiToolBridgeEnabled(),
|
|
250
254
|
includePiBridgeGuidance,
|
|
@@ -295,6 +299,7 @@ async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
|
295
299
|
agentMode,
|
|
296
300
|
localForce: resolvedConfig.local.force.value,
|
|
297
301
|
localResume: resolvedConfig.local.resume.value,
|
|
302
|
+
...(nativeToolsEnabled ? {} : { nativeToolsEnabled: false, tools: ["mcp"] }),
|
|
298
303
|
resumedAgent: sessionAgentLease.resumed,
|
|
299
304
|
activeToolNames: activeToolNames ? [...activeToolNames] : [],
|
|
300
305
|
sessionAgentScopeKey,
|
|
@@ -308,6 +313,7 @@ async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
|
308
313
|
? cursorLiveRuns.start({
|
|
309
314
|
id: useNativeToolReplay ? nativeReplayId : bridgeRun?.id ?? nativeReplayId,
|
|
310
315
|
agent,
|
|
316
|
+
nativeToolsEnabled,
|
|
311
317
|
bridgeRun,
|
|
312
318
|
sessionBridgeRun,
|
|
313
319
|
sessionAgentScopeKey,
|
|
@@ -387,6 +393,7 @@ async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
|
387
393
|
export async function prepareCursorProviderTurn(prepareParams) {
|
|
388
394
|
const { params, resolvedConfig } = prepareParams;
|
|
389
395
|
const { model, options } = params;
|
|
396
|
+
assertCursorNativeToolsRuntime(resolvedConfig.runtime.value, prepareParams.nativeToolsEnabled);
|
|
390
397
|
const fastEnabled = resolvedConfig.runtime.value === "cloud" ? undefined : getEffectiveFastForModelId(model.id, params.scope.scopeKey);
|
|
391
398
|
const selection = buildCursorModelSelection(model.id, options?.reasoning ?? "off", fastEnabled);
|
|
392
399
|
if (resolvedConfig.runtime.value === "local" && params.request.purpose !== "normal") {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { assertCursorNativeToolsRuntime, resolveCursorNativeToolsEnabled } from "./cursor-native-tools.js";
|
|
1
2
|
import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
|
|
2
|
-
import { drainExistingCursorLiveRunBeforeSend } from "./cursor-provider-live-run-drain.js";
|
|
3
|
+
import { cursorLiveRuns, drainExistingCursorLiveRunBeforeSend } from "./cursor-provider-live-run-drain.js";
|
|
3
4
|
import { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
|
|
4
5
|
import { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
|
|
5
6
|
import { awaitFinalizeCursorRunOutcome } from "./cursor-provider-turn-finalize.js";
|
|
@@ -45,6 +46,12 @@ export class CursorProviderTurnRunner {
|
|
|
45
46
|
this.throwIfAborted();
|
|
46
47
|
const { scope } = this.params;
|
|
47
48
|
const cwd = scope.cwd;
|
|
49
|
+
// Resolved once here, before any drain await, so the drain decision and the
|
|
50
|
+
// prepare dispatch below always act on the same config snapshot.
|
|
51
|
+
const resolvedConfig = resolveCursorProviderTurnConfig(cwd, scope.projectTrusted, scope.scopeKey);
|
|
52
|
+
this.runtimeTarget = resolvedConfig.runtime.value;
|
|
53
|
+
const nativeToolsEnabled = resolveCursorNativeToolsEnabled();
|
|
54
|
+
assertCursorNativeToolsRuntime(this.runtimeTarget, nativeToolsEnabled);
|
|
48
55
|
this.sdkEventDebug = CursorSdkEventDebugSink.maybeCreate({
|
|
49
56
|
cwd,
|
|
50
57
|
modelId: model.id,
|
|
@@ -53,12 +60,8 @@ export class CursorProviderTurnRunner {
|
|
|
53
60
|
});
|
|
54
61
|
sdkEventDebugRef.current = this.sdkEventDebug;
|
|
55
62
|
this.sdkEventDebug?.recordContextSnapshot(context);
|
|
56
|
-
// Resolved once here, before any drain await, so the drain decision and the
|
|
57
|
-
// prepare dispatch below always act on the same config snapshot.
|
|
58
|
-
const resolvedConfig = resolveCursorProviderTurnConfig(cwd, scope.projectTrusted, scope.scopeKey);
|
|
59
|
-
this.runtimeTarget = resolvedConfig.runtime.value;
|
|
60
63
|
if (resolvedConfig.runtime.value === "local" && this.params.request.purpose === "normal") {
|
|
61
|
-
if ((await drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, options?.signal, this.sdkEventDebug, scope.scopeKey, this.params.request.occupancyFloor)) ===
|
|
64
|
+
if ((await drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, options?.signal, this.sdkEventDebug, scope.scopeKey, this.params.request.occupancyFloor, nativeToolsEnabled)) ===
|
|
62
65
|
"stream_ended") {
|
|
63
66
|
return;
|
|
64
67
|
}
|
|
@@ -72,6 +75,7 @@ export class CursorProviderTurnRunner {
|
|
|
72
75
|
sdkEventDebug: this.sdkEventDebug,
|
|
73
76
|
throwIfAborted: () => this.throwIfAborted(),
|
|
74
77
|
resolvedConfig,
|
|
78
|
+
nativeToolsEnabled,
|
|
75
79
|
});
|
|
76
80
|
const usage = await this.params.usageRecorder.start({
|
|
77
81
|
agent: prepared.agent,
|
|
@@ -145,6 +149,13 @@ export class CursorProviderTurnRunner {
|
|
|
145
149
|
});
|
|
146
150
|
}
|
|
147
151
|
catch (error) {
|
|
152
|
+
// This body owns the session turn lease; a queued request aborted before
|
|
153
|
+
// entry must not release another request's run in handleOuterCatch().
|
|
154
|
+
if (!prepared && (error instanceof CursorLiveRunAbortError || options?.signal?.aborted)) {
|
|
155
|
+
const pending = cursorLiveRuns.getActiveForScope(this.params.scope.scopeKey);
|
|
156
|
+
if (pending && !pending.disposed)
|
|
157
|
+
await cursorLiveRuns.release(pending);
|
|
158
|
+
}
|
|
148
159
|
await runFinalizer.applyTerminalEvent({ kind: "error", prepared: started ?? prepared, error });
|
|
149
160
|
}
|
|
150
161
|
finally {
|
package/dist/cursor-provider.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createAssistantMessageEventStream, } from "@earendil-works/pi-ai";
|
|
2
|
-
import { cursorLiveRuns,
|
|
2
|
+
import { cursorLiveRuns, hasTrailingUserMessagesAfterToolResults, releaseAllPendingCursorLiveRunsForTests, resetCursorNativeReplayIdleDisposeMs, setCursorNativeReplayIdleDisposeMs, } from "./cursor-provider-live-run-drain.js";
|
|
3
3
|
import { disposeAllSessionCursorAgents } from "./cursor-session-agent.js";
|
|
4
4
|
import { attachCursorSdkEventDebugPiStreamTap } from "./cursor-sdk-event-debug.js";
|
|
5
5
|
import { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
|
|
@@ -66,10 +66,7 @@ export function streamCursor(model, context, options, ownership) {
|
|
|
66
66
|
return stream;
|
|
67
67
|
}
|
|
68
68
|
export const __testUtils = {
|
|
69
|
-
DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS,
|
|
70
69
|
pendingCursorNativeRunCount: cursorLiveRuns.count,
|
|
71
|
-
getPendingCursorLiveRun,
|
|
72
|
-
getActiveCursorLiveRunForScope: cursorLiveRuns.getActiveForScope,
|
|
73
70
|
hasTrailingUserMessagesAfterToolResults,
|
|
74
71
|
setCursorNativeReplayIdleDisposeMs,
|
|
75
72
|
resetCursorNativeReplayIdleDisposeMs,
|
|
@@ -4,8 +4,8 @@ import { asRecord } from "./cursor-record-utils.js";
|
|
|
4
4
|
import { getResolvedSessionCursorHttp1Enabled } from "./cursor-http1.js";
|
|
5
5
|
import { getCursorSessionScopeSnapshot } from "./cursor-session-scope.js";
|
|
6
6
|
import { cursorSettingsScopeForContext, getCursorSessionSettings } from "./cursor-session-settings.js";
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const CURSOR_RUNTIME_ENTRY_TYPE = "cursor-runtime-state";
|
|
8
|
+
const CURSOR_CLOUD_ACK_DISCLOSURE = [
|
|
9
9
|
"Cursor Cloud executes this work remotely.",
|
|
10
10
|
"Fresh context is used by default; prior Pi context is included only with explicit bootstrap opt-in.",
|
|
11
11
|
"Pi-local tools and the Pi bridge are unavailable, and Pi environment variables are not forwarded.",
|
|
@@ -76,7 +76,7 @@ export function resolveCursorStatusRuntime(ctx) {
|
|
|
76
76
|
return { kind: "invalid", message: error instanceof Error ? error.message : String(error) };
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
function formatResolvedCursorRuntime(runtime) {
|
|
80
80
|
const cap = runtime.cappedBy;
|
|
81
81
|
return cap
|
|
82
82
|
? `${runtime.value} (source: ${runtime.source} safety cap over ${cap.cappedSource} ${cap.cappedValue})`
|
|
@@ -60,10 +60,10 @@ export function resolveCursorSdkEventDebugEnabled(env = process.env) {
|
|
|
60
60
|
return parseEnvBoolean(env[CURSOR_SDK_EVENT_DEBUG_ENV], false);
|
|
61
61
|
}
|
|
62
62
|
export const DISCARDED_INCOMPLETE_TOOL_CALL_REASON = "no-completion-at-run-end";
|
|
63
|
-
|
|
63
|
+
function hashCursorSdkCallId(callId) {
|
|
64
64
|
return createHash("sha256").update(callId).digest("hex").slice(0, 8);
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
function serializeDiscardedIncompleteStartedToolCall(record) {
|
|
67
67
|
return {
|
|
68
68
|
event: "discarded-incomplete-started-tool-call",
|
|
69
69
|
toolName: record.toolName,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { __testUtils, installCursorSdkProcessOutputFilter, withCursorSdkOutputNoticeHandler, installCursorSdkOutputFilter, isCursorSdkOutputSuppressed, isCursorSdkStartupNoise, suppressCursorSdkOutput, } from "../shared/cursor-sdk-output-filter.mjs";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { classifyCursorConnectError,
|
|
1
|
+
import { classifyCursorConnectError, isCursorSdkConnectionStalledError } from "./cursor-provider-errors.js";
|
|
2
2
|
// Cursor SDK controlled-exec tasks can reject after their originating provider turn
|
|
3
3
|
// has ended. The exact closed-writable failure is therefore session-scoped; existing
|
|
4
4
|
// ConnectRPC suppression remains scoped to active provider turns.
|
|
@@ -128,7 +128,6 @@ export const __testUtils = {
|
|
|
128
128
|
lifecycleSessionGuards.clear();
|
|
129
129
|
},
|
|
130
130
|
};
|
|
131
|
-
export { isCursorSdkAbortConnectError };
|
|
132
131
|
export function installCursorSdkProcessErrorGuard() {
|
|
133
132
|
const token = { suppressAbortErrors: false };
|
|
134
133
|
activeProviderTurns.add(token);
|
|
@@ -42,7 +42,7 @@ function rethrowSupersededWhenReplacedByDifferentPoolIdentity(scopeKey, poolKey,
|
|
|
42
42
|
const sessionAgentsByScope = new Map();
|
|
43
43
|
const invalidatedScopeKeys = new Set();
|
|
44
44
|
const deadTransportScopeKeys = new Set();
|
|
45
|
-
|
|
45
|
+
const DEAD_TRANSPORT_AGENT_DISPOSE_TIMEOUT_MS = 3000;
|
|
46
46
|
const terminalDisposedScopeGenerations = new Map();
|
|
47
47
|
const scopeCreationGenerations = new Map();
|
|
48
48
|
const EMPTY_POOL_STATE = { status: "empty" };
|
|
@@ -121,6 +121,8 @@ function buildSessionAgentPoolKey(scopeKey, params) {
|
|
|
121
121
|
buildApiKeyPoolKeyFingerprint(params.apiKey),
|
|
122
122
|
buildBridgePoolKeySuffix("bridge" in params ? params.bridge : getRegisteredCursorPiToolBridge()),
|
|
123
123
|
...buildCustomSubagentPoolKeySuffix(params.agents),
|
|
124
|
+
// Omit for the default so historical durable keys remain byte-for-byte stable.
|
|
125
|
+
...(params.nativeToolsEnabled === false ? ["tools:mcp-only"] : []),
|
|
124
126
|
].join("\0");
|
|
125
127
|
}
|
|
126
128
|
async function disposePoolEntry(entry, options) {
|
|
@@ -141,7 +143,7 @@ async function disposePoolEntry(entry, options) {
|
|
|
141
143
|
await (options?.deadTransport
|
|
142
144
|
? Promise.race([
|
|
143
145
|
disposal,
|
|
144
|
-
new Promise((resolve) => setTimeout(resolve,
|
|
146
|
+
new Promise((resolve) => setTimeout(resolve, DEAD_TRANSPORT_AGENT_DISPOSE_TIMEOUT_MS).unref?.()),
|
|
145
147
|
])
|
|
146
148
|
: disposal);
|
|
147
149
|
}
|
|
@@ -343,6 +345,7 @@ async function createSessionAgentEntry(scopeKey, persistentStore, instanceId, se
|
|
|
343
345
|
localSafety: params.localSafety,
|
|
344
346
|
store: sessionStore.store,
|
|
345
347
|
}),
|
|
348
|
+
...(params.nativeToolsEnabled === false ? { tools: ["mcp"] } : {}),
|
|
346
349
|
...(bridgeRun?.mcpServers ? { mcpServers: bridgeRun.mcpServers } : {}),
|
|
347
350
|
...(params.agents && Object.keys(params.agents).length ? { agents: params.agents } : {}),
|
|
348
351
|
});
|
|
@@ -404,7 +407,7 @@ async function createSessionAgentEntry(scopeKey, persistentStore, instanceId, se
|
|
|
404
407
|
}
|
|
405
408
|
}
|
|
406
409
|
export { buildCursorSessionSendPrompt, MAX_COMPLETED_INCREMENTAL_SENDS_BEFORE_REBOOTSTRAP, planCursorSessionSend, } from "./cursor-session-send-policy.js";
|
|
407
|
-
export function invalidateSessionAgent(scopeKey
|
|
410
|
+
export function invalidateSessionAgent(scopeKey, options) {
|
|
408
411
|
invalidatedScopeKeys.add(scopeKey);
|
|
409
412
|
if (options?.deadTransport)
|
|
410
413
|
deadTransportScopeKeys.add(scopeKey);
|
|
@@ -534,11 +537,6 @@ export const __testUtils = {
|
|
|
534
537
|
resetSessionCursorAgent,
|
|
535
538
|
refreshSessionCursorAgentConfig,
|
|
536
539
|
disposeAllSessionCursorAgents,
|
|
537
|
-
setDeadTransportAgentDisposeTimeoutMs(ms) {
|
|
538
|
-
const previous = deadTransportAgentDisposeTimeoutMs;
|
|
539
|
-
deadTransportAgentDisposeTimeoutMs = ms;
|
|
540
|
-
return previous;
|
|
541
|
-
},
|
|
542
540
|
SessionCursorAgentCreationSupersededError,
|
|
543
541
|
SessionCursorAgentScopeClosedError,
|
|
544
542
|
};
|
|
@@ -3,7 +3,7 @@ import { parseArgs } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
import { truncateCursorDisplayLine } from "./cursor-display-text.js";
|
|
4
4
|
const ANONYMOUS_SESSION_SCOPE_KEY = "__anonymous__";
|
|
5
5
|
const EPHEMERAL_SESSION_SCOPE_PREFIX = "__ephemeral__:";
|
|
6
|
-
|
|
6
|
+
const MAX_CURSOR_SESSION_NAME_LENGTH = 100;
|
|
7
7
|
const state = {
|
|
8
8
|
sessionCwd: process.cwd(),
|
|
9
9
|
sessionFile: undefined,
|
|
@@ -65,15 +65,9 @@ export function getCursorSessionScopeKey() {
|
|
|
65
65
|
export function getCursorSessionScopeGeneration(scopeKey = getCursorSessionScopeKey()) {
|
|
66
66
|
return scopeGenerations.get(scopeKey) ?? 0;
|
|
67
67
|
}
|
|
68
|
-
export function getCursorSessionCwd() {
|
|
69
|
-
return state.sessionCwd;
|
|
70
|
-
}
|
|
71
68
|
export function getCursorSessionProjectTrusted(pi) {
|
|
72
69
|
return pi ? scopes.get(pi)?.projectTrusted === true : state.projectTrusted;
|
|
73
70
|
}
|
|
74
|
-
export function getCursorSessionName() {
|
|
75
|
-
return state.sessionName;
|
|
76
|
-
}
|
|
77
71
|
function normalizeCursorSessionName(name) {
|
|
78
72
|
if (name === undefined)
|
|
79
73
|
return undefined;
|
|
@@ -9,7 +9,7 @@ let sdkOperationsForTests;
|
|
|
9
9
|
const activeWorkspaceRoots = new Map();
|
|
10
10
|
// ponytail: leases protect only extension-owned stores in this process. Other SDK
|
|
11
11
|
// users/processes must stop before upgrade; a cross-process lock belongs in the SDK.
|
|
12
|
-
|
|
12
|
+
function hashCursorSessionStoreScope(scopeKey) {
|
|
13
13
|
return createHash("sha256")
|
|
14
14
|
.update("pi-cursor-sdk-session-store\0")
|
|
15
15
|
.update(scopeKey)
|