pi-cursor-sdk 0.5.3 → 0.5.5
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 +61 -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 +424 -1
- package/dist/cursor-live-run-coordinator.js +1 -0
- package/dist/cursor-local-run-terminal-error.js +57 -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-finalize.js +2 -0
- package/dist/cursor-provider-turn-prepare.js +8 -1
- package/dist/cursor-provider-turn-runner.js +17 -6
- package/dist/cursor-provider-turn-send.js +3 -0
- 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 +3 -2
- 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 +4 -4
- package/docs/cursor-model-ux-spec.md +18 -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 +22 -4
- package/docs/cursor-tool-surfaces.md +32 -3
- package/docs/development.md +101 -0
- package/docs/platform-smoke-implementation.md +3 -2
- package/docs/reference.md +810 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/package.json +1 -1
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/core/compile.ts +9 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/core/schemas.ts +35 -1
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/core/util.ts +13 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/core/versions.ts +1 -1
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/compile.cjs +8 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/compile.js +8 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/schemas.cjs +30 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/schemas.d.cts +3 -1
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/schemas.d.ts +3 -1
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/schemas.js +30 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/util.cjs +15 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/util.d.cts +2 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/util.d.ts +2 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/util.js +14 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/versions.cjs +1 -1
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/versions.js +1 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/package.json +1 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/core/compile.ts +9 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/core/schemas.ts +35 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/core/util.ts +13 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/core/versions.ts +1 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/compile.cjs +8 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/compile.js +8 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/schemas.cjs +30 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/schemas.d.cts +3 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/schemas.d.ts +3 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/schemas.js +30 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/util.cjs +15 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/util.d.cts +2 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/util.d.ts +2 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/util.js +14 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/versions.cjs +1 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/versions.js +1 -1
- package/package.json +14 -6
- 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/scripts/steering-rpc-smoke.mjs +1 -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 +424 -1
- package/src/cursor-live-run-coordinator.ts +5 -0
- package/src/cursor-local-run-terminal-error.ts +58 -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-finalize.ts +2 -0
- package/src/cursor-provider-turn-prepare.ts +10 -1
- package/src/cursor-provider-turn-runner.ts +17 -6
- package/src/cursor-provider-turn-send.ts +2 -0
- 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 +3 -2
- 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
|
@@ -8,9 +8,9 @@ import { inferImageMimeType } from "./cursor-tool-result-display-readers.js";
|
|
|
8
8
|
import { LOCAL_READ_PREVIEW_NOTICE, isLocalReadPreviewContent } from "./cursor-transcript-utils.js";
|
|
9
9
|
import { CURSOR_REPLAY_ACTIVITY_TOOL_NAME, getCursorReplayCallSummary, shouldShowCursorReplayCollapsedExpandHint, } from "./cursor-tool-presentation-registry.js";
|
|
10
10
|
import { CURSOR_REPLAY_GENERATE_IMAGE_RESULT_TITLE, isCursorReplayGenerateImageDetails, isCursorReplayActivityDetails, parseCursorReplayToolDetails, } from "./cursor-replay-tool-details.js";
|
|
11
|
-
export { isCursorReplayNativeEditDetails,
|
|
11
|
+
export { isCursorReplayNativeEditDetails, isCursorReplayNativeWriteDetails, parseCursorReplayToolDetails, } from "./cursor-replay-tool-details.js";
|
|
12
12
|
export const CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES = 8;
|
|
13
|
-
|
|
13
|
+
const CURSOR_REPLAY_PREVIEW_MAX_CHARS = 4000;
|
|
14
14
|
export const CURSOR_REPLAY_PREVIEW_MAX_LINE_CHARS = 240;
|
|
15
15
|
const CURSOR_REPLAY_HIGHLIGHT_MAX_CHARS = 12000;
|
|
16
16
|
export const cursorReplayToolSchema = Type.Object({}, { additionalProperties: true });
|
|
@@ -40,7 +40,7 @@ function buildImageReplayComponent(text, imageData, mimeType, filename, theme) {
|
|
|
40
40
|
},
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
function getCursorReplayPath(args, details) {
|
|
44
44
|
const argPath = args?.path;
|
|
45
45
|
return details?.path ?? (typeof argPath === "string" && argPath.trim() ? argPath : "unknown");
|
|
46
46
|
}
|
|
@@ -202,7 +202,7 @@ function formatCursorReplayActivityPreview(details, text, theme, maxLines, strip
|
|
|
202
202
|
}
|
|
203
203
|
return stripHeader ? formatCursorReplayPreview(text, theme, maxLines, true) : formatMutedBlock(text, theme);
|
|
204
204
|
}
|
|
205
|
-
|
|
205
|
+
function formatCursorReplayPreview(text, theme, maxLines = CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES, stripHeader = true) {
|
|
206
206
|
const body = (stripHeader ? stripCursorReplayHeader(text) : text).trimEnd();
|
|
207
207
|
if (!body)
|
|
208
208
|
return undefined;
|
|
@@ -240,7 +240,7 @@ function getCursorReplayCardTitle(toolName, args) {
|
|
|
240
240
|
}
|
|
241
241
|
return "Cursor activity";
|
|
242
242
|
}
|
|
243
|
-
|
|
243
|
+
function renderCursorReplayCall(toolName, args, theme, isPartial) {
|
|
244
244
|
if (!isPartial)
|
|
245
245
|
return new Text("", 0, 0);
|
|
246
246
|
let text = theme.fg("toolTitle", theme.bold(`${getCursorReplayCardTitle(toolName, args)} `));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { parseEnvBoolean } from "./cursor-env-boolean.js";
|
|
2
|
+
export const CURSOR_NATIVE_TOOLS_ENV = "PI_CURSOR_NATIVE_TOOLS";
|
|
3
|
+
/** Default preserves the SDK toolset; false selects the public MCP-only main loop. */
|
|
4
|
+
export function resolveCursorNativeToolsEnabled(env = process.env) {
|
|
5
|
+
return parseEnvBoolean(env[CURSOR_NATIVE_TOOLS_ENV], true);
|
|
6
|
+
}
|
|
7
|
+
export function assertCursorNativeToolsRuntime(runtime, nativeToolsEnabled) {
|
|
8
|
+
if (runtime === "cloud" && !nativeToolsEnabled) {
|
|
9
|
+
throw new Error("PI_CURSOR_NATIVE_TOOLS=0 requires local runtime; Cursor Cloud does not support the SDK tools restriction. Select local runtime or remove the opt-out.");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { parseEnvBoolean } from "./cursor-env-boolean.js";
|
|
2
2
|
import { resolveCursorMcpToolTimeoutMs } from "./cursor-mcp-timeout-override.js";
|
|
3
3
|
export const CURSOR_PI_TOOL_BRIDGE_ENV = "PI_CURSOR_PI_TOOL_BRIDGE";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV = "PI_CURSOR_EXPOSE_BUILTIN_TOOLS";
|
|
5
|
+
const CURSOR_PI_TOOL_BRIDGE_CALL_TIMEOUT_MS_ENV = "PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS";
|
|
6
6
|
export function resolveCursorPiToolBridgeEnabled(env = process.env) {
|
|
7
7
|
return parseEnvBoolean(env[CURSOR_PI_TOOL_BRIDGE_ENV], true);
|
|
8
8
|
}
|
|
@@ -16,3 +16,26 @@ export function resolveCursorPiToolBridgeCallTimeoutMs(env = process.env) {
|
|
|
16
16
|
return mcpToolTimeoutMs;
|
|
17
17
|
return Math.min(Math.max(Math.trunc(parsed), 1), mcpToolTimeoutMs);
|
|
18
18
|
}
|
|
19
|
+
const CURSOR_PI_TOOL_BRIDGE_HOST_ENV = "PI_CURSOR_PI_TOOL_BRIDGE_HOST";
|
|
20
|
+
const CURSOR_PI_TOOL_BRIDGE_PORT_ENV = "PI_CURSOR_PI_TOOL_BRIDGE_PORT";
|
|
21
|
+
const DEFAULT_URL_HOST = "127.0.0.1";
|
|
22
|
+
const PORT_PATTERN = /^\d{1,5}$/;
|
|
23
|
+
export function resolveCursorPiToolBridgeUrlHost(env = process.env) {
|
|
24
|
+
return env[CURSOR_PI_TOOL_BRIDGE_HOST_ENV]?.trim().toLowerCase() === "localhost" ? "localhost" : DEFAULT_URL_HOST;
|
|
25
|
+
}
|
|
26
|
+
function parsePort(text) {
|
|
27
|
+
if (!PORT_PATTERN.test(text))
|
|
28
|
+
return undefined;
|
|
29
|
+
const port = Number(text);
|
|
30
|
+
return port >= 1 && port <= 65535 ? port : undefined;
|
|
31
|
+
}
|
|
32
|
+
export function resolveCursorPiToolBridgePortRange(env = process.env) {
|
|
33
|
+
const parts = env[CURSOR_PI_TOOL_BRIDGE_PORT_ENV]?.trim().split("-");
|
|
34
|
+
if (!parts || parts.length > 2)
|
|
35
|
+
return undefined;
|
|
36
|
+
const first = parsePort(parts[0]);
|
|
37
|
+
const last = parts.length === 2 ? parsePort(parts[1]) : first;
|
|
38
|
+
if (first === undefined || last === undefined || first > last)
|
|
39
|
+
return undefined;
|
|
40
|
+
return { first, last };
|
|
41
|
+
}
|
|
@@ -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
|
}
|
|
@@ -4,6 +4,7 @@ import { getCheckpointContextWindow, saveCachedContextWindow } from "./context-w
|
|
|
4
4
|
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
5
5
|
import { isCursorRunFinishedSuccessfully, resolveCursorRunOutcome, } from "./cursor-provider-run-outcome.js";
|
|
6
6
|
import { loadCursorSdk } from "./cursor-sdk-runtime.js";
|
|
7
|
+
import { resolveCursorLocalRunTerminalError } from "./cursor-local-run-terminal-error.js";
|
|
7
8
|
export async function cacheSdkContextWindow(agentId, modelId, cwd, store) {
|
|
8
9
|
try {
|
|
9
10
|
const { createAgentPlatform } = await loadCursorSdk();
|
|
@@ -91,6 +92,7 @@ export async function awaitFinalizeCursorRunOutcome(params) {
|
|
|
91
92
|
let waitResult;
|
|
92
93
|
try {
|
|
93
94
|
waitResult = params.waitResult ?? (await params.run.wait());
|
|
95
|
+
waitResult = resolveCursorLocalRunTerminalError(params.run, waitResult, params.signal?.aborted === true);
|
|
94
96
|
}
|
|
95
97
|
catch (error) {
|
|
96
98
|
await recordCursorProviderTerminalUsage(params.prepared, params.signal?.aborted ? "abort" : "error");
|
|
@@ -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 {
|
|
@@ -3,6 +3,7 @@ import { createCursorCloudLifecyclePersistenceError } from "./cursor-cloud-lifec
|
|
|
3
3
|
import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
|
|
4
4
|
import { consumeCursorLocalForceOverride } from "./cursor-runtime-state.js";
|
|
5
5
|
import { recordCursorSessionAgentLineage } from "./cursor-session-agent-lineage.js";
|
|
6
|
+
import { watchCursorLocalRunTerminalError } from "./cursor-local-run-terminal-error.js";
|
|
6
7
|
const CLOUD_LEDGER_CANCEL_TIMEOUT_MS = 5000;
|
|
7
8
|
async function requestBoundedCloudRunCancellation(run) {
|
|
8
9
|
let timer;
|
|
@@ -105,6 +106,8 @@ export async function sendCursorProviderTurn(sendParams) {
|
|
|
105
106
|
recordCursorSessionAgentLineage(agent.agentId, params.scope.scopeKey);
|
|
106
107
|
}
|
|
107
108
|
const run = await runPromise;
|
|
109
|
+
if (prepared.runtimeTarget === "local")
|
|
110
|
+
watchCursorLocalRunTerminalError(prepared.store, run);
|
|
108
111
|
sdkRun = run;
|
|
109
112
|
runtime.sdkRun = run;
|
|
110
113
|
try {
|
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})`
|