pi-cursor-sdk 0.1.37 → 0.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +2 -2
  3. package/docs/cursor-model-ux-spec.md +1 -1
  4. package/docs/cursor-native-tool-replay.md +5 -5
  5. package/package.json +1 -1
  6. package/scripts/platform-smoke/card-detect.mjs +1 -1
  7. package/src/context-window-cache.ts +10 -14
  8. package/src/context.ts +1 -1
  9. package/src/cursor-agent-message-web-tools.ts +2 -1
  10. package/src/cursor-agents-context-registration.ts +18 -0
  11. package/src/cursor-agents-context.ts +21 -30
  12. package/src/cursor-edit-diff.ts +4 -2
  13. package/src/cursor-fallback-warning.ts +22 -0
  14. package/src/cursor-incomplete-tool-visibility.ts +5 -10
  15. package/src/cursor-live-run-coordinator.ts +1 -1
  16. package/src/cursor-mcp-timeout-override.ts +0 -2
  17. package/src/cursor-model-lifecycle.ts +72 -0
  18. package/src/cursor-native-replay-routing.ts +1 -1
  19. package/src/cursor-native-replay-trace.ts +1 -1
  20. package/src/cursor-native-tool-display-registration.ts +16 -28
  21. package/src/cursor-native-tool-display-replay.ts +4 -21
  22. package/src/cursor-native-tool-display-state.ts +1 -1
  23. package/src/cursor-native-tool-display-tools.ts +10 -17
  24. package/src/cursor-native-tool-names.ts +16 -0
  25. package/src/cursor-pi-tool-bridge-env.ts +12 -0
  26. package/src/cursor-pi-tool-bridge-mcp.ts +16 -21
  27. package/src/cursor-pi-tool-bridge-run.ts +5 -5
  28. package/src/cursor-pi-tool-bridge-server.ts +8 -3
  29. package/src/cursor-pi-tool-bridge-snapshot.ts +7 -13
  30. package/src/cursor-pi-tool-bridge.ts +7 -7
  31. package/src/cursor-provider-errors.ts +11 -4
  32. package/src/cursor-provider-lazy.ts +51 -0
  33. package/src/cursor-provider-live-run-drain.ts +1 -1
  34. package/src/cursor-provider-run-finalizer.ts +5 -5
  35. package/src/cursor-provider-run-outcome.ts +0 -1
  36. package/src/cursor-provider-turn-coordinator.ts +16 -6
  37. package/src/cursor-provider-turn-display-router.ts +5 -1
  38. package/src/cursor-provider-turn-emit.ts +1 -1
  39. package/src/cursor-provider-turn-lifecycle-emitter.ts +1 -5
  40. package/src/cursor-provider-turn-prepare.ts +13 -9
  41. package/src/cursor-provider-turn-runner.ts +3 -11
  42. package/src/cursor-provider-turn-sdk-normalizer.ts +28 -5
  43. package/src/cursor-provider-turn-send.ts +7 -2
  44. package/src/cursor-provider-turn-shell-output.ts +38 -3
  45. package/src/cursor-provider-turn-types.ts +1 -3
  46. package/src/cursor-provider.ts +3 -2
  47. package/src/cursor-question-tool.ts +5 -18
  48. package/src/cursor-record-utils.ts +42 -0
  49. package/src/cursor-replay-activity-builders.ts +16 -122
  50. package/src/cursor-replay-tool-details.ts +52 -80
  51. package/src/cursor-sdk-event-debug.ts +6 -6
  52. package/src/cursor-sensitive-text.ts +4 -4
  53. package/src/cursor-session-agent-lifecycle.ts +47 -0
  54. package/src/cursor-session-agent.ts +9 -47
  55. package/src/cursor-session-scope.ts +23 -4
  56. package/src/cursor-setting-sources.ts +8 -8
  57. package/src/cursor-skill-tool.ts +25 -32
  58. package/src/cursor-state.ts +66 -45
  59. package/src/cursor-tool-lifecycle.ts +22 -10
  60. package/src/cursor-tool-presentation-registry.ts +27 -18
  61. package/src/cursor-tool-result-display-readers.ts +185 -0
  62. package/src/cursor-tool-transcript.ts +17 -33
  63. package/src/cursor-tool-visibility.ts +9 -1
  64. package/src/cursor-transcript-tool-formatters.ts +23 -172
  65. package/src/cursor-transcript-tool-specs.ts +16 -41
  66. package/src/cursor-transcript-utils.ts +2 -34
  67. package/src/cursor-usage-accounting.ts +0 -6
  68. package/src/cursor-web-tool-activity.ts +4 -12
  69. package/src/cursor-web-tool-args.ts +1 -9
  70. package/src/index.ts +15 -16
  71. package/src/model-discovery.ts +5 -4
  72. package/src/model-list-cache.ts +37 -38
  73. package/src/cursor-native-tool-display.ts +0 -10
  74. package/src/cursor-provider-turn-api-key.ts +0 -1
  75. package/src/cursor-provider-turn-message-offset.ts +0 -15
  76. package/src/cursor-session-cwd.ts +0 -28
  77. package/src/cursor-tool-names.ts +0 -9
