pi-cursor-sdk 0.1.19 → 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 +52 -0
- package/README.md +72 -11
- package/docs/cursor-dogfood-checklist.md +57 -0
- package/docs/cursor-live-smoke-checklist.md +116 -10
- package/docs/cursor-model-ux-spec.md +60 -19
- package/docs/cursor-native-tool-replay.md +21 -11
- package/docs/cursor-native-tool-visual-audit.md +104 -59
- package/docs/cursor-testing-lessons.md +10 -5
- package/docs/cursor-tool-surfaces.md +69 -0
- package/package.json +37 -11
- 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 +226 -0
- 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 +72 -49
- package/src/cursor-mcp-timeout-override.ts +66 -11
- package/src/cursor-native-tool-display-registration.ts +63 -27
- package/src/cursor-native-tool-display-replay.ts +246 -143
- 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 +113 -440
- 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 -482
- 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 +8 -2
- package/src/cursor-sensitive-text.ts +3 -36
- package/src/cursor-session-agent.ts +265 -88
- package/src/cursor-setting-sources.ts +7 -10
- package/src/cursor-state.ts +232 -28
- package/src/cursor-tool-lifecycle.ts +17 -42
- package/src/cursor-tool-manifest.ts +41 -0
- package/src/cursor-tool-names.ts +18 -79
- package/src/cursor-tool-presentation-registry.ts +556 -0
- package/src/cursor-tool-transcript.ts +1 -1
- package/src/cursor-tool-visibility.ts +39 -0
- package/src/cursor-transcript-tool-formatters.ts +0 -59
- package/src/cursor-transcript-tool-specs.ts +169 -232
- 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,29 +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
|
-
}
|
|
39
|
-
|
|
40
|
-
export function asCursorReplayToolDetails(value: unknown): CursorReplayToolDetails | undefined {
|
|
41
|
-
return value && typeof value === "object" ? (value as CursorReplayToolDetails) : undefined;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
58
|
type CursorReplayRenderCall = NonNullable<ToolDefinition<typeof cursorReplayToolSchema, unknown>["renderCall"]>;
|
|
45
59
|
type CursorReplayRenderResult = NonNullable<ToolDefinition<typeof cursorReplayToolSchema, unknown>["renderResult"]>;
|
|
46
60
|
export type CursorReplayRenderTheme = Parameters<CursorReplayRenderCall>[1];
|
|
@@ -86,13 +100,10 @@ function buildImageReplayComponent(text: string, imageData: string, mimeType: st
|
|
|
86
100
|
};
|
|
87
101
|
}
|
|
88
102
|
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
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 {
|
|
96
107
|
const argPath = args?.path;
|
|
97
108
|
return details?.path ?? (typeof argPath === "string" && argPath.trim() ? argPath : "unknown");
|
|
98
109
|
}
|
|
@@ -211,6 +222,93 @@ function formatMutedBlock(text: string, theme: CursorReplayRenderTheme): string
|
|
|
211
222
|
return text.split("\n").map((line) => theme.fg("muted", line)).join("\n");
|
|
212
223
|
}
|
|
213
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
|
+
|
|
214
312
|
export function formatCursorReplayPreview(
|
|
215
313
|
text: string,
|
|
216
314
|
theme: CursorReplayRenderTheme,
|
|
@@ -256,69 +354,7 @@ function getCursorReplayActivityTitle(toolName: CursorReplayToolName, args: Reco
|
|
|
256
354
|
if (toolName === CURSOR_REPLAY_ACTIVITY_TOOL_NAME && typeof args?.activityTitle === "string" && args.activityTitle.trim()) {
|
|
257
355
|
return args.activityTitle.trim();
|
|
258
356
|
}
|
|
259
|
-
return
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function formatReplayRecordingDurationMs(ms: number | undefined): string | undefined {
|
|
263
|
-
if (ms === undefined || !Number.isFinite(ms) || ms < 0) return undefined;
|
|
264
|
-
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
265
|
-
const seconds = ms / 1000;
|
|
266
|
-
return seconds < 60 ? `${seconds.toFixed(1)}s` : `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function formatReplaySemSearchQuery(args: Record<string, unknown> | undefined): string | undefined {
|
|
270
|
-
const query = typeof args?.query === "string" ? args.query.trim() : undefined;
|
|
271
|
-
if (!query) return undefined;
|
|
272
|
-
const targetDirectories = Array.isArray(args?.targetDirectories)
|
|
273
|
-
? args.targetDirectories.filter((entry): entry is string => typeof entry === "string")
|
|
274
|
-
: [];
|
|
275
|
-
const dirHint =
|
|
276
|
-
targetDirectories.length > 0 ? ` (${targetDirectories.length} dir${targetDirectories.length === 1 ? "" : "s"})` : "";
|
|
277
|
-
return `${query}${dirHint}`;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function getCursorReplayCallSummary(toolName: CursorReplayToolName, args: Record<string, unknown> | undefined): string | undefined {
|
|
281
|
-
const activitySummary = typeof args?.activitySummary === "string" && args.activitySummary.trim() ? args.activitySummary.trim() : undefined;
|
|
282
|
-
if (activitySummary) return activitySummary;
|
|
283
|
-
|
|
284
|
-
const path = typeof args?.path === "string" ? args.path : undefined;
|
|
285
|
-
const description = typeof args?.description === "string" ? args.description : undefined;
|
|
286
|
-
const prompt = typeof args?.prompt === "string" ? args.prompt : undefined;
|
|
287
|
-
const totalCount = typeof args?.totalCount === "number" ? args.totalCount : undefined;
|
|
288
|
-
const diagnosticCount = typeof args?.diagnosticCount === "number" ? args.diagnosticCount : undefined;
|
|
289
|
-
const paths = Array.isArray(args?.paths) ? args.paths.filter((entry): entry is string => typeof entry === "string") : [];
|
|
290
|
-
|
|
291
|
-
if (toolName === "cursor_edit" || toolName === "cursor_write" || toolName === "cursor_delete") return path ?? "unknown";
|
|
292
|
-
if (toolName === "cursor_read_lints") {
|
|
293
|
-
const target = paths.length > 0 ? paths.join(", ") : path;
|
|
294
|
-
if (target && diagnosticCount !== undefined) {
|
|
295
|
-
return `${diagnosticCount} diagnostic${diagnosticCount === 1 ? "" : "s"} in ${target}`;
|
|
296
|
-
}
|
|
297
|
-
return target;
|
|
298
|
-
}
|
|
299
|
-
if (toolName === "cursor_update_todos" || toolName === "cursor_create_plan") {
|
|
300
|
-
return totalCount !== undefined ? `${totalCount} item${totalCount === 1 ? "" : "s"}` : undefined;
|
|
301
|
-
}
|
|
302
|
-
if (toolName === "cursor_task") return description;
|
|
303
|
-
if (toolName === "cursor_generate_image") return path ?? prompt;
|
|
304
|
-
if (toolName === "cursor_mcp") return typeof args?.toolName === "string" ? args.toolName : undefined;
|
|
305
|
-
if (toolName === "cursor_sem_search") return formatReplaySemSearchQuery(args);
|
|
306
|
-
if (toolName === "cursor_record_screen") {
|
|
307
|
-
const duration = formatReplayRecordingDurationMs(
|
|
308
|
-
typeof args?.recordingDurationMs === "number" ? args.recordingDurationMs : undefined,
|
|
309
|
-
);
|
|
310
|
-
if (path && duration) return `${path} · ${duration}`;
|
|
311
|
-
if (path) return path;
|
|
312
|
-
if (typeof args?.mode === "string") return args.mode;
|
|
313
|
-
}
|
|
314
|
-
if (toolName === "cursor_web_search") return typeof args?.query === "string" ? args.query : undefined;
|
|
315
|
-
if (toolName === "cursor_web_fetch") return typeof args?.url === "string" ? args.url : undefined;
|
|
316
|
-
if (toolName === CURSOR_REPLAY_ACTIVITY_TOOL_NAME) {
|
|
317
|
-
if (path) return path;
|
|
318
|
-
if (typeof args?.toolName === "string") return args.toolName;
|
|
319
|
-
return formatReplaySemSearchQuery(args);
|
|
320
|
-
}
|
|
321
|
-
return undefined;
|
|
357
|
+
return getCursorReplayWrapperLabel(toolName);
|
|
322
358
|
}
|
|
323
359
|
|
|
324
360
|
export function renderCursorReplayCall(
|
|
@@ -360,15 +396,15 @@ function pluralize(count: number, noun: string): string {
|
|
|
360
396
|
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
361
397
|
}
|
|
362
398
|
|
|
363
|
-
function getCursorEditDiff(details:
|
|
399
|
+
function getCursorEditDiff(details: CursorReplayNativeEditDetails): string | undefined {
|
|
364
400
|
return resolveCursorEditDiff(details);
|
|
365
401
|
}
|
|
366
402
|
|
|
367
|
-
function hasCursorEditChanges(details:
|
|
403
|
+
function hasCursorEditChanges(details: CursorReplayNativeEditDetails): boolean {
|
|
368
404
|
return Boolean(getCursorEditDiff(details)) || Boolean(details.linesAdded) || Boolean(details.linesRemoved);
|
|
369
405
|
}
|
|
370
406
|
|
|
371
|
-
function classifyCursorEditOperation(details:
|
|
407
|
+
function classifyCursorEditOperation(details: CursorReplayNativeEditDetails): "created" | "deleted" | "updated" | "unchanged" {
|
|
372
408
|
if (!hasCursorEditChanges(details)) return "unchanged";
|
|
373
409
|
const diff = getCursorEditDiff(details);
|
|
374
410
|
if (diff?.startsWith("--- /dev/null")) return "created";
|
|
@@ -376,7 +412,7 @@ function classifyCursorEditOperation(details: CursorReplayToolDetails): "created
|
|
|
376
412
|
return "updated";
|
|
377
413
|
}
|
|
378
414
|
|
|
379
|
-
function formatCursorEditSummary(details:
|
|
415
|
+
function formatCursorEditSummary(details: CursorReplayNativeEditDetails): string {
|
|
380
416
|
const operation = classifyCursorEditOperation(details);
|
|
381
417
|
if (operation === "unchanged") return "no changes needed";
|
|
382
418
|
if (operation === "created" && details.linesAdded !== undefined) return `created ${pluralize(details.linesAdded, "line")}`;
|
|
@@ -393,24 +429,52 @@ function firstContentText(result: Parameters<CursorReplayRenderResult>[0]): stri
|
|
|
393
429
|
return content?.type === "text" ? content.text : "";
|
|
394
430
|
}
|
|
395
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
|
+
|
|
396
454
|
function renderExpandableCursorReplayResult(
|
|
397
455
|
title: string,
|
|
456
|
+
details: CursorReplayExpandableResultDetails,
|
|
398
457
|
result: Parameters<CursorReplayRenderResult>[0],
|
|
399
458
|
options: Parameters<CursorReplayRenderResult>[1],
|
|
400
459
|
theme: Parameters<CursorReplayRenderResult>[2],
|
|
401
460
|
context: Parameters<CursorReplayRenderResult>[3],
|
|
402
461
|
isError: boolean,
|
|
403
462
|
): Component {
|
|
404
|
-
const details = asCursorReplayToolDetails(result.details);
|
|
405
463
|
const text = firstContentText(result);
|
|
406
|
-
const summary = details
|
|
464
|
+
const summary = details.summary ?? text.split("\n").find((line) => line.trim()) ?? "completed";
|
|
407
465
|
let rendered = `${theme.fg("toolTitle", theme.bold(title))} ${theme.fg(isError ? "error" : "success", summary)}`;
|
|
408
|
-
const expandedText = details
|
|
409
|
-
if (expandedText) {
|
|
410
|
-
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
|
+
);
|
|
411
475
|
if (preview) rendered += `\n${preview}`;
|
|
412
476
|
}
|
|
413
|
-
if (details
|
|
477
|
+
if (details.imagePath && !isError && context.showImages) {
|
|
414
478
|
const imageData = readImageFileForReplay(details.imagePath);
|
|
415
479
|
const mimeType = details.imageMimeType ?? inferImageMimeTypeFromPath(details.imagePath);
|
|
416
480
|
if (imageData && mimeType) return buildImageReplayComponent(rendered, imageData, mimeType, basename(details.imagePath ?? "generated-image"), theme);
|
|
@@ -418,14 +482,81 @@ function renderExpandableCursorReplayResult(
|
|
|
418
482
|
return new Text(rendered, 0, 0);
|
|
419
483
|
}
|
|
420
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
|
+
|
|
421
522
|
function renderCursorGenerateImageResult(
|
|
523
|
+
details: CursorReplayGenerateImageDetails,
|
|
422
524
|
result: Parameters<CursorReplayRenderResult>[0],
|
|
423
525
|
options: Parameters<CursorReplayRenderResult>[1],
|
|
424
526
|
theme: Parameters<CursorReplayRenderResult>[2],
|
|
425
527
|
context: Parameters<CursorReplayRenderResult>[3],
|
|
426
528
|
isError: boolean,
|
|
427
529
|
): Component {
|
|
428
|
-
|
|
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,
|
|
536
|
+
result: Parameters<CursorReplayRenderResult>[0],
|
|
537
|
+
options: Parameters<CursorReplayRenderResult>[1],
|
|
538
|
+
theme: Parameters<CursorReplayRenderResult>[2],
|
|
539
|
+
context: Parameters<CursorReplayRenderResult>[3],
|
|
540
|
+
isError: boolean,
|
|
541
|
+
text: string,
|
|
542
|
+
): Component {
|
|
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);
|
|
429
560
|
}
|
|
430
561
|
|
|
431
562
|
export function renderCursorReplayResult(
|
|
@@ -436,41 +567,13 @@ export function renderCursorReplayResult(
|
|
|
436
567
|
isError: boolean,
|
|
437
568
|
): Component {
|
|
438
569
|
if (options.isPartial) return new Text(theme.fg("warning", "Replaying Cursor tool result..."), 0, 0);
|
|
439
|
-
const details =
|
|
570
|
+
const details = parseCursorReplayToolDetails(result.details);
|
|
440
571
|
const text = firstContentText(result);
|
|
441
|
-
if (isError && !details
|
|
442
|
-
|
|
443
|
-
if (details?.cursorToolName === "edit" && hasCursorEditChanges(details)) {
|
|
444
|
-
const summary = formatCursorEditSummary(details);
|
|
445
|
-
const title = details.title ?? "edit";
|
|
446
|
-
let rendered = `${theme.fg("toolTitle", theme.bold(title))} ${theme.fg("accent", getCursorReplayPath(undefined, details))} ${theme.fg("success", summary)}`;
|
|
447
|
-
const diff = getCursorEditDiff(details);
|
|
448
|
-
if (diff) rendered += `\n${formatCursorReplayDiff(diff, theme, options.expanded ? 40 : CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES)}`;
|
|
449
|
-
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);
|
|
450
574
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const parts = [
|
|
454
|
-
details.linesCreated !== undefined ? `${details.linesCreated} line${details.linesCreated === 1 ? "" : "s"}` : undefined,
|
|
455
|
-
details.fileSize !== undefined ? `${details.fileSize} bytes` : undefined,
|
|
456
|
-
].filter(Boolean);
|
|
457
|
-
const summary = parts.length > 0 ? parts.join(", ") : "written";
|
|
458
|
-
let rendered = `${theme.fg("toolTitle", theme.bold("write"))} ${theme.fg("accent", getCursorReplayPath(undefined, details))} ${theme.fg("success", summary)}`;
|
|
459
|
-
const previewSource = details.fileContentAfterWrite ?? details.expandedText ?? text;
|
|
460
|
-
const preview = formatCursorReplayFilePreview(
|
|
461
|
-
previewSource,
|
|
462
|
-
getCursorReplayPath(undefined, details),
|
|
463
|
-
theme,
|
|
464
|
-
CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES,
|
|
465
|
-
details.fileContentAfterWrite === undefined,
|
|
466
|
-
);
|
|
467
|
-
if (preview) rendered += `\n${preview}`;
|
|
468
|
-
return new Text(rendered, 0, 0);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (details?.cursorToolName === "generateImage") return renderCursorGenerateImageResult(result, options, theme, context, isError);
|
|
472
|
-
if (details?.title) return renderExpandableCursorReplayResult(details.title, result, options, theme, context, isError);
|
|
473
|
-
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);
|
|
474
577
|
}
|
|
475
578
|
|
|
476
579
|
export function renderNativeLookingCursorReadReplayResult(
|
|
@@ -499,11 +602,11 @@ export function renderNativeLookingCursorReadReplayResult(
|
|
|
499
602
|
}
|
|
500
603
|
|
|
501
604
|
export function createCursorReplayOnlyToolDefinition(toolName: CursorReplayToolName): ToolDefinition<typeof cursorReplayToolSchema, unknown> {
|
|
502
|
-
const cursorToolName = toolName === CURSOR_REPLAY_ACTIVITY_TOOL_NAME ? "activity" :
|
|
503
|
-
const sideEffectDescription = toolName
|
|
605
|
+
const cursorToolName = toolName === CURSOR_REPLAY_ACTIVITY_TOOL_NAME ? "activity" : getCursorReplayOperationLabel(toolName);
|
|
606
|
+
const sideEffectDescription = getCursorReplaySideEffectDescription(toolName);
|
|
504
607
|
return {
|
|
505
608
|
name: toolName,
|
|
506
|
-
label:
|
|
609
|
+
label: getCursorReplayWrapperLabel(toolName),
|
|
507
610
|
description: `Replay display for a Cursor SDK ${cursorToolName} operation. This tool only returns recorded Cursor results and never executes ${sideEffectDescription} directly.`,
|
|
508
611
|
promptSnippet: `Render a recorded Cursor SDK ${cursorToolName} operation without executing ${sideEffectDescription}.`,
|
|
509
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
|
};
|