pi-cursor-sdk 0.1.20 → 0.1.21
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 +32 -0
- package/README.md +49 -9
- package/docs/cursor-dogfood-checklist.md +57 -0
- package/docs/cursor-live-smoke-checklist.md +115 -9
- package/docs/cursor-model-ux-spec.md +57 -17
- package/docs/cursor-native-tool-replay.md +15 -7
- package/docs/cursor-native-tool-visual-audit.md +104 -59
- package/docs/cursor-testing-lessons.md +8 -3
- package/docs/cursor-tool-surfaces.md +69 -0
- package/package.json +34 -10
- package/scripts/debug-provider-events.d.mts +59 -0
- package/scripts/debug-provider-events.mjs +70 -175
- package/scripts/debug-sdk-events.d.mts +90 -0
- package/scripts/debug-sdk-events.mjs +36 -98
- package/scripts/fixtures/plan-strip-shim/index.ts +12 -0
- package/scripts/isolated-cursor-smoke.sh +264 -102
- package/scripts/lib/cursor-child-process.d.mts +10 -0
- package/scripts/lib/cursor-child-process.mjs +50 -0
- package/scripts/lib/cursor-cli-args.d.mts +63 -0
- package/scripts/lib/cursor-cli-args.mjs +129 -0
- package/scripts/lib/cursor-script-fail.d.mts +1 -0
- package/scripts/lib/cursor-script-fail.mjs +13 -0
- package/scripts/lib/cursor-sdk-output-filter.d.mts +5 -0
- package/scripts/lib/cursor-smoke-env.d.mts +38 -0
- package/scripts/lib/cursor-smoke-env.mjs +81 -0
- package/scripts/lib/cursor-smoke-shell.sh +174 -0
- package/scripts/lib/cursor-visual-render.d.mts +15 -0
- package/scripts/lib/cursor-visual-render.mjs +131 -0
- package/scripts/probe-mcp-coldstart.mjs +20 -38
- package/scripts/refresh-cursor-model-snapshots.mjs +29 -65
- package/scripts/steering-rpc-smoke.mjs +170 -65
- package/scripts/tmux-live-smoke.sh +152 -98
- package/scripts/visual-tui-smoke.mjs +659 -0
- package/shared/cursor-sdk-event-debug-env.d.mts +12 -0
- package/shared/cursor-sdk-event-debug-env.mjs +13 -0
- package/shared/cursor-sensitive-text.d.mts +1 -0
- package/{scripts/lib/cursor-probe-utils.mjs → shared/cursor-sensitive-text.mjs} +1 -13
- package/shared/cursor-setting-sources.d.mts +5 -0
- package/shared/cursor-setting-sources.mjs +22 -0
- package/src/context.ts +21 -12
- package/src/cursor-bridge-contract.ts +1 -3
- package/src/cursor-incomplete-tool-visibility.ts +22 -5
- package/src/cursor-native-tool-display-registration.ts +63 -27
- package/src/cursor-native-tool-display-replay.ts +246 -144
- package/src/cursor-native-tool-display-state.ts +2 -0
- package/src/cursor-native-tool-display-tools.ts +149 -41
- package/src/cursor-provider-live-run-drain.ts +1 -52
- package/src/cursor-provider-run-finalizer.ts +235 -0
- package/src/cursor-provider-run-outcome.ts +149 -0
- package/src/cursor-provider-turn-api-key.ts +8 -0
- package/src/cursor-provider-turn-coordinator.ts +98 -446
- package/src/cursor-provider-turn-display-router.ts +216 -0
- package/src/cursor-provider-turn-emit.ts +59 -0
- package/src/cursor-provider-turn-finalize.ts +119 -0
- package/src/cursor-provider-turn-lifecycle-emitter.ts +97 -0
- package/src/cursor-provider-turn-message-offset.ts +15 -0
- package/src/cursor-provider-turn-prepare.ts +216 -0
- package/src/cursor-provider-turn-runner.ts +138 -0
- package/src/cursor-provider-turn-sdk-normalizer.ts +88 -0
- package/src/cursor-provider-turn-send.ts +103 -0
- package/src/cursor-provider-turn-shell-output.ts +107 -0
- package/src/cursor-provider-turn-tool-ledger.ts +126 -0
- package/src/cursor-provider-turn-types.ts +87 -0
- package/src/cursor-provider.ts +16 -504
- package/src/cursor-replay-activity-builders.ts +276 -0
- package/src/cursor-replay-source-names.ts +33 -0
- package/src/cursor-replay-summary-args.ts +191 -0
- package/src/cursor-replay-tool-details.ts +464 -0
- package/src/cursor-run-final-text.ts +56 -0
- package/src/cursor-sdk-abort-error-guard.ts +4 -0
- package/src/cursor-sdk-event-debug-constants.ts +14 -5
- package/src/cursor-sdk-event-debug.ts +2 -1
- package/src/cursor-sensitive-text.ts +3 -36
- package/src/cursor-session-agent.ts +3 -1
- package/src/cursor-setting-sources.ts +7 -10
- package/src/cursor-state.ts +232 -28
- package/src/cursor-tool-lifecycle.ts +9 -8
- package/src/cursor-tool-manifest.ts +41 -0
- package/src/cursor-tool-names.ts +18 -106
- package/src/cursor-tool-presentation-registry.ts +556 -0
- package/src/cursor-tool-transcript.ts +1 -1
- package/src/cursor-tool-visibility.ts +3 -27
- package/src/cursor-transcript-tool-formatters.ts +0 -59
- package/src/cursor-transcript-tool-specs.ts +158 -233
- package/src/cursor-transcript-utils.ts +0 -44
- package/src/cursor-web-tool-activity.ts +10 -60
- package/src/cursor-web-tool-args.ts +39 -0
- package/src/index.ts +4 -10
|
@@ -7,10 +7,47 @@ import { resolveCursorEditDiff } from "./cursor-edit-diff.js";
|
|
|
7
7
|
import { LOCAL_READ_PREVIEW_NOTICE, isLocalReadPreviewContent } from "./cursor-transcript-utils.js";
|
|
8
8
|
import {
|
|
9
9
|
CURSOR_REPLAY_ACTIVITY_TOOL_NAME,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
getCursorReplayCallSummary,
|
|
11
|
+
getCursorReplaySideEffectDescription,
|
|
12
|
+
getCursorReplayOperationLabel,
|
|
13
|
+
getCursorReplayWrapperLabel,
|
|
12
14
|
type CursorReplayToolName,
|
|
13
|
-
} from "./cursor-tool-
|
|
15
|
+
} from "./cursor-tool-presentation-registry.js";
|
|
16
|
+
import {
|
|
17
|
+
CURSOR_REPLAY_GENERATE_IMAGE_RESULT_TITLE,
|
|
18
|
+
type CursorReplayNativeEditDetails,
|
|
19
|
+
type CursorReplayGenerateImageDetails,
|
|
20
|
+
type CursorReplayActivityDetails,
|
|
21
|
+
type CursorReplayToolDetails,
|
|
22
|
+
type CursorReplayNativeWriteDetails,
|
|
23
|
+
isCursorReplayNativeEditDetails,
|
|
24
|
+
isCursorReplayGenerateImageDetails,
|
|
25
|
+
isCursorReplayActivityDetails,
|
|
26
|
+
parseCursorReplayToolDetails,
|
|
27
|
+
} from "./cursor-replay-tool-details.js";
|
|
28
|
+
|
|
29
|
+
export type {
|
|
30
|
+
CursorReplayNativeEditDetails,
|
|
31
|
+
CursorReplayEditDetails,
|
|
32
|
+
CursorReplayGenerateImageDetails,
|
|
33
|
+
CursorReplayGenericFallbackDetails,
|
|
34
|
+
CursorReplayActivityDetails,
|
|
35
|
+
CursorReplayTitledActivityDetails,
|
|
36
|
+
CursorReplayToolDetails,
|
|
37
|
+
CursorReplayNativeWriteDetails,
|
|
38
|
+
CursorReplayWriteDetails,
|
|
39
|
+
} from "./cursor-replay-tool-details.js";
|
|
40
|
+
export {
|
|
41
|
+
asCursorReplayToolDetails,
|
|
42
|
+
isCursorReplayNativeEditDetails,
|
|
43
|
+
isCursorReplayEditDetails,
|
|
44
|
+
isCursorReplayGenerateImageDetails,
|
|
45
|
+
isCursorReplayActivityDetails,
|
|
46
|
+
isCursorReplayTitledActivityDetails,
|
|
47
|
+
isCursorReplayNativeWriteDetails,
|
|
48
|
+
isCursorReplayWriteDetails,
|
|
49
|
+
parseCursorReplayToolDetails,
|
|
50
|
+
} from "./cursor-replay-tool-details.js";
|
|
14
51
|
|
|
15
52
|
export const CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES = 8;
|
|
16
53
|
export const CURSOR_REPLAY_PREVIEW_MAX_CHARS = 4000;
|
|
@@ -18,30 +55,6 @@ export const CURSOR_REPLAY_PREVIEW_MAX_LINE_CHARS = 240;
|
|
|
18
55
|
const CURSOR_REPLAY_HIGHLIGHT_MAX_CHARS = 12000;
|
|
19
56
|
export const cursorReplayToolSchema = Type.Object({}, { additionalProperties: true });
|
|
20
57
|
|
|
21
|
-
export interface CursorReplayToolDetails {
|
|
22
|
-
cursorToolName?: string;
|
|
23
|
-
title?: string;
|
|
24
|
-
summary?: string;
|
|
25
|
-
path?: string;
|
|
26
|
-
imagePath?: string;
|
|
27
|
-
imageDisplayPath?: string;
|
|
28
|
-
imageMimeType?: string;
|
|
29
|
-
linesAdded?: number;
|
|
30
|
-
linesRemoved?: number;
|
|
31
|
-
linesCreated?: number;
|
|
32
|
-
fileSize?: number;
|
|
33
|
-
fileContentAfterWrite?: string;
|
|
34
|
-
diffString?: string;
|
|
35
|
-
diff?: string;
|
|
36
|
-
firstChangedLine?: number;
|
|
37
|
-
expandedText?: string;
|
|
38
|
-
collapseDetailsByDefault?: boolean;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function asCursorReplayToolDetails(value: unknown): CursorReplayToolDetails | undefined {
|
|
42
|
-
return value && typeof value === "object" ? (value as CursorReplayToolDetails) : undefined;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
58
|
type CursorReplayRenderCall = NonNullable<ToolDefinition<typeof cursorReplayToolSchema, unknown>["renderCall"]>;
|
|
46
59
|
type CursorReplayRenderResult = NonNullable<ToolDefinition<typeof cursorReplayToolSchema, unknown>["renderResult"]>;
|
|
47
60
|
export type CursorReplayRenderTheme = Parameters<CursorReplayRenderCall>[1];
|
|
@@ -87,13 +100,10 @@ function buildImageReplayComponent(text: string, imageData: string, mimeType: st
|
|
|
87
100
|
};
|
|
88
101
|
}
|
|
89
102
|
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function getCursorReplayPath(args: Record<string, unknown> | undefined, details: CursorReplayToolDetails | undefined): string {
|
|
103
|
+
export function getCursorReplayPath(
|
|
104
|
+
args: Record<string, unknown> | undefined,
|
|
105
|
+
details: Pick<CursorReplayNativeEditDetails, "path"> | Pick<CursorReplayNativeWriteDetails, "path"> | undefined,
|
|
106
|
+
): string {
|
|
97
107
|
const argPath = args?.path;
|
|
98
108
|
return details?.path ?? (typeof argPath === "string" && argPath.trim() ? argPath : "unknown");
|
|
99
109
|
}
|
|
@@ -212,6 +222,93 @@ function formatMutedBlock(text: string, theme: CursorReplayRenderTheme): string
|
|
|
212
222
|
return text.split("\n").map((line) => theme.fg("muted", line)).join("\n");
|
|
213
223
|
}
|
|
214
224
|
|
|
225
|
+
function hasUnifiedDiffHunk(text: string): boolean {
|
|
226
|
+
return text.split("\n").some((line) => Boolean(parseUnifiedDiffHunkHeader(line)));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** First unified-diff marker (`---`/`+++`/`@@`) so collapsed previews budget diff lines, not transcript preamble. */
|
|
230
|
+
function extractUnifiedDiffSection(text: string): string | undefined {
|
|
231
|
+
const lines = text.split("\n");
|
|
232
|
+
const markerIndex = lines.findIndex((line) => line.startsWith("--- ") || line.startsWith("+++ "));
|
|
233
|
+
const hunkIndex = lines.findIndex((line) => Boolean(parseUnifiedDiffHunkHeader(line)));
|
|
234
|
+
const start = markerIndex >= 0 ? markerIndex : hunkIndex;
|
|
235
|
+
if (start < 0) return undefined;
|
|
236
|
+
return lines.slice(start).join("\n");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function formatCursorReplayActivityDiffPreview(
|
|
240
|
+
text: string,
|
|
241
|
+
theme: CursorReplayRenderTheme,
|
|
242
|
+
maxLines: number,
|
|
243
|
+
stripHeader: boolean,
|
|
244
|
+
): string | undefined {
|
|
245
|
+
const body = (stripHeader ? stripCursorReplayHeader(text) : text).trimEnd();
|
|
246
|
+
const diffSection = body ? extractUnifiedDiffSection(body) : undefined;
|
|
247
|
+
if (!diffSection || !hasUnifiedDiffHunk(diffSection)) return undefined;
|
|
248
|
+
// Legacy-only shim (old JSONL with no diffString on the activity details).
|
|
249
|
+
// All actual diff coloring now lives in the single `formatCursorReplayDiff` renderer.
|
|
250
|
+
// This path exists solely so pre-structured sessions still get colored diffs via the same
|
|
251
|
+
// high-quality implementation used for nativeEdit and new structured activity cases.
|
|
252
|
+
return formatCursorReplayDiff(diffSection, theme, maxLines);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function formatCursorReplayActivityEditPreview(
|
|
256
|
+
details: CursorReplayExpandableResultDetails,
|
|
257
|
+
text: string,
|
|
258
|
+
theme: CursorReplayRenderTheme,
|
|
259
|
+
maxLines: number,
|
|
260
|
+
stripHeader: boolean,
|
|
261
|
+
): string | undefined {
|
|
262
|
+
const structuredDiff = details.diffString ?? details.diff;
|
|
263
|
+
if (structuredDiff) {
|
|
264
|
+
return formatCursorReplayDiff(structuredDiff, theme, maxLines);
|
|
265
|
+
}
|
|
266
|
+
const diffPreview = formatCursorReplayActivityDiffPreview(text, theme, maxLines, stripHeader);
|
|
267
|
+
if (diffPreview) return diffPreview;
|
|
268
|
+
return stripHeader ? formatCursorReplayPreview(text, theme, maxLines, true) : formatMutedBlock(text, theme);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function formatCursorReplayActivityWritePreview(
|
|
272
|
+
details: CursorReplayExpandableResultDetails,
|
|
273
|
+
text: string,
|
|
274
|
+
theme: CursorReplayRenderTheme,
|
|
275
|
+
maxLines: number,
|
|
276
|
+
stripHeader: boolean,
|
|
277
|
+
): string | undefined {
|
|
278
|
+
const structuredDiff = details.diffString ?? details.diff;
|
|
279
|
+
if (structuredDiff) {
|
|
280
|
+
return formatCursorReplayDiff(structuredDiff, theme, maxLines);
|
|
281
|
+
}
|
|
282
|
+
if (details.fileContentAfterWrite) {
|
|
283
|
+
return formatCursorReplayFilePreview(
|
|
284
|
+
details.fileContentAfterWrite,
|
|
285
|
+
details.path,
|
|
286
|
+
theme,
|
|
287
|
+
maxLines,
|
|
288
|
+
false,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
const diffPreview = formatCursorReplayActivityDiffPreview(text, theme, maxLines, stripHeader);
|
|
292
|
+
if (diffPreview) return diffPreview;
|
|
293
|
+
return stripHeader ? formatCursorReplayPreview(text, theme, maxLines, true) : formatMutedBlock(text, theme);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function formatCursorReplayActivityPreview(
|
|
297
|
+
details: CursorReplayExpandableResultDetails,
|
|
298
|
+
text: string,
|
|
299
|
+
theme: CursorReplayRenderTheme,
|
|
300
|
+
maxLines: number,
|
|
301
|
+
stripHeader: boolean,
|
|
302
|
+
): string | undefined {
|
|
303
|
+
if (details.sourceToolName === "edit") {
|
|
304
|
+
return formatCursorReplayActivityEditPreview(details, text, theme, maxLines, stripHeader);
|
|
305
|
+
}
|
|
306
|
+
if (details.sourceToolName === "write") {
|
|
307
|
+
return formatCursorReplayActivityWritePreview(details, text, theme, maxLines, stripHeader);
|
|
308
|
+
}
|
|
309
|
+
return stripHeader ? formatCursorReplayPreview(text, theme, maxLines, true) : formatMutedBlock(text, theme);
|
|
310
|
+
}
|
|
311
|
+
|
|
215
312
|
export function formatCursorReplayPreview(
|
|
216
313
|
text: string,
|
|
217
314
|
theme: CursorReplayRenderTheme,
|
|
@@ -257,69 +354,7 @@ function getCursorReplayActivityTitle(toolName: CursorReplayToolName, args: Reco
|
|
|
257
354
|
if (toolName === CURSOR_REPLAY_ACTIVITY_TOOL_NAME && typeof args?.activityTitle === "string" && args.activityTitle.trim()) {
|
|
258
355
|
return args.activityTitle.trim();
|
|
259
356
|
}
|
|
260
|
-
return
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function formatReplayRecordingDurationMs(ms: number | undefined): string | undefined {
|
|
264
|
-
if (ms === undefined || !Number.isFinite(ms) || ms < 0) return undefined;
|
|
265
|
-
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
266
|
-
const seconds = ms / 1000;
|
|
267
|
-
return seconds < 60 ? `${seconds.toFixed(1)}s` : `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function formatReplaySemSearchQuery(args: Record<string, unknown> | undefined): string | undefined {
|
|
271
|
-
const query = typeof args?.query === "string" ? args.query.trim() : undefined;
|
|
272
|
-
if (!query) return undefined;
|
|
273
|
-
const targetDirectories = Array.isArray(args?.targetDirectories)
|
|
274
|
-
? args.targetDirectories.filter((entry): entry is string => typeof entry === "string")
|
|
275
|
-
: [];
|
|
276
|
-
const dirHint =
|
|
277
|
-
targetDirectories.length > 0 ? ` (${targetDirectories.length} dir${targetDirectories.length === 1 ? "" : "s"})` : "";
|
|
278
|
-
return `${query}${dirHint}`;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function getCursorReplayCallSummary(toolName: CursorReplayToolName, args: Record<string, unknown> | undefined): string | undefined {
|
|
282
|
-
const activitySummary = typeof args?.activitySummary === "string" && args.activitySummary.trim() ? args.activitySummary.trim() : undefined;
|
|
283
|
-
if (activitySummary) return activitySummary;
|
|
284
|
-
|
|
285
|
-
const path = typeof args?.path === "string" ? args.path : undefined;
|
|
286
|
-
const description = typeof args?.description === "string" ? args.description : undefined;
|
|
287
|
-
const prompt = typeof args?.prompt === "string" ? args.prompt : undefined;
|
|
288
|
-
const totalCount = typeof args?.totalCount === "number" ? args.totalCount : undefined;
|
|
289
|
-
const diagnosticCount = typeof args?.diagnosticCount === "number" ? args.diagnosticCount : undefined;
|
|
290
|
-
const paths = Array.isArray(args?.paths) ? args.paths.filter((entry): entry is string => typeof entry === "string") : [];
|
|
291
|
-
|
|
292
|
-
if (toolName === "cursor_edit" || toolName === "cursor_write" || toolName === "cursor_delete") return path ?? "unknown";
|
|
293
|
-
if (toolName === "cursor_read_lints") {
|
|
294
|
-
const target = paths.length > 0 ? paths.join(", ") : path;
|
|
295
|
-
if (target && diagnosticCount !== undefined) {
|
|
296
|
-
return `${diagnosticCount} diagnostic${diagnosticCount === 1 ? "" : "s"} in ${target}`;
|
|
297
|
-
}
|
|
298
|
-
return target;
|
|
299
|
-
}
|
|
300
|
-
if (toolName === "cursor_update_todos" || toolName === "cursor_create_plan") {
|
|
301
|
-
return totalCount !== undefined ? `${totalCount} item${totalCount === 1 ? "" : "s"}` : undefined;
|
|
302
|
-
}
|
|
303
|
-
if (toolName === "cursor_task") return description;
|
|
304
|
-
if (toolName === "cursor_generate_image") return path ?? prompt;
|
|
305
|
-
if (toolName === "cursor_mcp") return typeof args?.toolName === "string" ? args.toolName : undefined;
|
|
306
|
-
if (toolName === "cursor_sem_search") return formatReplaySemSearchQuery(args);
|
|
307
|
-
if (toolName === "cursor_record_screen") {
|
|
308
|
-
const duration = formatReplayRecordingDurationMs(
|
|
309
|
-
typeof args?.recordingDurationMs === "number" ? args.recordingDurationMs : undefined,
|
|
310
|
-
);
|
|
311
|
-
if (path && duration) return `${path} · ${duration}`;
|
|
312
|
-
if (path) return path;
|
|
313
|
-
if (typeof args?.mode === "string") return args.mode;
|
|
314
|
-
}
|
|
315
|
-
if (toolName === "cursor_web_search") return typeof args?.query === "string" ? args.query : undefined;
|
|
316
|
-
if (toolName === "cursor_web_fetch") return typeof args?.url === "string" ? args.url : undefined;
|
|
317
|
-
if (toolName === CURSOR_REPLAY_ACTIVITY_TOOL_NAME) {
|
|
318
|
-
if (path) return path;
|
|
319
|
-
if (typeof args?.toolName === "string") return args.toolName;
|
|
320
|
-
return formatReplaySemSearchQuery(args);
|
|
321
|
-
}
|
|
322
|
-
return undefined;
|
|
357
|
+
return getCursorReplayWrapperLabel(toolName);
|
|
323
358
|
}
|
|
324
359
|
|
|
325
360
|
export function renderCursorReplayCall(
|
|
@@ -361,15 +396,15 @@ function pluralize(count: number, noun: string): string {
|
|
|
361
396
|
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
362
397
|
}
|
|
363
398
|
|
|
364
|
-
function getCursorEditDiff(details:
|
|
399
|
+
function getCursorEditDiff(details: CursorReplayNativeEditDetails): string | undefined {
|
|
365
400
|
return resolveCursorEditDiff(details);
|
|
366
401
|
}
|
|
367
402
|
|
|
368
|
-
function hasCursorEditChanges(details:
|
|
403
|
+
function hasCursorEditChanges(details: CursorReplayNativeEditDetails): boolean {
|
|
369
404
|
return Boolean(getCursorEditDiff(details)) || Boolean(details.linesAdded) || Boolean(details.linesRemoved);
|
|
370
405
|
}
|
|
371
406
|
|
|
372
|
-
function classifyCursorEditOperation(details:
|
|
407
|
+
function classifyCursorEditOperation(details: CursorReplayNativeEditDetails): "created" | "deleted" | "updated" | "unchanged" {
|
|
373
408
|
if (!hasCursorEditChanges(details)) return "unchanged";
|
|
374
409
|
const diff = getCursorEditDiff(details);
|
|
375
410
|
if (diff?.startsWith("--- /dev/null")) return "created";
|
|
@@ -377,7 +412,7 @@ function classifyCursorEditOperation(details: CursorReplayToolDetails): "created
|
|
|
377
412
|
return "updated";
|
|
378
413
|
}
|
|
379
414
|
|
|
380
|
-
function formatCursorEditSummary(details:
|
|
415
|
+
function formatCursorEditSummary(details: CursorReplayNativeEditDetails): string {
|
|
381
416
|
const operation = classifyCursorEditOperation(details);
|
|
382
417
|
if (operation === "unchanged") return "no changes needed";
|
|
383
418
|
if (operation === "created" && details.linesAdded !== undefined) return `created ${pluralize(details.linesAdded, "line")}`;
|
|
@@ -394,24 +429,52 @@ function firstContentText(result: Parameters<CursorReplayRenderResult>[0]): stri
|
|
|
394
429
|
return content?.type === "text" ? content.text : "";
|
|
395
430
|
}
|
|
396
431
|
|
|
432
|
+
type CursorReplayExpandableResultDetails = {
|
|
433
|
+
summary?: string;
|
|
434
|
+
expandedText?: string;
|
|
435
|
+
collapseDetailsByDefault?: boolean;
|
|
436
|
+
imagePath?: string;
|
|
437
|
+
imageMimeType?: string;
|
|
438
|
+
sourceToolName?: CursorReplayActivityDetails["sourceToolName"];
|
|
439
|
+
path?: string;
|
|
440
|
+
/** Structured diff fields populated on activity edit/write details for canonical coloring (primary over text parse). */
|
|
441
|
+
diffString?: string;
|
|
442
|
+
diff?: string;
|
|
443
|
+
linesAdded?: number;
|
|
444
|
+
linesRemoved?: number;
|
|
445
|
+
/** Structured post-write content for activity write fallbacks; drives canonical file preview (mirrors nativeWrite). */
|
|
446
|
+
fileContentAfterWrite?: string;
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
function hasCursorReplayDisplayTitle(details: CursorReplayToolDetails | undefined): boolean {
|
|
450
|
+
if (!details) return false;
|
|
451
|
+
return isCursorReplayActivityDetails(details) || isCursorReplayGenerateImageDetails(details);
|
|
452
|
+
}
|
|
453
|
+
|
|
397
454
|
function renderExpandableCursorReplayResult(
|
|
398
455
|
title: string,
|
|
456
|
+
details: CursorReplayExpandableResultDetails,
|
|
399
457
|
result: Parameters<CursorReplayRenderResult>[0],
|
|
400
458
|
options: Parameters<CursorReplayRenderResult>[1],
|
|
401
459
|
theme: Parameters<CursorReplayRenderResult>[2],
|
|
402
460
|
context: Parameters<CursorReplayRenderResult>[3],
|
|
403
461
|
isError: boolean,
|
|
404
462
|
): Component {
|
|
405
|
-
const details = asCursorReplayToolDetails(result.details);
|
|
406
463
|
const text = firstContentText(result);
|
|
407
|
-
const summary = details
|
|
464
|
+
const summary = details.summary ?? text.split("\n").find((line) => line.trim()) ?? "completed";
|
|
408
465
|
let rendered = `${theme.fg("toolTitle", theme.bold(title))} ${theme.fg(isError ? "error" : "success", summary)}`;
|
|
409
|
-
const expandedText = details
|
|
410
|
-
if (expandedText && (options.expanded || !details
|
|
411
|
-
const preview =
|
|
466
|
+
const expandedText = details.expandedText ?? (text.includes("\n") ? text : undefined);
|
|
467
|
+
if (expandedText && (options.expanded || !details.collapseDetailsByDefault)) {
|
|
468
|
+
const preview = formatCursorReplayActivityPreview(
|
|
469
|
+
details,
|
|
470
|
+
expandedText,
|
|
471
|
+
theme,
|
|
472
|
+
options.expanded ? Number.POSITIVE_INFINITY : CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES,
|
|
473
|
+
!options.expanded,
|
|
474
|
+
);
|
|
412
475
|
if (preview) rendered += `\n${preview}`;
|
|
413
476
|
}
|
|
414
|
-
if (details
|
|
477
|
+
if (details.imagePath && !isError && context.showImages) {
|
|
415
478
|
const imageData = readImageFileForReplay(details.imagePath);
|
|
416
479
|
const mimeType = details.imageMimeType ?? inferImageMimeTypeFromPath(details.imagePath);
|
|
417
480
|
if (imageData && mimeType) return buildImageReplayComponent(rendered, imageData, mimeType, basename(details.imagePath ?? "generated-image"), theme);
|
|
@@ -419,14 +482,81 @@ function renderExpandableCursorReplayResult(
|
|
|
419
482
|
return new Text(rendered, 0, 0);
|
|
420
483
|
}
|
|
421
484
|
|
|
485
|
+
function renderCursorReplayEditResult(
|
|
486
|
+
details: CursorReplayNativeEditDetails,
|
|
487
|
+
options: Parameters<CursorReplayRenderResult>[1],
|
|
488
|
+
theme: Parameters<CursorReplayRenderResult>[2],
|
|
489
|
+
): Component {
|
|
490
|
+
const summary = formatCursorEditSummary(details);
|
|
491
|
+
let rendered = `${theme.fg("toolTitle", theme.bold("edit"))} ${theme.fg("accent", getCursorReplayPath(undefined, details))} ${theme.fg("success", summary)}`;
|
|
492
|
+
const diff = getCursorEditDiff(details);
|
|
493
|
+
if (diff) rendered += `\n${formatCursorReplayDiff(diff, theme, options.expanded ? 40 : CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES)}`;
|
|
494
|
+
return new Text(rendered, 0, 0);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function renderCursorReplayWriteResult(
|
|
498
|
+
details: CursorReplayNativeWriteDetails,
|
|
499
|
+
result: Parameters<CursorReplayRenderResult>[0],
|
|
500
|
+
options: Parameters<CursorReplayRenderResult>[1],
|
|
501
|
+
theme: Parameters<CursorReplayRenderResult>[2],
|
|
502
|
+
): Component {
|
|
503
|
+
const text = firstContentText(result);
|
|
504
|
+
const parts = [
|
|
505
|
+
details.linesCreated !== undefined ? `${details.linesCreated} line${details.linesCreated === 1 ? "" : "s"}` : undefined,
|
|
506
|
+
details.fileSize !== undefined ? `${details.fileSize} bytes` : undefined,
|
|
507
|
+
].filter(Boolean);
|
|
508
|
+
const summary = parts.length > 0 ? parts.join(", ") : "written";
|
|
509
|
+
let rendered = `${theme.fg("toolTitle", theme.bold("write"))} ${theme.fg("accent", getCursorReplayPath(undefined, details))} ${theme.fg("success", summary)}`;
|
|
510
|
+
const previewSource = details.fileContentAfterWrite ?? details.expandedText ?? text;
|
|
511
|
+
const preview = formatCursorReplayFilePreview(
|
|
512
|
+
previewSource,
|
|
513
|
+
getCursorReplayPath(undefined, details),
|
|
514
|
+
theme,
|
|
515
|
+
CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES,
|
|
516
|
+
details.fileContentAfterWrite === undefined,
|
|
517
|
+
);
|
|
518
|
+
if (preview) rendered += `\n${preview}`;
|
|
519
|
+
return new Text(rendered, 0, 0);
|
|
520
|
+
}
|
|
521
|
+
|
|
422
522
|
function renderCursorGenerateImageResult(
|
|
523
|
+
details: CursorReplayGenerateImageDetails,
|
|
524
|
+
result: Parameters<CursorReplayRenderResult>[0],
|
|
525
|
+
options: Parameters<CursorReplayRenderResult>[1],
|
|
526
|
+
theme: Parameters<CursorReplayRenderResult>[2],
|
|
527
|
+
context: Parameters<CursorReplayRenderResult>[3],
|
|
528
|
+
isError: boolean,
|
|
529
|
+
): Component {
|
|
530
|
+
const title = CURSOR_REPLAY_GENERATE_IMAGE_RESULT_TITLE;
|
|
531
|
+
return renderExpandableCursorReplayResult(title, details, result, options, theme, context, isError);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function renderCursorReplayDetails(
|
|
535
|
+
details: CursorReplayToolDetails,
|
|
423
536
|
result: Parameters<CursorReplayRenderResult>[0],
|
|
424
537
|
options: Parameters<CursorReplayRenderResult>[1],
|
|
425
538
|
theme: Parameters<CursorReplayRenderResult>[2],
|
|
426
539
|
context: Parameters<CursorReplayRenderResult>[3],
|
|
427
540
|
isError: boolean,
|
|
541
|
+
text: string,
|
|
428
542
|
): Component {
|
|
429
|
-
|
|
543
|
+
switch (details.variant) {
|
|
544
|
+
case "nativeEdit":
|
|
545
|
+
return renderCursorReplayEditResult(details, options, theme);
|
|
546
|
+
case "nativeWrite":
|
|
547
|
+
return renderCursorReplayWriteResult(details, result, options, theme);
|
|
548
|
+
case "generateImage":
|
|
549
|
+
return renderCursorGenerateImageResult(details, result, options, theme, context, isError);
|
|
550
|
+
case "activity":
|
|
551
|
+
return renderExpandableCursorReplayResult(details.title, details, result, options, theme, context, isError);
|
|
552
|
+
case "genericFallback":
|
|
553
|
+
break;
|
|
554
|
+
default: {
|
|
555
|
+
const _exhaustive: never = details;
|
|
556
|
+
return _exhaustive;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return new Text(text || theme.fg("success", "Cursor tool result replayed"), 0, 0);
|
|
430
560
|
}
|
|
431
561
|
|
|
432
562
|
export function renderCursorReplayResult(
|
|
@@ -437,41 +567,13 @@ export function renderCursorReplayResult(
|
|
|
437
567
|
isError: boolean,
|
|
438
568
|
): Component {
|
|
439
569
|
if (options.isPartial) return new Text(theme.fg("warning", "Replaying Cursor tool result..."), 0, 0);
|
|
440
|
-
const details =
|
|
570
|
+
const details = parseCursorReplayToolDetails(result.details);
|
|
441
571
|
const text = firstContentText(result);
|
|
442
|
-
if (isError && !details
|
|
443
|
-
|
|
444
|
-
if (details?.cursorToolName === "edit" && hasCursorEditChanges(details)) {
|
|
445
|
-
const summary = formatCursorEditSummary(details);
|
|
446
|
-
const title = details.title ?? "edit";
|
|
447
|
-
let rendered = `${theme.fg("toolTitle", theme.bold(title))} ${theme.fg("accent", getCursorReplayPath(undefined, details))} ${theme.fg("success", summary)}`;
|
|
448
|
-
const diff = getCursorEditDiff(details);
|
|
449
|
-
if (diff) rendered += `\n${formatCursorReplayDiff(diff, theme, options.expanded ? 40 : CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES)}`;
|
|
450
|
-
return new Text(rendered, 0, 0);
|
|
572
|
+
if (isError && !hasCursorReplayDisplayTitle(details)) {
|
|
573
|
+
return new Text(theme.fg("error", text.split("\n")[0] || "Cursor replay failed"), 0, 0);
|
|
451
574
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const parts = [
|
|
455
|
-
details.linesCreated !== undefined ? `${details.linesCreated} line${details.linesCreated === 1 ? "" : "s"}` : undefined,
|
|
456
|
-
details.fileSize !== undefined ? `${details.fileSize} bytes` : undefined,
|
|
457
|
-
].filter(Boolean);
|
|
458
|
-
const summary = parts.length > 0 ? parts.join(", ") : "written";
|
|
459
|
-
let rendered = `${theme.fg("toolTitle", theme.bold("write"))} ${theme.fg("accent", getCursorReplayPath(undefined, details))} ${theme.fg("success", summary)}`;
|
|
460
|
-
const previewSource = details.fileContentAfterWrite ?? details.expandedText ?? text;
|
|
461
|
-
const preview = formatCursorReplayFilePreview(
|
|
462
|
-
previewSource,
|
|
463
|
-
getCursorReplayPath(undefined, details),
|
|
464
|
-
theme,
|
|
465
|
-
CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES,
|
|
466
|
-
details.fileContentAfterWrite === undefined,
|
|
467
|
-
);
|
|
468
|
-
if (preview) rendered += `\n${preview}`;
|
|
469
|
-
return new Text(rendered, 0, 0);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
if (details?.cursorToolName === "generateImage") return renderCursorGenerateImageResult(result, options, theme, context, isError);
|
|
473
|
-
if (details?.title) return renderExpandableCursorReplayResult(details.title, result, options, theme, context, isError);
|
|
474
|
-
return new Text(text || theme.fg("success", "Cursor tool result replayed"), 0, 0);
|
|
575
|
+
if (!details) return new Text(text || theme.fg("success", "Cursor tool result replayed"), 0, 0);
|
|
576
|
+
return renderCursorReplayDetails(details, result, options, theme, context, isError, text);
|
|
475
577
|
}
|
|
476
578
|
|
|
477
579
|
export function renderNativeLookingCursorReadReplayResult(
|
|
@@ -500,11 +602,11 @@ export function renderNativeLookingCursorReadReplayResult(
|
|
|
500
602
|
}
|
|
501
603
|
|
|
502
604
|
export function createCursorReplayOnlyToolDefinition(toolName: CursorReplayToolName): ToolDefinition<typeof cursorReplayToolSchema, unknown> {
|
|
503
|
-
const cursorToolName = toolName === CURSOR_REPLAY_ACTIVITY_TOOL_NAME ? "activity" :
|
|
504
|
-
const sideEffectDescription = toolName
|
|
605
|
+
const cursorToolName = toolName === CURSOR_REPLAY_ACTIVITY_TOOL_NAME ? "activity" : getCursorReplayOperationLabel(toolName);
|
|
606
|
+
const sideEffectDescription = getCursorReplaySideEffectDescription(toolName);
|
|
505
607
|
return {
|
|
506
608
|
name: toolName,
|
|
507
|
-
label:
|
|
609
|
+
label: getCursorReplayWrapperLabel(toolName),
|
|
508
610
|
description: `Replay display for a Cursor SDK ${cursorToolName} operation. This tool only returns recorded Cursor results and never executes ${sideEffectDescription} directly.`,
|
|
509
611
|
promptSnippet: `Render a recorded Cursor SDK ${cursorToolName} operation without executing ${sideEffectDescription}.`,
|
|
510
612
|
promptGuidelines: [
|
|
@@ -10,6 +10,7 @@ export const NATIVE_CURSOR_TOOL_DISPLAY_ENV = "PI_CURSOR_NATIVE_TOOL_DISPLAY";
|
|
|
10
10
|
export const NATIVE_CURSOR_TOOL_REGISTRATION_ENV = "PI_CURSOR_REGISTER_NATIVE_TOOLS";
|
|
11
11
|
|
|
12
12
|
export const registeredNativeToolNames = new Set<string>();
|
|
13
|
+
export const skippedNativeToolNames = new Set<string>();
|
|
13
14
|
export const nativeToolResults = new Map<string, CursorNativeToolDisplayItem>();
|
|
14
15
|
|
|
15
16
|
export function readBooleanEnv(name: string, env: Record<string, string | undefined> = process.env): boolean | undefined {
|
|
@@ -73,6 +74,7 @@ export const __testUtils = {
|
|
|
73
74
|
},
|
|
74
75
|
reset(): void {
|
|
75
76
|
registeredNativeToolNames.clear();
|
|
77
|
+
skippedNativeToolNames.clear();
|
|
76
78
|
nativeToolResults.clear();
|
|
77
79
|
},
|
|
78
80
|
};
|