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
|
@@ -36,7 +36,8 @@ import {
|
|
|
36
36
|
} from "./cursor-config.js";
|
|
37
37
|
import { asRecord } from "./cursor-record-utils.js";
|
|
38
38
|
import { getResolvedSessionCursorHttp1Enabled } from "./cursor-http1.js";
|
|
39
|
-
import {
|
|
39
|
+
import { getCursorSessionScopeSnapshot } from "./cursor-session-scope.js";
|
|
40
|
+
import { cursorSettingsScopeForContext, getCursorSessionSettings } from "./cursor-session-settings.js";
|
|
40
41
|
|
|
41
42
|
export const CURSOR_RUNTIME_ENTRY_TYPE = "cursor-runtime-state";
|
|
42
43
|
|
|
@@ -59,20 +60,12 @@ export type CursorRuntimeStateExtensionApi = Pick<
|
|
|
59
60
|
"appendEntry" | "getFlag" | "registerFlag" | "registerCommand" | "on"
|
|
60
61
|
>;
|
|
61
62
|
|
|
62
|
-
type CursorRuntimeContext = Pick<ExtensionContext, "cwd"
|
|
63
|
+
type CursorRuntimeContext = Pick<ExtensionContext, "cwd"> & Partial<Pick<ExtensionContext, "sessionManager">>;
|
|
63
64
|
|
|
64
65
|
type CursorStatusRefresh = (ctx: ExtensionContext) => void;
|
|
65
66
|
|
|
66
|
-
interface CursorCliConfigSnapshot {
|
|
67
|
-
config: CursorExplicitSdkConfig;
|
|
68
|
-
cloudEnvNames?: string;
|
|
69
|
-
}
|
|
70
67
|
|
|
71
|
-
let cliCursorSnapshot: CursorCliConfigSnapshot = { config: {} };
|
|
72
|
-
let cliLocalForceConsumed = false;
|
|
73
68
|
let envLocalForceConsumed = false;
|
|
74
|
-
let sessionCursorRuntime: CursorRuntime | undefined;
|
|
75
|
-
let sessionCursorCloudAcknowledged = false;
|
|
76
69
|
|
|
77
70
|
function isCursorRuntimeEntryData(value: unknown): value is CursorRuntimeEntryData {
|
|
78
71
|
const record = asRecord(value);
|
|
@@ -85,15 +78,17 @@ function stringFlagValue(value: boolean | string | undefined): string | undefine
|
|
|
85
78
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
86
79
|
}
|
|
87
80
|
|
|
88
|
-
export function getCursorCliConfig(): CursorExplicitSdkConfig {
|
|
89
|
-
const
|
|
90
|
-
const
|
|
81
|
+
export function getCursorCliConfig(scopeKey?: string): CursorExplicitSdkConfig {
|
|
82
|
+
const cli = getCursorSessionSettings(scopeKey).cli;
|
|
83
|
+
const config = structuredClone(cli.config);
|
|
84
|
+
const envNames = parseExplicitCursorCloudEnvNames(cli.cloudEnvNames, "--cursor-cloud-env");
|
|
91
85
|
if (envNames) config.cloud = { ...config.cloud, envNames };
|
|
92
86
|
return config;
|
|
93
87
|
}
|
|
94
88
|
|
|
95
|
-
export function getCursorSessionConfig(): CursorSdkConfig {
|
|
96
|
-
const
|
|
89
|
+
export function getCursorSessionConfig(scopeKey?: string): CursorSdkConfig {
|
|
90
|
+
const { runtime: sessionCursorRuntime, cloudAcknowledged: sessionCursorCloudAcknowledged } = getCursorSessionSettings(scopeKey);
|
|
91
|
+
const useHttp1ForAgent = getResolvedSessionCursorHttp1Enabled(scopeKey);
|
|
97
92
|
return {
|
|
98
93
|
...(sessionCursorRuntime
|
|
99
94
|
? {
|
|
@@ -108,11 +103,12 @@ export function getCursorSessionConfig(): CursorSdkConfig {
|
|
|
108
103
|
export function resolveEffectiveCursorConfig(options: {
|
|
109
104
|
cwd: string;
|
|
110
105
|
projectTrusted?: boolean;
|
|
106
|
+
scopeKey?: string;
|
|
111
107
|
}): CursorResolvedSdkConfig {
|
|
112
108
|
const loadedConfig = loadCursorSdkConfig({ cwd: options.cwd, projectTrusted: options.projectTrusted === true });
|
|
113
109
|
return resolveCursorSdkConfig({
|
|
114
|
-
cli: getCursorCliConfig(),
|
|
115
|
-
session: getCursorSessionConfig(),
|
|
110
|
+
cli: getCursorCliConfig(options.scopeKey),
|
|
111
|
+
session: getCursorSessionConfig(options.scopeKey),
|
|
116
112
|
user: loadedConfig.user,
|
|
117
113
|
project: loadedConfig.project,
|
|
118
114
|
});
|
|
@@ -121,7 +117,10 @@ export function resolveEffectiveCursorConfig(options: {
|
|
|
121
117
|
export function resolveEffectiveCursorConfigForContext(ctx: CursorRuntimeContext): CursorResolvedSdkConfig {
|
|
122
118
|
return resolveEffectiveCursorConfig({
|
|
123
119
|
cwd: ctx.cwd,
|
|
124
|
-
projectTrusted:
|
|
120
|
+
projectTrusted: cursorSettingsScopeForContext(ctx) === undefined
|
|
121
|
+
? getCursorSessionScopeSnapshot().cwd === ctx.cwd && getCursorSessionScopeSnapshot().projectTrusted
|
|
122
|
+
: getCursorSessionSettings(cursorSettingsScopeForContext(ctx)).projectTrusted,
|
|
123
|
+
scopeKey: cursorSettingsScopeForContext(ctx),
|
|
125
124
|
});
|
|
126
125
|
}
|
|
127
126
|
|
|
@@ -166,14 +165,15 @@ export function formatCursorStatus(
|
|
|
166
165
|
return parts.join(" · ");
|
|
167
166
|
}
|
|
168
167
|
|
|
169
|
-
export function consumeCursorLocalForceOverride(resolved: { value: boolean; source: string }): boolean {
|
|
168
|
+
export function consumeCursorLocalForceOverride(resolved: { value: boolean; source: string }, scopeKey?: string): boolean {
|
|
170
169
|
if (!resolved.value) return false;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
170
|
+
const cli = getCursorSessionSettings(scopeKey).cli;
|
|
171
|
+
if (resolved.source === "cli" && !cli.localForceConsumed) {
|
|
172
|
+
if (cli.config.local) {
|
|
173
|
+
const { force: _, ...local } = cli.config.local;
|
|
174
|
+
cli.config.local = local;
|
|
175
175
|
}
|
|
176
|
-
|
|
176
|
+
cli.localForceConsumed = true;
|
|
177
177
|
return true;
|
|
178
178
|
}
|
|
179
179
|
if (resolved.source === "environment" && !envLocalForceConsumed) {
|
|
@@ -183,19 +183,21 @@ export function consumeCursorLocalForceOverride(resolved: { value: boolean; sour
|
|
|
183
183
|
return false;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
export function restoreSessionCursorRuntimeState(branch: readonly SessionEntry[]): void {
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
export function restoreSessionCursorRuntimeState(branch: readonly SessionEntry[], scopeKey?: string): void {
|
|
187
|
+
const state = getCursorSessionSettings(scopeKey);
|
|
188
|
+
state.runtime = undefined;
|
|
189
|
+
state.cloudAcknowledged = false;
|
|
189
190
|
for (const entry of branch) {
|
|
190
191
|
if (entry.type !== "custom" || entry.customType !== CURSOR_RUNTIME_ENTRY_TYPE) continue;
|
|
191
192
|
if (isCursorRuntimeEntryData(entry.data)) {
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
state.runtime = entry.data.runtime;
|
|
194
|
+
state.cloudAcknowledged ||= entry.data.cloudAcknowledged === true;
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
|
|
198
|
-
export function restoreCursorCliState(pi: Pick<ExtensionAPI, "getFlag"
|
|
199
|
+
export function restoreCursorCliState(pi: Pick<ExtensionAPI, "getFlag">, scopeKey?: string): void {
|
|
200
|
+
const cli = getCursorSessionSettings(scopeKey).cli;
|
|
199
201
|
const runtime = stringFlagValue(pi.getFlag("cursor-runtime"));
|
|
200
202
|
const repo = stringFlagValue(pi.getFlag("cursor-cloud-repo"));
|
|
201
203
|
const branch = stringFlagValue(pi.getFlag("cursor-cloud-branch"));
|
|
@@ -220,35 +222,35 @@ export function restoreCursorCliState(pi: Pick<ExtensionAPI, "getFlag">): void {
|
|
|
220
222
|
const local: NonNullable<CursorExplicitSdkConfig["local"]> = {
|
|
221
223
|
...(pi.getFlag("cursor-auto-review") === true ? { autoReview: true } : {}),
|
|
222
224
|
...(pi.getFlag("cursor-sandbox") === true ? { sandboxOptions: { enabled: true } } : {}),
|
|
223
|
-
...(!
|
|
225
|
+
...(!cli.localForceConsumed && pi.getFlag("cursor-local-force") === true ? { force: true } : {}),
|
|
224
226
|
...(pi.getFlag("cursor-no-local-resume") === true
|
|
225
227
|
? { resume: false }
|
|
226
228
|
: pi.getFlag("cursor-local-resume") === true
|
|
227
229
|
? { resume: true }
|
|
228
230
|
: {}),
|
|
229
231
|
};
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
...(Object.keys(local).length ? { local } : {}),
|
|
235
|
-
},
|
|
236
|
-
...(cloudEnvNames ? { cloudEnvNames } : {}),
|
|
232
|
+
cli.config = {
|
|
233
|
+
...(runtime ? { runtime } : {}),
|
|
234
|
+
...(Object.keys(cloud).length ? { cloud } : {}),
|
|
235
|
+
...(Object.keys(local).length ? { local } : {}),
|
|
237
236
|
};
|
|
237
|
+
cli.cloudEnvNames = cloudEnvNames;
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
function persistCursorRuntimePreference(
|
|
241
241
|
pi: Pick<ExtensionAPI, "appendEntry">,
|
|
242
242
|
runtime: CursorRuntime,
|
|
243
|
-
cloudAcknowledged
|
|
243
|
+
cloudAcknowledged: boolean,
|
|
244
|
+
scopeKey: string | undefined,
|
|
244
245
|
): void {
|
|
245
|
-
const
|
|
246
|
+
const state = getCursorSessionSettings(scopeKey);
|
|
247
|
+
const acknowledged = state.cloudAcknowledged || cloudAcknowledged;
|
|
246
248
|
pi.appendEntry<CursorRuntimeEntryData>(CURSOR_RUNTIME_ENTRY_TYPE, {
|
|
247
249
|
runtime,
|
|
248
250
|
...(acknowledged ? { cloudAcknowledged: true } : {}),
|
|
249
251
|
});
|
|
250
|
-
|
|
251
|
-
|
|
252
|
+
state.runtime = runtime;
|
|
253
|
+
state.cloudAcknowledged = acknowledged;
|
|
252
254
|
}
|
|
253
255
|
|
|
254
256
|
function registerCursorRuntimeFlags(pi: Pick<ExtensionAPI, "registerFlag">): void {
|
|
@@ -368,6 +370,7 @@ function registerCursorRuntimeCommand(
|
|
|
368
370
|
pi.registerCommand("cursor-runtime", {
|
|
369
371
|
description: "Set Cursor runtime for this session: local or cloud",
|
|
370
372
|
handler: async (args, ctx) => {
|
|
373
|
+
const scopeKey = cursorSettingsScopeForContext(ctx);
|
|
371
374
|
const usage = "Usage: /cursor-runtime local|cloud [--save-user|--save-project]";
|
|
372
375
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
373
376
|
const raw = tokens[0];
|
|
@@ -392,7 +395,7 @@ function registerCursorRuntimeCommand(
|
|
|
392
395
|
ctx.ui.notify(`${currentResolution.message} Fix the explicit override before changing the session runtime.`, "error");
|
|
393
396
|
return;
|
|
394
397
|
}
|
|
395
|
-
if (saveProject && (
|
|
398
|
+
if (saveProject && (getCursorSessionScopeSnapshot(pi).cwd !== ctx.cwd || !getCursorSessionScopeSnapshot(pi).projectTrusted)) {
|
|
396
399
|
ctx.ui.notify(
|
|
397
400
|
"Cannot save Cursor project config without explicit project-trust provenance. Ensure .pi/settings.json or another Pi project resource exists, trust the project, then restart pi; or restart with --approve. Project-local package installs must use --approve on every run that reads or writes .pi/cursor-sdk.json.",
|
|
398
401
|
"error",
|
|
@@ -435,7 +438,7 @@ function registerCursorRuntimeCommand(
|
|
|
435
438
|
}
|
|
436
439
|
}
|
|
437
440
|
try {
|
|
438
|
-
persistCursorRuntimePreference(pi, raw, cloudAcknowledged);
|
|
441
|
+
persistCursorRuntimePreference(pi, raw, cloudAcknowledged, scopeKey);
|
|
439
442
|
} catch (error) {
|
|
440
443
|
const effectiveResolution = resolveCursorStatusRuntime(ctx);
|
|
441
444
|
refreshStatus(ctx);
|
|
@@ -489,9 +492,11 @@ export function registerCursorCloudRuntimeControls(
|
|
|
489
492
|
}
|
|
490
493
|
|
|
491
494
|
export function resetCursorRuntimeStateForTests(): void {
|
|
492
|
-
|
|
493
|
-
|
|
495
|
+
const cli = getCursorSessionSettings().cli;
|
|
496
|
+
cli.config = {};
|
|
497
|
+
cli.cloudEnvNames = undefined;
|
|
498
|
+
cli.localForceConsumed = false;
|
|
494
499
|
envLocalForceConsumed = false;
|
|
495
|
-
|
|
496
|
-
|
|
500
|
+
getCursorSessionSettings().runtime = undefined;
|
|
501
|
+
getCursorSessionSettings().cloudAcknowledged = false;
|
|
497
502
|
}
|
|
@@ -1,107 +1,59 @@
|
|
|
1
|
-
import type { SDKAgent } from "@cursor/sdk";
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
return
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
1
|
+
import type { AgentUsage, SDKAgent, TokenUsage, UsageCost } from "@cursor/sdk";
|
|
2
|
+
import { asRecord } from "./cursor-record-utils.js";
|
|
3
|
+
|
|
4
|
+
export type CursorBillingObservation =
|
|
5
|
+
| { status: "observed-pending-settlement"; snapshot: AgentUsage }
|
|
6
|
+
| { status: "unavailable"; reason: "timeout" | "unsupported" | "request-failed" | "invalid-response" };
|
|
7
|
+
|
|
8
|
+
const fields = ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens", "totalTokens"] as const;
|
|
9
|
+
export function readCursorUsageTokens(value: unknown): TokenUsage | undefined {
|
|
10
|
+
const record = asRecord(value);
|
|
11
|
+
if (!record || fields.some(key => !Number.isSafeInteger(record[key]) || (record[key] as number) < 0)) return undefined;
|
|
12
|
+
if (record.reasoningTokens !== undefined && (!Number.isSafeInteger(record.reasoningTokens) || (record.reasoningTokens as number) < 0 || (record.reasoningTokens as number) > (record.outputTokens as number))) return undefined;
|
|
13
|
+
return { inputTokens: record.inputTokens as number, outputTokens: record.outputTokens as number, cacheReadTokens: record.cacheReadTokens as number, cacheWriteTokens: record.cacheWriteTokens as number, totalTokens: record.totalTokens as number, ...(record.reasoningTokens === undefined ? {} : { reasoningTokens: record.reasoningTokens as number }) };
|
|
14
|
+
}
|
|
15
|
+
function readCost(value: unknown): UsageCost | undefined {
|
|
16
|
+
const record = asRecord(value);
|
|
17
|
+
if (!record || ["rawCostCents", "chargedCents"].some(key => typeof record[key] !== "number" || !Number.isFinite(record[key]) || (record[key] as number) < 0)) return undefined;
|
|
18
|
+
return { rawCostCents: record.rawCostCents as number, chargedCents: record.chargedCents as number };
|
|
19
|
+
}
|
|
20
|
+
function billedTokens(value: unknown): TokenUsage | undefined {
|
|
21
|
+
const usage = readCursorUsageTokens(value);
|
|
22
|
+
if (!usage || usage.totalTokens !== usage.inputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens) return undefined;
|
|
23
|
+
return usage;
|
|
24
|
+
}
|
|
25
|
+
export function readCursorBilledSnapshot(value: unknown): AgentUsage | undefined {
|
|
26
|
+
const record = asRecord(value);
|
|
27
|
+
const usage = billedTokens(record?.usage);
|
|
28
|
+
if (!record || !usage || !Array.isArray(record.runs)) return undefined;
|
|
29
|
+
const cost = record.cost === undefined ? undefined : readCost(record.cost);
|
|
30
|
+
if (record.cost !== undefined && !cost) return undefined;
|
|
31
|
+
const runs: AgentUsage["runs"] = [];
|
|
32
|
+
const ids = new Set<string>();
|
|
33
|
+
for (const item of record.runs) {
|
|
34
|
+
const row = asRecord(item);
|
|
35
|
+
const tokens = billedTokens(row?.usage);
|
|
36
|
+
const rowCost = row?.cost === undefined ? undefined : readCost(row.cost);
|
|
37
|
+
if (!row || typeof row.runId !== "string" || !row.runId || row.runId.length > 256 || ids.has(row.runId) || !tokens || (row.cost !== undefined && !rowCost)) return undefined;
|
|
38
|
+
ids.add(row.runId);
|
|
39
|
+
runs.push({ runId: row.runId, usage: tokens, ...(rowCost ? { cost: rowCost } : {}) });
|
|
38
40
|
}
|
|
39
|
-
|
|
41
|
+
if (fields.some(key => runs.reduce((sum, row) => sum + row.usage[key], 0) > usage[key])) return undefined;
|
|
42
|
+
return { usage, runs, ...(cost ? { cost } : {}) };
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
/** Fetch is only observation; the origin journal owns recognition. No run-ID guesses or private fallback. */
|
|
46
|
+
export async function fetchCursorSdkAgentUsage(agent: Pick<SDKAgent, "getUsage">): Promise<CursorBillingObservation> {
|
|
47
|
+
if (typeof agent.getUsage !== "function") return { status: "unavailable", reason: "unsupported" };
|
|
43
48
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
try {
|
|
50
|
+
const timeout = new Promise<CursorBillingObservation>(resolve => {
|
|
51
|
+
timer = setTimeout(() => resolve({ status: "unavailable", reason: "timeout" }), 5000);
|
|
52
|
+
timer.unref?.();
|
|
53
|
+
});
|
|
54
|
+
return await Promise.race([Promise.resolve().then(() => agent.getUsage()).then(value => {
|
|
55
|
+
const snapshot = readCursorBilledSnapshot(value);
|
|
56
|
+
return snapshot ? { status: "observed-pending-settlement" as const, snapshot } : { status: "unavailable" as const, reason: "invalid-response" as const };
|
|
57
|
+
}, () => ({ status: "unavailable" as const, reason: "request-failed" as const })), timeout]);
|
|
58
|
+
} finally { if (timer) clearTimeout(timer); }
|
|
51
59
|
}
|
|
52
|
-
|
|
53
|
-
export function selectCursorBilledTurnUsage(
|
|
54
|
-
agentUsage: unknown,
|
|
55
|
-
options: { runtime: CursorRuntime; runId?: string; seenRunIds?: ReadonlySet<string> },
|
|
56
|
-
): { turn?: CursorSdkTurnUsage; runIds: string[] } {
|
|
57
|
-
const runs = (getArray(asRecord(agentUsage), "runs") ?? []).flatMap((item) => {
|
|
58
|
-
const record = asRecord(item);
|
|
59
|
-
const runId = getString(record, "runId");
|
|
60
|
-
const usage = readCursorSdkTurnUsage(record?.usage);
|
|
61
|
-
return runId && usage ? [{ runId, usage }] : [];
|
|
62
|
-
});
|
|
63
|
-
if (options.runtime === "cloud" && options.runId) {
|
|
64
|
-
const match = runs.find((run) => run.runId === options.runId);
|
|
65
|
-
return match ? { turn: match.usage, runIds: [match.runId] } : { runIds: [] };
|
|
66
|
-
}
|
|
67
|
-
const unseen = runs.filter((run) => !options.seenRunIds?.has(run.runId));
|
|
68
|
-
return { turn: sumCursorSdkTurnUsage(unseen.map((run) => run.usage)), runIds: unseen.map((run) => run.runId) };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export async function fetchCursorSdkAgentUsage(
|
|
72
|
-
agent: SDKAgent,
|
|
73
|
-
options: { runtime: CursorRuntime; runId?: string },
|
|
74
|
-
): Promise<unknown | undefined> {
|
|
75
|
-
if (typeof agent.getUsage !== "function") return undefined;
|
|
76
|
-
const query =
|
|
77
|
-
options.runtime === "cloud" && options.runId && isCursorSdkClientMintedRunId(options.runId)
|
|
78
|
-
? { runId: options.runId }
|
|
79
|
-
: undefined;
|
|
80
|
-
return withTimeout(Promise.resolve(agent.getUsage(query)));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export async function attachCursorSdkBilledTurnUsage(options: {
|
|
84
|
-
agent: SDKAgent;
|
|
85
|
-
agentId: string;
|
|
86
|
-
runtime: CursorRuntime;
|
|
87
|
-
runId?: string;
|
|
88
|
-
}): Promise<{ agentUsage?: unknown; turn?: CursorSdkTurnUsage }> {
|
|
89
|
-
const agentUsage = await fetchCursorSdkAgentUsage(options.agent, {
|
|
90
|
-
runtime: options.runtime,
|
|
91
|
-
runId: options.runId,
|
|
92
|
-
});
|
|
93
|
-
if (!agentUsage) return {};
|
|
94
|
-
const selected = selectCursorBilledTurnUsage(agentUsage, {
|
|
95
|
-
runtime: options.runtime,
|
|
96
|
-
runId: options.runtime === "cloud" ? options.runId : undefined,
|
|
97
|
-
seenRunIds: peekCursorBilledUsageRunIds(options.agentId),
|
|
98
|
-
});
|
|
99
|
-
rememberCursorBilledUsageRunIds(options.agentId, selected.runIds);
|
|
100
|
-
return { agentUsage, turn: selected.turn };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export const __testUtils = {
|
|
104
|
-
reset(): void {
|
|
105
|
-
seenBilledRunIdsByAgent.clear();
|
|
106
|
-
},
|
|
107
|
-
};
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
SESSION_MANIFEST,
|
|
8
8
|
resolveCursorSdkEventDebugBaseDir,
|
|
9
9
|
} from "./cursor-sdk-event-debug-constants.js";
|
|
10
|
-
import {
|
|
10
|
+
import { getCursorSessionScopeSnapshot, type CursorTurnScope } from "./cursor-session-scope.js";
|
|
11
11
|
|
|
12
12
|
const ANONYMOUS_SESSION_SCOPE_KEY = "__anonymous__";
|
|
13
13
|
|
|
@@ -93,13 +93,14 @@ function resolveSessionDebugDir(
|
|
|
93
93
|
export function allocateCursorSdkEventDebugTurn(
|
|
94
94
|
cwd: string,
|
|
95
95
|
env: Record<string, string | undefined>,
|
|
96
|
+
scope: CursorTurnScope = getCursorSessionScopeSnapshot(),
|
|
96
97
|
): CursorSdkEventDebugTurnAllocation {
|
|
97
98
|
const pinnedRunDir = resolvePinnedRunArtifactDir(env[CURSOR_SDK_EVENT_DEBUG_RUN_DIR_ENV]);
|
|
98
99
|
if (pinnedRunDir) {
|
|
99
100
|
return { artifactDir: pinnedRunDir, pinnedRun: true };
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
const scopeKey =
|
|
103
|
+
const scopeKey = scope.scopeKey;
|
|
103
104
|
const sessionDir = resolveSessionDebugDir(cwd, env, scopeKey);
|
|
104
105
|
mkdirSync(sessionDir, { recursive: true });
|
|
105
106
|
|
|
@@ -118,13 +119,13 @@ export function allocateCursorSdkEventDebugTurn(
|
|
|
118
119
|
const existing = readSessionManifest(sessionDir);
|
|
119
120
|
const manifest: CursorSdkEventDebugSessionManifest = existing ?? {
|
|
120
121
|
sessionKey: scopeKey,
|
|
121
|
-
sessionFile:
|
|
122
|
+
sessionFile: scope.sessionFile,
|
|
122
123
|
sessionDir,
|
|
123
124
|
createdAt: new Date().toISOString(),
|
|
124
125
|
updatedAt: new Date().toISOString(),
|
|
125
126
|
turns: [],
|
|
126
127
|
};
|
|
127
|
-
manifest.sessionFile =
|
|
128
|
+
manifest.sessionFile = scope.sessionFile;
|
|
128
129
|
manifest.updatedAt = new Date().toISOString();
|
|
129
130
|
manifest.turns.push({
|
|
130
131
|
turn: state.turnCounter,
|
|
@@ -154,7 +155,6 @@ export function updateCursorSdkEventDebugSessionManifest(
|
|
|
154
155
|
turnEntry.finalizedAt = new Date().toISOString();
|
|
155
156
|
turnEntry.summary = summary;
|
|
156
157
|
manifest.updatedAt = new Date().toISOString();
|
|
157
|
-
manifest.sessionFile = getCursorSessionFile();
|
|
158
158
|
writeSessionManifest(sessionDir, manifest);
|
|
159
159
|
}
|
|
160
160
|
|
|
@@ -8,7 +8,7 @@ import { serializeCursorPiToolBridgeDiagnostic } from "./cursor-pi-tool-bridge-d
|
|
|
8
8
|
import type { CursorPiBridgeToolRequest } from "./cursor-pi-tool-bridge-types.js";
|
|
9
9
|
import type { CursorLiveQueuedEvent } from "./cursor-live-run-coordinator.js";
|
|
10
10
|
import { asRecord } from "./cursor-record-utils.js";
|
|
11
|
-
import {
|
|
11
|
+
import { getCursorSessionScopeSnapshot, type CursorTurnScope } from "./cursor-session-scope.js";
|
|
12
12
|
import { parseEnvBoolean } from "./cursor-env-boolean.js";
|
|
13
13
|
import {
|
|
14
14
|
ARTIFACTS,
|
|
@@ -58,6 +58,7 @@ export interface CursorSdkDisplayDecisionRecord {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export interface CursorSdkEventDebugSinkOptions {
|
|
61
|
+
scope?: CursorTurnScope;
|
|
61
62
|
cwd: string;
|
|
62
63
|
modelId: string;
|
|
63
64
|
provider: string;
|
|
@@ -250,6 +251,7 @@ export class CursorSdkEventDebugSink {
|
|
|
250
251
|
timeline: {} as Record<string, number>,
|
|
251
252
|
errors: 0,
|
|
252
253
|
};
|
|
254
|
+
private readonly sessionFile: string | undefined;
|
|
253
255
|
private metadata: Record<string, unknown>;
|
|
254
256
|
private readonly jsonlBuffers = new Map<string, string[]>();
|
|
255
257
|
private readonly jsonlBufferBytes = new Map<string, number>();
|
|
@@ -263,8 +265,9 @@ export class CursorSdkEventDebugSink {
|
|
|
263
265
|
static maybeCreate(options: CursorSdkEventDebugSinkOptions): CursorSdkEventDebugSink | undefined {
|
|
264
266
|
const env = options.env ?? process.env;
|
|
265
267
|
if (!resolveCursorSdkEventDebugEnabled(env)) return undefined;
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
+
const scope = options.scope ?? getCursorSessionScopeSnapshot();
|
|
269
|
+
const allocation = allocateCursorSdkEventDebugTurn(options.cwd, env, scope);
|
|
270
|
+
return new CursorSdkEventDebugSink(allocation, { ...options, scope }, env);
|
|
268
271
|
}
|
|
269
272
|
|
|
270
273
|
private constructor(
|
|
@@ -278,6 +281,7 @@ export class CursorSdkEventDebugSink {
|
|
|
278
281
|
this.sessionKey = allocation.sessionKey;
|
|
279
282
|
this.pinnedRun = allocation.pinnedRun;
|
|
280
283
|
this.env = env;
|
|
284
|
+
this.sessionFile = options.scope?.sessionFile;
|
|
281
285
|
this.metadata = {
|
|
282
286
|
capturedAt: new Date().toISOString(),
|
|
283
287
|
modelId: options.modelId,
|
|
@@ -285,7 +289,7 @@ export class CursorSdkEventDebugSink {
|
|
|
285
289
|
cwd: options.cwd,
|
|
286
290
|
sessionDir: allocation.sessionDir,
|
|
287
291
|
sessionKey: allocation.sessionKey,
|
|
288
|
-
sessionFile:
|
|
292
|
+
sessionFile: this.sessionFile,
|
|
289
293
|
turn: allocation.turn,
|
|
290
294
|
pinnedRun: allocation.pinnedRun,
|
|
291
295
|
artifacts: ARTIFACTS,
|
|
@@ -452,7 +456,7 @@ export class CursorSdkEventDebugSink {
|
|
|
452
456
|
}
|
|
453
457
|
|
|
454
458
|
private capturePiSessionSnapshot(): { copied: boolean; sessionFile?: string; reason?: string } {
|
|
455
|
-
const sessionFile =
|
|
459
|
+
const sessionFile = this.sessionFile;
|
|
456
460
|
if (!sessionFile) {
|
|
457
461
|
return { copied: false, reason: "session file unknown" };
|
|
458
462
|
}
|
|
@@ -509,7 +513,7 @@ export class CursorSdkEventDebugSink {
|
|
|
509
513
|
artifactDir: this.artifactDir,
|
|
510
514
|
sessionDir: this.sessionDir,
|
|
511
515
|
sessionKey: this.sessionKey,
|
|
512
|
-
sessionFile:
|
|
516
|
+
sessionFile: this.sessionFile,
|
|
513
517
|
turn: this.turn,
|
|
514
518
|
elapsedMs: Date.now() - this.startedAt,
|
|
515
519
|
counts: {
|
|
@@ -23,7 +23,7 @@ type GenericProcessEmit = (event: string | symbol, ...args: unknown[]) => boolea
|
|
|
23
23
|
// ConnectRPC suppression remains scoped to active provider turns.
|
|
24
24
|
const activeProviderTurns = new Set<CursorSdkProcessErrorGuardToken>();
|
|
25
25
|
const activeSessions = new Set<CursorSdkSessionProcessErrorGuardToken>();
|
|
26
|
-
|
|
26
|
+
const lifecycleSessionGuards = new Map<object, CursorSdkSessionProcessErrorGuard>();
|
|
27
27
|
let originalProcessEmit: GenericProcessEmit | undefined;
|
|
28
28
|
let cursorProcessEmit: GenericProcessEmit | undefined;
|
|
29
29
|
let bunUnhandledRejectionListenerInstalled = false;
|
|
@@ -143,8 +143,8 @@ export const __testUtils = {
|
|
|
143
143
|
activeProviderTurnCount: (): number => activeProviderTurns.size,
|
|
144
144
|
activeSessionCount: (): number => activeSessions.size,
|
|
145
145
|
resetLifecycleSessionGuard(): void {
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
for (const guard of lifecycleSessionGuards.values()) guard.dispose();
|
|
147
|
+
lifecycleSessionGuards.clear();
|
|
148
148
|
},
|
|
149
149
|
};
|
|
150
150
|
|
|
@@ -186,11 +186,11 @@ export function installCursorSdkSessionProcessErrorGuard(): CursorSdkSessionProc
|
|
|
186
186
|
|
|
187
187
|
export function registerCursorSdkSessionProcessErrorGuard(pi: Pick<ExtensionAPI, "on">): void {
|
|
188
188
|
pi.on("session_start", () => {
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
lifecycleSessionGuards.get(pi)?.dispose();
|
|
190
|
+
lifecycleSessionGuards.set(pi, installCursorSdkSessionProcessErrorGuard());
|
|
191
191
|
});
|
|
192
192
|
pi.on("session_shutdown", () => {
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
lifecycleSessionGuards.get(pi)?.dispose();
|
|
194
|
+
lifecycleSessionGuards.delete(pi);
|
|
195
195
|
});
|
|
196
196
|
}
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { resolveCursorSdkPlatformPackageDirectory } from "./cursor-ripgrep-path.js";
|
|
4
|
+
|
|
1
5
|
export type CursorSdkModule = typeof import("@cursor/sdk");
|
|
2
6
|
|
|
3
7
|
export async function loadCursorSdk(): Promise<CursorSdkModule> {
|
|
8
|
+
// The SDK searches launcher ancestors, which miss Pi's separately installed extension packages.
|
|
9
|
+
if (process.env.CURSOR_TREE_SITTER_VENDOR_DIR === undefined) {
|
|
10
|
+
const packageDirectory = resolveCursorSdkPlatformPackageDirectory();
|
|
11
|
+
if (packageDirectory) {
|
|
12
|
+
const vendor = join(packageDirectory, "vendor");
|
|
13
|
+
if (existsSync(join(vendor, "tree-sitter", "index.js")) && existsSync(join(vendor, "tree-sitter-bash", "index.js"))) {
|
|
14
|
+
process.env.CURSOR_TREE_SITTER_VENDOR_DIR = vendor;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
4
18
|
return import("@cursor/sdk");
|
|
5
19
|
}
|
|
@@ -4,7 +4,7 @@ import { asRecord, getString } from "./cursor-record-utils.js";
|
|
|
4
4
|
import { fsyncExistingRegularFile } from "./cursor-durable-fs.js";
|
|
5
5
|
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
6
6
|
import { loadCursorSdk } from "./cursor-sdk-runtime.js";
|
|
7
|
-
import {
|
|
7
|
+
import { cursorSessionScopeKeyForManager } from "./cursor-session-scope.js";
|
|
8
8
|
import {
|
|
9
9
|
CURSOR_SESSION_AGENT_RESUME_ENTRY_TYPE,
|
|
10
10
|
isCursorLocalAgentId,
|
|
@@ -96,7 +96,7 @@ function getCurrentCleanupScope(ctx: LocalResumeCleanupCommandContext): CursorSe
|
|
|
96
96
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
97
97
|
const repoRoot = resolveCursorSessionRepoRoot(ctx.cwd);
|
|
98
98
|
return {
|
|
99
|
-
scopeKey:
|
|
99
|
+
scopeKey: cursorSessionScopeKeyForManager(ctx.sessionManager),
|
|
100
100
|
...(sessionFile ? { sessionFile } : {}),
|
|
101
101
|
...(sessionId ? { sessionId } : {}),
|
|
102
102
|
cwd: ctx.cwd,
|
|
@@ -4,9 +4,10 @@ import type {
|
|
|
4
4
|
SessionCompactEvent,
|
|
5
5
|
SessionShutdownEvent,
|
|
6
6
|
SessionTreeEvent,
|
|
7
|
+
SessionStartEvent,
|
|
7
8
|
} from "@earendil-works/pi-coding-agent";
|
|
8
9
|
import { clearCursorSdkHttp1 } from "./cursor-http1.js";
|
|
9
|
-
import { onCursorSessionScopeKeyChange } from "./cursor-session-scope.js";
|
|
10
|
+
import { cursorSessionScopeKeyForManager, hasLiveCursorSessions, onCursorSessionScopeKeyChange } from "./cursor-session-scope.js";
|
|
10
11
|
import {
|
|
11
12
|
disposeSessionCursorAgent,
|
|
12
13
|
invalidateSessionAgent,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
} from "./cursor-session-agent.js";
|
|
15
16
|
|
|
16
17
|
export interface CursorSessionAgentLifecycleExtensionApi {
|
|
18
|
+
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
|
|
17
19
|
on(event: "session_shutdown", handler: ExtensionHandler<SessionShutdownEvent>): void;
|
|
18
20
|
on(event: "session_compact", handler: ExtensionHandler<SessionCompactEvent>): void;
|
|
19
21
|
on(event: "session_before_tree", handler: ExtensionHandler<SessionBeforeTreeEvent>): void;
|
|
@@ -22,30 +24,33 @@ export interface CursorSessionAgentLifecycleExtensionApi {
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export function registerCursorSessionAgentLifecycle(pi: CursorSessionAgentLifecycleExtensionApi): void {
|
|
25
|
-
|
|
27
|
+
let scopeKey: string | undefined;
|
|
28
|
+
pi.on("session_start", (_event, ctx) => { scopeKey = cursorSessionScopeKeyForManager(ctx.sessionManager); });
|
|
29
|
+
onCursorSessionScopeKeyChange(pi, async (previousScopeKey) => {
|
|
26
30
|
await disposeSessionCursorAgent(previousScopeKey);
|
|
27
31
|
});
|
|
28
32
|
pi.on("session_shutdown", async (event) => {
|
|
29
33
|
try {
|
|
34
|
+
if (scopeKey === undefined) return;
|
|
30
35
|
if (event.reason === "reload") {
|
|
31
|
-
await resetSessionCursorAgent();
|
|
36
|
+
await resetSessionCursorAgent(scopeKey);
|
|
32
37
|
return;
|
|
33
38
|
}
|
|
34
|
-
await disposeSessionCursorAgent();
|
|
39
|
+
await disposeSessionCursorAgent(scopeKey);
|
|
35
40
|
} finally {
|
|
36
|
-
clearCursorSdkHttp1();
|
|
41
|
+
if (!hasLiveCursorSessions()) clearCursorSdkHttp1();
|
|
37
42
|
}
|
|
38
43
|
});
|
|
39
44
|
pi.on("session_compact", () => {
|
|
40
|
-
invalidateSessionAgent();
|
|
45
|
+
if (scopeKey !== undefined) invalidateSessionAgent(scopeKey);
|
|
41
46
|
});
|
|
42
47
|
pi.on("session_before_tree", () => {
|
|
43
|
-
invalidateSessionAgent();
|
|
48
|
+
if (scopeKey !== undefined) invalidateSessionAgent(scopeKey);
|
|
44
49
|
});
|
|
45
50
|
pi.on("session_tree", async () => {
|
|
46
|
-
await resetSessionCursorAgent();
|
|
51
|
+
if (scopeKey !== undefined) await resetSessionCursorAgent(scopeKey);
|
|
47
52
|
});
|
|
48
53
|
pi.on("model_select", () => {
|
|
49
|
-
invalidateSessionAgent();
|
|
54
|
+
if (scopeKey !== undefined) invalidateSessionAgent(scopeKey);
|
|
50
55
|
});
|
|
51
56
|
}
|