pi-cursor-sdk 0.5.2 → 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 +66 -1
- package/README.md +62 -795
- package/dist/context.js +13 -7
- 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-custom-subagent-definitions.js +1 -1
- package/dist/cursor-fallback-models.generated.js +665 -1
- package/dist/cursor-live-run-coordinator.js +1 -0
- package/dist/cursor-local-agent-ownership.js +17 -0
- package/dist/cursor-mcp-timeout-override.js +7 -18
- package/dist/cursor-native-tool-display-replay.js +5 -5
- package/dist/cursor-native-tools.js +11 -0
- package/dist/cursor-pi-tool-bridge-diagnostics.js +4 -0
- package/dist/cursor-pi-tool-bridge-env.js +25 -2
- package/dist/cursor-pi-tool-bridge-run.js +10 -3
- 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 +2 -8
- 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-turn-summary.js +1 -1
- 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-cleanup.js +10 -1
- package/dist/cursor-session-agent-lifecycle.js +2 -2
- package/dist/cursor-session-agent-lineage.js +11 -7
- package/dist/cursor-session-agent-resume.js +15 -9
- package/dist/cursor-session-agent.js +28 -15
- package/dist/cursor-session-scope.js +28 -21
- package/dist/cursor-session-settings.js +3 -3
- package/dist/cursor-session-store.js +2 -2
- package/dist/cursor-state.js +26 -29
- 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 +8 -10
- package/dist/model-list-cache.js +4 -9
- package/docs/cursor-live-smoke-checklist.md +4 -4
- package/docs/cursor-model-ux-spec.md +36 -19
- package/docs/cursor-native-tool-replay.md +8 -5
- package/docs/cursor-native-tool-visual-audit.md +1 -1
- package/docs/cursor-testing-lessons.md +39 -9
- package/docs/cursor-tool-surfaces.md +48 -4
- package/docs/development.md +93 -0
- package/docs/platform-smoke-implementation.md +3 -2
- package/docs/reference.md +806 -0
- package/package.json +13 -11
- 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 +18 -11
- 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-custom-subagent-definitions.ts +1 -1
- package/src/cursor-fallback-models.generated.ts +665 -1
- package/src/cursor-live-run-coordinator.ts +5 -0
- package/src/cursor-local-agent-ownership.ts +18 -0
- package/src/cursor-mcp-timeout-override.ts +7 -31
- package/src/cursor-native-tool-display-replay.ts +4 -6
- package/src/cursor-native-tools.ts +14 -0
- package/src/cursor-pi-tool-bridge-diagnostics.ts +17 -0
- package/src/cursor-pi-tool-bridge-env.ts +31 -2
- package/src/cursor-pi-tool-bridge-run.ts +10 -3
- 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 +2 -10
- 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-turn-summary.ts +1 -1
- 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-cleanup.ts +6 -1
- package/src/cursor-session-agent-lifecycle.ts +2 -2
- package/src/cursor-session-agent-lineage.ts +10 -5
- package/src/cursor-session-agent-resume.ts +14 -7
- package/src/cursor-session-agent.ts +31 -15
- package/src/cursor-session-scope.ts +34 -20
- package/src/cursor-session-settings.ts +3 -3
- package/src/cursor-session-store.ts +2 -2
- package/src/cursor-state.ts +34 -41
- 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 +7 -9
- package/src/model-list-cache.ts +4 -10
|
@@ -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 {
|
|
@@ -33,7 +33,7 @@ export async function prepareCursorSummaryProviderTurn(prepareParams, selection)
|
|
|
33
33
|
configureCursorSdkHttp1(sdk, resolvedConfig.local.useHttp1ForAgent);
|
|
34
34
|
restoreCursorSdkOutputFilter = installCursorSdkOutputFilter();
|
|
35
35
|
sessionStore = (await openCursorSessionStoreForScope({
|
|
36
|
-
cwd, scopeKey: params.scope.scopeKey, persistent: false,
|
|
36
|
+
cwd, scopeKey: params.scope.persistentScopeKey ?? params.scope.scopeKey, persistent: false,
|
|
37
37
|
})).sessionStore;
|
|
38
38
|
throwIfAborted();
|
|
39
39
|
agent = await suppressCursorSdkOutput(() => sdk.Agent.create({
|
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);
|
|
@@ -3,6 +3,7 @@ import { fsyncExistingRegularFile } from "./cursor-durable-fs.js";
|
|
|
3
3
|
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
4
4
|
import { loadCursorSdk } from "./cursor-sdk-runtime.js";
|
|
5
5
|
import { cursorSessionScopeKeyForManager } from "./cursor-session-scope.js";
|
|
6
|
+
import { claimCursorLocalAgent } from "./cursor-local-agent-ownership.js";
|
|
6
7
|
import { CURSOR_SESSION_AGENT_RESUME_ENTRY_TYPE, isCursorLocalAgentId, parseCursorSessionAgentResumeEntryData, readResumableCursorSessionAgentIds, resolveCursorSessionRepoRoot, } from "./cursor-session-agent-resume.js";
|
|
7
8
|
import { withCursorSessionStoreIdentities, openCursorSessionStore, resolveCursorSessionStoreIdentity, } from "./cursor-session-store.js";
|
|
8
9
|
export const CURSOR_SESSION_AGENT_CLEANUP_ENTRY_TYPE = "cursor-sdk-agent-cleanup";
|
|
@@ -260,7 +261,15 @@ export async function runCursorSessionAgentCleanupCommand(pi, args, ctx, storeRo
|
|
|
260
261
|
openedStore = await openCursorSessionStore(ctx.cwd, identity, identities.workspaceRoot, identities.storeRoot);
|
|
261
262
|
openedStores.set(identity.stateRoot, openedStore);
|
|
262
263
|
}
|
|
263
|
-
|
|
264
|
+
const release = claimCursorLocalAgent(identity, agentId);
|
|
265
|
+
if (!release)
|
|
266
|
+
throw new Error("Cursor local agent is still owned by an active runtime");
|
|
267
|
+
try {
|
|
268
|
+
await operations.delete(agentId, { cwd: ctx.cwd, store: openedStore.store });
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
release();
|
|
272
|
+
}
|
|
264
273
|
deletedAgentIds.push(agentId);
|
|
265
274
|
}
|
|
266
275
|
catch (error) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { clearCursorSdkHttp1 } from "./cursor-http1.js";
|
|
2
|
-
import {
|
|
2
|
+
import { cursorRuntimeSessionScopeKeyForManager, hasLiveCursorSessions, onCursorSessionScopeKeyChange } from "./cursor-session-scope.js";
|
|
3
3
|
import { disposeSessionCursorAgent, invalidateSessionAgent, resetSessionCursorAgent, } from "./cursor-session-agent.js";
|
|
4
4
|
export function registerCursorSessionAgentLifecycle(pi) {
|
|
5
5
|
let scopeKey;
|
|
6
|
-
pi.on("session_start", (_event, ctx) => { scopeKey =
|
|
6
|
+
pi.on("session_start", (_event, ctx) => { scopeKey = cursorRuntimeSessionScopeKeyForManager(ctx.sessionManager); });
|
|
7
7
|
onCursorSessionScopeKeyChange(pi, async (previousScopeKey) => {
|
|
8
8
|
await disposeSessionCursorAgent(previousScopeKey);
|
|
9
9
|
});
|
|
@@ -52,7 +52,7 @@ function readRecordedAgentIds(entries, sessionId) {
|
|
|
52
52
|
export function recordCursorSessionAgentLineage(agentId, turnScopeKey) {
|
|
53
53
|
const sessionState = turnScopeKey === undefined
|
|
54
54
|
? state
|
|
55
|
-
: statesByScope.get(turnScopeKey) ?? (state.scopeKey === turnScopeKey ? state : undefined);
|
|
55
|
+
: statesByScope.get(turnScopeKey) ?? ((state.runtimeScopeKey ?? state.scopeKey) === turnScopeKey ? state : undefined);
|
|
56
56
|
if (!sessionState)
|
|
57
57
|
return;
|
|
58
58
|
const { appendEntry, sessionId, sessionFile, scopeKey, cwd } = sessionState;
|
|
@@ -83,24 +83,27 @@ export function registerCursorSessionAgentLineage(pi) {
|
|
|
83
83
|
pi.on("session_start", (_event, ctx) => {
|
|
84
84
|
state = sessionState;
|
|
85
85
|
const record = sessionState;
|
|
86
|
-
if (record.
|
|
87
|
-
statesByScope.delete(record.
|
|
86
|
+
if (record.runtimeScopeKey && statesByScope.get(record.runtimeScopeKey) === record)
|
|
87
|
+
statesByScope.delete(record.runtimeScopeKey);
|
|
88
88
|
record.appendEntry = pi.appendEntry;
|
|
89
89
|
record.sessionId = ctx.sessionManager.getSessionId();
|
|
90
90
|
record.sessionFile = ctx.sessionManager.getSessionFile() ?? undefined;
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
const scope = getCursorSessionScopeSnapshot(pi);
|
|
92
|
+
record.runtimeScopeKey = scope.scopeKey;
|
|
93
|
+
record.scopeKey = scope.persistentScopeKey ?? scope.scopeKey;
|
|
94
|
+
statesByScope.set(scope.scopeKey, record);
|
|
93
95
|
record.cwd = ctx.cwd;
|
|
94
96
|
record.recordedAgentIds = readRecordedAgentIds(ctx.sessionManager.getEntries(), record.sessionId);
|
|
95
97
|
});
|
|
96
98
|
pi.on("session_shutdown", () => {
|
|
97
99
|
const state = sessionState;
|
|
98
|
-
if (state.
|
|
99
|
-
statesByScope.delete(state.
|
|
100
|
+
if (state.runtimeScopeKey && statesByScope.get(state.runtimeScopeKey) === state)
|
|
101
|
+
statesByScope.delete(state.runtimeScopeKey);
|
|
100
102
|
state.appendEntry = undefined;
|
|
101
103
|
state.sessionId = undefined;
|
|
102
104
|
state.sessionFile = undefined;
|
|
103
105
|
state.scopeKey = undefined;
|
|
106
|
+
state.runtimeScopeKey = undefined;
|
|
104
107
|
state.cwd = undefined;
|
|
105
108
|
state.recordedAgentIds = new Set();
|
|
106
109
|
});
|
|
@@ -111,6 +114,7 @@ function resetStateForTests() {
|
|
|
111
114
|
state.sessionId = undefined;
|
|
112
115
|
state.sessionFile = undefined;
|
|
113
116
|
state.scopeKey = undefined;
|
|
117
|
+
state.runtimeScopeKey = undefined;
|
|
114
118
|
state.cwd = undefined;
|
|
115
119
|
state.recordedAgentIds = new Set();
|
|
116
120
|
}
|
|
@@ -266,7 +266,7 @@ function restoreFromBranch(branch, allEntries, state) {
|
|
|
266
266
|
}
|
|
267
267
|
export function getMatchingCursorSessionAgentResumeHandle(poolKey, scopeKey) {
|
|
268
268
|
const state = getResumeState(scopeKey);
|
|
269
|
-
if (scopeKey !== undefined && state.scopeKey !== scopeKey)
|
|
269
|
+
if (scopeKey !== undefined && (state.runtimeScopeKey ?? state.scopeKey) !== scopeKey)
|
|
270
270
|
return undefined;
|
|
271
271
|
const handle = state.activeHandle;
|
|
272
272
|
if (!handle || !isCursorLocalAgentId(handle.agentId))
|
|
@@ -292,7 +292,7 @@ export function getMatchingCursorSessionAgentResumeHandle(poolKey, scopeKey) {
|
|
|
292
292
|
}
|
|
293
293
|
export function persistCursorSessionAgentResumeHandle(input, scopeKey) {
|
|
294
294
|
const state = getResumeState(scopeKey);
|
|
295
|
-
if (scopeKey !== undefined && state.scopeKey !== scopeKey)
|
|
295
|
+
if (scopeKey !== undefined && (state.runtimeScopeKey ?? state.scopeKey) !== scopeKey)
|
|
296
296
|
return;
|
|
297
297
|
if (!isCursorLocalAgentId(input.agentId))
|
|
298
298
|
return;
|
|
@@ -343,8 +343,9 @@ function flushPendingCursorSessionAgentResumeHandle(branch, state) {
|
|
|
343
343
|
}
|
|
344
344
|
}
|
|
345
345
|
export function registerCursorSessionAgentResume(pi) {
|
|
346
|
+
const scope = getCursorSessionScopeSnapshot(pi);
|
|
346
347
|
const sessionState = {
|
|
347
|
-
appendEntry: pi.appendEntry, scopeKey:
|
|
348
|
+
appendEntry: pi.appendEntry, scopeKey: scope.persistentScopeKey ?? scope.scopeKey,
|
|
348
349
|
cwd: process.cwd(), branchPathHash: EMPTY_BRANCH_HASH,
|
|
349
350
|
compactionGeneration: 0, unownedUserEntryIds: new Set(),
|
|
350
351
|
};
|
|
@@ -355,10 +356,13 @@ export function registerCursorSessionAgentResume(pi) {
|
|
|
355
356
|
restoreFromBranch(branch, entries.length > 0 ? entries : branch, state);
|
|
356
357
|
};
|
|
357
358
|
pi.on("session_start", (_event, ctx) => {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
359
|
+
const key = state.runtimeScopeKey ?? state.scopeKey;
|
|
360
|
+
if (statesByScope.get(key) === state)
|
|
361
|
+
statesByScope.delete(key);
|
|
362
|
+
const scope = getCursorSessionScopeSnapshot(pi);
|
|
363
|
+
state.runtimeScopeKey = scope.scopeKey;
|
|
364
|
+
state.scopeKey = scope.persistentScopeKey ?? scope.scopeKey;
|
|
365
|
+
statesByScope.set(scope.scopeKey, state);
|
|
362
366
|
lastBoundState = state;
|
|
363
367
|
state.sessionFile = ctx.sessionManager.getSessionFile?.() ?? undefined;
|
|
364
368
|
state.sessionId = ctx.sessionManager.getSessionId?.() ?? undefined;
|
|
@@ -399,8 +403,9 @@ export function registerCursorSessionAgentResume(pi) {
|
|
|
399
403
|
state.branchPathHash = hashBranchStep(state.branchPathHash, event.compactionEntry);
|
|
400
404
|
});
|
|
401
405
|
pi.on("session_shutdown", () => {
|
|
402
|
-
|
|
403
|
-
|
|
406
|
+
const key = state.runtimeScopeKey ?? state.scopeKey;
|
|
407
|
+
if (statesByScope.get(key) === state)
|
|
408
|
+
statesByScope.delete(key);
|
|
404
409
|
state.appendEntry = undefined;
|
|
405
410
|
state.pendingHandle = undefined;
|
|
406
411
|
});
|
|
@@ -412,6 +417,7 @@ function resetStateForTests() {
|
|
|
412
417
|
const state = lastBoundState;
|
|
413
418
|
state.appendEntry = undefined;
|
|
414
419
|
state.scopeKey = getCursorSessionScopeKey();
|
|
420
|
+
state.runtimeScopeKey = undefined;
|
|
415
421
|
state.sessionFile = undefined;
|
|
416
422
|
state.sessionId = undefined;
|
|
417
423
|
state.cwd = process.cwd();
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { normalizeCursorCustomStoreRoot } from "./cursor-custom-store-path.js";
|
|
3
|
+
import { claimCursorLocalAgent } from "./cursor-local-agent-ownership.js";
|
|
3
4
|
import { getRegisteredCursorPiToolBridge, } from "./cursor-pi-tool-bridge.js";
|
|
4
5
|
import { computeCursorContextFingerprint } from "./context.js";
|
|
5
|
-
import {
|
|
6
|
+
import { getCursorSessionScopeSnapshot, getCursorSessionScopeGeneration, getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
6
7
|
import { getMatchingCursorSessionAgentResumeHandle, persistCursorSessionAgentResumeHandle, } from "./cursor-session-agent-resume.js";
|
|
7
8
|
import { loadCursorSdk } from "./cursor-sdk-runtime.js";
|
|
8
9
|
import { cursorSessionStoreIdentitiesEqual, openCursorSessionStore, openCursorSessionStoreForScope, } from "./cursor-session-store.js";
|
|
@@ -41,7 +42,7 @@ function rethrowSupersededWhenReplacedByDifferentPoolIdentity(scopeKey, poolKey,
|
|
|
41
42
|
const sessionAgentsByScope = new Map();
|
|
42
43
|
const invalidatedScopeKeys = new Set();
|
|
43
44
|
const deadTransportScopeKeys = new Set();
|
|
44
|
-
|
|
45
|
+
const DEAD_TRANSPORT_AGENT_DISPOSE_TIMEOUT_MS = 3000;
|
|
45
46
|
const terminalDisposedScopeGenerations = new Map();
|
|
46
47
|
const scopeCreationGenerations = new Map();
|
|
47
48
|
const EMPTY_POOL_STATE = { status: "empty" };
|
|
@@ -107,7 +108,7 @@ function buildCustomSubagentPoolKeySuffix(agents) {
|
|
|
107
108
|
}
|
|
108
109
|
function buildSessionAgentPoolKey(scopeKey, params) {
|
|
109
110
|
return [
|
|
110
|
-
scopeKey,
|
|
111
|
+
params.scope?.persistentScopeKey ?? scopeKey,
|
|
111
112
|
params.cwd,
|
|
112
113
|
buildModelPoolKey(params.modelSelection),
|
|
113
114
|
buildSettingSourcesPoolKey(params.settingSources),
|
|
@@ -120,6 +121,8 @@ function buildSessionAgentPoolKey(scopeKey, params) {
|
|
|
120
121
|
buildApiKeyPoolKeyFingerprint(params.apiKey),
|
|
121
122
|
buildBridgePoolKeySuffix("bridge" in params ? params.bridge : getRegisteredCursorPiToolBridge()),
|
|
122
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"] : []),
|
|
123
126
|
].join("\0");
|
|
124
127
|
}
|
|
125
128
|
async function disposePoolEntry(entry, options) {
|
|
@@ -132,14 +135,15 @@ async function disposePoolEntry(entry, options) {
|
|
|
132
135
|
catch {
|
|
133
136
|
// disposal failure should not block session replacement
|
|
134
137
|
}
|
|
138
|
+
let disposal;
|
|
135
139
|
try {
|
|
136
|
-
|
|
140
|
+
disposal = Promise.resolve(entry.agent[Symbol.asyncDispose]());
|
|
137
141
|
// A dead local transport may never settle SDK disposal; bound the wait so the
|
|
138
142
|
// next acquire recreates instead of hanging on the dead agent.
|
|
139
143
|
await (options?.deadTransport
|
|
140
144
|
? Promise.race([
|
|
141
145
|
disposal,
|
|
142
|
-
new Promise((resolve) => setTimeout(resolve,
|
|
146
|
+
new Promise((resolve) => setTimeout(resolve, DEAD_TRANSPORT_AGENT_DISPOSE_TIMEOUT_MS).unref?.()),
|
|
143
147
|
])
|
|
144
148
|
: disposal);
|
|
145
149
|
}
|
|
@@ -147,6 +151,8 @@ async function disposePoolEntry(entry, options) {
|
|
|
147
151
|
// disposal failure should not block session replacement
|
|
148
152
|
}
|
|
149
153
|
await entry.sessionStore.dispose().catch(() => undefined);
|
|
154
|
+
// Timeout/rejection is not proof of closure; only successful SDK disposal releases ownership.
|
|
155
|
+
void disposal?.then(entry.releaseAgentClaim, () => { });
|
|
150
156
|
}
|
|
151
157
|
async function disposePoolEntryForScope(scopeKey, options) {
|
|
152
158
|
invalidateScopeCreations(scopeKey);
|
|
@@ -296,6 +302,7 @@ async function tryLeaseReadyEntry(entry, scopeKey, params, poolKey, created) {
|
|
|
296
302
|
async function createSessionAgentEntry(scopeKey, persistentStore, instanceId, sendState, params) {
|
|
297
303
|
let bridgeRun;
|
|
298
304
|
let sessionStore;
|
|
305
|
+
let releaseAgentClaim;
|
|
299
306
|
try {
|
|
300
307
|
const registeredBridge = params.bridge;
|
|
301
308
|
if (registeredBridge) {
|
|
@@ -320,7 +327,7 @@ async function createSessionAgentEntry(scopeKey, persistentStore, instanceId, se
|
|
|
320
327
|
const resumeHandle = resumeEligible ? getMatchingCursorSessionAgentResumeHandle(resolvedPoolKey, scopeKey) : undefined;
|
|
321
328
|
const storeSelection = await openCursorSessionStoreForScope({
|
|
322
329
|
cwd: params.cwd,
|
|
323
|
-
scopeKey,
|
|
330
|
+
scopeKey: params.scope?.persistentScopeKey ?? scopeKey,
|
|
324
331
|
persistent: persistentStore,
|
|
325
332
|
storeRoot: params.storeRoot,
|
|
326
333
|
resume: resumeHandle ? { identity: resumeHandle.storeIdentity, agentId: resumeHandle.agentId } : undefined,
|
|
@@ -338,6 +345,7 @@ async function createSessionAgentEntry(scopeKey, persistentStore, instanceId, se
|
|
|
338
345
|
localSafety: params.localSafety,
|
|
339
346
|
store: sessionStore.store,
|
|
340
347
|
}),
|
|
348
|
+
...(params.nativeToolsEnabled === false ? { tools: ["mcp"] } : {}),
|
|
341
349
|
...(bridgeRun?.mcpServers ? { mcpServers: bridgeRun.mcpServers } : {}),
|
|
342
350
|
...(params.agents && Object.keys(params.agents).length ? { agents: params.agents } : {}),
|
|
343
351
|
});
|
|
@@ -345,12 +353,17 @@ async function createSessionAgentEntry(scopeKey, persistentStore, instanceId, se
|
|
|
345
353
|
let effectiveSendState = sendState;
|
|
346
354
|
let resumed = false;
|
|
347
355
|
if (resumeHandle && resumeAttemptAllowed && resumeAgent) {
|
|
356
|
+
releaseAgentClaim = claimCursorLocalAgent(sessionStore.identity, resumeHandle.agentId);
|
|
357
|
+
}
|
|
358
|
+
if (resumeHandle && resumeAttemptAllowed && resumeAgent && releaseAgentClaim) {
|
|
348
359
|
try {
|
|
349
360
|
agent = await resumeAgent(resumeHandle.agentId, buildAgentOptions());
|
|
350
361
|
effectiveSendState = { ...resumeHandle.sendState };
|
|
351
362
|
resumed = true;
|
|
352
363
|
}
|
|
353
364
|
catch {
|
|
365
|
+
releaseAgentClaim();
|
|
366
|
+
releaseAgentClaim = undefined;
|
|
354
367
|
if (persistentStore)
|
|
355
368
|
resumeNotice = LOCAL_RESUME_FALLBACK_NOTICE;
|
|
356
369
|
if (storeSelection.persistent && !cursorSessionStoreIdentitiesEqual(sessionStore.identity, storeSelection.identities.sessionStore)) {
|
|
@@ -366,6 +379,9 @@ async function createSessionAgentEntry(scopeKey, persistentStore, instanceId, se
|
|
|
366
379
|
throw new Error("Cursor SDK agent creation returned no agent");
|
|
367
380
|
if (!sessionStore)
|
|
368
381
|
throw new Error("Cursor SDK session store was not opened");
|
|
382
|
+
releaseAgentClaim ??= claimCursorLocalAgent(sessionStore.identity, agent.agentId);
|
|
383
|
+
if (!releaseAgentClaim)
|
|
384
|
+
throw new Error("Cursor SDK agent is already owned by another runtime");
|
|
369
385
|
return {
|
|
370
386
|
status: "ready",
|
|
371
387
|
poolKey: resolvedPoolKey,
|
|
@@ -378,6 +394,7 @@ async function createSessionAgentEntry(scopeKey, persistentStore, instanceId, se
|
|
|
378
394
|
sendState: effectiveSendState,
|
|
379
395
|
resumeEnabled: params.localResume === true,
|
|
380
396
|
resumed,
|
|
397
|
+
releaseAgentClaim,
|
|
381
398
|
...(resumeNotice ? { resumeNotice } : {}),
|
|
382
399
|
};
|
|
383
400
|
}
|
|
@@ -385,19 +402,20 @@ async function createSessionAgentEntry(scopeKey, persistentStore, instanceId, se
|
|
|
385
402
|
bridgeRun?.cancel("Cursor session agent create failed");
|
|
386
403
|
await bridgeRun?.dispose().catch(() => undefined);
|
|
387
404
|
await sessionStore?.dispose().catch(() => undefined);
|
|
405
|
+
releaseAgentClaim?.();
|
|
388
406
|
throw error;
|
|
389
407
|
}
|
|
390
408
|
}
|
|
391
409
|
export { buildCursorSessionSendPrompt, MAX_COMPLETED_INCREMENTAL_SENDS_BEFORE_REBOOTSTRAP, planCursorSessionSend, } from "./cursor-session-send-policy.js";
|
|
392
|
-
export function invalidateSessionAgent(scopeKey
|
|
410
|
+
export function invalidateSessionAgent(scopeKey, options) {
|
|
393
411
|
invalidatedScopeKeys.add(scopeKey);
|
|
394
412
|
if (options?.deadTransport)
|
|
395
413
|
deadTransportScopeKeys.add(scopeKey);
|
|
396
414
|
}
|
|
397
415
|
export async function acquireSessionCursorAgent(input) {
|
|
398
|
-
const params = { ...input, bridge: "bridge" in input ? input.bridge : getRegisteredCursorPiToolBridge() };
|
|
399
|
-
const scopeKey = params.scope
|
|
400
|
-
const persistentStore =
|
|
416
|
+
const params = { ...input, scope: input.scope ?? getCursorSessionScopeSnapshot(), bridge: "bridge" in input ? input.bridge : getRegisteredCursorPiToolBridge() };
|
|
417
|
+
const scopeKey = params.scope.scopeKey;
|
|
418
|
+
const persistentStore = params.scope.sessionFile !== undefined;
|
|
401
419
|
params.storeRoot = persistentStore && params.storeRoot !== undefined ? normalizeCursorCustomStoreRoot(params.storeRoot) : undefined;
|
|
402
420
|
while (true) {
|
|
403
421
|
assertScopeAcceptsAcquire(scopeKey);
|
|
@@ -519,11 +537,6 @@ export const __testUtils = {
|
|
|
519
537
|
resetSessionCursorAgent,
|
|
520
538
|
refreshSessionCursorAgentConfig,
|
|
521
539
|
disposeAllSessionCursorAgents,
|
|
522
|
-
setDeadTransportAgentDisposeTimeoutMs(ms) {
|
|
523
|
-
const previous = deadTransportAgentDisposeTimeoutMs;
|
|
524
|
-
deadTransportAgentDisposeTimeoutMs = ms;
|
|
525
|
-
return previous;
|
|
526
|
-
},
|
|
527
540
|
SessionCursorAgentCreationSupersededError,
|
|
528
541
|
SessionCursorAgentScopeClosedError,
|
|
529
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,
|
|
@@ -11,23 +11,31 @@ const state = {
|
|
|
11
11
|
sessionName: undefined,
|
|
12
12
|
projectTrusted: false,
|
|
13
13
|
sessionGeneration: 0,
|
|
14
|
+
runtimeOwner: undefined,
|
|
14
15
|
};
|
|
15
16
|
const scopes = new Map();
|
|
16
17
|
const scopeChangeHandlers = new Map();
|
|
17
18
|
const scopeGenerations = new Map([[ANONYMOUS_SESSION_SCOPE_KEY, 0]]);
|
|
18
19
|
const projectTrustResolutionCwds = new Set();
|
|
19
20
|
let nextSessionGeneration = 1;
|
|
21
|
+
let nextRuntimeOwner = 1;
|
|
22
|
+
let runtimeOwners = new WeakMap();
|
|
23
|
+
function persistentScopeKeyFor(record) {
|
|
24
|
+
return record.sessionFile ?? (record.sessionId ? `${EPHEMERAL_SESSION_SCOPE_PREFIX}${record.sessionId}` : ANONYMOUS_SESSION_SCOPE_KEY);
|
|
25
|
+
}
|
|
20
26
|
function scopeKeyFor(record) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (record.sessionId)
|
|
24
|
-
return `${EPHEMERAL_SESSION_SCOPE_PREFIX}${record.sessionId}`;
|
|
25
|
-
return ANONYMOUS_SESSION_SCOPE_KEY;
|
|
27
|
+
const persistent = persistentScopeKeyFor(record);
|
|
28
|
+
return record.runtimeOwner === undefined ? persistent : `${persistent}\0runtime:${record.runtimeOwner}`;
|
|
26
29
|
}
|
|
27
30
|
export function cursorSessionScopeKeyForManager(manager) {
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
return persistentScopeKeyFor({
|
|
32
|
+
sessionFile: manager.getSessionFile?.() ?? undefined, sessionId: manager.getSessionId?.(),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function cursorRuntimeSessionScopeKeyForManager(manager) {
|
|
36
|
+
const persistent = cursorSessionScopeKeyForManager(manager);
|
|
37
|
+
const owner = runtimeOwners.get(manager);
|
|
38
|
+
return owner === undefined ? persistent : `${persistent}\0runtime:${owner}`;
|
|
31
39
|
}
|
|
32
40
|
export function getCursorSessionScopeSnapshot(pi) {
|
|
33
41
|
if (!pi && scopes.size > 1)
|
|
@@ -39,6 +47,7 @@ export function getCursorSessionScopeSnapshot(pi) {
|
|
|
39
47
|
return Object.freeze({
|
|
40
48
|
cwd: record.sessionCwd,
|
|
41
49
|
scopeKey,
|
|
50
|
+
persistentScopeKey: persistentScopeKeyFor(record),
|
|
42
51
|
sessionFile: record.sessionFile,
|
|
43
52
|
sessionId: record.sessionId,
|
|
44
53
|
sessionName: record.sessionName,
|
|
@@ -49,37 +58,29 @@ export function getCursorSessionScopeSnapshot(pi) {
|
|
|
49
58
|
export function hasLiveCursorSessions() {
|
|
50
59
|
return scopes.size > 0;
|
|
51
60
|
}
|
|
52
|
-
|
|
53
|
-
return state.sessionFile;
|
|
54
|
-
}
|
|
55
|
-
/** Stable pool key, with process-local anonymous fallback before session_start. */
|
|
61
|
+
/** Runtime ownership key; persisted identities are captured separately. */
|
|
56
62
|
export function getCursorSessionScopeKey() {
|
|
57
63
|
return scopeKeyFor(state);
|
|
58
64
|
}
|
|
59
65
|
export function getCursorSessionScopeGeneration(scopeKey = getCursorSessionScopeKey()) {
|
|
60
66
|
return scopeGenerations.get(scopeKey) ?? 0;
|
|
61
67
|
}
|
|
62
|
-
export function getCursorSessionCwd() {
|
|
63
|
-
return state.sessionCwd;
|
|
64
|
-
}
|
|
65
68
|
export function getCursorSessionProjectTrusted(pi) {
|
|
66
69
|
return pi ? scopes.get(pi)?.projectTrusted === true : state.projectTrusted;
|
|
67
70
|
}
|
|
68
|
-
export function getCursorSessionName() {
|
|
69
|
-
return state.sessionName;
|
|
70
|
-
}
|
|
71
71
|
function normalizeCursorSessionName(name) {
|
|
72
72
|
if (name === undefined)
|
|
73
73
|
return undefined;
|
|
74
74
|
return truncateCursorDisplayLine(name, MAX_CURSOR_SESSION_NAME_LENGTH) || undefined;
|
|
75
75
|
}
|
|
76
|
-
function setCursorSessionScope(cwd, sessionFile, sessionId, projectTrusted = false, sessionName) {
|
|
76
|
+
function setCursorSessionScope(cwd, sessionFile, sessionId, projectTrusted = false, sessionName, runtimeOwner) {
|
|
77
77
|
state.sessionCwd = cwd;
|
|
78
78
|
state.sessionFile = sessionFile;
|
|
79
79
|
state.sessionId = sessionId;
|
|
80
80
|
state.sessionName = normalizeCursorSessionName(sessionName);
|
|
81
81
|
state.projectTrusted = projectTrusted;
|
|
82
82
|
state.sessionGeneration = nextSessionGeneration++;
|
|
83
|
+
state.runtimeOwner = runtimeOwner;
|
|
83
84
|
scopeGenerations.set(scopeKeyFor(state), state.sessionGeneration);
|
|
84
85
|
}
|
|
85
86
|
function recordProjectTrustResolution(cwd) {
|
|
@@ -92,6 +93,7 @@ function resetCursorSessionScope() {
|
|
|
92
93
|
Object.assign(state, {
|
|
93
94
|
sessionCwd: process.cwd(), sessionFile: undefined, sessionId: undefined,
|
|
94
95
|
sessionName: undefined, projectTrusted: false, sessionGeneration: 0,
|
|
96
|
+
runtimeOwner: undefined,
|
|
95
97
|
});
|
|
96
98
|
nextSessionGeneration = 1;
|
|
97
99
|
scopeGenerations.clear();
|
|
@@ -99,6 +101,8 @@ function resetCursorSessionScope() {
|
|
|
99
101
|
projectTrustResolutionCwds.clear();
|
|
100
102
|
scopes.clear();
|
|
101
103
|
scopeChangeHandlers.clear();
|
|
104
|
+
runtimeOwners = new WeakMap();
|
|
105
|
+
nextRuntimeOwner = 1;
|
|
102
106
|
}
|
|
103
107
|
export function onCursorSessionScopeKeyChange(pi, handler) {
|
|
104
108
|
scopeChangeHandlers.set(pi, handler);
|
|
@@ -108,6 +112,7 @@ export function registerCursorSessionScope(pi) {
|
|
|
108
112
|
const record = {
|
|
109
113
|
sessionCwd: process.cwd(), sessionFile: undefined, sessionId: undefined,
|
|
110
114
|
sessionName: undefined, projectTrusted: false, sessionGeneration: 0,
|
|
115
|
+
runtimeOwner: undefined,
|
|
111
116
|
};
|
|
112
117
|
scopes.set(pi, record);
|
|
113
118
|
const trustedCwds = new Set();
|
|
@@ -118,8 +123,10 @@ export function registerCursorSessionScope(pi) {
|
|
|
118
123
|
pi.on("session_start", async (_event, ctx) => {
|
|
119
124
|
const previousScopeKey = scopeKeyFor(record);
|
|
120
125
|
const wasBound = record.sessionGeneration > 0;
|
|
126
|
+
if (!runtimeOwners.has(ctx.sessionManager))
|
|
127
|
+
runtimeOwners.set(ctx.sessionManager, nextRuntimeOwner++);
|
|
121
128
|
setCursorSessionScope(ctx.cwd, ctx.sessionManager?.getSessionFile?.() ?? undefined, ctx.sessionManager?.getSessionId?.() ?? undefined, ctx.isProjectTrusted?.() === true
|
|
122
|
-
&& (trustedCwds.has(resolve(ctx.cwd)) || projectTrustResolutionCwds.has(resolve(ctx.cwd)) || isCliProjectTrustApproved()), ctx.sessionManager?.getSessionName?.() ?? undefined);
|
|
129
|
+
&& (trustedCwds.has(resolve(ctx.cwd)) || projectTrustResolutionCwds.has(resolve(ctx.cwd)) || isCliProjectTrustApproved()), ctx.sessionManager?.getSessionName?.() ?? undefined, runtimeOwners.get(ctx.sessionManager));
|
|
123
130
|
Object.assign(record, state);
|
|
124
131
|
scopes.set(pi, record);
|
|
125
132
|
if (wasBound && previousScopeKey !== scopeKeyFor(record)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { cursorRuntimeSessionScopeKeyForManager, getCursorSessionProjectTrusted } from "./cursor-session-scope.js";
|
|
2
2
|
function createCliSettings() {
|
|
3
3
|
return {
|
|
4
4
|
forceFast: false, forceNoFast: false,
|
|
@@ -34,7 +34,7 @@ export function getCursorSessionSettings(scopeKey) {
|
|
|
34
34
|
}
|
|
35
35
|
export function cursorSettingsScopeForContext(ctx) {
|
|
36
36
|
const manager = ctx.sessionManager;
|
|
37
|
-
return manager ?
|
|
37
|
+
return manager ? cursorRuntimeSessionScopeKeyForManager(manager) : undefined;
|
|
38
38
|
}
|
|
39
39
|
export function registerCursorSessionSettings(pi) {
|
|
40
40
|
let scopeKey;
|
|
@@ -42,7 +42,7 @@ export function registerCursorSessionSettings(pi) {
|
|
|
42
42
|
pi.on("session_start", (_event, ctx) => {
|
|
43
43
|
if (scopeKey)
|
|
44
44
|
settings.delete(scopeKey);
|
|
45
|
-
scopeKey =
|
|
45
|
+
scopeKey = cursorRuntimeSessionScopeKeyForManager(ctx.sessionManager);
|
|
46
46
|
legacySettings = getCursorSessionSettings(scopeKey);
|
|
47
47
|
// Reload creates a new ExtensionAPI but retains the same public manager.
|
|
48
48
|
// Keep the run's one-shot CLI consumption without retaining dead managers.
|
|
@@ -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)
|
|
@@ -143,7 +143,7 @@ function workspaceHashes(cwd) {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
function assertSafeWorkspaceLayout(cwd) {
|
|
146
|
-
// ponytail: SDK 1.0.
|
|
146
|
+
// ponytail: SDK 1.0.37 has no public read-only root resolver; use one when exposed.
|
|
147
147
|
// This pure layout is contract-verified against its factory and public getter;
|
|
148
148
|
// guard it before that getter can rename MD5 history through an owned link.
|
|
149
149
|
const slug = cwd.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|