pi-cursor-sdk 0.1.37 → 0.1.39
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 +34 -0
- package/README.md +2 -2
- package/docs/cursor-model-ux-spec.md +1 -1
- package/docs/cursor-native-tool-replay.md +5 -5
- package/package.json +1 -1
- package/scripts/platform-smoke/card-detect.mjs +1 -1
- package/src/context-window-cache.ts +10 -14
- package/src/context.ts +1 -1
- package/src/cursor-agent-message-web-tools.ts +2 -1
- package/src/cursor-agents-context-registration.ts +18 -0
- package/src/cursor-agents-context.ts +21 -30
- package/src/cursor-edit-diff.ts +4 -2
- package/src/cursor-fallback-warning.ts +22 -0
- package/src/cursor-incomplete-tool-visibility.ts +5 -10
- package/src/cursor-live-run-coordinator.ts +1 -1
- package/src/cursor-mcp-timeout-override.ts +0 -2
- package/src/cursor-model-lifecycle.ts +72 -0
- package/src/cursor-native-replay-routing.ts +1 -1
- package/src/cursor-native-replay-trace.ts +1 -1
- package/src/cursor-native-tool-display-registration.ts +16 -28
- package/src/cursor-native-tool-display-replay.ts +4 -21
- package/src/cursor-native-tool-display-state.ts +1 -1
- package/src/cursor-native-tool-display-tools.ts +10 -17
- package/src/cursor-native-tool-names.ts +16 -0
- package/src/cursor-pi-tool-bridge-env.ts +12 -0
- package/src/cursor-pi-tool-bridge-mcp.ts +16 -21
- package/src/cursor-pi-tool-bridge-run.ts +5 -5
- package/src/cursor-pi-tool-bridge-server.ts +8 -3
- package/src/cursor-pi-tool-bridge-snapshot.ts +7 -13
- package/src/cursor-pi-tool-bridge.ts +7 -7
- package/src/cursor-provider-errors.ts +11 -4
- package/src/cursor-provider-lazy.ts +51 -0
- package/src/cursor-provider-live-run-drain.ts +1 -1
- package/src/cursor-provider-run-finalizer.ts +5 -5
- package/src/cursor-provider-run-outcome.ts +0 -1
- package/src/cursor-provider-turn-coordinator.ts +16 -6
- package/src/cursor-provider-turn-display-router.ts +5 -1
- package/src/cursor-provider-turn-emit.ts +1 -1
- package/src/cursor-provider-turn-lifecycle-emitter.ts +1 -5
- package/src/cursor-provider-turn-prepare.ts +13 -9
- package/src/cursor-provider-turn-runner.ts +3 -11
- package/src/cursor-provider-turn-sdk-normalizer.ts +28 -5
- package/src/cursor-provider-turn-send.ts +7 -2
- package/src/cursor-provider-turn-shell-output.ts +38 -3
- package/src/cursor-provider-turn-types.ts +1 -3
- package/src/cursor-provider.ts +3 -2
- package/src/cursor-question-tool.ts +5 -18
- package/src/cursor-record-utils.ts +42 -0
- package/src/cursor-replay-activity-builders.ts +16 -122
- package/src/cursor-replay-tool-details.ts +52 -80
- package/src/cursor-sdk-event-debug.ts +6 -6
- package/src/cursor-sensitive-text.ts +4 -4
- package/src/cursor-session-agent-lifecycle.ts +47 -0
- package/src/cursor-session-agent.ts +9 -47
- package/src/cursor-session-scope.ts +23 -4
- package/src/cursor-setting-sources.ts +8 -8
- package/src/cursor-skill-tool.ts +25 -32
- package/src/cursor-state.ts +66 -45
- package/src/cursor-tool-lifecycle.ts +22 -10
- package/src/cursor-tool-presentation-registry.ts +27 -18
- package/src/cursor-tool-result-display-readers.ts +185 -0
- package/src/cursor-tool-transcript.ts +17 -33
- package/src/cursor-tool-visibility.ts +9 -1
- package/src/cursor-transcript-tool-formatters.ts +23 -172
- package/src/cursor-transcript-tool-specs.ts +16 -41
- package/src/cursor-transcript-utils.ts +2 -34
- package/src/cursor-usage-accounting.ts +0 -6
- package/src/cursor-web-tool-activity.ts +4 -12
- package/src/cursor-web-tool-args.ts +1 -9
- package/src/index.ts +15 -16
- package/src/model-discovery.ts +5 -4
- package/src/model-list-cache.ts +37 -38
- package/src/cursor-native-tool-display.ts +0 -10
- package/src/cursor-provider-turn-api-key.ts +0 -1
- package/src/cursor-provider-turn-message-offset.ts +0 -15
- package/src/cursor-session-cwd.ts +0 -28
- package/src/cursor-tool-names.ts +0 -9
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
2
1
|
import type {
|
|
3
2
|
CursorReplayGenerateImageSummaryArgs,
|
|
4
3
|
CursorReplayMcpSummaryArgs,
|
|
@@ -13,16 +12,20 @@ import type {
|
|
|
13
12
|
CursorReplayWebSearchSummaryArgs,
|
|
14
13
|
} from "./cursor-replay-summary-args.js";
|
|
15
14
|
import type { CursorReplayGenerateImageDetailFields } from "./cursor-replay-tool-details.js";
|
|
16
|
-
import { asRecord } from "./cursor-record-utils.js";
|
|
15
|
+
import { asRecord, getArray, getNumber, getString } from "./cursor-record-utils.js";
|
|
16
|
+
import { firstNonEmptyLine, formatDisplayPath, truncateArg } from "./cursor-transcript-utils.js";
|
|
17
17
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
collectTaskText,
|
|
19
|
+
getGenerateImageDisplayPath,
|
|
20
|
+
getGenerateImagePath,
|
|
21
|
+
readMcpDisplayResult,
|
|
22
|
+
getReadLintDiagnostics,
|
|
23
|
+
getReadLintPaths,
|
|
24
|
+
getTaskDescription,
|
|
25
|
+
getTodoItems,
|
|
26
|
+
getTodoTotalCount,
|
|
27
|
+
inferImageMimeType,
|
|
28
|
+
} from "./cursor-tool-result-display-readers.js";
|
|
26
29
|
import { extractWebFetchTarget, extractWebSearchQuery } from "./cursor-web-tool-args.js";
|
|
27
30
|
|
|
28
31
|
export interface CursorReplayActivityBuildContext {
|
|
@@ -48,14 +51,6 @@ export function buildDeleteReplayDetailFields({ args, result, options }: CursorR
|
|
|
48
51
|
};
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
export function buildEmptyReplayDetailFields(): Record<string, never> {
|
|
52
|
-
return {};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function buildCollapsedReplayDetailFields(): { collapseDetailsByDefault: true } {
|
|
56
|
-
return { collapseDetailsByDefault: true };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
54
|
export function buildReadLintsReplaySummaryArgs({
|
|
60
55
|
args,
|
|
61
56
|
result,
|
|
@@ -75,7 +70,7 @@ export function buildTodoReplaySummaryArgs(
|
|
|
75
70
|
result: CursorReplayActivityBuildContext["result"],
|
|
76
71
|
): CursorReplayTodoSummaryArgs {
|
|
77
72
|
const todos = getTodoItems(args, result);
|
|
78
|
-
const totalCount =
|
|
73
|
+
const totalCount = getTodoTotalCount(args, result, todos);
|
|
79
74
|
const completedCount = todos.filter((todo) => todo.status === "completed").length;
|
|
80
75
|
const inProgressCount = todos.filter((todo) => todo.status === "inProgress").length;
|
|
81
76
|
const pendingCount = todos.filter((todo) => todo.status === "pending").length;
|
|
@@ -94,7 +89,7 @@ export function buildCreatePlanReplaySummaryArgs({ args, result }: CursorReplayA
|
|
|
94
89
|
}
|
|
95
90
|
|
|
96
91
|
export function buildTaskReplaySummaryArgs({ args, result }: CursorReplayActivityBuildContext): CursorReplayTaskSummaryArgs {
|
|
97
|
-
const description =
|
|
92
|
+
const description = getTaskDescription(args, result);
|
|
98
93
|
const preview = firstNonEmptyLine(collectTaskText(result));
|
|
99
94
|
return {
|
|
100
95
|
description: truncateArg(description),
|
|
@@ -117,7 +112,7 @@ export function buildGenerateImageReplaySummaryArgs({
|
|
|
117
112
|
|
|
118
113
|
export function buildMcpReplaySummaryArgs({ args, result }: CursorReplayActivityBuildContext): CursorReplayMcpSummaryArgs {
|
|
119
114
|
const toolName = getString(args, "toolName") ?? "mcp";
|
|
120
|
-
const preview =
|
|
115
|
+
const preview = readMcpDisplayResult(result).preview;
|
|
121
116
|
return {
|
|
122
117
|
toolName: truncateArg(toolName),
|
|
123
118
|
...(preview ? { preview } : {}),
|
|
@@ -173,104 +168,3 @@ export function buildGenerateImageReplayDetailFields(
|
|
|
173
168
|
expandedText: contentText,
|
|
174
169
|
};
|
|
175
170
|
}
|
|
176
|
-
|
|
177
|
-
function getReadLintPaths(args: Record<string, unknown>, result: CursorReplayActivityBuildContext["result"], options: CursorReplayActivityBuildContext["options"]): string[] {
|
|
178
|
-
const explicitPaths = Array.isArray(args.paths)
|
|
179
|
-
? args.paths.filter((entry): entry is string => typeof entry === "string")
|
|
180
|
-
: typeof args.path === "string"
|
|
181
|
-
? [args.path]
|
|
182
|
-
: [];
|
|
183
|
-
const resultPaths = (getArray(asRecord(result.value), "fileDiagnostics") ?? [])
|
|
184
|
-
.map((file) => getString(asRecord(file), "path"))
|
|
185
|
-
.filter((entry): entry is string => Boolean(entry));
|
|
186
|
-
return [...new Set([...explicitPaths, ...resultPaths].map((entry) => formatDisplayPath(entry, options.cwd)))];
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function getReadLintDiagnostics(result: CursorReplayActivityBuildContext["result"], options: CursorReplayActivityBuildContext["options"]): string[] {
|
|
190
|
-
const value = asRecord(result.value);
|
|
191
|
-
const files = getArray(value, "fileDiagnostics") ?? [];
|
|
192
|
-
const lines: string[] = [];
|
|
193
|
-
for (const file of files) {
|
|
194
|
-
const fileRecord = asRecord(file);
|
|
195
|
-
const pathValue = getString(fileRecord, "path");
|
|
196
|
-
const path = pathValue ? formatDisplayPath(pathValue, options.cwd) : "unknown";
|
|
197
|
-
const diagnostics = getArray(fileRecord, "diagnostics") ?? [];
|
|
198
|
-
for (const diagnostic of diagnostics) {
|
|
199
|
-
const diagnosticRecord = asRecord(diagnostic);
|
|
200
|
-
const severity = getString(diagnosticRecord, "severity") ?? "diagnostic";
|
|
201
|
-
const message = getString(diagnosticRecord, "message") ?? "";
|
|
202
|
-
const source = getString(diagnosticRecord, "source");
|
|
203
|
-
lines.push(`${path}: ${severity}${source ? ` ${source}` : ""}: ${message}`);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return lines;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function getTodoItems(args: Record<string, unknown>, result: CursorReplayActivityBuildContext["result"]): Array<{ content: string; status?: string }> {
|
|
210
|
-
const value = asRecord(result.value);
|
|
211
|
-
const rawTodos = getArray(value, "todos") ?? getArray(args, "todos") ?? [];
|
|
212
|
-
const todos: Array<{ content: string; status?: string }> = [];
|
|
213
|
-
for (const todo of rawTodos) {
|
|
214
|
-
const record = asRecord(todo);
|
|
215
|
-
const content = getString(record, "content");
|
|
216
|
-
if (!content) continue;
|
|
217
|
-
const status = getString(record, "status");
|
|
218
|
-
todos.push(status ? { content, status } : { content });
|
|
219
|
-
}
|
|
220
|
-
return todos;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function collectTaskText(result: CursorReplayActivityBuildContext["result"]): string {
|
|
224
|
-
const value = asRecord(result.value);
|
|
225
|
-
const success = getRecord(getRecord(value, "result"), "success");
|
|
226
|
-
const command = getString(success, "command");
|
|
227
|
-
const stdout = getString(success, "stdout");
|
|
228
|
-
const interleavedOutput = getString(success, "interleavedOutput");
|
|
229
|
-
const assistantMessages = (getArray(value, "conversationSteps") ?? [])
|
|
230
|
-
.map((step) => getString(getRecord(asRecord(step), "assistantMessage"), "text"))
|
|
231
|
-
.filter((entry): entry is string => Boolean(entry));
|
|
232
|
-
const parts = [command ? `$ ${command}` : undefined, stdout || interleavedOutput, ...assistantMessages].filter((part): part is string => Boolean(part));
|
|
233
|
-
return parts.join("\n");
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function getGenerateImagePath(args: Record<string, unknown>, result: CursorReplayActivityBuildContext["result"]): string | undefined {
|
|
237
|
-
const value = asRecord(result.value);
|
|
238
|
-
return getString(value, "filePath") ?? getString(args, "filePath") ?? getString(args, "path");
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function getGenerateImageDisplayPath(args: Record<string, unknown>, result: CursorReplayActivityBuildContext["result"], options: CursorReplayActivityBuildContext["options"]): string | undefined {
|
|
242
|
-
const path = getGenerateImagePath(args, result);
|
|
243
|
-
return path ? formatDisplayPath(path, options.cwd) : undefined;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function inferImageMimeType(path: string | undefined): string | undefined {
|
|
247
|
-
const lower = path?.toLowerCase();
|
|
248
|
-
if (!lower) return undefined;
|
|
249
|
-
if (lower.endsWith(".png")) return "image/png";
|
|
250
|
-
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
251
|
-
if (lower.endsWith(".gif")) return "image/gif";
|
|
252
|
-
if (lower.endsWith(".webp")) return "image/webp";
|
|
253
|
-
return undefined;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function getMcpContentText(entry: unknown): string | undefined {
|
|
257
|
-
const record = asRecord(entry);
|
|
258
|
-
const directText = getString(record, "text");
|
|
259
|
-
if (directText) return directText;
|
|
260
|
-
const nestedText = getRecord(record, "text");
|
|
261
|
-
return getString(nestedText, "text");
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function getMcpResultPreview(result: CursorReplayActivityBuildContext["result"]): string | undefined {
|
|
265
|
-
if (result.status === "error") return undefined;
|
|
266
|
-
const value = asRecord(result.value);
|
|
267
|
-
const content = getArray(value, "content") ?? [];
|
|
268
|
-
for (const entry of content) {
|
|
269
|
-
const text = getMcpContentText(entry);
|
|
270
|
-
if (text) {
|
|
271
|
-
const line = firstNonEmptyLine(text);
|
|
272
|
-
if (line) return truncateArg(scrubSensitiveText(line), 120);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return undefined;
|
|
276
|
-
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { asRecord, getBoolean, getNumber, getString } from "./cursor-record-utils.js";
|
|
1
2
|
import { isCursorReplayActivitySourceName, type CursorReplayActivitySourceName } from "./cursor-replay-source-names.js";
|
|
2
3
|
|
|
3
4
|
/** Replay detail variants keyed by replay card disposition, not SDK source tool alone. */
|
|
@@ -111,70 +112,51 @@ export type CursorReplayGenerateImageDetailFields = Pick<
|
|
|
111
112
|
"summary" | "expandedText" | "imagePath" | "imageDisplayPath" | "imageMimeType"
|
|
112
113
|
>;
|
|
113
114
|
|
|
114
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
115
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function readOptionalString(record: Record<string, unknown>, key: string): string | undefined {
|
|
119
|
-
const value = record[key];
|
|
120
|
-
return typeof value === "string" ? value : undefined;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function readOptionalNumber(record: Record<string, unknown>, key: string): number | undefined {
|
|
124
|
-
const value = record[key];
|
|
125
|
-
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function readOptionalBoolean(record: Record<string, unknown>, key: string): boolean | undefined {
|
|
129
|
-
const value = record[key];
|
|
130
|
-
return typeof value === "boolean" ? value : undefined;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
115
|
function readSourceToolName(record: Record<string, unknown>): string | undefined {
|
|
134
|
-
const sourceToolName =
|
|
116
|
+
const sourceToolName = getString(record, "sourceToolName");
|
|
135
117
|
return sourceToolName?.trim() ? sourceToolName.trim() : undefined;
|
|
136
118
|
}
|
|
137
119
|
|
|
138
120
|
function readVariant(record: Record<string, unknown>): string | undefined {
|
|
139
|
-
const variant =
|
|
121
|
+
const variant = getString(record, "variant");
|
|
140
122
|
return variant?.trim() ? variant.trim() : undefined;
|
|
141
123
|
}
|
|
142
124
|
|
|
143
125
|
function parseCursorReplayNativeEditDetails(record: Record<string, unknown>): CursorReplayNativeEditDetails {
|
|
144
126
|
return {
|
|
145
127
|
variant: "nativeEdit",
|
|
146
|
-
path:
|
|
147
|
-
linesAdded:
|
|
148
|
-
linesRemoved:
|
|
149
|
-
diffString:
|
|
150
|
-
diff:
|
|
151
|
-
firstChangedLine:
|
|
152
|
-
summary:
|
|
153
|
-
expandedText:
|
|
128
|
+
path: getString(record, "path"),
|
|
129
|
+
linesAdded: getNumber(record, "linesAdded"),
|
|
130
|
+
linesRemoved: getNumber(record, "linesRemoved"),
|
|
131
|
+
diffString: getString(record, "diffString"),
|
|
132
|
+
diff: getString(record, "diff"),
|
|
133
|
+
firstChangedLine: getNumber(record, "firstChangedLine"),
|
|
134
|
+
summary: getString(record, "summary"),
|
|
135
|
+
expandedText: getString(record, "expandedText"),
|
|
154
136
|
};
|
|
155
137
|
}
|
|
156
138
|
|
|
157
139
|
function parseCursorReplayNativeWriteDetails(record: Record<string, unknown>): CursorReplayNativeWriteDetails {
|
|
158
140
|
return {
|
|
159
141
|
variant: "nativeWrite",
|
|
160
|
-
path:
|
|
161
|
-
linesCreated:
|
|
162
|
-
fileSize:
|
|
163
|
-
fileContentAfterWrite:
|
|
164
|
-
expandedText:
|
|
165
|
-
summary:
|
|
142
|
+
path: getString(record, "path"),
|
|
143
|
+
linesCreated: getNumber(record, "linesCreated"),
|
|
144
|
+
fileSize: getNumber(record, "fileSize"),
|
|
145
|
+
fileContentAfterWrite: getString(record, "fileContentAfterWrite"),
|
|
146
|
+
expandedText: getString(record, "expandedText"),
|
|
147
|
+
summary: getString(record, "summary"),
|
|
166
148
|
};
|
|
167
149
|
}
|
|
168
150
|
|
|
169
151
|
function parseCursorReplayGenerateImageDetails(record: Record<string, unknown>): CursorReplayGenerateImageDetails {
|
|
170
|
-
const collapseDetailsByDefault =
|
|
152
|
+
const collapseDetailsByDefault = getBoolean(record, "collapseDetailsByDefault");
|
|
171
153
|
return {
|
|
172
154
|
variant: "generateImage",
|
|
173
|
-
imagePath:
|
|
174
|
-
imageDisplayPath:
|
|
175
|
-
imageMimeType:
|
|
176
|
-
summary:
|
|
177
|
-
expandedText:
|
|
155
|
+
imagePath: getString(record, "imagePath"),
|
|
156
|
+
imageDisplayPath: getString(record, "imageDisplayPath"),
|
|
157
|
+
imageMimeType: getString(record, "imageMimeType"),
|
|
158
|
+
summary: getString(record, "summary"),
|
|
159
|
+
expandedText: getString(record, "expandedText"),
|
|
178
160
|
...(collapseDetailsByDefault !== undefined ? { collapseDetailsByDefault } : {}),
|
|
179
161
|
};
|
|
180
162
|
}
|
|
@@ -188,16 +170,16 @@ function parseCursorReplayActivityDetails(
|
|
|
188
170
|
variant: "activity",
|
|
189
171
|
sourceToolName,
|
|
190
172
|
title,
|
|
191
|
-
summary:
|
|
192
|
-
expandedText:
|
|
193
|
-
collapseDetailsByDefault:
|
|
194
|
-
path:
|
|
195
|
-
fileSize:
|
|
196
|
-
diffString:
|
|
197
|
-
diff:
|
|
198
|
-
linesAdded:
|
|
199
|
-
linesRemoved:
|
|
200
|
-
fileContentAfterWrite:
|
|
173
|
+
summary: getString(record, "summary"),
|
|
174
|
+
expandedText: getString(record, "expandedText"),
|
|
175
|
+
collapseDetailsByDefault: getBoolean(record, "collapseDetailsByDefault"),
|
|
176
|
+
path: getString(record, "path"),
|
|
177
|
+
fileSize: getNumber(record, "fileSize"),
|
|
178
|
+
diffString: getString(record, "diffString"),
|
|
179
|
+
diff: getString(record, "diff"),
|
|
180
|
+
linesAdded: getNumber(record, "linesAdded"),
|
|
181
|
+
linesRemoved: getNumber(record, "linesRemoved"),
|
|
182
|
+
fileContentAfterWrite: getString(record, "fileContentAfterWrite"),
|
|
201
183
|
};
|
|
202
184
|
}
|
|
203
185
|
|
|
@@ -212,8 +194,8 @@ function parseCursorReplayGenericFallbackDetails(
|
|
|
212
194
|
return {
|
|
213
195
|
variant: "genericFallback",
|
|
214
196
|
sourceToolName: brandCursorReplayUnknownSourceToolName(sourceToolName),
|
|
215
|
-
summary:
|
|
216
|
-
expandedText:
|
|
197
|
+
summary: getString(record, "summary"),
|
|
198
|
+
expandedText: getString(record, "expandedText"),
|
|
217
199
|
};
|
|
218
200
|
}
|
|
219
201
|
|
|
@@ -237,10 +219,8 @@ export function resolveIncompleteReplayActivitySourceToolName(
|
|
|
237
219
|
return resolveParseActivitySourceToolName(sourceToolName);
|
|
238
220
|
}
|
|
239
221
|
|
|
240
|
-
type CursorReplayVariantParser = (record: Record<string, unknown>) => CursorReplayToolDetails | undefined;
|
|
241
|
-
|
|
242
222
|
function parseActivityVariantDetails(record: Record<string, unknown>): CursorReplayActivityDetails | undefined {
|
|
243
|
-
const title =
|
|
223
|
+
const title = getString(record, "title")?.trim();
|
|
244
224
|
if (!title) return undefined;
|
|
245
225
|
return parseCursorReplayActivityDetails(
|
|
246
226
|
record,
|
|
@@ -249,31 +229,23 @@ function parseActivityVariantDetails(record: Record<string, unknown>): CursorRep
|
|
|
249
229
|
);
|
|
250
230
|
}
|
|
251
231
|
|
|
252
|
-
const CURRENT_REPLAY_VARIANT_PARSERS: Readonly<Record<CursorReplayToolDetailsVariant, CursorReplayVariantParser>> = {
|
|
253
|
-
nativeEdit: parseCursorReplayNativeEditDetails,
|
|
254
|
-
nativeWrite: parseCursorReplayNativeWriteDetails,
|
|
255
|
-
generateImage: parseCursorReplayGenerateImageDetails,
|
|
256
|
-
activity: parseActivityVariantDetails,
|
|
257
|
-
genericFallback: (record) => parseCursorReplayGenericFallbackDetails(record, readSourceToolName(record) ?? "tool"),
|
|
258
|
-
};
|
|
259
|
-
|
|
260
232
|
export function parseCursorReplayToolDetails(value: unknown): CursorReplayToolDetails | undefined {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
233
|
+
const record = asRecord(value);
|
|
234
|
+
if (!record) return undefined;
|
|
235
|
+
switch (readVariant(record)) {
|
|
236
|
+
case "nativeEdit":
|
|
237
|
+
return parseCursorReplayNativeEditDetails(record);
|
|
238
|
+
case "nativeWrite":
|
|
239
|
+
return parseCursorReplayNativeWriteDetails(record);
|
|
240
|
+
case "generateImage":
|
|
241
|
+
return parseCursorReplayGenerateImageDetails(record);
|
|
242
|
+
case "activity":
|
|
243
|
+
return parseActivityVariantDetails(record);
|
|
244
|
+
case "genericFallback":
|
|
245
|
+
return parseCursorReplayGenericFallbackDetails(record, readSourceToolName(record) ?? "tool");
|
|
246
|
+
default:
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
277
249
|
}
|
|
278
250
|
|
|
279
251
|
export function assembleCursorReplayActivityDetails(
|
|
@@ -7,6 +7,7 @@ import type { CursorPiToolBridgeDiagnosticEvent } from "./cursor-pi-tool-bridge-
|
|
|
7
7
|
import { serializeCursorPiToolBridgeDiagnostic } from "./cursor-pi-tool-bridge-diagnostics.js";
|
|
8
8
|
import type { CursorPiBridgeToolRequest } from "./cursor-pi-tool-bridge-types.js";
|
|
9
9
|
import type { CursorLiveQueuedEvent } from "./cursor-live-run-coordinator.js";
|
|
10
|
+
import { asRecord } from "./cursor-record-utils.js";
|
|
10
11
|
import { getCursorSessionFile } from "./cursor-session-scope.js";
|
|
11
12
|
import { parseEnvBoolean } from "./cursor-env-boolean.js";
|
|
12
13
|
import {
|
|
@@ -93,11 +94,10 @@ interface CursorSdkRunLike {
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
function eventType(value: unknown): string {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
97
|
+
const record = asRecord(value);
|
|
98
|
+
if (typeof record?.type === "string") return record.type;
|
|
99
|
+
if (typeof record?.event === "string") return record.event;
|
|
100
|
+
if (typeof record?.kind === "string") return record.kind;
|
|
101
101
|
return "unknown";
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -106,7 +106,7 @@ function resolveCursorSdkEventDebugStderrEnabled(env: Record<string, string | un
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
function isNodeErrorWithCode(error: unknown, code: string): boolean {
|
|
109
|
-
return
|
|
109
|
+
return asRecord(error)?.code === code;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
function snapshotCursorSdkEventDebugRecord(record: unknown): unknown {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { asRecord } from "./cursor-record-utils.js";
|
|
1
2
|
import type { CursorPiToolDisplay } from "./cursor-transcript-utils.js";
|
|
2
3
|
/** Provider-facing wrapper; canonical scrubbing lives in shared/cursor-sensitive-text.mjs. */
|
|
3
4
|
import { scrubSensitiveText as scrubSensitiveTextJs } from "../shared/cursor-sensitive-text.mjs";
|
|
@@ -9,10 +10,9 @@ export function scrubSensitiveText(text: string, apiKey?: string): string {
|
|
|
9
10
|
function scrubDisplayValue(value: unknown, apiKey?: string): unknown {
|
|
10
11
|
if (typeof value === "string") return scrubSensitiveText(value, apiKey);
|
|
11
12
|
if (Array.isArray(value)) return value.map((entry) => scrubDisplayValue(entry, apiKey));
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return value;
|
|
13
|
+
const record = asRecord(value);
|
|
14
|
+
if (!record) return value;
|
|
15
|
+
return Object.fromEntries(Object.entries(record).map(([key, entry]) => [key, scrubDisplayValue(entry, apiKey)]));
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function scrubPiToolDisplay(display: CursorPiToolDisplay, apiKey?: string): CursorPiToolDisplay {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionHandler,
|
|
3
|
+
SessionBeforeTreeEvent,
|
|
4
|
+
SessionCompactEvent,
|
|
5
|
+
SessionShutdownEvent,
|
|
6
|
+
SessionTreeEvent,
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { onCursorSessionScopeKeyChange } from "./cursor-session-scope.js";
|
|
9
|
+
|
|
10
|
+
export interface CursorSessionAgentLifecycleExtensionApi {
|
|
11
|
+
on(event: "session_shutdown", handler: ExtensionHandler<SessionShutdownEvent>): void;
|
|
12
|
+
on(event: "session_compact", handler: ExtensionHandler<SessionCompactEvent>): void;
|
|
13
|
+
on(event: "session_before_tree", handler: ExtensionHandler<SessionBeforeTreeEvent>): void;
|
|
14
|
+
on(event: "session_tree", handler: ExtensionHandler<SessionTreeEvent>): void;
|
|
15
|
+
on(event: "model_select", handler: () => Promise<void> | void): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function registerCursorSessionAgentLifecycle(pi: CursorSessionAgentLifecycleExtensionApi): void {
|
|
19
|
+
onCursorSessionScopeKeyChange(async (previousScopeKey) => {
|
|
20
|
+
const { disposeSessionCursorAgent } = await import("./cursor-session-agent.js");
|
|
21
|
+
await disposeSessionCursorAgent(previousScopeKey);
|
|
22
|
+
});
|
|
23
|
+
pi.on("session_shutdown", async (event) => {
|
|
24
|
+
const { disposeSessionCursorAgent, resetSessionCursorAgent } = await import("./cursor-session-agent.js");
|
|
25
|
+
if (event.reason === "reload") {
|
|
26
|
+
await resetSessionCursorAgent();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await disposeSessionCursorAgent();
|
|
30
|
+
});
|
|
31
|
+
pi.on("session_compact", async () => {
|
|
32
|
+
const { invalidateSessionAgent } = await import("./cursor-session-agent.js");
|
|
33
|
+
invalidateSessionAgent();
|
|
34
|
+
});
|
|
35
|
+
pi.on("session_before_tree", async () => {
|
|
36
|
+
const { invalidateSessionAgent } = await import("./cursor-session-agent.js");
|
|
37
|
+
invalidateSessionAgent();
|
|
38
|
+
});
|
|
39
|
+
pi.on("session_tree", async () => {
|
|
40
|
+
const { resetSessionCursorAgent } = await import("./cursor-session-agent.js");
|
|
41
|
+
await resetSessionCursorAgent();
|
|
42
|
+
});
|
|
43
|
+
pi.on("model_select", async () => {
|
|
44
|
+
const { invalidateSessionAgent } = await import("./cursor-session-agent.js");
|
|
45
|
+
invalidateSessionAgent();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExtensionHandler,
|
|
3
|
-
SessionBeforeTreeEvent,
|
|
4
|
-
SessionCompactEvent,
|
|
5
|
-
SessionShutdownEvent,
|
|
6
|
-
SessionTreeEvent,
|
|
7
|
-
} from "@earendil-works/pi-coding-agent";
|
|
8
1
|
import { createHash } from "node:crypto";
|
|
9
2
|
import type { AgentModeOption, ModelSelection, SDKAgent, SettingSource } from "@cursor/sdk";
|
|
10
3
|
import type { Context } from "@earendil-works/pi-ai";
|
|
@@ -14,7 +7,7 @@ import {
|
|
|
14
7
|
type CursorPiToolBridgeRun,
|
|
15
8
|
} from "./cursor-pi-tool-bridge.js";
|
|
16
9
|
import { computeCursorContextFingerprint } from "./context.js";
|
|
17
|
-
import {
|
|
10
|
+
import { getCursorSessionScopeGeneration, getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
18
11
|
import type { CursorSdkEventDebugRecorder } from "./cursor-sdk-event-debug.js";
|
|
19
12
|
import { loadCursorSdk, type CursorSdkModule } from "./cursor-sdk-runtime.js";
|
|
20
13
|
|
|
@@ -88,9 +81,12 @@ export class SessionCursorAgentScopeClosedError extends Error {
|
|
|
88
81
|
}
|
|
89
82
|
|
|
90
83
|
function assertScopeAcceptsAcquire(scopeKey: string): void {
|
|
91
|
-
|
|
84
|
+
const terminalGeneration = terminalDisposedScopeGenerations.get(scopeKey);
|
|
85
|
+
if (terminalGeneration === undefined) return;
|
|
86
|
+
if (terminalGeneration >= getCursorSessionScopeGeneration(scopeKey)) {
|
|
92
87
|
throw new SessionCursorAgentScopeClosedError();
|
|
93
88
|
}
|
|
89
|
+
terminalDisposedScopeGenerations.delete(scopeKey);
|
|
94
90
|
}
|
|
95
91
|
|
|
96
92
|
function rethrowSupersededWhenReplacedByDifferentPoolKey(scopeKey: string, poolKey: string, error: unknown): void {
|
|
@@ -112,17 +108,9 @@ interface SessionCursorAgentCreateParams {
|
|
|
112
108
|
createAgent?: CursorSdkModule["Agent"]["create"];
|
|
113
109
|
}
|
|
114
110
|
|
|
115
|
-
interface CursorSessionAgentExtensionApi {
|
|
116
|
-
on(event: "session_shutdown", handler: ExtensionHandler<SessionShutdownEvent>): void;
|
|
117
|
-
on(event: "session_compact", handler: ExtensionHandler<SessionCompactEvent>): void;
|
|
118
|
-
on(event: "session_before_tree", handler: ExtensionHandler<SessionBeforeTreeEvent>): void;
|
|
119
|
-
on(event: "session_tree", handler: ExtensionHandler<SessionTreeEvent>): void;
|
|
120
|
-
on(event: "model_select", handler: ExtensionHandler<{ model: unknown }>): void;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
111
|
const sessionAgentsByScope = new Map<string, SessionCursorAgentPoolEntry>();
|
|
124
112
|
const invalidatedScopeKeys = new Set<string>();
|
|
125
|
-
const
|
|
113
|
+
const terminalDisposedScopeGenerations = new Map<string, number>();
|
|
126
114
|
const scopeCreationGenerations = new Map<string, number>();
|
|
127
115
|
const EMPTY_POOL_STATE: SessionCursorAgentPoolState = { status: "empty" };
|
|
128
116
|
let nextSessionAgentInstanceId = 1;
|
|
@@ -194,7 +182,7 @@ async function disposePoolEntry(entry: SessionCursorAgentPoolEntry): Promise<voi
|
|
|
194
182
|
async function disposePoolEntryForScope(scopeKey: string, options?: { terminal?: boolean }): Promise<void> {
|
|
195
183
|
invalidateScopeCreations(scopeKey);
|
|
196
184
|
if (options?.terminal) {
|
|
197
|
-
|
|
185
|
+
terminalDisposedScopeGenerations.set(scopeKey, getCursorSessionScopeGeneration(scopeKey));
|
|
198
186
|
}
|
|
199
187
|
const entry = sessionAgentsByScope.get(scopeKey);
|
|
200
188
|
invalidatedScopeKeys.delete(scopeKey);
|
|
@@ -416,7 +404,6 @@ export {
|
|
|
416
404
|
planCursorSessionSend,
|
|
417
405
|
type CursorSessionSendPlan,
|
|
418
406
|
} from "./cursor-session-send-policy.js";
|
|
419
|
-
export { shouldBootstrapCursorContext, shouldBootstrapCursorSend } from "./context.js";
|
|
420
407
|
|
|
421
408
|
export function invalidateSessionAgent(scopeKey: string = getCursorSessionScopeKey()): void {
|
|
422
409
|
invalidatedScopeKeys.add(scopeKey);
|
|
@@ -526,35 +513,10 @@ export async function disposeSessionCursorAgent(scopeKey: string = getCursorSess
|
|
|
526
513
|
}
|
|
527
514
|
|
|
528
515
|
export async function disposeAllSessionCursorAgents(): Promise<void> {
|
|
529
|
-
const scopeKeys = [...new Set([...sessionAgentsByScope.keys(), ...
|
|
516
|
+
const scopeKeys = [...new Set([...sessionAgentsByScope.keys(), ...terminalDisposedScopeGenerations.keys()])];
|
|
530
517
|
await Promise.all(scopeKeys.map((scopeKey) => disposePoolEntryForScope(scopeKey, { terminal: true })));
|
|
531
518
|
invalidatedScopeKeys.clear();
|
|
532
|
-
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
export function registerCursorSessionAgent(_pi: CursorSessionAgentExtensionApi): void {
|
|
536
|
-
onCursorSessionScopeKeyChange((previousScopeKey) => {
|
|
537
|
-
void disposePoolEntryForScope(previousScopeKey, { terminal: true });
|
|
538
|
-
});
|
|
539
|
-
_pi.on("session_shutdown", async (event) => {
|
|
540
|
-
if (event.reason === "reload") {
|
|
541
|
-
await resetSessionCursorAgent();
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
await disposeSessionCursorAgent();
|
|
545
|
-
});
|
|
546
|
-
_pi.on("session_compact", () => {
|
|
547
|
-
invalidateSessionAgent();
|
|
548
|
-
});
|
|
549
|
-
_pi.on("session_before_tree", () => {
|
|
550
|
-
invalidateSessionAgent();
|
|
551
|
-
});
|
|
552
|
-
_pi.on("session_tree", async () => {
|
|
553
|
-
await resetSessionCursorAgent();
|
|
554
|
-
});
|
|
555
|
-
_pi.on("model_select", () => {
|
|
556
|
-
invalidateSessionAgent();
|
|
557
|
-
});
|
|
519
|
+
terminalDisposedScopeGenerations.clear();
|
|
558
520
|
}
|
|
559
521
|
|
|
560
522
|
export const __testUtils = {
|
|
@@ -7,14 +7,17 @@ interface CursorSessionScopeExtensionApi {
|
|
|
7
7
|
const ANONYMOUS_SESSION_SCOPE_KEY = "__anonymous__";
|
|
8
8
|
const EPHEMERAL_SESSION_SCOPE_PREFIX = "__ephemeral__:";
|
|
9
9
|
|
|
10
|
-
type CursorSessionScopeChangeHandler = (previousScopeKey: string) => void;
|
|
10
|
+
type CursorSessionScopeChangeHandler = (previousScopeKey: string) => Promise<void> | void;
|
|
11
11
|
|
|
12
12
|
const state = {
|
|
13
13
|
sessionCwd: process.cwd(),
|
|
14
14
|
sessionFile: undefined as string | undefined,
|
|
15
15
|
sessionId: undefined as string | undefined,
|
|
16
|
+
sessionGeneration: 0,
|
|
16
17
|
};
|
|
17
18
|
|
|
19
|
+
const scopeGenerations = new Map<string, number>([[ANONYMOUS_SESSION_SCOPE_KEY, state.sessionGeneration]]);
|
|
20
|
+
let nextSessionGeneration = 1;
|
|
18
21
|
let scopeChangeHandler: CursorSessionScopeChangeHandler | undefined;
|
|
19
22
|
|
|
20
23
|
/**
|
|
@@ -34,7 +37,16 @@ export function getCursorSessionScopeKey(): string {
|
|
|
34
37
|
return ANONYMOUS_SESSION_SCOPE_KEY;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
export function
|
|
40
|
+
export function getCursorSessionScopeGeneration(scopeKey: string = getCursorSessionScopeKey()): number {
|
|
41
|
+
return scopeGenerations.get(scopeKey) ?? 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Pi session cwd when known; falls back to process.cwd() before session_start.
|
|
46
|
+
* Updated on session_start only until pi threads cwd into streamSimple—mid-session cwd
|
|
47
|
+
* changes without a new session_start event are not reflected here.
|
|
48
|
+
*/
|
|
49
|
+
export function getCursorSessionCwd(): string {
|
|
38
50
|
return state.sessionCwd;
|
|
39
51
|
}
|
|
40
52
|
|
|
@@ -42,12 +54,19 @@ function setCursorSessionScope(cwd: string, sessionFile: string | undefined, ses
|
|
|
42
54
|
state.sessionCwd = cwd;
|
|
43
55
|
state.sessionFile = sessionFile;
|
|
44
56
|
state.sessionId = sessionId;
|
|
57
|
+
state.sessionGeneration = nextSessionGeneration;
|
|
58
|
+
nextSessionGeneration += 1;
|
|
59
|
+
scopeGenerations.set(getCursorSessionScopeKey(), state.sessionGeneration);
|
|
45
60
|
}
|
|
46
61
|
|
|
47
62
|
function resetCursorSessionScope(): void {
|
|
48
63
|
state.sessionCwd = process.cwd();
|
|
49
64
|
state.sessionFile = undefined;
|
|
50
65
|
state.sessionId = undefined;
|
|
66
|
+
state.sessionGeneration = 0;
|
|
67
|
+
nextSessionGeneration = 1;
|
|
68
|
+
scopeGenerations.clear();
|
|
69
|
+
scopeGenerations.set(ANONYMOUS_SESSION_SCOPE_KEY, state.sessionGeneration);
|
|
51
70
|
}
|
|
52
71
|
|
|
53
72
|
export function onCursorSessionScopeKeyChange(handler: CursorSessionScopeChangeHandler): void {
|
|
@@ -55,7 +74,7 @@ export function onCursorSessionScopeKeyChange(handler: CursorSessionScopeChangeH
|
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
export function registerCursorSessionScope(pi: CursorSessionScopeExtensionApi): void {
|
|
58
|
-
pi.on("session_start", (_event, ctx) => {
|
|
77
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
59
78
|
const previousScopeKey = getCursorSessionScopeKey();
|
|
60
79
|
setCursorSessionScope(
|
|
61
80
|
ctx.cwd,
|
|
@@ -63,7 +82,7 @@ export function registerCursorSessionScope(pi: CursorSessionScopeExtensionApi):
|
|
|
63
82
|
ctx.sessionManager?.getSessionId?.() ?? undefined,
|
|
64
83
|
);
|
|
65
84
|
if (previousScopeKey !== getCursorSessionScopeKey()) {
|
|
66
|
-
scopeChangeHandler?.(previousScopeKey);
|
|
85
|
+
await scopeChangeHandler?.(previousScopeKey);
|
|
67
86
|
}
|
|
68
87
|
});
|
|
69
88
|
}
|