@@ -11,16 +11,16 @@ export function resolveCursorSettingSources(raw?: string): SettingSource[] | und
11
11
  return resolveCursorSettingSourcesJs(raw) as SettingSource[] | undefined;
12
12
  }
13
13
 
14
- export function getEffectiveCursorSettingSources(raw: string | undefined = process.env[CURSOR_SETTING_SOURCES_ENV]): SettingSource[] | undefined {
14
+ export function getEffectiveCursorSettingSources(
15
+ raw: string | undefined = process.env[CURSOR_SETTING_SOURCES_ENV],
16
+ ): SettingSource[] | undefined {
15
17
  return resolveCursorSettingSources(raw);
16
18
  }
17
19
 
18
- export function cursorSettingSourcesLoadUserAgentsRules(settingSources: SettingSource[] | undefined): boolean {
20
+ export function cursorSettingSourcesIncludes(
21
+ settingSources: SettingSource[] | undefined,
22
+ source: Extract<SettingSource, "user" | "project">,
23
+ ): boolean {
19
24
  if (!settingSources?.length) return false;
20
- return settingSources.includes("all") || settingSources.includes("user");
21
- }
22
-
23
- export function cursorSettingSourcesLoadProjectAgentsRules(settingSources: SettingSource[] | undefined): boolean {
24
- if (!settingSources?.length) return false;
25
- return settingSources.includes("all") || settingSources.includes("project");
25
+ return settingSources.includes("all") || settingSources.includes(source);
26
26
  }
@@ -2,19 +2,15 @@ import type { Dirent } from "node:fs";
2
2
  import { readdir, readFile } from "node:fs/promises";
3
3
  import { dirname, join, relative } from "node:path";
4
4
  import type {
5
- BeforeAgentStartEvent,
6
- BeforeAgentStartEventResult,
7
5
  BuildSystemPromptOptions,
8
6
  ExtensionAPI,
9
7
  ExtensionContext,
10
- ExtensionHandler,
11
- SessionStartEvent,
12
8
  Skill,
13
- TurnStartEvent,
14
9
  } from "@earendil-works/pi-coding-agent";
15
10
  import { Type } from "typebox";
16
11
  import { isCursorModel } from "./cursor-model.js";
17
- import { resolveCursorPiToolBridgeEnabled } from "./cursor-pi-tool-bridge-snapshot.js";
12
+ import { registerCursorModelLifecycle, type CursorModelLifecycleExtensionApi } from "./cursor-model-lifecycle.js";
13
+ import { resolveCursorPiToolBridgeEnabled } from "./cursor-pi-tool-bridge-env.js";
18
14
 
19
15
  export const CURSOR_ACTIVATE_SKILL_TOOL_NAME = "cursor_activate_skill";
20
16
  export const CURSOR_ACTIVATE_SKILL_MCP_NAME = "pi__cursor_activate_skill";
@@ -23,12 +19,7 @@ const AVAILABLE_SKILLS_SECTION_PATTERN = /\n\nThe following skills provide speci
23
19
  const MAX_SKILL_RESOURCES = 80;
24
20
  const RESOURCE_DIR_NAMES = ["scripts", "references", "assets"] as const;
25
21
 
26
- type CursorSkillToolExtensionApi = Pick<ExtensionAPI, "getActiveTools" | "registerTool" | "setActiveTools"> & {
27
- on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
28
- on(event: "before_agent_start", handler: ExtensionHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>): void;
29
- on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;
30
- on(event: "model_select", handler: (event: { model: ExtensionContext["model"] }, ctx: ExtensionContext) => Promise<void> | void): void;
31
- };
22
+ type CursorSkillToolExtensionApi = Pick<ExtensionAPI, "getActiveTools" | "registerTool" | "setActiveTools"> & CursorModelLifecycleExtensionApi;
32
23
 
33
24
  type CursorActivateSkillParams = {
34
25
  name?: string;
@@ -229,26 +220,28 @@ export function registerCursorSkillTool(pi: CursorSkillToolExtensionApi): void {
229
220
  syncCursorSkillToolForModel(pi, model);
230
221
  };
231
222
 
232
- pi.on("session_start", (_event, ctx) => {
233
- clearSkillsAndSync(ctx.model);
234
- });
235
- pi.on("model_select", (event) => {
236
- clearSkillsAndSync(event.model);
237
- });
238
- pi.on("turn_start", (_event, ctx) => {
239
- if (!isCursorModel(ctx.model)) setCurrentSkills([]);
240
- syncCursorSkillToolForModel(pi, ctx.model);
241
- });
242
- pi.on("before_agent_start", (event, ctx) => {
243
- if (isCursorModel(ctx.model)) {
244
- setCurrentSkills(event.systemPromptOptions?.skills);
245
- } else {
246
- setCurrentSkills([]);
247
- }
248
- syncCursorSkillToolForModel(pi, ctx.model);
249
- const resolved = resolveCursorSkillSystemPrompt(event.systemPrompt, ctx.model, event.systemPromptOptions);
250
- if (resolved === event.systemPrompt) return undefined;
251
- return { systemPrompt: resolved };
223
+ registerCursorModelLifecycle(pi, {
224
+ sessionStart: (_event, ctx) => {
225
+ clearSkillsAndSync(ctx.model);
226
+ },
227
+ modelSelect: (event) => {
228
+ clearSkillsAndSync(event.model);
229
+ },
230
+ turnStart: (_event, ctx) => {
231
+ if (!isCursorModel(ctx.model)) setCurrentSkills([]);
232
+ syncCursorSkillToolForModel(pi, ctx.model);
233
+ },
234
+ beforeAgentStart: (event, ctx) => {
235
+ if (isCursorModel(ctx.model)) {
236
+ setCurrentSkills(event.systemPromptOptions?.skills);
237
+ } else {
238
+ setCurrentSkills([]);
239
+ }
240
+ syncCursorSkillToolForModel(pi, ctx.model);
241
+ const resolved = resolveCursorSkillSystemPrompt(event.systemPrompt, ctx.model, event.systemPromptOptions);
242
+ if (resolved === event.systemPrompt) return undefined;
243
+ return { systemPrompt: resolved };
244
+ },
252
245
  });
253
246
  }
254
247
 
@@ -15,9 +15,12 @@ import {
15
15
  } from "./cursor-pi-tool-bridge-snapshot.js";
16
16
  import {
17
17
  CURSOR_SETTING_SOURCES_ENV,
18
- getEffectiveCursorSettingSources,
18
+ resolveCursorSettingSources,
19
19
  } from "./cursor-setting-sources.js";
20
20
  import { isCursorModel } from "./cursor-model.js";
21
+ import { registerCursorModelLifecycle } from "./cursor-model-lifecycle.js";
22
+ import { asRecord } from "./cursor-record-utils.js";
23
+ import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
21
24
  import { getCursorModelMetadata } from "./model-discovery.js";
22
25
 
23
26
  const FAST_ENTRY_TYPE = "cursor-fast-state";
@@ -58,6 +61,7 @@ let cliForceFast = false;
58
61
  let cliForceNoFast = false;
59
62
  let sessionCursorAgentMode: AgentModeOption | undefined;
60
63
  let cliCursorModeState: CursorCliModeState = { kind: "unset" };
64
+ const invalidCursorModeNotifiedSessionScopeKeys = new Set<string>();
61
65
 
62
66
  export function isCursorAgentMode(value: unknown): value is AgentModeOption {
63
67
  return value === "agent" || value === "plan";
@@ -69,13 +73,10 @@ export function parseCursorAgentMode(raw: unknown): AgentModeOption | undefined
69
73
  return isCursorAgentMode(mode) ? mode : undefined;
70
74
  }
71
75
 
72
- function isRecord(value: unknown): value is Record<string, unknown> {
73
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
74
- }
75
-
76
76
  function isCursorFastEntryData(value: unknown): value is CursorFastEntryData {
77
- if (!isRecord(value)) return false;
78
- return (typeof value.modelId === "string" || typeof value.baseModelId === "string") && typeof value.fast === "boolean";
77
+ const record = asRecord(value);
78
+ if (!record) return false;
79
+ return (typeof record.modelId === "string" || typeof record.baseModelId === "string") && typeof record.fast === "boolean";
79
80
  }
80
81
 
81
82
  function getCursorFastEntryModelId(data: CursorFastEntryData): string {
@@ -83,17 +84,19 @@ function getCursorFastEntryModelId(data: CursorFastEntryData): string {
83
84
  }
84
85
 
85
86
  function isCursorModeEntryData(value: unknown): value is CursorModeEntryData {
86
- return isRecord(value) && isCursorAgentMode(value.mode);
87
+ return isCursorAgentMode(asRecord(value)?.mode);
87
88
  }
88
89
 
89
90
  function parseCursorGlobalConfig(value: unknown): CursorGlobalConfig | undefined {
90
- if (!isRecord(value)) return undefined;
91
- const { fastDefaults } = value;
91
+ const record = asRecord(value);
92
+ if (!record) return undefined;
93
+ const { fastDefaults } = record;
92
94
  if (fastDefaults === undefined) return {};
93
- if (!isRecord(fastDefaults)) return undefined;
95
+ const fastDefaultsRecord = asRecord(fastDefaults);
96
+ if (!fastDefaultsRecord) return undefined;
94
97
  return {
95
98
  fastDefaults: Object.fromEntries(
96
- Object.entries(fastDefaults).filter((entry): entry is [string, boolean] => typeof entry[1] === "boolean"),
99
+ Object.entries(fastDefaultsRecord).filter((entry): entry is [string, boolean] => typeof entry[1] === "boolean"),
97
100
  ),
98
101
  };
99
102
  }
@@ -174,23 +177,38 @@ function formatInvalidCursorMode(raw: string): string {
174
177
  return `Invalid --cursor-mode "${raw}". Use "agent" or "plan".`;
175
178
  }
176
179
 
177
- export function getEffectiveCursorAgentMode(): AgentModeOption {
180
+ export type CursorAgentModeResolution =
181
+ | { kind: "valid"; mode: AgentModeOption }
182
+ | { kind: "invalid"; raw: string; message: string };
183
+
184
+ export function getStoredCursorAgentMode(): AgentModeOption {
185
+ return sessionCursorAgentMode ?? DEFAULT_CURSOR_AGENT_MODE;
186
+ }
187
+
188
+ export function resolveCursorAgentMode(): CursorAgentModeResolution {
178
189
  switch (cliCursorModeState.kind) {
179
190
  case "valid":
180
- return cliCursorModeState.mode;
191
+ return { kind: "valid", mode: cliCursorModeState.mode };
181
192
  case "invalid":
182
- throw new Error(cliCursorModeState.message);
193
+ return { kind: "invalid", raw: cliCursorModeState.raw, message: cliCursorModeState.message };
183
194
  case "unset":
184
- return sessionCursorAgentMode ?? DEFAULT_CURSOR_AGENT_MODE;
195
+ return { kind: "valid", mode: getStoredCursorAgentMode() };
185
196
  }
186
197
  }
187
198
 
199
+ export function getCursorProviderAgentModeOrThrow(): AgentModeOption {
200
+ const resolution = resolveCursorAgentMode();
201
+ if (resolution.kind === "invalid") throw new Error(resolution.message);
202
+ return resolution.mode;
203
+ }
204
+
188
205
  function formatCursorStatus(fast: boolean | undefined): string | undefined {
189
206
  const parts: string[] = [];
207
+ const modeResolution = resolveCursorAgentMode();
190
208
  if (fast === true) parts.push("fast");
191
- if (cliCursorModeState.kind === "invalid") {
209
+ if (modeResolution.kind === "invalid") {
192
210
  parts.push("mode invalid");
193
- } else if (getEffectiveCursorAgentMode() === "plan") {
211
+ } else if (modeResolution.mode === "plan") {
194
212
  parts.push("plan");
195
213
  }
196
214
  return parts.length > 0 ? `cursor ${parts.join(" · ")}` : undefined;
@@ -255,7 +273,7 @@ function persistCursorModePreference(pi: Pick<ExtensionAPI, "appendEntry">, mode
255
273
  }
256
274
  }
257
275
 
258
- function restoreCliCursorMode(raw: boolean | string | undefined, mode: ExtensionContext["mode"], notify: ExtensionContext["ui"]["notify"]): void {
276
+ function restoreCliCursorMode(raw: boolean | string | undefined): void {
259
277
  cliCursorModeState = { kind: "unset" };
260
278
  if (raw === undefined || raw === "" || raw === false) return;
261
279
  const parsed = parseCursorAgentMode(raw);
@@ -266,15 +284,19 @@ function restoreCliCursorMode(raw: boolean | string | undefined, mode: Extension
266
284
  const rawText = String(raw);
267
285
  const message = formatInvalidCursorMode(rawText);
268
286
  cliCursorModeState = { kind: "invalid", raw: rawText, message };
269
- if (mode === "tui") {
270
- notify(message, "error");
271
- return;
272
- }
273
- throw new Error(message);
287
+ }
288
+
289
+ function notifyInvalidCursorModeIfCursorActive(ctx: Pick<ExtensionContext, "hasUI" | "mode" | "ui">): void {
290
+ const modeResolution = resolveCursorAgentMode();
291
+ if (modeResolution.kind !== "invalid" || !ctx.hasUI || ctx.mode !== "tui") return;
292
+ const scopeKey = getCursorSessionScopeKey();
293
+ if (invalidCursorModeNotifiedSessionScopeKeys.has(scopeKey)) return;
294
+ invalidCursorModeNotifiedSessionScopeKeys.add(scopeKey);
295
+ ctx.ui.notify(modeResolution.message, "error");
274
296
  }
275
297
 
276
298
  function formatEffectiveCursorSettingSourcesLabel(raw: string | undefined = process.env[CURSOR_SETTING_SOURCES_ENV]): string {
277
- const effective = getEffectiveCursorSettingSources(raw);
299
+ const effective = resolveCursorSettingSources(raw);
278
300
  const effectiveLabel = effective === undefined ? "none" : effective.join(",");
279
301
  const rawLabel = raw?.trim() ? raw.trim() : "(unset → all)";
280
302
  return `${rawLabel} (effective: ${effectiveLabel})`;
@@ -395,10 +417,11 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
395
417
  const usage = "Usage: /cursor-mode agent|plan";
396
418
  const mode = parseCursorAgentMode(args);
397
419
  if (!args.trim()) {
398
- try {
399
- ctx.ui.notify(`Cursor mode is ${getEffectiveCursorAgentMode()}. ${usage}`, "info");
400
- } catch (error) {
401
- ctx.ui.notify(`${error instanceof Error ? error.message : String(error)} ${usage}`, "error");
420
+ const modeResolution = resolveCursorAgentMode();
421
+ if (modeResolution.kind === "invalid") {
422
+ ctx.ui.notify(`${modeResolution.message} ${usage}`, "error");
423
+ } else {
424
+ ctx.ui.notify(`Cursor mode is ${modeResolution.mode}. ${usage}`, "info");
402
425
  }
403
426
  return;
404
427
  }
@@ -429,28 +452,26 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
429
452
  },
430
453
  });
431
454
 
432
- pi.on("session_start", async (_event, ctx) => {
433
- globalFastPreferences = loadGlobalFastPreferences();
434
- cliForceFast = pi.getFlag("cursor-fast") === true;
435
- cliForceNoFast = pi.getFlag("cursor-no-fast") === true;
436
- restoreSessionFastPreferences(ctx);
437
- restoreSessionCursorMode(ctx);
438
- restoreCliCursorMode(pi.getFlag("cursor-mode"), ctx.mode, ctx.ui.notify.bind(ctx.ui));
439
- updateCursorStatus(ctx);
440
- });
441
-
442
- pi.on("model_select", async (event, ctx) => {
443
- updateCursorStatus(ctx, event.model);
444
- });
445
-
446
- pi.on("turn_start", async (_event, ctx) => {
447
- updateCursorStatus(ctx);
455
+ registerCursorModelLifecycle(pi, {
456
+ sessionStart: (_event, ctx) => {
457
+ globalFastPreferences = loadGlobalFastPreferences();
458
+ cliForceFast = pi.getFlag("cursor-fast") === true;
459
+ cliForceNoFast = pi.getFlag("cursor-no-fast") === true;
460
+ restoreSessionFastPreferences(ctx);
461
+ restoreSessionCursorMode(ctx);
462
+ restoreCliCursorMode(pi.getFlag("cursor-mode"));
463
+ },
464
+ sync: (ctx) => {
465
+ if (isCursorModel(ctx.model)) notifyInvalidCursorModeIfCursorActive(ctx);
466
+ updateCursorStatus(ctx);
467
+ },
448
468
  });
449
469
  }
450
470
 
451
471
  function resetCursorModeStateForTests(): void {
452
472
  sessionCursorAgentMode = undefined;
453
473
  cliCursorModeState = { kind: "unset" };
474
+ invalidCursorModeNotifiedSessionScopeKeys.clear();
454
475
  }
455
476
 
456
477
  export const __testUtils = {
@@ -1,9 +1,10 @@
1
1
  import { truncateCursorDisplayLine } from "./cursor-display-text.js";
2
2
  import { scrubSensitiveText } from "./cursor-sensitive-text.js";
3
3
  import { getCursorToolLifecycleLabelKind } from "./cursor-tool-presentation-registry.js";
4
- import { extractWebSearchQuery } from "./cursor-web-tool-activity.js";
5
- import { firstNonEmptyLine, getArray, getString, truncateArg } from "./cursor-transcript-utils.js";
6
- import { classifyCursorToolVisibility } from "./cursor-tool-visibility.js";
4
+ import { extractWebSearchQuery } from "./cursor-web-tool-args.js";
5
+ import { getArray, getString } from "./cursor-record-utils.js";
6
+ import { firstNonEmptyLine, truncateArg } from "./cursor-transcript-utils.js";
7
+ import { classifyCursorToolVisibility, type CursorToolVisibility } from "./cursor-tool-visibility.js";
7
8
 
8
9
  /** Defer pending lifecycle lines so fast start+complete pairs coalesce into the completed replay card only. */
9
10
  export const CURSOR_TOOL_LIFECYCLE_DEFER_MS = 75;
@@ -12,8 +13,7 @@ export function isCursorToolLifecycleEligible(toolCall: unknown): boolean {
12
13
  return classifyCursorToolVisibility(toolCall).lifecycleEligible;
13
14
  }
14
15
 
15
- function getCursorToolLifecycleTitle(toolCall: unknown): string {
16
- const visibility = classifyCursorToolVisibility(toolCall);
16
+ function getCursorToolLifecycleTitle(visibility: CursorToolVisibility): string {
17
17
  return visibility.lifecycleTitle ?? `Cursor ${visibility.normalizedName}`;
18
18
  }
19
19
 
@@ -36,8 +36,15 @@ function scrubLifecycleDetail(value: string | undefined, apiKey?: string): strin
36
36
  return scrubbed;
37
37
  }
38
38
 
39
- export function buildCursorToolLifecycleLabel(toolCall: unknown, apiKey?: string): string | undefined {
40
- const visibility = classifyCursorToolVisibility(toolCall);
39
+ function scrubShellLifecycleDetail(value: string | undefined, apiKey?: string): string | undefined {
40
+ if (!value?.trim()) return undefined;
41
+ return truncateCursorDisplayLine(scrubSensitiveText(value, apiKey));
42
+ }
43
+
44
+ function buildCursorToolLifecycleLabelFromVisibility(
45
+ visibility: CursorToolVisibility,
46
+ apiKey?: string,
47
+ ): string | undefined {
41
48
  const args = visibility.args;
42
49
 
43
50
  switch (getCursorToolLifecycleLabelKind(visibility.normalizedKey)) {
@@ -45,7 +52,7 @@ export function buildCursorToolLifecycleLabel(toolCall: unknown, apiKey?: string
45
52
  return scrubLifecycleDetail(getString(args, "description"), apiKey) ?? "task";
46
53
  }
47
54
  case "shell": {
48
- return scrubLifecycleDetail(getString(args, "command") ?? getString(args, "cmd"), apiKey);
55
+ return scrubShellLifecycleDetail(getString(args, "command") ?? getString(args, "cmd"), apiKey);
49
56
  }
50
57
  case "mcp": {
51
58
  return scrubLifecycleDetail(getString(args, "toolName"), apiKey) ?? "mcp";
@@ -79,8 +86,13 @@ export function buildCursorToolLifecycleLabel(toolCall: unknown, apiKey?: string
79
86
  }
80
87
  }
81
88
 
89
+ export function buildCursorToolLifecycleLabel(toolCall: unknown, apiKey?: string): string | undefined {
90
+ return buildCursorToolLifecycleLabelFromVisibility(classifyCursorToolVisibility(toolCall), apiKey);
91
+ }
92
+
82
93
  export function formatCursorToolLifecycleProgressText(toolCall: unknown, apiKey?: string): string | undefined {
83
- const label = buildCursorToolLifecycleLabel(toolCall, apiKey);
94
+ const visibility = classifyCursorToolVisibility(toolCall);
95
+ const label = buildCursorToolLifecycleLabelFromVisibility(visibility, apiKey);
84
96
  if (!label) return undefined;
85
- return `${getCursorToolLifecycleTitle(toolCall)}: ${label}\n`;
97
+ return `${getCursorToolLifecycleTitle(visibility)}: ${label}\n`;
86
98
  }
@@ -18,23 +18,15 @@ import {
18
18
  withActivitySummaryFallback,
19
19
  type CursorReplayActivitySummaryOverride,
20
20
  type CursorReplayGenerateImageSummaryArgs,
21
- type CursorReplayPathSummaryArgs,
22
- type CursorReplayPlanSummaryArgs,
23
- type CursorReplayReadLintsSummaryArgs,
24
- type CursorReplayRecordScreenSummaryArgs,
25
- type CursorReplaySemSearchSummaryArgs,
26
21
  type CursorReplaySummaryArgs,
27
- type CursorReplayTaskSummaryArgs,
28
- type CursorReplayTodoSummaryArgs,
29
22
  type CursorReplayWebFetchSummaryArgs,
30
23
  type CursorReplayWebSearchSummaryArgs,
31
24
  } from "./cursor-replay-summary-args.js";
25
+ import { truncateCursorDisplayLine } from "./cursor-display-text.js";
32
26
  import {
33
- buildCollapsedReplayDetailFields,
34
27
  buildCreatePlanReplaySummaryArgs,
35
28
  buildDeleteReplayDetailFields,
36
29
  buildDeleteReplaySummaryArgs,
37
- buildEmptyReplayDetailFields,
38
30
  buildGenerateImageReplayDetailFields,
39
31
  buildGenerateImageReplaySummaryArgs,
40
32
  buildMcpReplaySummaryArgs,
@@ -55,6 +47,9 @@ import type {
55
47
 
56
48
  export const CURSOR_REPLAY_ACTIVITY_TOOL_NAME = "cursor" as const;
57
49
 
50
+ const EMPTY_REPLAY_DETAIL_FIELDS = (): Record<string, never> => ({});
51
+ const COLLAPSED_REPLAY_DETAIL_FIELDS = (): { collapseDetailsByDefault: true } => ({ collapseDetailsByDefault: true });
52
+
58
53
  export type CursorWebToolKind = "webSearch" | "webFetch";
59
54
 
60
55
  export type CursorToolLifecycleLabelKind =
@@ -187,7 +182,7 @@ export const CURSOR_TOOL_PRESENTATION_SPECS = [
187
182
  replayCallSummary: withActivitySummaryFallback(summarizeReplayReadLints),
188
183
  activityReplay: {
189
184
  buildActivityArgs: buildReadLintsReplaySummaryArgs,
190
- buildDetails: buildEmptyReplayDetailFields,
185
+ buildDetails: EMPTY_REPLAY_DETAIL_FIELDS,
191
186
  },
192
187
  },
193
188
  {
@@ -198,7 +193,7 @@ export const CURSOR_TOOL_PRESENTATION_SPECS = [
198
193
  replayCallSummary: withActivitySummaryFallback(summarizeReplayTodoCount),
199
194
  activityReplay: {
200
195
  buildActivityArgs: ({ args, result }) => buildTodoReplaySummaryArgs(args, result),
201
- buildDetails: buildEmptyReplayDetailFields,
196
+ buildDetails: EMPTY_REPLAY_DETAIL_FIELDS,
202
197
  },
203
198
  },
204
199
  {
@@ -209,7 +204,7 @@ export const CURSOR_TOOL_PRESENTATION_SPECS = [
209
204
  replayCallSummary: withActivitySummaryFallback(summarizeReplayPlan),
210
205
  activityReplay: {
211
206
  buildActivityArgs: buildCreatePlanReplaySummaryArgs,
212
- buildDetails: buildEmptyReplayDetailFields,
207
+ buildDetails: EMPTY_REPLAY_DETAIL_FIELDS,
213
208
  },
214
209
  },
215
210
  {
@@ -220,7 +215,7 @@ export const CURSOR_TOOL_PRESENTATION_SPECS = [
220
215
  replayCallSummary: withActivitySummaryFallback(summarizeReplayTask),
221
216
  activityReplay: {
222
217
  buildActivityArgs: buildTaskReplaySummaryArgs,
223
- buildDetails: buildEmptyReplayDetailFields,
218
+ buildDetails: EMPTY_REPLAY_DETAIL_FIELDS,
224
219
  },
225
220
  },
226
221
  {
@@ -244,7 +239,7 @@ export const CURSOR_TOOL_PRESENTATION_SPECS = [
244
239
  replayCallSummary: withActivitySummaryFallback(summarizeReplayMcp),
245
240
  activityReplay: {
246
241
  buildActivityArgs: buildMcpReplaySummaryArgs,
247
- buildDetails: buildEmptyReplayDetailFields,
242
+ buildDetails: EMPTY_REPLAY_DETAIL_FIELDS,
248
243
  },
249
244
  },
250
245
  {
@@ -255,7 +250,7 @@ export const CURSOR_TOOL_PRESENTATION_SPECS = [
255
250
  replayCallSummary: withActivitySummaryFallback(formatReplaySemSearchQuery),
256
251
  activityReplay: {
257
252
  buildActivityArgs: buildSemSearchReplaySummaryArgs,
258
- buildDetails: buildEmptyReplayDetailFields,
253
+ buildDetails: EMPTY_REPLAY_DETAIL_FIELDS,
259
254
  },
260
255
  },
261
256
  {
@@ -266,7 +261,7 @@ export const CURSOR_TOOL_PRESENTATION_SPECS = [
266
261
  replayCallSummary: withActivitySummaryFallback(summarizeReplayRecordScreen),
267
262
  activityReplay: {
268
263
  buildActivityArgs: buildRecordScreenReplaySummaryArgs,
269
- buildDetails: buildEmptyReplayDetailFields,
264
+ buildDetails: EMPTY_REPLAY_DETAIL_FIELDS,
270
265
  },
271
266
  },
272
267
  {
@@ -282,7 +277,7 @@ export const CURSOR_TOOL_PRESENTATION_SPECS = [
282
277
  ),
283
278
  activityReplay: {
284
279
  buildActivityArgs: buildWebSearchReplaySummaryArgs,
285
- buildDetails: buildCollapsedReplayDetailFields,
280
+ buildDetails: COLLAPSED_REPLAY_DETAIL_FIELDS,
286
281
  },
287
282
  },
288
283
  {
@@ -298,7 +293,7 @@ export const CURSOR_TOOL_PRESENTATION_SPECS = [
298
293
  ),
299
294
  activityReplay: {
300
295
  buildActivityArgs: buildWebFetchReplaySummaryArgs,
301
- buildDetails: buildCollapsedReplayDetailFields,
296
+ buildDetails: COLLAPSED_REPLAY_DETAIL_FIELDS,
302
297
  },
303
298
  },
304
299
  ] as const satisfies readonly CursorToolPresentationSpec[];
@@ -389,6 +384,20 @@ export function getCursorReplayActivityTitle(toolName: string): string | undefin
389
384
  return spec.displayLabel;
390
385
  }
391
386
 
387
+ function buildCursorGenericActivityTitle(displayName: string): string {
388
+ if (!displayName || displayName === "unknown") return "Cursor tool";
389
+ return `Cursor ${truncateCursorDisplayLine(displayName, 120)}`;
390
+ }
391
+
392
+ /** Canonical activity title: registry label when known, otherwise neutral fallback. */
393
+ export function getCursorToolActivityTitle(toolName: string): string {
394
+ const normalized = normalizeCursorToolName(toolName);
395
+ const known = getCursorReplayActivityTitle(normalized);
396
+ if (known) return known;
397
+ const label = toolName.trim() || normalized;
398
+ return buildCursorGenericActivityTitle(label);
399
+ }
400
+
392
401
  function getCursorToolPresentationSpecByNormalizedKey(normalizedKey: string): CursorToolPresentationSpec | undefined {
393
402
  return SPECS_BY_NORMALIZED_KEY.get(normalizedKey.toLowerCase());
394
403
  }