pi-cursor-sdk 0.4.0 → 0.5.0
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 +30 -0
- package/README.md +65 -13
- package/dist/context-window-cache.js +0 -6
- package/dist/context.js +8 -0
- package/dist/cursor-cloud-lifecycle.js +122 -98
- package/dist/cursor-durable-fs.js +97 -3
- package/dist/cursor-fallback-models.generated.js +288 -1
- package/dist/cursor-fallback-warning.js +2 -2
- package/dist/cursor-http1.js +8 -8
- package/dist/cursor-live-run-coordinator.js +9 -13
- package/dist/cursor-native-replay-routing.js +1 -1
- package/dist/cursor-native-tool-display-registration.js +46 -22
- package/dist/cursor-native-tool-display-state.js +57 -22
- package/dist/cursor-native-tool-display-tools.js +5 -6
- package/dist/cursor-pi-tool-bridge-constants.js +23 -2
- package/dist/cursor-pi-tool-bridge-run.js +7 -3
- package/dist/cursor-pi-tool-bridge-snapshot.js +1 -1
- package/dist/cursor-pi-tool-bridge.js +31 -9
- package/dist/cursor-provider-binding.js +58 -0
- package/dist/cursor-provider-errors.js +28 -7
- package/dist/cursor-provider-lazy.js +23 -11
- package/dist/cursor-provider-live-run-drain.js +15 -12
- package/dist/cursor-provider-run-finalizer.js +19 -8
- package/dist/cursor-provider-turn-coordinator.js +1 -0
- package/dist/cursor-provider-turn-display-router.js +4 -0
- package/dist/cursor-provider-turn-emit.js +1 -0
- package/dist/cursor-provider-turn-finalize.js +29 -20
- package/dist/cursor-provider-turn-prepare.js +24 -11
- package/dist/cursor-provider-turn-runner.js +37 -11
- package/dist/cursor-provider-turn-send.js +22 -9
- package/dist/cursor-provider-turn-summary.js +89 -0
- package/dist/cursor-provider.js +9 -3
- package/dist/cursor-request-provenance.js +42 -0
- package/dist/cursor-ripgrep-path.js +14 -3
- package/dist/cursor-runtime-state.js +50 -43
- package/dist/cursor-sdk-billed-usage.js +54 -75
- package/dist/cursor-sdk-event-debug-session.js +5 -6
- package/dist/cursor-sdk-event-debug.js +9 -6
- package/dist/cursor-sdk-process-error-guard.js +8 -7
- package/dist/cursor-sdk-runtime.js +13 -0
- package/dist/cursor-session-agent-cleanup.js +2 -2
- package/dist/cursor-session-agent-lifecycle.js +18 -9
- package/dist/cursor-session-agent-lineage.js +28 -12
- package/dist/cursor-session-agent-resume.js +51 -35
- package/dist/cursor-session-agent.js +9 -9
- package/dist/cursor-session-compaction-prep.js +2 -7
- package/dist/cursor-session-scope.js +71 -39
- package/dist/cursor-session-settings.js +61 -0
- package/dist/cursor-skill-tool.js +32 -49
- package/dist/cursor-state.js +72 -62
- package/dist/cursor-usage-accounting.js +13 -70
- package/dist/cursor-usage-command.js +89 -0
- package/dist/cursor-usage-ledger.js +512 -0
- package/dist/index.js +11 -19
- package/dist/model-discovery.js +11 -19
- package/docs/cursor-dogfood-checklist.md +3 -3
- package/docs/cursor-live-smoke-checklist.md +15 -15
- package/docs/cursor-model-ux-spec.md +59 -18
- package/docs/cursor-native-tool-replay.md +5 -3
- package/docs/cursor-native-tool-visual-audit.md +8 -8
- package/docs/cursor-testing-lessons.md +47 -23
- package/docs/cursor-tool-surfaces.md +1 -1
- package/docs/platform-smoke-implementation.md +10 -6
- package/docs/platform-smoke.md +35 -46
- package/package.json +6 -6
- package/scripts/cloud-runtime-smoke.mjs +4 -2
- package/scripts/local-resume-smoke.mjs +1 -1
- package/scripts/platform-smoke/card-detect.mjs +2 -8
- package/scripts/platform-smoke/targets.mjs +8 -15
- package/scripts/platform-smoke/visual-evidence.mjs +7 -2
- package/scripts/tmux-live-smoke.sh +57 -90
- package/src/context-window-cache.ts +0 -6
- package/src/context.ts +8 -0
- package/src/cursor-cloud-lifecycle.ts +124 -98
- package/src/cursor-durable-fs.ts +65 -3
- package/src/cursor-fallback-models.generated.ts +288 -1
- package/src/cursor-fallback-warning.ts +2 -2
- package/src/cursor-http1.ts +8 -8
- package/src/cursor-live-run-coordinator.ts +12 -13
- package/src/cursor-native-replay-routing.ts +2 -1
- package/src/cursor-native-tool-display-registration.ts +48 -24
- package/src/cursor-native-tool-display-state.ts +65 -21
- package/src/cursor-native-tool-display-tools.ts +7 -4
- package/src/cursor-pi-tool-bridge-constants.ts +27 -2
- package/src/cursor-pi-tool-bridge-run.ts +11 -3
- package/src/cursor-pi-tool-bridge-snapshot.ts +1 -1
- package/src/cursor-pi-tool-bridge.ts +26 -9
- package/src/cursor-provider-binding.ts +62 -0
- package/src/cursor-provider-errors.ts +27 -7
- package/src/cursor-provider-lazy.ts +21 -15
- package/src/cursor-provider-live-run-drain.ts +20 -13
- package/src/cursor-provider-run-finalizer.ts +24 -15
- package/src/cursor-provider-turn-coordinator.ts +2 -0
- package/src/cursor-provider-turn-display-router.ts +5 -0
- package/src/cursor-provider-turn-emit.ts +3 -1
- package/src/cursor-provider-turn-finalize.ts +38 -23
- package/src/cursor-provider-turn-prepare.ts +23 -14
- package/src/cursor-provider-turn-runner.ts +40 -13
- package/src/cursor-provider-turn-send.ts +23 -15
- package/src/cursor-provider-turn-summary.ts +94 -0
- package/src/cursor-provider-turn-types.ts +37 -4
- package/src/cursor-provider.ts +25 -3
- package/src/cursor-request-provenance.ts +64 -0
- package/src/cursor-ripgrep-path.ts +15 -5
- package/src/cursor-runtime-state.ts +53 -48
- package/src/cursor-sdk-billed-usage.ts +54 -102
- package/src/cursor-sdk-event-debug-session.ts +5 -5
- package/src/cursor-sdk-event-debug.ts +10 -6
- package/src/cursor-sdk-process-error-guard.ts +7 -7
- package/src/cursor-sdk-runtime.ts +14 -0
- package/src/cursor-session-agent-cleanup.ts +2 -2
- package/src/cursor-session-agent-lifecycle.ts +14 -9
- package/src/cursor-session-agent-lineage.ts +26 -12
- package/src/cursor-session-agent-resume.ts +49 -36
- package/src/cursor-session-agent.ts +13 -10
- package/src/cursor-session-compaction-prep.ts +2 -7
- package/src/cursor-session-scope.ts +87 -39
- package/src/cursor-session-settings.ts +72 -0
- package/src/cursor-skill-tool.ts +37 -61
- package/src/cursor-state.ts +74 -67
- package/src/cursor-usage-accounting.ts +17 -82
- package/src/cursor-usage-command.ts +76 -0
- package/src/cursor-usage-ledger.ts +500 -0
- package/src/index.ts +14 -23
- package/src/model-discovery.ts +14 -22
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { isCursorLocalAgentId } from "./cursor-session-agent-resume.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getCursorSessionScopeSnapshot } from "./cursor-session-scope.js";
|
|
4
4
|
import { asRecord } from "./cursor-record-utils.js";
|
|
5
5
|
|
|
6
6
|
export const CURSOR_SESSION_AGENT_LINEAGE_ENTRY_TYPE = "cursor-sdk-agent-lineage";
|
|
@@ -33,10 +33,12 @@ interface CursorSessionAgentLineageState {
|
|
|
33
33
|
recordedAgentIds: Set<string>;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
let state: CursorSessionAgentLineageState = {
|
|
37
37
|
recordedAgentIds: new Set(),
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
const statesByScope = new Map<string, CursorSessionAgentLineageState>();
|
|
41
|
+
|
|
40
42
|
export function parseCursorSessionAgentLineageEntryData(value: unknown): CursorSessionAgentLineageEntryData | undefined {
|
|
41
43
|
const record = asRecord(value);
|
|
42
44
|
if (
|
|
@@ -77,10 +79,14 @@ function readRecordedAgentIds(entries: readonly SessionEntry[], sessionId: strin
|
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
/** Best-effort forensic lineage at the local Agent.send() boundary. Independent of resume. */
|
|
80
|
-
export function recordCursorSessionAgentLineage(agentId: string): void {
|
|
81
|
-
const
|
|
82
|
+
export function recordCursorSessionAgentLineage(agentId: string, turnScopeKey?: string): void {
|
|
83
|
+
const sessionState = turnScopeKey === undefined
|
|
84
|
+
? state
|
|
85
|
+
: statesByScope.get(turnScopeKey) ?? (state.scopeKey === turnScopeKey ? state : undefined);
|
|
86
|
+
if (!sessionState) return;
|
|
87
|
+
const { appendEntry, sessionId, sessionFile, scopeKey, cwd } = sessionState;
|
|
82
88
|
if (!appendEntry || !sessionId || !scopeKey || !cwd) return;
|
|
83
|
-
if (!isCursorLocalAgentId(agentId) ||
|
|
89
|
+
if (!isCursorLocalAgentId(agentId) || sessionState.recordedAgentIds.has(agentId)) return;
|
|
84
90
|
const data: CursorSessionAgentLineageEntryData = {
|
|
85
91
|
version: LINEAGE_ENTRY_VERSION,
|
|
86
92
|
runtime: "local",
|
|
@@ -93,7 +99,7 @@ export function recordCursorSessionAgentLineage(agentId: string): void {
|
|
|
93
99
|
};
|
|
94
100
|
try {
|
|
95
101
|
appendEntry<CursorSessionAgentLineageEntryData>(CURSOR_SESSION_AGENT_LINEAGE_ENTRY_TYPE, data);
|
|
96
|
-
|
|
102
|
+
sessionState.recordedAgentIds.add(agentId);
|
|
97
103
|
} catch {
|
|
98
104
|
// Lineage is forensic metadata; a failed stock pi append must not affect the session.
|
|
99
105
|
}
|
|
@@ -105,15 +111,22 @@ interface CursorSessionAgentLineageExtensionApi {
|
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
export function registerCursorSessionAgentLineage(pi: CursorSessionAgentLineageExtensionApi): void {
|
|
114
|
+
const sessionState: CursorSessionAgentLineageState = { recordedAgentIds: new Set() };
|
|
108
115
|
pi.on("session_start", (_event, ctx) => {
|
|
109
|
-
state
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
state = sessionState;
|
|
117
|
+
const record = sessionState;
|
|
118
|
+
if (record.scopeKey && statesByScope.get(record.scopeKey) === record) statesByScope.delete(record.scopeKey);
|
|
119
|
+
record.appendEntry = pi.appendEntry;
|
|
120
|
+
record.sessionId = ctx.sessionManager.getSessionId();
|
|
121
|
+
record.sessionFile = ctx.sessionManager.getSessionFile() ?? undefined;
|
|
122
|
+
record.scopeKey = getCursorSessionScopeSnapshot(pi).scopeKey;
|
|
123
|
+
statesByScope.set(record.scopeKey, record);
|
|
124
|
+
record.cwd = ctx.cwd;
|
|
125
|
+
record.recordedAgentIds = readRecordedAgentIds(ctx.sessionManager.getEntries(), record.sessionId);
|
|
115
126
|
});
|
|
116
127
|
pi.on("session_shutdown", () => {
|
|
128
|
+
const state = sessionState;
|
|
129
|
+
if (state.scopeKey && statesByScope.get(state.scopeKey) === state) statesByScope.delete(state.scopeKey);
|
|
117
130
|
state.appendEntry = undefined;
|
|
118
131
|
state.sessionId = undefined;
|
|
119
132
|
state.sessionFile = undefined;
|
|
@@ -124,6 +137,7 @@ export function registerCursorSessionAgentLineage(pi: CursorSessionAgentLineageE
|
|
|
124
137
|
}
|
|
125
138
|
|
|
126
139
|
function resetStateForTests(): void {
|
|
140
|
+
statesByScope.clear();
|
|
127
141
|
state.appendEntry = undefined;
|
|
128
142
|
state.sessionId = undefined;
|
|
129
143
|
state.sessionFile = undefined;
|
|
@@ -3,7 +3,7 @@ import { execFileSync } from "node:child_process";
|
|
|
3
3
|
import type { ExtensionAPI, SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import type { SessionCursorAgentSendState } from "./cursor-session-agent.js";
|
|
5
5
|
import { asRecord } from "./cursor-record-utils.js";
|
|
6
|
-
import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
6
|
+
import { getCursorSessionScopeSnapshot, getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
7
7
|
import type { CursorSessionStoreIdentity } from "./cursor-session-store.js";
|
|
8
8
|
|
|
9
9
|
export const CURSOR_SESSION_AGENT_RESUME_ENTRY_TYPE = "cursor-sdk-agent-resume";
|
|
@@ -73,7 +73,7 @@ interface CursorSessionResumeState {
|
|
|
73
73
|
unownedUserEntryIds: Set<string>;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
let lastBoundState: CursorSessionResumeState = {
|
|
77
77
|
scopeKey: getCursorSessionScopeKey(),
|
|
78
78
|
cwd: process.cwd(),
|
|
79
79
|
branchPathHash: EMPTY_BRANCH_HASH,
|
|
@@ -81,17 +81,13 @@ const state: CursorSessionResumeState = {
|
|
|
81
81
|
unownedUserEntryIds: new Set(),
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
// next normal turn_end cannot persist that lineage (#223).
|
|
86
|
-
let resumeHandlePersistSuppressed = false;
|
|
84
|
+
const statesByScope = new Map<string, CursorSessionResumeState>();
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function allowCursorSessionAgentResumeHandlePersist(): void {
|
|
94
|
-
resumeHandlePersistSuppressed = false;
|
|
86
|
+
function getResumeState(scopeKey?: string): CursorSessionResumeState {
|
|
87
|
+
if (scopeKey !== undefined) {
|
|
88
|
+
return statesByScope.get(scopeKey) ?? lastBoundState;
|
|
89
|
+
}
|
|
90
|
+
return lastBoundState;
|
|
95
91
|
}
|
|
96
92
|
|
|
97
93
|
function hashParts(parts: readonly string[]): string {
|
|
@@ -211,7 +207,8 @@ function matchesResumeScope(data: CursorSessionAgentResumeEntryData, scope: Curs
|
|
|
211
207
|
function matchesCurrentSession(
|
|
212
208
|
data: CursorSessionAgentResumeEntryData,
|
|
213
209
|
branchPathHash: string,
|
|
214
|
-
compactionGeneration
|
|
210
|
+
compactionGeneration: number,
|
|
211
|
+
state: CursorSessionResumeState,
|
|
215
212
|
): boolean {
|
|
216
213
|
return matchesResumeScope(data, state) &&
|
|
217
214
|
data.compactionGeneration === compactionGeneration &&
|
|
@@ -333,19 +330,19 @@ export function readResumableCursorSessionAgentIds(
|
|
|
333
330
|
return [...agentIds].sort((a, b) => a.localeCompare(b));
|
|
334
331
|
}
|
|
335
332
|
|
|
336
|
-
function canRestoreHandleSpanEntry(entry: SessionEntry): boolean {
|
|
333
|
+
function canRestoreHandleSpanEntry(entry: SessionEntry, state: CursorSessionResumeState): boolean {
|
|
337
334
|
if (entry.type === "message" && entry.message.role === "user") return !state.unownedUserEntryIds.has(entry.id);
|
|
338
335
|
return canResumeHandleSpanEntry(entry);
|
|
339
336
|
}
|
|
340
337
|
|
|
341
|
-
function restoreFromBranch(branch: readonly SessionEntry[], allEntries: readonly SessionEntry[]
|
|
338
|
+
function restoreFromBranch(branch: readonly SessionEntry[], allEntries: readonly SessionEntry[], state: CursorSessionResumeState): void {
|
|
342
339
|
const resumeIndex = indexLatestResumeEntries(allEntries);
|
|
343
340
|
let fold: ResumeBranchFoldState = { branchPathHash: EMPTY_BRANCH_HASH, compactionGeneration: 0 };
|
|
344
341
|
let lastBranchHandle: CursorSessionAgentResumeEntryData | undefined;
|
|
345
342
|
for (const entry of branch) {
|
|
346
343
|
const next = advanceResumeBranchState(entry, fold, resumeIndex, {
|
|
347
|
-
matchesEntry: matchesCurrentSession,
|
|
348
|
-
canSpanEntry: canRestoreHandleSpanEntry,
|
|
344
|
+
matchesEntry: (data, hash, generation) => matchesCurrentSession(data, hash, generation, state),
|
|
345
|
+
canSpanEntry: (entry) => canRestoreHandleSpanEntry(entry, state),
|
|
349
346
|
});
|
|
350
347
|
if (next.activeHandle && next.activeHandle !== fold.activeHandle) lastBranchHandle = next.activeHandle;
|
|
351
348
|
fold = next;
|
|
@@ -356,7 +353,9 @@ function restoreFromBranch(branch: readonly SessionEntry[], allEntries: readonly
|
|
|
356
353
|
state.lastBranchHandle = lastBranchHandle;
|
|
357
354
|
}
|
|
358
355
|
|
|
359
|
-
export function getMatchingCursorSessionAgentResumeHandle(poolKey: string): CursorSessionAgentResumeEntryData | undefined {
|
|
356
|
+
export function getMatchingCursorSessionAgentResumeHandle(poolKey: string, scopeKey?: string): CursorSessionAgentResumeEntryData | undefined {
|
|
357
|
+
const state = getResumeState(scopeKey);
|
|
358
|
+
if (scopeKey !== undefined && state.scopeKey !== scopeKey) return undefined;
|
|
360
359
|
const handle = state.activeHandle;
|
|
361
360
|
if (!handle || !isCursorLocalAgentId(handle.agentId)) return undefined;
|
|
362
361
|
if (handle.poolKey !== poolKey) return undefined;
|
|
@@ -372,8 +371,9 @@ export function getMatchingCursorSessionAgentResumeHandle(poolKey: string): Curs
|
|
|
372
371
|
};
|
|
373
372
|
}
|
|
374
373
|
|
|
375
|
-
export function persistCursorSessionAgentResumeHandle(input: PendingCursorSessionAgentResumeHandle): void {
|
|
376
|
-
|
|
374
|
+
export function persistCursorSessionAgentResumeHandle(input: PendingCursorSessionAgentResumeHandle, scopeKey?: string): void {
|
|
375
|
+
const state = getResumeState(scopeKey);
|
|
376
|
+
if (scopeKey !== undefined && state.scopeKey !== scopeKey) return;
|
|
377
377
|
if (!isCursorLocalAgentId(input.agentId)) return;
|
|
378
378
|
state.pendingHandle = {
|
|
379
379
|
runtime: input.runtime,
|
|
@@ -384,13 +384,8 @@ export function persistCursorSessionAgentResumeHandle(input: PendingCursorSessio
|
|
|
384
384
|
};
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
function flushPendingCursorSessionAgentResumeHandle(branch: readonly SessionEntry[]): void {
|
|
388
|
-
|
|
389
|
-
state.pendingHandle = undefined;
|
|
390
|
-
restoreFromBranch(branch);
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
restoreFromBranch(branch);
|
|
387
|
+
function flushPendingCursorSessionAgentResumeHandle(branch: readonly SessionEntry[], state: CursorSessionResumeState): void {
|
|
388
|
+
restoreFromBranch(branch, branch, state);
|
|
394
389
|
const pending = state.pendingHandle;
|
|
395
390
|
state.pendingHandle = undefined;
|
|
396
391
|
if (!pending || !state.appendEntry) return;
|
|
@@ -432,14 +427,22 @@ interface CursorSessionAgentResumeExtensionApi {
|
|
|
432
427
|
}
|
|
433
428
|
|
|
434
429
|
export function registerCursorSessionAgentResume(pi: CursorSessionAgentResumeExtensionApi): void {
|
|
435
|
-
|
|
430
|
+
const sessionState: CursorSessionResumeState = {
|
|
431
|
+
appendEntry: pi.appendEntry, scopeKey: getCursorSessionScopeSnapshot(pi).scopeKey,
|
|
432
|
+
cwd: process.cwd(), branchPathHash: EMPTY_BRANCH_HASH,
|
|
433
|
+
compactionGeneration: 0, unownedUserEntryIds: new Set(),
|
|
434
|
+
};
|
|
435
|
+
const state = sessionState;
|
|
436
436
|
const restoreFromSessionManager = (sessionManager: { getBranch(): SessionEntry[]; getEntries(): SessionEntry[] }): void => {
|
|
437
437
|
const branch = sessionManager.getBranch();
|
|
438
438
|
const entries = sessionManager.getEntries();
|
|
439
|
-
restoreFromBranch(branch, entries.length > 0 ? entries : branch);
|
|
439
|
+
restoreFromBranch(branch, entries.length > 0 ? entries : branch, state);
|
|
440
440
|
};
|
|
441
441
|
pi.on("session_start", (_event, ctx) => {
|
|
442
|
-
state.scopeKey
|
|
442
|
+
if (statesByScope.get(state.scopeKey) === state) statesByScope.delete(state.scopeKey);
|
|
443
|
+
state.scopeKey = getCursorSessionScopeSnapshot(pi).scopeKey;
|
|
444
|
+
statesByScope.set(state.scopeKey, state);
|
|
445
|
+
lastBoundState = state;
|
|
443
446
|
state.sessionFile = ctx.sessionManager.getSessionFile?.() ?? undefined;
|
|
444
447
|
state.sessionId = ctx.sessionManager.getSessionId?.() ?? undefined;
|
|
445
448
|
state.cwd = ctx.cwd;
|
|
@@ -452,7 +455,7 @@ export function registerCursorSessionAgentResume(pi: CursorSessionAgentResumeExt
|
|
|
452
455
|
restoreFromSessionManager(ctx.sessionManager);
|
|
453
456
|
});
|
|
454
457
|
pi.on("turn_end", (_event, ctx) => {
|
|
455
|
-
flushPendingCursorSessionAgentResumeHandle(ctx.sessionManager.getBranch());
|
|
458
|
+
flushPendingCursorSessionAgentResumeHandle(ctx.sessionManager.getBranch(), state);
|
|
456
459
|
});
|
|
457
460
|
pi.on("session_tree", (_event, ctx) => {
|
|
458
461
|
for (const entry of ctx.sessionManager.getBranch()) {
|
|
@@ -460,9 +463,14 @@ export function registerCursorSessionAgentResume(pi: CursorSessionAgentResumeExt
|
|
|
460
463
|
}
|
|
461
464
|
restoreFromSessionManager(ctx.sessionManager);
|
|
462
465
|
});
|
|
466
|
+
pi.on("session_before_compact", () => {
|
|
467
|
+
state.pendingHandle = undefined;
|
|
468
|
+
});
|
|
469
|
+
pi.on("session_compact_failed", () => {
|
|
470
|
+
state.pendingHandle = undefined;
|
|
471
|
+
});
|
|
463
472
|
pi.on("session_compact", (event, ctx) => {
|
|
464
473
|
state.pendingHandle = undefined;
|
|
465
|
-
resumeHandlePersistSuppressed = false;
|
|
466
474
|
const branch = ctx.sessionManager.getBranch();
|
|
467
475
|
if (branch.length > 0) {
|
|
468
476
|
restoreFromSessionManager(ctx.sessionManager);
|
|
@@ -473,13 +481,19 @@ export function registerCursorSessionAgentResume(pi: CursorSessionAgentResumeExt
|
|
|
473
481
|
state.compactionGeneration += 1;
|
|
474
482
|
state.branchPathHash = hashBranchStep(state.branchPathHash, event.compactionEntry);
|
|
475
483
|
});
|
|
484
|
+
pi.on("session_shutdown", () => {
|
|
485
|
+
if (statesByScope.get(state.scopeKey) === state) statesByScope.delete(state.scopeKey);
|
|
486
|
+
state.appendEntry = undefined;
|
|
487
|
+
state.pendingHandle = undefined;
|
|
488
|
+
});
|
|
476
489
|
}
|
|
477
490
|
|
|
478
491
|
function setStateForTests(next: Partial<CursorSessionResumeState>): void {
|
|
479
|
-
Object.assign(
|
|
492
|
+
Object.assign(getResumeState(), next);
|
|
480
493
|
}
|
|
481
494
|
|
|
482
495
|
function resetStateForTests(): void {
|
|
496
|
+
const state = lastBoundState;
|
|
483
497
|
state.appendEntry = undefined;
|
|
484
498
|
state.scopeKey = getCursorSessionScopeKey();
|
|
485
499
|
state.sessionFile = undefined;
|
|
@@ -492,7 +506,7 @@ function resetStateForTests(): void {
|
|
|
492
506
|
state.lastBranchHandle = undefined;
|
|
493
507
|
state.pendingHandle = undefined;
|
|
494
508
|
state.unownedUserEntryIds = new Set();
|
|
495
|
-
|
|
509
|
+
statesByScope.clear();
|
|
496
510
|
}
|
|
497
511
|
|
|
498
512
|
export const __testUtils = {
|
|
@@ -500,6 +514,5 @@ export const __testUtils = {
|
|
|
500
514
|
hashBranchStep,
|
|
501
515
|
reset: resetStateForTests,
|
|
502
516
|
set: setStateForTests,
|
|
503
|
-
state,
|
|
504
|
-
isResumeHandlePersistSuppressed: () => resumeHandlePersistSuppressed,
|
|
517
|
+
get state() { return getResumeState(); },
|
|
505
518
|
};
|
|
@@ -3,11 +3,12 @@ import type { AgentModeOption, LocalAgentOptions, LocalAgentStore, ModelSelectio
|
|
|
3
3
|
import type { Context } from "@earendil-works/pi-ai";
|
|
4
4
|
import {
|
|
5
5
|
getRegisteredCursorPiToolBridge,
|
|
6
|
+
type CursorPiToolBridge,
|
|
6
7
|
type CursorPiBridgeToolRequest,
|
|
7
8
|
type CursorPiToolBridgeRun,
|
|
8
9
|
} from "./cursor-pi-tool-bridge.js";
|
|
9
10
|
import { computeCursorContextFingerprint } from "./context.js";
|
|
10
|
-
import { getCursorSessionFile, getCursorSessionScopeGeneration, getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
11
|
+
import { getCursorSessionFile, getCursorSessionScopeGeneration, getCursorSessionScopeKey, type CursorTurnScope } from "./cursor-session-scope.js";
|
|
11
12
|
import {
|
|
12
13
|
getMatchingCursorSessionAgentResumeHandle,
|
|
13
14
|
persistCursorSessionAgentResumeHandle,
|
|
@@ -121,6 +122,8 @@ function rethrowSupersededWhenReplacedByDifferentPoolKey(scopeKey: string, poolK
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
interface SessionCursorAgentCreateParams {
|
|
125
|
+
scope?: CursorTurnScope;
|
|
126
|
+
bridge?: CursorPiToolBridge;
|
|
124
127
|
apiKey: string;
|
|
125
128
|
agentMode: AgentModeOption;
|
|
126
129
|
cwd: string;
|
|
@@ -205,8 +208,7 @@ function buildApiKeyPoolKeyFingerprint(apiKey: string): string {
|
|
|
205
208
|
return createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
function buildBridgePoolKeySuffix(): string {
|
|
209
|
-
const registeredBridge = getRegisteredCursorPiToolBridge();
|
|
211
|
+
function buildBridgePoolKeySuffix(registeredBridge: CursorPiToolBridge | undefined): string {
|
|
210
212
|
if (!registeredBridge) return "bridge:absent";
|
|
211
213
|
return registeredBridge.getToolSurfaceSignature();
|
|
212
214
|
}
|
|
@@ -224,7 +226,7 @@ function buildSessionAgentPoolKey(scopeKey: string, params: SessionCursorAgentCr
|
|
|
224
226
|
? "http1:on"
|
|
225
227
|
: "http1:off",
|
|
226
228
|
buildApiKeyPoolKeyFingerprint(params.apiKey),
|
|
227
|
-
buildBridgePoolKeySuffix(),
|
|
229
|
+
buildBridgePoolKeySuffix("bridge" in params ? params.bridge : getRegisteredCursorPiToolBridge()),
|
|
228
230
|
].join("\0");
|
|
229
231
|
}
|
|
230
232
|
|
|
@@ -309,7 +311,7 @@ function commitSessionAgentSendForLease(
|
|
|
309
311
|
poolKey: entry.poolKey,
|
|
310
312
|
sendState: entry.sendState,
|
|
311
313
|
storeIdentity: entry.sessionStore.identity,
|
|
312
|
-
});
|
|
314
|
+
}, scopeKey);
|
|
313
315
|
}
|
|
314
316
|
}
|
|
315
317
|
|
|
@@ -445,7 +447,7 @@ async function createSessionAgentEntry(
|
|
|
445
447
|
let bridgeRun: CursorPiToolBridgeRun | undefined;
|
|
446
448
|
let sessionStore: OpenCursorSessionStore | undefined;
|
|
447
449
|
try {
|
|
448
|
-
const registeredBridge =
|
|
450
|
+
const registeredBridge = params.bridge;
|
|
449
451
|
if (registeredBridge) {
|
|
450
452
|
bridgeRun = await registeredBridge.createRun({
|
|
451
453
|
onToolRequest: params.onBridgeToolRequest,
|
|
@@ -466,7 +468,7 @@ async function createSessionAgentEntry(
|
|
|
466
468
|
createAgent ??= sdk.Agent.create;
|
|
467
469
|
resumeAgent ??= sdk.Agent.resume;
|
|
468
470
|
}
|
|
469
|
-
const resumeHandle = resumeEligible ? getMatchingCursorSessionAgentResumeHandle(resolvedPoolKey) : undefined;
|
|
471
|
+
const resumeHandle = resumeEligible ? getMatchingCursorSessionAgentResumeHandle(resolvedPoolKey, scopeKey) : undefined;
|
|
470
472
|
const storeSelection = await openCursorSessionStoreForScope({
|
|
471
473
|
cwd: params.cwd,
|
|
472
474
|
scopeKey,
|
|
@@ -546,9 +548,10 @@ export function invalidateSessionAgent(
|
|
|
546
548
|
if (options?.deadTransport) deadTransportScopeKeys.add(scopeKey);
|
|
547
549
|
}
|
|
548
550
|
|
|
549
|
-
export async function acquireSessionCursorAgent(
|
|
550
|
-
const
|
|
551
|
-
const
|
|
551
|
+
export async function acquireSessionCursorAgent(input: SessionCursorAgentCreateParams): Promise<SessionCursorAgentLease> {
|
|
552
|
+
const params = { ...input, bridge: "bridge" in input ? input.bridge : getRegisteredCursorPiToolBridge() };
|
|
553
|
+
const scopeKey = params.scope?.scopeKey ?? getCursorSessionScopeKey();
|
|
554
|
+
const persistentStore = (params.scope ? params.scope.sessionFile : getCursorSessionFile()) !== undefined;
|
|
552
555
|
|
|
553
556
|
while (true) {
|
|
554
557
|
assertScopeAcceptsAcquire(scopeKey);
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
|
|
2
2
|
import { resetSessionCursorAgent } from "./cursor-session-agent.js";
|
|
3
|
-
import { suppressCursorSessionAgentResumeHandlePersist } from "./cursor-session-agent-resume.js";
|
|
4
3
|
import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* disposes the pool entry so summarization acquires a clean SDK agent.
|
|
10
|
-
* Suppresses local-resume handle persist so the summarizer send cannot leak
|
|
11
|
-
* a one-message handle into the next normal turn (#223).
|
|
6
|
+
* Release scoped conversation runs and reset the pool before compaction.
|
|
7
|
+
* Summary execution owns a separate temporary agent, never this pool.
|
|
12
8
|
*/
|
|
13
9
|
export async function prepareCursorSessionForCompaction(
|
|
14
10
|
scopeKey: string = getCursorSessionScopeKey(),
|
|
15
11
|
): Promise<void> {
|
|
16
|
-
suppressCursorSessionAgentResumeHandlePersist();
|
|
17
12
|
while (true) {
|
|
18
13
|
const run = cursorLiveRuns.getActiveForScope(scopeKey);
|
|
19
14
|
if (!run || run.disposed) break;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { parseArgs } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import type { ExtensionHandler, ProjectTrustHandler, SessionInfoChangedEvent, SessionStartEvent } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { ExtensionHandler, ProjectTrustHandler, SessionInfoChangedEvent, SessionStartEvent, SessionShutdownEvent } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { truncateCursorDisplayLine } from "./cursor-display-text.js";
|
|
5
5
|
|
|
6
6
|
interface CursorSessionScopeExtensionApi {
|
|
7
7
|
on(event: "project_trust", handler: ProjectTrustHandler): void;
|
|
8
8
|
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
|
|
9
9
|
on(event: "session_info_changed", handler: ExtensionHandler<SessionInfoChangedEvent>): void;
|
|
10
|
+
on(event: "session_shutdown", handler: ExtensionHandler<SessionShutdownEvent>): void;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
const ANONYMOUS_SESSION_SCOPE_KEY = "__anonymous__";
|
|
@@ -15,6 +16,16 @@ export const MAX_CURSOR_SESSION_NAME_LENGTH = 100;
|
|
|
15
16
|
|
|
16
17
|
type CursorSessionScopeChangeHandler = (previousScopeKey: string) => Promise<void> | void;
|
|
17
18
|
|
|
19
|
+
export interface CursorTurnScope {
|
|
20
|
+
readonly cwd: string;
|
|
21
|
+
readonly scopeKey: string;
|
|
22
|
+
readonly sessionFile: string | undefined;
|
|
23
|
+
readonly sessionId: string | undefined;
|
|
24
|
+
readonly sessionName: string | undefined;
|
|
25
|
+
readonly projectTrusted: boolean;
|
|
26
|
+
readonly generation: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
18
29
|
const state = {
|
|
19
30
|
sessionCwd: process.cwd(),
|
|
20
31
|
sessionFile: undefined as string | undefined,
|
|
@@ -23,44 +34,67 @@ const state = {
|
|
|
23
34
|
projectTrusted: false,
|
|
24
35
|
sessionGeneration: 0,
|
|
25
36
|
};
|
|
26
|
-
|
|
27
|
-
const
|
|
37
|
+
type ScopeRecord = typeof state;
|
|
38
|
+
const scopes = new Map<object, ScopeRecord>();
|
|
39
|
+
const scopeChangeHandlers = new Map<object, CursorSessionScopeChangeHandler>();
|
|
40
|
+
const scopeGenerations = new Map<string, number>([[ANONYMOUS_SESSION_SCOPE_KEY, 0]]);
|
|
28
41
|
const projectTrustResolutionCwds = new Set<string>();
|
|
29
42
|
let nextSessionGeneration = 1;
|
|
30
|
-
let scopeChangeHandler: CursorSessionScopeChangeHandler | undefined;
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
function scopeKeyFor(record: ScopeRecord): string {
|
|
45
|
+
if (record.sessionFile) return record.sessionFile;
|
|
46
|
+
if (record.sessionId) return `${EPHEMERAL_SESSION_SCOPE_PREFIX}${record.sessionId}`;
|
|
47
|
+
return ANONYMOUS_SESSION_SCOPE_KEY;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function cursorSessionScopeKeyForManager(manager: {
|
|
51
|
+
getSessionFile?(): string | undefined | null;
|
|
52
|
+
getSessionId?(): string | undefined;
|
|
53
|
+
}): string {
|
|
54
|
+
return manager.getSessionFile?.() ?? (manager.getSessionId?.()
|
|
55
|
+
? `${EPHEMERAL_SESSION_SCOPE_PREFIX}${manager.getSessionId()}`
|
|
56
|
+
: ANONYMOUS_SESSION_SCOPE_KEY);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getCursorSessionScopeSnapshot(pi?: object): CursorTurnScope {
|
|
60
|
+
if (!pi && scopes.size > 1) throw new Error("Cursor provider ownership requires a session-bound provider closure when multiple extensions are loaded.");
|
|
61
|
+
const record = pi ? scopes.get(pi) : state;
|
|
62
|
+
if (!record) throw new Error("Cursor session binding is no longer active.");
|
|
63
|
+
const scopeKey = scopeKeyFor(record);
|
|
64
|
+
return Object.freeze({
|
|
65
|
+
cwd: record.sessionCwd,
|
|
66
|
+
scopeKey,
|
|
67
|
+
sessionFile: record.sessionFile,
|
|
68
|
+
sessionId: record.sessionId,
|
|
69
|
+
sessionName: record.sessionName,
|
|
70
|
+
projectTrusted: record.projectTrusted,
|
|
71
|
+
generation: getCursorSessionScopeGeneration(scopeKey),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function hasLiveCursorSessions(): boolean {
|
|
76
|
+
return scopes.size > 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
35
79
|
export function getCursorSessionFile(): string | undefined {
|
|
36
80
|
return state.sessionFile;
|
|
37
81
|
}
|
|
38
82
|
|
|
39
|
-
/**
|
|
40
|
-
* Stable scope key for session-agent pooling. Falls back to a process-local anonymous key
|
|
41
|
-
* before the first session_start (tests and early startup).
|
|
42
|
-
*/
|
|
83
|
+
/** Stable pool key, with process-local anonymous fallback before session_start. */
|
|
43
84
|
export function getCursorSessionScopeKey(): string {
|
|
44
|
-
|
|
45
|
-
if (state.sessionId) return `${EPHEMERAL_SESSION_SCOPE_PREFIX}${state.sessionId}`;
|
|
46
|
-
return ANONYMOUS_SESSION_SCOPE_KEY;
|
|
85
|
+
return scopeKeyFor(state);
|
|
47
86
|
}
|
|
48
87
|
|
|
49
88
|
export function getCursorSessionScopeGeneration(scopeKey: string = getCursorSessionScopeKey()): number {
|
|
50
89
|
return scopeGenerations.get(scopeKey) ?? 0;
|
|
51
90
|
}
|
|
52
91
|
|
|
53
|
-
/**
|
|
54
|
-
* Pi session cwd when known; falls back to process.cwd() before session_start.
|
|
55
|
-
* Updated on session_start only until pi threads cwd into streamSimple—mid-session cwd
|
|
56
|
-
* changes without a new session_start event are not reflected here.
|
|
57
|
-
*/
|
|
58
92
|
export function getCursorSessionCwd(): string {
|
|
59
93
|
return state.sessionCwd;
|
|
60
94
|
}
|
|
61
95
|
|
|
62
|
-
export function getCursorSessionProjectTrusted(): boolean {
|
|
63
|
-
return state.projectTrusted;
|
|
96
|
+
export function getCursorSessionProjectTrusted(pi?: object): boolean {
|
|
97
|
+
return pi ? scopes.get(pi)?.projectTrusted === true : state.projectTrusted;
|
|
64
98
|
}
|
|
65
99
|
|
|
66
100
|
export function getCursorSessionName(): string | undefined {
|
|
@@ -84,9 +118,8 @@ function setCursorSessionScope(
|
|
|
84
118
|
state.sessionId = sessionId;
|
|
85
119
|
state.sessionName = normalizeCursorSessionName(sessionName);
|
|
86
120
|
state.projectTrusted = projectTrusted;
|
|
87
|
-
state.sessionGeneration = nextSessionGeneration
|
|
88
|
-
|
|
89
|
-
scopeGenerations.set(getCursorSessionScopeKey(), state.sessionGeneration);
|
|
121
|
+
state.sessionGeneration = nextSessionGeneration++;
|
|
122
|
+
scopeGenerations.set(scopeKeyFor(state), state.sessionGeneration);
|
|
90
123
|
}
|
|
91
124
|
|
|
92
125
|
function recordProjectTrustResolution(cwd: string): void {
|
|
@@ -98,43 +131,58 @@ function isCliProjectTrustApproved(args = process.argv.slice(2)): boolean {
|
|
|
98
131
|
}
|
|
99
132
|
|
|
100
133
|
function resetCursorSessionScope(): void {
|
|
101
|
-
state
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
state.projectTrusted = false;
|
|
106
|
-
state.sessionGeneration = 0;
|
|
134
|
+
Object.assign(state, {
|
|
135
|
+
sessionCwd: process.cwd(), sessionFile: undefined, sessionId: undefined,
|
|
136
|
+
sessionName: undefined, projectTrusted: false, sessionGeneration: 0,
|
|
137
|
+
});
|
|
107
138
|
nextSessionGeneration = 1;
|
|
108
139
|
scopeGenerations.clear();
|
|
109
|
-
scopeGenerations.set(ANONYMOUS_SESSION_SCOPE_KEY,
|
|
140
|
+
scopeGenerations.set(ANONYMOUS_SESSION_SCOPE_KEY, 0);
|
|
110
141
|
projectTrustResolutionCwds.clear();
|
|
142
|
+
scopes.clear();
|
|
143
|
+
scopeChangeHandlers.clear();
|
|
111
144
|
}
|
|
112
145
|
|
|
113
|
-
export function onCursorSessionScopeKeyChange(handler: CursorSessionScopeChangeHandler): void {
|
|
114
|
-
|
|
146
|
+
export function onCursorSessionScopeKeyChange(pi: object, handler: CursorSessionScopeChangeHandler): void {
|
|
147
|
+
scopeChangeHandlers.set(pi, handler);
|
|
115
148
|
}
|
|
116
149
|
|
|
117
150
|
export function registerCursorSessionScope(pi: CursorSessionScopeExtensionApi): void {
|
|
151
|
+
// A fresh binding has no previous pooled scope to steal from the last-bound mirror.
|
|
152
|
+
const record: ScopeRecord = {
|
|
153
|
+
sessionCwd: process.cwd(), sessionFile: undefined, sessionId: undefined,
|
|
154
|
+
sessionName: undefined, projectTrusted: false, sessionGeneration: 0,
|
|
155
|
+
};
|
|
156
|
+
scopes.set(pi, record);
|
|
157
|
+
const trustedCwds = new Set<string>();
|
|
118
158
|
pi.on("project_trust", (event) => {
|
|
119
|
-
|
|
159
|
+
trustedCwds.add(resolve(event.cwd));
|
|
120
160
|
return { trusted: "undecided" };
|
|
121
161
|
});
|
|
122
162
|
pi.on("session_start", async (_event, ctx) => {
|
|
123
|
-
const previousScopeKey =
|
|
163
|
+
const previousScopeKey = scopeKeyFor(record);
|
|
164
|
+
const wasBound = record.sessionGeneration > 0;
|
|
124
165
|
setCursorSessionScope(
|
|
125
166
|
ctx.cwd,
|
|
126
167
|
ctx.sessionManager?.getSessionFile?.() ?? undefined,
|
|
127
168
|
ctx.sessionManager?.getSessionId?.() ?? undefined,
|
|
128
169
|
ctx.isProjectTrusted?.() === true
|
|
129
|
-
&& (projectTrustResolutionCwds.has(resolve(ctx.cwd)) || isCliProjectTrustApproved()),
|
|
170
|
+
&& (trustedCwds.has(resolve(ctx.cwd)) || projectTrustResolutionCwds.has(resolve(ctx.cwd)) || isCliProjectTrustApproved()),
|
|
130
171
|
ctx.sessionManager?.getSessionName?.() ?? undefined,
|
|
131
172
|
);
|
|
132
|
-
|
|
133
|
-
|
|
173
|
+
Object.assign(record, state);
|
|
174
|
+
scopes.set(pi, record);
|
|
175
|
+
if (wasBound && previousScopeKey !== scopeKeyFor(record)) {
|
|
176
|
+
await scopeChangeHandlers.get(pi)?.(previousScopeKey);
|
|
134
177
|
}
|
|
135
178
|
});
|
|
136
179
|
pi.on("session_info_changed", (event) => {
|
|
137
|
-
|
|
180
|
+
record.sessionName = normalizeCursorSessionName(event.name);
|
|
181
|
+
Object.assign(state, record);
|
|
182
|
+
});
|
|
183
|
+
pi.on("session_shutdown", () => {
|
|
184
|
+
scopes.delete(pi);
|
|
185
|
+
scopeChangeHandlers.delete(pi);
|
|
138
186
|
});
|
|
139
187
|
}
|
|
140
188
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { AgentModeOption } from "@cursor/sdk";
|
|
2
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { CursorExplicitSdkConfig, CursorRuntime } from "./cursor-config.js";
|
|
4
|
+
import { cursorSessionScopeKeyForManager, getCursorSessionProjectTrusted } from "./cursor-session-scope.js";
|
|
5
|
+
|
|
6
|
+
export type CursorCliModeState =
|
|
7
|
+
| { kind: "unset" }
|
|
8
|
+
| { kind: "valid"; mode: AgentModeOption }
|
|
9
|
+
| { kind: "invalid"; raw: string; message: string };
|
|
10
|
+
|
|
11
|
+
function createCliSettings() {
|
|
12
|
+
return {
|
|
13
|
+
forceFast: false, forceNoFast: false,
|
|
14
|
+
mode: { kind: "unset" } as CursorCliModeState,
|
|
15
|
+
config: {} as CursorExplicitSdkConfig,
|
|
16
|
+
cloudEnvNames: undefined as string | undefined,
|
|
17
|
+
localForceConsumed: false,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createSettings() {
|
|
22
|
+
return {
|
|
23
|
+
fast: new Map<string, boolean>(),
|
|
24
|
+
mode: undefined as AgentModeOption | undefined,
|
|
25
|
+
runtime: undefined as CursorRuntime | undefined,
|
|
26
|
+
cloudAcknowledged: false,
|
|
27
|
+
http1: undefined as boolean | undefined,
|
|
28
|
+
projectTrusted: false,
|
|
29
|
+
cli: createCliSettings(),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const settings = new Map<string, ReturnType<typeof createSettings>>();
|
|
33
|
+
let cliOwners = new WeakMap<object, ReturnType<typeof createCliSettings>>();
|
|
34
|
+
let legacySettings = createSettings();
|
|
35
|
+
|
|
36
|
+
export function getCursorSessionSettings(scopeKey?: string) {
|
|
37
|
+
if (scopeKey === undefined) return legacySettings;
|
|
38
|
+
let value = settings.get(scopeKey);
|
|
39
|
+
if (!value) {
|
|
40
|
+
value = createSettings();
|
|
41
|
+
settings.set(scopeKey, value);
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function cursorSettingsScopeForContext(ctx: Partial<Pick<ExtensionContext, "sessionManager">>): string | undefined {
|
|
47
|
+
const manager = ctx.sessionManager;
|
|
48
|
+
return manager ? cursorSessionScopeKeyForManager(manager) : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function registerCursorSessionSettings(pi: Pick<ExtensionAPI, "on">): void {
|
|
52
|
+
let scopeKey: string | undefined;
|
|
53
|
+
let cli = createCliSettings();
|
|
54
|
+
pi.on("session_start", (_event, ctx) => {
|
|
55
|
+
if (scopeKey) settings.delete(scopeKey);
|
|
56
|
+
scopeKey = cursorSessionScopeKeyForManager(ctx.sessionManager);
|
|
57
|
+
legacySettings = getCursorSessionSettings(scopeKey);
|
|
58
|
+
// Reload creates a new ExtensionAPI but retains the same public manager.
|
|
59
|
+
// Keep the run's one-shot CLI consumption without retaining dead managers.
|
|
60
|
+
cli = cliOwners.get(ctx.sessionManager) ?? cli;
|
|
61
|
+
cliOwners.set(ctx.sessionManager, cli);
|
|
62
|
+
legacySettings.cli = cli;
|
|
63
|
+
legacySettings.projectTrusted = getCursorSessionProjectTrusted(pi);
|
|
64
|
+
});
|
|
65
|
+
pi.on("session_shutdown", () => { if (scopeKey) settings.delete(scopeKey); });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function resetCursorSessionSettingsForTests(): void {
|
|
69
|
+
settings.clear();
|
|
70
|
+
cliOwners = new WeakMap();
|
|
71
|
+
legacySettings = createSettings();
|
|
72
|
+
}
|