pi-cursor-sdk 0.1.37 → 0.1.38
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 +27 -0
- package/docs/cursor-native-tool-replay.md +3 -3
- 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-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 +4 -5
- 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-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 +16 -9
- 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
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.38 - 2026-06-08
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Add shared Cursor replay result readers so transcript formatting, native replay cards, and activity builders consume the same MCP-like content/diff/file-preview extraction logic.
|
|
10
|
+
- Add a canonical Cursor model lifecycle sync helper for session start, before-agent-start, model selection, and turn start registration paths.
|
|
11
|
+
- Add lazy Cursor provider registration so extension startup can register models and commands without importing the Cursor SDK runtime until the provider is invoked.
|
|
12
|
+
- Add shared Cursor native tool-name and pi-tool-bridge environment helpers for provider/runtime registration code.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Centralize Cursor tool presentation ownership in the typed presentation registry, including labels, aliases, lifecycle titles, replay metadata, side-effect policies, and web-tool classification.
|
|
17
|
+
- Consolidate Cursor session cwd, session file/id, generation, and scope-key handling in `cursor-session-scope`; remove the older cwd/message-offset helper split.
|
|
18
|
+
- Simplify Cursor session-agent lifecycle invalidation on model select, compaction preparation, tree navigation, shutdown, and scope changes.
|
|
19
|
+
- Refine Cursor tool lifecycle/replay display routing so completed replay cards, inactive traces, native replay activation, and duplicate step/delta completions share one display path.
|
|
20
|
+
- Keep Cursor agents-context dedup and fallback-catalog warning registration model-scoped through the shared lifecycle helper.
|
|
21
|
+
- Keep edit/write replay previews on the shared structured diff/file preview renderers while retaining SDK expanded-text fallback behavior.
|
|
22
|
+
- Update maintainer docs and repo map entries for the new ownership boundaries.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Clear started Cursor tool calls when a completed delta reports the same tool under a different SDK call id, preventing stale native replay edit starts from surfacing as `Cursor edit did not complete` after successful final text.
|
|
27
|
+
- Keep Cursor agents-context dedup registration in a tracked module so clean package builds resolve `src/index.ts` imports.
|
|
28
|
+
- Accept Windows-rendered absolute `README.md` paths in platform-smoke grep-card detection without weakening prompt false-positive checks.
|
|
29
|
+
- Preserve Cursor skill activation and question-tool registration through lazy provider/runtime import boundaries.
|
|
30
|
+
- Preserve fast local discovery incomplete-tool suppression while still surfacing aborts, SDK failures, and no-text incomplete runs.
|
|
31
|
+
|
|
5
32
|
## 0.1.37 - 2026-06-06
|
|
6
33
|
|
|
7
34
|
### Changed
|
|
@@ -70,7 +70,7 @@ Edit and write activity replays through pi-facing `edit` and `write` cards only
|
|
|
70
70
|
|
|
71
71
|
Source of truth for SDK tool names: `@cursor/sdk@1.0.17` conversation `ToolType` values and https://cursor.com/docs/sdk/typescript
|
|
72
72
|
|
|
73
|
-
Implementation owners: `src/cursor-tool-presentation-registry.ts` (canonical names, labels, visibility, replay policy, bridge exclusions for internal replay wrappers, and display-spec key completeness), `src/cursor-transcript-tool-specs.ts` (registry-keyed
|
|
73
|
+
Implementation owners: `src/cursor-tool-presentation-registry.ts` (canonical names, labels, visibility, replay policy, bridge exclusions for internal replay wrappers, alias normalization, and display-spec key completeness), `src/cursor-transcript-tool-specs.ts` (registry-keyed display implementations for transcript formatting and pi display builders), `src/cursor-native-tool-display-replay.ts` (replay card rendering derived from registry replay metadata), and `src/cursor-web-tool-activity.ts` (MCP/web alias remapping before display lookup).
|
|
74
74
|
|
|
75
75
|
**Maintainer invariants — edit/write replay previews:** All colored diff rendering (native `edit` cards and `Cursor edit` activity fallbacks) flows through the single `formatCursorReplayDiff()` in `src/cursor-native-tool-display-replay.ts`. Activity write fallbacks with structured `fileContentAfterWrite` use the same `formatCursorReplayFilePreview()` path as native `write` cards. Structured `diffString` (and `diff`/`lines*`) or `fileContentAfterWrite` on `CursorReplay*Details` (including activity variants) is the source of truth for TUI preview coloring/highlighting. `expandedText` on activity details is for summary/expansion and as a fallback when the current SDK reports a unified diff only in text; it is never the primary preview source when structured fields are present. No parallel +/- coloring loops exist.
|
|
76
76
|
|
|
@@ -98,7 +98,7 @@ This matrix covers **Cursor native tool replay only**. It does not describe the
|
|
|
98
98
|
| *(host/MCP alias)* `WebFetch` / `web_fetch` / similar | neutral activity | `cursor` | Collapsed label **Cursor web fetch**; display-only Cursor web access reported by the SDK, not an executable pi web tool |
|
|
99
99
|
| _(no spec; future/unknown SDK name)_ | neutral activity | `cursor` | Collapsed label **Cursor** plus SDK tool name via `buildGenericPiToolDisplay()`; bounded fallback transcript only |
|
|
100
100
|
|
|
101
|
-
**Unknown/future fallback path:** SDK tool names with no registry-backed
|
|
101
|
+
**Unknown/future fallback path:** SDK tool names with no registry-backed display implementation entry (future or unknown types) use `buildGenericPiToolDisplay()` in `src/cursor-transcript-tool-specs.ts` with bounded `formatFallback()` content from `src/cursor-transcript-tool-formatters.ts`. Lookup uses `Object.hasOwn()` on the display implementation table so inherited object keys such as `constructor` or `toString` cannot accidentally match a registry spec. When native replay is enabled, those completions queue through neutral pi tool name `cursor` (not native pi `read`/`bash`/… cards). Collapsed labels read like **Cursor futureSemSearchWidget** (title `Cursor` plus the SDK tool name) with optional bounded `activitySummary` from scrubbed args/result lines. Errors keep `details.summary` undefined so unbounded raw errors do not leak into replay cards (#52). Known explicit specs still win over this path; real pi bridge tool names such as `edit` and `write` are not suppressed by internal replay-wrapper exclusions.
|
|
102
102
|
|
|
103
103
|
**Replay detail disposition model:** `src/cursor-replay-tool-details.ts` stores replay card disposition separately from SDK source tool identity. Variants are `nativeEdit`, `nativeWrite`, `activity` (`sourceToolName` + display `title`), `generateImage`, and `genericFallback`. Path-only or notebook edit/write fallbacks produce `activity` details (neutral `cursor` cards) instead of structured edit/write variants with optional `title` escape hatches. Native edit/write cards use `nativeEdit` / `nativeWrite` only when pi-facing replay args satisfy the matching schema. The renderer dispatches on `variant` only.
|
|
104
104
|
|
|
@@ -106,7 +106,7 @@ Neutral activity rows use pi tool name `cursor` with `activityTitle` / `activity
|
|
|
106
106
|
|
|
107
107
|
## Runtime alias normalization
|
|
108
108
|
|
|
109
|
-
Before lookup
|
|
109
|
+
Before display lookup, completed SDK tool names pass through `normalizeCursorToolName()` in `src/cursor-tool-presentation-registry.ts`; MCP web tool names are additionally remapped by `resolveTranscriptToolName()` in `src/cursor-web-tool-activity.ts`. Documented aliases:
|
|
110
110
|
|
|
111
111
|
| Runtime alias | Canonical SDK name |
|
|
112
112
|
| --- | --- |
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ import { resolve } from "node:path";
|
|
|
11
11
|
|
|
12
12
|
const CARD_PATTERNS = [
|
|
13
13
|
{ id: "read", pattern: /^\s*read (?:\.\/)?package\.json\s*$/i },
|
|
14
|
-
{ id: "grep", pattern: /^\s*grep \/pi-cursor-sdk\/ in
|
|
14
|
+
{ id: "grep", pattern: /^\s*grep \/pi-cursor-sdk\/ in\s+(?:(?:\S+[\\/])?README\.md)\s*$/i },
|
|
15
15
|
{ id: "find", pattern: /^\s*find README\.md in\s+\S+/i },
|
|
16
16
|
{ id: "list", pattern: /^\s*(?:find \* in src|find src\/\* in \.|Get-ChildItem -Name \.\/src)\s*/i },
|
|
17
17
|
{ id: "shell-success", pattern: /^\s*cursor visual smoke\s*$/i },
|
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { BUNDLED_CONTEXT_WINDOWS } from "./bundled-context-windows.js";
|
|
5
|
+
import { asRecord } from "./cursor-record-utils.js";
|
|
5
6
|
|
|
6
7
|
const CONTEXT_WINDOW_CACHE_FILE = "cursor-sdk-context-windows.json";
|
|
7
8
|
let userContextWindowOverrideLoadCount = 0;
|
|
@@ -18,18 +19,16 @@ function isPositiveInteger(value: unknown): value is number {
|
|
|
18
19
|
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
22
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
22
|
function parseContextWindowCacheFile(value: unknown): ContextWindowCacheFile | undefined {
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
const record = asRecord(value);
|
|
24
|
+
if (!record) return undefined;
|
|
25
|
+
const { contextWindows } = record;
|
|
28
26
|
if (contextWindows === undefined) return {};
|
|
29
|
-
|
|
27
|
+
const contextWindowRecord = asRecord(contextWindows);
|
|
28
|
+
if (!contextWindowRecord) return undefined;
|
|
30
29
|
return {
|
|
31
30
|
contextWindows: Object.fromEntries(
|
|
32
|
-
Object.entries(
|
|
31
|
+
Object.entries(contextWindowRecord).filter((entry): entry is [string, number] => isPositiveInteger(entry[1])),
|
|
33
32
|
),
|
|
34
33
|
};
|
|
35
34
|
}
|
|
@@ -68,12 +67,9 @@ export function getCachedContextWindow(modelId: string): number | undefined {
|
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
export function getCheckpointContextWindow(checkpoint: unknown): number | undefined {
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
const { maxTokens } = tokenDetails;
|
|
75
|
-
if (!isPositiveInteger(maxTokens)) return undefined;
|
|
76
|
-
return maxTokens;
|
|
70
|
+
const tokenDetails = asRecord(checkpoint)?.tokenDetails;
|
|
71
|
+
const maxTokens = asRecord(tokenDetails)?.maxTokens;
|
|
72
|
+
return isPositiveInteger(maxTokens) ? maxTokens : undefined;
|
|
77
73
|
}
|
|
78
74
|
|
|
79
75
|
export function saveCachedContextWindow(modelId: string, contextWindow: number): void {
|
package/src/context.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import type { Context, Message, ToolCall } from "@earendil-works/pi-ai";
|
|
3
3
|
import { convertToLlm } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import type { SDKImage } from "@cursor/sdk";
|
|
5
|
-
import { getCursorReplayPromptLabel } from "./cursor-tool-
|
|
5
|
+
import { getCursorReplayPromptLabel } from "./cursor-tool-presentation-registry.js";
|
|
6
6
|
|
|
7
7
|
export interface CursorPrompt {
|
|
8
8
|
text: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentMessage } from "@cursor/sdk";
|
|
2
|
-
import { asRecord, getArray, getString
|
|
2
|
+
import { asRecord, getArray, getString } from "./cursor-record-utils.js";
|
|
3
|
+
import { stringifyUnknown } from "./cursor-transcript-utils.js";
|
|
3
4
|
import { loadCursorSdk } from "./cursor-sdk-runtime.js";
|
|
4
5
|
|
|
5
6
|
const CURSOR_AGENT_MESSAGE_PAGE_LIMIT = 8;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { registerCursorModelLifecycle, type CursorModelLifecycleExtensionApi } from "./cursor-model-lifecycle.js";
|
|
2
|
+
|
|
3
|
+
export type CursorAgentsContextExtensionApi = CursorModelLifecycleExtensionApi;
|
|
4
|
+
|
|
5
|
+
export function registerCursorAgentsContextDedup(pi: CursorAgentsContextExtensionApi): void {
|
|
6
|
+
registerCursorModelLifecycle(pi, {
|
|
7
|
+
beforeAgentStart: async (event, ctx) => {
|
|
8
|
+
const { resolveCursorFacingSystemPrompt } = await import("./cursor-agents-context.js");
|
|
9
|
+
const resolved = resolveCursorFacingSystemPrompt(
|
|
10
|
+
event.systemPrompt,
|
|
11
|
+
ctx.model,
|
|
12
|
+
event.systemPromptOptions,
|
|
13
|
+
);
|
|
14
|
+
if (resolved === event.systemPrompt) return undefined;
|
|
15
|
+
return { systemPrompt: resolved };
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
BeforeAgentStartEvent,
|
|
3
|
-
BeforeAgentStartEventResult,
|
|
4
2
|
BuildSystemPromptOptions,
|
|
5
|
-
ExtensionAPI,
|
|
6
3
|
ExtensionContext,
|
|
7
|
-
ExtensionHandler,
|
|
8
4
|
} from "@earendil-works/pi-coding-agent";
|
|
9
5
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
10
6
|
import { parseEnvBoolean } from "./cursor-env-boolean.js";
|
|
11
7
|
import { isCursorModel } from "./cursor-model.js";
|
|
12
8
|
import {
|
|
13
|
-
|
|
14
|
-
cursorSettingSourcesLoadUserAgentsRules,
|
|
9
|
+
cursorSettingSourcesIncludes,
|
|
15
10
|
getEffectiveCursorSettingSources,
|
|
11
|
+
resolveCursorSettingSources,
|
|
16
12
|
} from "./cursor-setting-sources.js";
|
|
17
13
|
import type { SettingSource } from "@cursor/sdk";
|
|
14
|
+
export { registerCursorAgentsContextDedup, type CursorAgentsContextExtensionApi } from "./cursor-agents-context-registration.js";
|
|
18
15
|
|
|
19
16
|
export const CURSOR_PRESERVE_PI_AGENTS_MD_ENV = "PI_CURSOR_PRESERVE_PI_AGENTS_MD";
|
|
20
17
|
|
|
@@ -49,18 +46,24 @@ export function getAgentsContextFileBaseName(filePath: string): string {
|
|
|
49
46
|
return normalized.slice(normalized.lastIndexOf("/") + 1).toLowerCase();
|
|
50
47
|
}
|
|
51
48
|
|
|
49
|
+
function isPiAgentDirContextFilePath(
|
|
50
|
+
filePath: string,
|
|
51
|
+
fileName: "agents.md" | "claude.md",
|
|
52
|
+
agentDir: string = getAgentDir(),
|
|
53
|
+
): boolean {
|
|
54
|
+
const normalized = normalizeContextPath(filePath);
|
|
55
|
+
const expectedPath = `${normalizeDirPath(agentDir)}/${fileName}`;
|
|
56
|
+
return normalized.toLowerCase() === expectedPath.toLowerCase();
|
|
57
|
+
}
|
|
58
|
+
|
|
52
59
|
/** Actual pi agent dir `AGENTS.md` — overlaps Cursor `user` setting source (global agent instructions). */
|
|
53
60
|
export function isPiAgentDirAgentsMdPath(filePath: string, agentDir: string = getAgentDir()): boolean {
|
|
54
|
-
|
|
55
|
-
const agentsMdPath = `${normalizeDirPath(agentDir)}/agents.md`;
|
|
56
|
-
return normalized.toLowerCase() === agentsMdPath.toLowerCase();
|
|
61
|
+
return isPiAgentDirContextFilePath(filePath, "agents.md", agentDir);
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
/** Actual pi agent dir `CLAUDE.md` — kept because Cursor user rules use `~/.claude/CLAUDE.md`. */
|
|
60
65
|
export function isPiAgentDirClaudeMdPath(filePath: string, agentDir: string = getAgentDir()): boolean {
|
|
61
|
-
|
|
62
|
-
const claudeMdPath = `${normalizeDirPath(agentDir)}/claude.md`;
|
|
63
|
-
return normalized.toLowerCase() === claudeMdPath.toLowerCase();
|
|
66
|
+
return isPiAgentDirContextFilePath(filePath, "claude.md", agentDir);
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
/**
|
|
@@ -87,9 +90,9 @@ export function shouldRemovePiAgentsContextFile(
|
|
|
87
90
|
): boolean {
|
|
88
91
|
switch (classifyContextFileOverlap(file.path, agentDir)) {
|
|
89
92
|
case "cursor-user-agents":
|
|
90
|
-
return
|
|
93
|
+
return cursorSettingSourcesIncludes(settingSources, "user");
|
|
91
94
|
case "cursor-project-rules":
|
|
92
|
-
return
|
|
95
|
+
return cursorSettingSourcesIncludes(settingSources, "project");
|
|
93
96
|
default:
|
|
94
97
|
return false;
|
|
95
98
|
}
|
|
@@ -153,24 +156,12 @@ export function resolveCursorFacingSystemPrompt(
|
|
|
153
156
|
): string {
|
|
154
157
|
if (!systemPromptOptions) return systemPrompt;
|
|
155
158
|
const contextFiles = systemPromptOptions.contextFiles ?? [];
|
|
156
|
-
const settingSources =
|
|
159
|
+
const settingSources =
|
|
160
|
+
settingSourcesRaw === undefined
|
|
161
|
+
? getEffectiveCursorSettingSources()
|
|
162
|
+
: resolveCursorSettingSources(settingSourcesRaw);
|
|
157
163
|
if (!shouldSuppressPiAgentsContext(model, contextFiles, settingSources, agentDir)) {
|
|
158
164
|
return systemPrompt;
|
|
159
165
|
}
|
|
160
166
|
return removePiAgentsContextFromSystemPrompt(systemPrompt, contextFiles, settingSources, agentDir);
|
|
161
167
|
}
|
|
162
|
-
|
|
163
|
-
type CursorAgentsContextExtensionApi = Pick<ExtensionAPI, "on">;
|
|
164
|
-
|
|
165
|
-
export function registerCursorAgentsContextDedup(pi: CursorAgentsContextExtensionApi): void {
|
|
166
|
-
const handler: ExtensionHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult> = (event, ctx) => {
|
|
167
|
-
const resolved = resolveCursorFacingSystemPrompt(
|
|
168
|
-
event.systemPrompt,
|
|
169
|
-
ctx.model,
|
|
170
|
-
event.systemPromptOptions,
|
|
171
|
-
);
|
|
172
|
-
if (resolved === event.systemPrompt) return;
|
|
173
|
-
return { systemPrompt: resolved };
|
|
174
|
-
};
|
|
175
|
-
pi.on("before_agent_start", handler);
|
|
176
|
-
}
|
package/src/cursor-edit-diff.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { asRecord } from "./cursor-record-utils.js";
|
|
2
|
+
|
|
1
3
|
const CURSOR_EDIT_DIFF_FIELD_ORDER = ["diffString", "diff", "unifiedDiff", "patch"] as const;
|
|
2
4
|
|
|
3
5
|
export function resolveCursorEditDiff(source: unknown): string | undefined {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
const record = asRecord(source);
|
|
7
|
+
if (!record) return undefined;
|
|
6
8
|
for (const key of CURSOR_EDIT_DIFF_FIELD_ORDER) {
|
|
7
9
|
const value = record[key];
|
|
8
10
|
if (typeof value === "string" && value.length > 0) return value;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { isCursorModel } from "./cursor-model.js";
|
|
3
|
+
import { registerCursorModelLifecycle, type CursorModelLifecycleExtensionApi } from "./cursor-model-lifecycle.js";
|
|
4
|
+
import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
5
|
+
import type { CursorModelFallbackIssue } from "./model-discovery.js";
|
|
6
|
+
|
|
7
|
+
export type CursorFallbackWarningExtensionApi = CursorModelLifecycleExtensionApi;
|
|
8
|
+
|
|
9
|
+
export function registerCursorFallbackIssueWarning(
|
|
10
|
+
pi: CursorFallbackWarningExtensionApi,
|
|
11
|
+
issue: CursorModelFallbackIssue,
|
|
12
|
+
): void {
|
|
13
|
+
const warnedSessionScopeKeys = new Set<string>();
|
|
14
|
+
|
|
15
|
+
registerCursorModelLifecycle(pi, (ctx: ExtensionContext) => {
|
|
16
|
+
if (!isCursorModel(ctx.model) || !ctx.hasUI) return;
|
|
17
|
+
const scopeKey = getCursorSessionScopeKey();
|
|
18
|
+
if (warnedSessionScopeKeys.has(scopeKey)) return;
|
|
19
|
+
warnedSessionScopeKeys.add(scopeKey);
|
|
20
|
+
ctx.ui.notify(issue.message, "warning");
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CURSOR_REPLAY_ACTIVITY_TOOL_NAME } from "./cursor-tool-
|
|
1
|
+
import { CURSOR_REPLAY_ACTIVITY_TOOL_NAME, getCursorToolActivityTitle } from "./cursor-tool-presentation-registry.js";
|
|
2
2
|
import { truncateCursorDisplayLine } from "./cursor-display-text.js";
|
|
3
3
|
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
4
4
|
import {
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
parseCursorReplayToolDetails,
|
|
11
11
|
resolveIncompleteReplayActivitySourceToolName,
|
|
12
12
|
} from "./cursor-replay-tool-details.js";
|
|
13
|
-
import {
|
|
13
|
+
import { asRecord } from "./cursor-record-utils.js";
|
|
14
|
+
import { type CursorPiToolDisplay } from "./cursor-transcript-utils.js";
|
|
14
15
|
import { classifyCursorToolVisibility } from "./cursor-tool-visibility.js";
|
|
15
16
|
|
|
16
17
|
export type IncompleteCursorToolDiscardReason = DiscardedIncompleteStartedToolCallReason;
|
|
@@ -59,11 +60,6 @@ export function resolveIncompleteCursorToolVisibility(
|
|
|
59
60
|
return "emit";
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
function buildGenericIncompleteActivityTitle(displayName: string): string {
|
|
63
|
-
if (!displayName || displayName === "unknown") return "Cursor tool";
|
|
64
|
-
return `Cursor ${truncateArg(displayName)}`;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
63
|
export function formatIncompleteCursorToolReasonText(reason: IncompleteCursorToolDiscardReason): string {
|
|
68
64
|
switch (reason) {
|
|
69
65
|
case DISCARDED_INCOMPLETE_TOOL_CALL_REASON:
|
|
@@ -79,7 +75,7 @@ export function formatIncompleteCursorToolReasonText(reason: IncompleteCursorToo
|
|
|
79
75
|
|
|
80
76
|
export function getIncompleteCursorToolActivityTitle(toolCall: unknown): string {
|
|
81
77
|
const visibility = classifyCursorToolVisibility(toolCall);
|
|
82
|
-
return visibility.incompleteTitle ??
|
|
78
|
+
return visibility.incompleteTitle ?? getCursorToolActivityTitle(visibility.displayName);
|
|
83
79
|
}
|
|
84
80
|
|
|
85
81
|
export function buildIncompleteCursorToolDisplay(
|
|
@@ -124,8 +120,7 @@ export function formatIncompleteCursorToolTrace(display: CursorPiToolDisplay): s
|
|
|
124
120
|
formatIncompleteCursorToolReasonText(DISCARDED_INCOMPLETE_TOOL_CALL_REASON);
|
|
125
121
|
return `${truncateCursorDisplayLine(parsed.title)}: ${truncateCursorDisplayLine(summary)}\n`;
|
|
126
122
|
}
|
|
127
|
-
const
|
|
128
|
-
const detailRecord = details && typeof details === "object" ? (details as Record<string, unknown>) : undefined;
|
|
123
|
+
const detailRecord = asRecord(display.result.details);
|
|
129
124
|
const argsRecord = display.args;
|
|
130
125
|
const title =
|
|
131
126
|
(typeof detailRecord?.title === "string" && detailRecord.title.trim()) ||
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type CursorLiveRunAccountingState,
|
|
8
8
|
type CursorLiveToolResultConsumption,
|
|
9
9
|
} from "./cursor-live-run-accounting.js";
|
|
10
|
-
import type { CursorNativeToolDisplayItem } from "./cursor-native-tool-display.js";
|
|
10
|
+
import type { CursorNativeToolDisplayItem } from "./cursor-native-tool-display-state.js";
|
|
11
11
|
import type { CursorPiBridgeToolRequest, CursorPiToolBridgeRun } from "./cursor-pi-tool-bridge.js";
|
|
12
12
|
import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
13
13
|
import type { CursorSdkEventDebugRecorder } from "./cursor-sdk-event-debug.js";
|
|
@@ -151,8 +151,6 @@ export function restoreCursorMcpToolTimeoutOverride(): void {
|
|
|
151
151
|
installedConnectTimeoutMs = DEFAULT_CURSOR_MCP_CONNECT_TIMEOUT_MS;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
export const restoreCursorMcpToolTimeoutOverrideForTests = restoreCursorMcpToolTimeoutOverride;
|
|
155
|
-
|
|
156
154
|
export const cursorMcpToolTimeoutOverrideDefaults = {
|
|
157
155
|
cursorSdkDefaultTimeoutMs: CURSOR_SDK_MCP_DEFAULT_TIMEOUT_MS,
|
|
158
156
|
defaultOverrideTimeoutMs: DEFAULT_CURSOR_MCP_TOOL_TIMEOUT_MS,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BeforeAgentStartEvent,
|
|
3
|
+
BeforeAgentStartEventResult,
|
|
4
|
+
ExtensionContext,
|
|
5
|
+
ExtensionHandler,
|
|
6
|
+
SessionStartEvent,
|
|
7
|
+
TurnStartEvent,
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
|
|
10
|
+
export type CursorModelLifecycleContext = ExtensionContext;
|
|
11
|
+
|
|
12
|
+
type CursorModelSelectEvent = { model: ExtensionContext["model"] };
|
|
13
|
+
|
|
14
|
+
type CursorModelLifecycleSyncHandler = (ctx: CursorModelLifecycleContext) => Promise<void> | void;
|
|
15
|
+
type CursorModelSessionStartHandler = ExtensionHandler<SessionStartEvent>;
|
|
16
|
+
type CursorModelSelectHandler = (event: CursorModelSelectEvent, ctx: CursorModelLifecycleContext) => Promise<void> | void;
|
|
17
|
+
type CursorModelTurnStartHandler = ExtensionHandler<TurnStartEvent>;
|
|
18
|
+
type CursorModelBeforeAgentStartHandler = ExtensionHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>;
|
|
19
|
+
|
|
20
|
+
export interface CursorModelLifecycleExtensionApi {
|
|
21
|
+
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
|
|
22
|
+
on(event: "before_agent_start", handler: CursorModelBeforeAgentStartHandler): void;
|
|
23
|
+
on(event: "model_select", handler: (event: CursorModelSelectEvent, ctx: ExtensionContext) => Promise<void> | void): void;
|
|
24
|
+
on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CursorModelLifecycleHandlers {
|
|
28
|
+
sessionStart?: CursorModelSessionStartHandler;
|
|
29
|
+
modelSelect?: CursorModelSelectHandler;
|
|
30
|
+
turnStart?: CursorModelTurnStartHandler;
|
|
31
|
+
sync?: CursorModelLifecycleSyncHandler;
|
|
32
|
+
beforeAgentStart?: CursorModelBeforeAgentStartHandler;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeLifecycleHandlers(
|
|
36
|
+
handlerOrHandlers: CursorModelLifecycleSyncHandler | CursorModelLifecycleHandlers,
|
|
37
|
+
): CursorModelLifecycleHandlers {
|
|
38
|
+
return typeof handlerOrHandlers === "function" ? { sync: handlerOrHandlers } : handlerOrHandlers;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function registerCursorModelLifecycle(
|
|
42
|
+
pi: CursorModelLifecycleExtensionApi,
|
|
43
|
+
handlerOrHandlers: CursorModelLifecycleSyncHandler | CursorModelLifecycleHandlers,
|
|
44
|
+
): void {
|
|
45
|
+
const handlers = normalizeLifecycleHandlers(handlerOrHandlers);
|
|
46
|
+
const sync = handlers.sync;
|
|
47
|
+
if (handlers.sessionStart || sync) {
|
|
48
|
+
pi.on("session_start", async (event, ctx) => {
|
|
49
|
+
await handlers.sessionStart?.(event, ctx);
|
|
50
|
+
await sync?.(ctx);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (handlers.modelSelect || sync) {
|
|
54
|
+
pi.on("model_select", async (event, ctx) => {
|
|
55
|
+
const effectiveCtx = { ...ctx, model: event.model };
|
|
56
|
+
await handlers.modelSelect?.(event, effectiveCtx);
|
|
57
|
+
await sync?.(effectiveCtx);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (handlers.turnStart || sync) {
|
|
61
|
+
pi.on("turn_start", async (event, ctx) => {
|
|
62
|
+
await handlers.turnStart?.(event, ctx);
|
|
63
|
+
await sync?.(ctx);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (handlers.beforeAgentStart || sync) {
|
|
67
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
68
|
+
await sync?.(ctx);
|
|
69
|
+
return await handlers.beforeAgentStart?.(event, ctx);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { canRenderCursorToolNatively } from "./cursor-native-tool-display.js";
|
|
1
|
+
import { canRenderCursorToolNatively } from "./cursor-native-tool-display-state.js";
|
|
2
2
|
import { getActiveContextToolNames } from "./cursor-context-tools.js";
|
|
3
3
|
import type { Context } from "@earendil-works/pi-ai";
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CursorPiToolDisplay } from "./cursor-
|
|
1
|
+
import type { CursorPiToolDisplay } from "./cursor-transcript-utils.js";
|
|
2
2
|
import { asRecord } from "./cursor-record-utils.js";
|
|
3
3
|
import { truncateCursorDisplayLine } from "./cursor-display-text.js";
|
|
4
4
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import {
|
|
3
3
|
CURSOR_MODEL_ACTIVE_REPLAY_TOOL_NAMES,
|
|
4
4
|
isNativeCursorToolName,
|
|
5
5
|
NATIVE_CURSOR_TOOL_NAMES,
|
|
6
|
-
registerNativeCursorTool,
|
|
7
6
|
type NativeCursorToolName,
|
|
8
|
-
} from "./cursor-native-tool-
|
|
7
|
+
} from "./cursor-native-tool-names.js";
|
|
9
8
|
import { isCursorModel } from "./cursor-model.js";
|
|
9
|
+
import { registerCursorModelLifecycle, type CursorModelLifecycleExtensionApi } from "./cursor-model-lifecycle.js";
|
|
10
10
|
import {
|
|
11
11
|
isCursorNativeToolDisplayRequested,
|
|
12
12
|
isCursorNativeToolRegistrationRequested,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
registeredNativeToolNames,
|
|
16
16
|
skippedNativeToolNames,
|
|
17
17
|
} from "./cursor-native-tool-display-state.js";
|
|
18
|
-
import { isCursorReplayToolName } from "./cursor-tool-
|
|
18
|
+
import { isCursorReplayToolName } from "./cursor-tool-presentation-registry.js";
|
|
19
19
|
|
|
20
20
|
export const CURSOR_CORE_PI_REPLAY_TOOL_NAMES = ["read", "bash", "edit", "write"] as const;
|
|
21
21
|
const CORE_PI_TOOL_NAMES = new Set<string>(CURSOR_CORE_PI_REPLAY_TOOL_NAMES);
|
|
@@ -27,12 +27,7 @@ function isCursorCorePiReplayToolName(toolName: string): toolName is (typeof CUR
|
|
|
27
27
|
type CursorNativeToolActivationApi = Pick<ExtensionAPI, "getActiveTools" | "setActiveTools">;
|
|
28
28
|
type CursorNativeToolRegistryApi = CursorNativeToolActivationApi & Pick<ExtensionAPI, "getAllTools" | "registerTool">;
|
|
29
29
|
|
|
30
|
-
export interface CursorNativeToolDisplayExtensionApi extends CursorNativeToolRegistryApi {
|
|
31
|
-
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
|
|
32
|
-
on(event: "before_agent_start", handler: ExtensionHandler<BeforeAgentStartEvent>): void;
|
|
33
|
-
on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;
|
|
34
|
-
on(event: "model_select", handler: (event: { model: ExtensionContext["model"] }, ctx: ExtensionContext) => Promise<void> | void): void;
|
|
35
|
-
}
|
|
30
|
+
export interface CursorNativeToolDisplayExtensionApi extends CursorNativeToolRegistryApi, CursorModelLifecycleExtensionApi {}
|
|
36
31
|
|
|
37
32
|
function hasNonBuiltinTool(pi: Pick<ExtensionAPI, "getAllTools">, toolName: NativeCursorToolName): boolean {
|
|
38
33
|
const existingTool = pi.getAllTools().find((tool) => tool.name === toolName);
|
|
@@ -43,11 +38,12 @@ type NativeRegistrationContext = Pick<ExtensionContext, "mode" | "model"> & {
|
|
|
43
38
|
ui: Pick<ExtensionContext["ui"], "notify">;
|
|
44
39
|
};
|
|
45
40
|
|
|
46
|
-
function registerNativeCursorToolsFromSet(
|
|
41
|
+
async function registerNativeCursorToolsFromSet(
|
|
47
42
|
pi: CursorNativeToolRegistryApi,
|
|
48
43
|
toolNames: readonly NativeCursorToolName[],
|
|
49
|
-
): NativeCursorToolName[] {
|
|
44
|
+
): Promise<NativeCursorToolName[]> {
|
|
50
45
|
const newlySkippedToolNames: NativeCursorToolName[] = [];
|
|
46
|
+
let registerNativeCursorTool: ((pi: CursorNativeToolRegistryApi, toolName: NativeCursorToolName) => void) | undefined;
|
|
51
47
|
for (const toolName of toolNames) {
|
|
52
48
|
if (registeredNativeToolNames.has(toolName) || skippedNativeToolNames.has(toolName)) continue;
|
|
53
49
|
if (hasNonBuiltinTool(pi, toolName)) {
|
|
@@ -55,6 +51,7 @@ function registerNativeCursorToolsFromSet(
|
|
|
55
51
|
newlySkippedToolNames.push(toolName);
|
|
56
52
|
continue;
|
|
57
53
|
}
|
|
54
|
+
registerNativeCursorTool ??= (await import("./cursor-native-tool-display-tools.js")).registerNativeCursorTool;
|
|
58
55
|
registerNativeCursorTool(pi, toolName);
|
|
59
56
|
registeredNativeToolNames.add(toolName);
|
|
60
57
|
}
|
|
@@ -97,7 +94,7 @@ export function syncRegisteredNativeCursorToolsForModel(
|
|
|
97
94
|
if (changed) pi.setActiveTools([...activeToolNames]);
|
|
98
95
|
}
|
|
99
96
|
|
|
100
|
-
function ensureNativeCursorToolsRegisteredForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext): void {
|
|
97
|
+
async function ensureNativeCursorToolsRegisteredForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext): Promise<void> {
|
|
101
98
|
if (!isCursorNativeToolRegistrationRequested()) {
|
|
102
99
|
registeredNativeToolNames.clear();
|
|
103
100
|
skippedNativeToolNames.clear();
|
|
@@ -107,31 +104,22 @@ function ensureNativeCursorToolsRegisteredForModel(pi: CursorNativeToolRegistryA
|
|
|
107
104
|
|
|
108
105
|
const nonCoreToolNames = NATIVE_CURSOR_TOOL_NAMES.filter((toolName) => !isCursorCorePiReplayToolName(toolName));
|
|
109
106
|
const skippedToolNames = [
|
|
110
|
-
...registerNativeCursorToolsFromSet(pi, nonCoreToolNames),
|
|
111
|
-
...registerNativeCursorToolsFromSet(pi, CURSOR_CORE_PI_REPLAY_TOOL_NAMES),
|
|
107
|
+
...(await registerNativeCursorToolsFromSet(pi, nonCoreToolNames)),
|
|
108
|
+
...(await registerNativeCursorToolsFromSet(pi, CURSOR_CORE_PI_REPLAY_TOOL_NAMES)),
|
|
112
109
|
];
|
|
113
110
|
notifySkippedNativeCursorToolsIfNeeded(ctx, skippedToolNames);
|
|
114
111
|
}
|
|
115
112
|
|
|
116
|
-
function ensureThenSyncNativeCursorToolsForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext): void {
|
|
113
|
+
async function ensureThenSyncNativeCursorToolsForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext): Promise<void> {
|
|
117
114
|
if (isCursorModel(ctx.model) && !hasAttemptedNativeCursorToolRegistration()) {
|
|
118
|
-
ensureNativeCursorToolsRegisteredForModel(pi, ctx);
|
|
115
|
+
await ensureNativeCursorToolsRegisteredForModel(pi, ctx);
|
|
119
116
|
}
|
|
120
117
|
syncRegisteredNativeCursorToolsForModel(pi, ctx.model);
|
|
121
118
|
}
|
|
122
119
|
|
|
123
120
|
export function registerCursorNativeToolDisplay(pi: CursorNativeToolDisplayExtensionApi): void {
|
|
124
|
-
pi
|
|
125
|
-
ensureThenSyncNativeCursorToolsForModel(pi, ctx);
|
|
126
|
-
});
|
|
127
|
-
pi.on("before_agent_start", (_event, ctx) => {
|
|
128
|
-
ensureThenSyncNativeCursorToolsForModel(pi, ctx);
|
|
129
|
-
});
|
|
130
|
-
pi.on("turn_start", (_event, ctx) => {
|
|
131
|
-
ensureThenSyncNativeCursorToolsForModel(pi, ctx);
|
|
132
|
-
});
|
|
133
|
-
pi.on("model_select", (event, ctx) => {
|
|
134
|
-
ensureThenSyncNativeCursorToolsForModel(pi, { ...ctx, model: event.model });
|
|
121
|
+
registerCursorModelLifecycle(pi, async (ctx) => {
|
|
122
|
+
await ensureThenSyncNativeCursorToolsForModel(pi, ctx);
|
|
135
123
|
});
|
|
136
124
|
}
|
|
137
125
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { basename
|
|
2
|
+
import { basename } from "node:path";
|
|
3
3
|
import { getLanguageFromPath, highlightCode, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { Image, Text, type Component } from "@earendil-works/pi-tui";
|
|
5
5
|
import { Type } from "typebox";
|
|
6
6
|
import { resolveCursorEditDiff } from "./cursor-edit-diff.js";
|
|
7
|
+
import { inferImageMimeType } from "./cursor-tool-result-display-readers.js";
|
|
7
8
|
import { LOCAL_READ_PREVIEW_NOTICE, isLocalReadPreviewContent } from "./cursor-transcript-utils.js";
|
|
8
9
|
import {
|
|
9
10
|
CURSOR_REPLAY_ACTIVITY_TOOL_NAME,
|
|
@@ -17,7 +18,6 @@ import {
|
|
|
17
18
|
type CursorReplayActivityDetails,
|
|
18
19
|
type CursorReplayToolDetails,
|
|
19
20
|
type CursorReplayNativeWriteDetails,
|
|
20
|
-
isCursorReplayNativeEditDetails,
|
|
21
21
|
isCursorReplayGenerateImageDetails,
|
|
22
22
|
isCursorReplayActivityDetails,
|
|
23
23
|
parseCursorReplayToolDetails,
|
|
@@ -49,22 +49,6 @@ type CursorReplayRenderCall = NonNullable<ToolDefinition<typeof cursorReplayTool
|
|
|
49
49
|
type CursorReplayRenderResult = NonNullable<ToolDefinition<typeof cursorReplayToolSchema, unknown>["renderResult"]>;
|
|
50
50
|
export type CursorReplayRenderTheme = Parameters<CursorReplayRenderCall>[1];
|
|
51
51
|
|
|
52
|
-
function inferImageMimeTypeFromPath(path: string | undefined): string | undefined {
|
|
53
|
-
switch (extname(path ?? "").toLowerCase()) {
|
|
54
|
-
case ".png":
|
|
55
|
-
return "image/png";
|
|
56
|
-
case ".jpg":
|
|
57
|
-
case ".jpeg":
|
|
58
|
-
return "image/jpeg";
|
|
59
|
-
case ".gif":
|
|
60
|
-
return "image/gif";
|
|
61
|
-
case ".webp":
|
|
62
|
-
return "image/webp";
|
|
63
|
-
default:
|
|
64
|
-
return undefined;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
52
|
function readImageFileForReplay(path: string | undefined): string | undefined {
|
|
69
53
|
if (!path) return undefined;
|
|
70
54
|
try {
|
|
@@ -464,7 +448,7 @@ function renderExpandableCursorReplayResult(
|
|
|
464
448
|
}
|
|
465
449
|
if (details.imagePath && !isError && context.showImages) {
|
|
466
450
|
const imageData = readImageFileForReplay(details.imagePath);
|
|
467
|
-
const mimeType = details.imageMimeType ??
|
|
451
|
+
const mimeType = details.imageMimeType ?? inferImageMimeType(details.imagePath);
|
|
468
452
|
if (imageData && mimeType) return buildImageReplayComponent(rendered, imageData, mimeType, basename(details.imagePath ?? "generated-image"), theme);
|
|
469
453
|
}
|
|
470
454
|
return new Text(rendered, 0, 0);
|
|
@@ -485,7 +469,6 @@ function renderCursorReplayEditResult(
|
|
|
485
469
|
function renderCursorReplayWriteResult(
|
|
486
470
|
details: CursorReplayNativeWriteDetails,
|
|
487
471
|
result: Parameters<CursorReplayRenderResult>[0],
|
|
488
|
-
options: Parameters<CursorReplayRenderResult>[1],
|
|
489
472
|
theme: Parameters<CursorReplayRenderResult>[2],
|
|
490
473
|
): Component {
|
|
491
474
|
const text = firstContentText(result);
|
|
@@ -532,7 +515,7 @@ function renderCursorReplayDetails(
|
|
|
532
515
|
case "nativeEdit":
|
|
533
516
|
return renderCursorReplayEditResult(details, options, theme);
|
|
534
517
|
case "nativeWrite":
|
|
535
|
-
return renderCursorReplayWriteResult(details, result,
|
|
518
|
+
return renderCursorReplayWriteResult(details, result, theme);
|
|
536
519
|
case "generateImage":
|
|
537
520
|
return renderCursorGenerateImageResult(details, result, options, theme, context, isError);
|
|
538
521
|
case "activity":
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CursorPiToolDisplay } from "./cursor-
|
|
1
|
+
import type { CursorPiToolDisplay } from "./cursor-transcript-utils.js";
|
|
2
2
|
import { parseOptionalEnvBoolean } from "./cursor-env-boolean.js";
|
|
3
3
|
|
|
4
4
|
export interface CursorNativeToolDisplayItem extends CursorPiToolDisplay {
|