pi-cursor-sdk 0.1.20 → 0.1.22

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 (89) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +49 -9
  3. package/docs/cursor-dogfood-checklist.md +57 -0
  4. package/docs/cursor-live-smoke-checklist.md +115 -9
  5. package/docs/cursor-model-ux-spec.md +58 -18
  6. package/docs/cursor-native-tool-replay.md +15 -7
  7. package/docs/cursor-native-tool-visual-audit.md +104 -59
  8. package/docs/cursor-testing-lessons.md +8 -3
  9. package/docs/cursor-tool-surfaces.md +69 -0
  10. package/package.json +34 -10
  11. package/scripts/debug-provider-events.d.mts +59 -0
  12. package/scripts/debug-provider-events.mjs +70 -175
  13. package/scripts/debug-sdk-events.d.mts +90 -0
  14. package/scripts/debug-sdk-events.mjs +36 -98
  15. package/scripts/fixtures/plan-strip-shim/index.ts +12 -0
  16. package/scripts/isolated-cursor-smoke.sh +264 -102
  17. package/scripts/lib/cursor-child-process.d.mts +10 -0
  18. package/scripts/lib/cursor-child-process.mjs +50 -0
  19. package/scripts/lib/cursor-cli-args.d.mts +63 -0
  20. package/scripts/lib/cursor-cli-args.mjs +129 -0
  21. package/scripts/lib/cursor-script-fail.d.mts +1 -0
  22. package/scripts/lib/cursor-script-fail.mjs +13 -0
  23. package/scripts/lib/cursor-sdk-output-filter.d.mts +5 -0
  24. package/scripts/lib/cursor-smoke-env.d.mts +38 -0
  25. package/scripts/lib/cursor-smoke-env.mjs +81 -0
  26. package/scripts/lib/cursor-smoke-shell.sh +174 -0
  27. package/scripts/lib/cursor-visual-render.d.mts +15 -0
  28. package/scripts/lib/cursor-visual-render.mjs +131 -0
  29. package/scripts/probe-mcp-coldstart.mjs +20 -38
  30. package/scripts/refresh-cursor-model-snapshots.mjs +29 -65
  31. package/scripts/steering-rpc-smoke.mjs +170 -65
  32. package/scripts/tmux-live-smoke.sh +152 -98
  33. package/scripts/visual-tui-smoke.mjs +659 -0
  34. package/shared/cursor-sdk-event-debug-env.d.mts +12 -0
  35. package/shared/cursor-sdk-event-debug-env.mjs +13 -0
  36. package/shared/cursor-sensitive-text.d.mts +1 -0
  37. package/{scripts/lib/cursor-probe-utils.mjs → shared/cursor-sensitive-text.mjs} +1 -13
  38. package/shared/cursor-setting-sources.d.mts +5 -0
  39. package/shared/cursor-setting-sources.mjs +22 -0
  40. package/src/context.ts +21 -12
  41. package/src/cursor-bridge-contract.ts +1 -3
  42. package/src/cursor-incomplete-tool-visibility.ts +22 -5
  43. package/src/cursor-native-tool-display-registration.ts +63 -27
  44. package/src/cursor-native-tool-display-replay.ts +246 -144
  45. package/src/cursor-native-tool-display-state.ts +2 -0
  46. package/src/cursor-native-tool-display-tools.ts +149 -41
  47. package/src/cursor-provider-live-run-drain.ts +1 -52
  48. package/src/cursor-provider-run-finalizer.ts +237 -0
  49. package/src/cursor-provider-run-outcome.ts +149 -0
  50. package/src/cursor-provider-turn-api-key.ts +8 -0
  51. package/src/cursor-provider-turn-coordinator.ts +98 -446
  52. package/src/cursor-provider-turn-display-router.ts +216 -0
  53. package/src/cursor-provider-turn-emit.ts +59 -0
  54. package/src/cursor-provider-turn-finalize.ts +119 -0
  55. package/src/cursor-provider-turn-lifecycle-emitter.ts +97 -0
  56. package/src/cursor-provider-turn-message-offset.ts +15 -0
  57. package/src/cursor-provider-turn-prepare.ts +216 -0
  58. package/src/cursor-provider-turn-runner.ts +140 -0
  59. package/src/cursor-provider-turn-sdk-normalizer.ts +88 -0
  60. package/src/cursor-provider-turn-send.ts +103 -0
  61. package/src/cursor-provider-turn-shell-output.ts +107 -0
  62. package/src/cursor-provider-turn-tool-ledger.ts +126 -0
  63. package/src/cursor-provider-turn-types.ts +87 -0
  64. package/src/cursor-provider.ts +16 -504
  65. package/src/cursor-replay-activity-builders.ts +276 -0
  66. package/src/cursor-replay-source-names.ts +33 -0
  67. package/src/cursor-replay-summary-args.ts +191 -0
  68. package/src/cursor-replay-tool-details.ts +464 -0
  69. package/src/cursor-run-final-text.ts +56 -0
  70. package/src/cursor-sdk-abort-error-guard.ts +4 -0
  71. package/src/cursor-sdk-event-debug-constants.ts +14 -5
  72. package/src/cursor-sdk-event-debug.ts +2 -1
  73. package/src/cursor-sensitive-text.ts +3 -36
  74. package/src/cursor-session-agent.ts +3 -1
  75. package/src/cursor-session-compaction-prep.ts +19 -0
  76. package/src/cursor-setting-sources.ts +7 -10
  77. package/src/cursor-state.ts +232 -28
  78. package/src/cursor-tool-lifecycle.ts +9 -8
  79. package/src/cursor-tool-manifest.ts +41 -0
  80. package/src/cursor-tool-names.ts +18 -106
  81. package/src/cursor-tool-presentation-registry.ts +556 -0
  82. package/src/cursor-tool-transcript.ts +1 -1
  83. package/src/cursor-tool-visibility.ts +3 -27
  84. package/src/cursor-transcript-tool-formatters.ts +0 -59
  85. package/src/cursor-transcript-tool-specs.ts +158 -233
  86. package/src/cursor-transcript-utils.ts +0 -44
  87. package/src/cursor-web-tool-activity.ts +10 -60
  88. package/src/cursor-web-tool-args.ts +39 -0
  89. package/src/index.ts +8 -10
@@ -0,0 +1 @@
1
+ export declare function scrubSensitiveText(text: string, apiKey?: string): string;
@@ -1,21 +1,9 @@
1
- export const CURSOR_SETTING_SOURCES_ENV = "PI_CURSOR_SETTING_SOURCES";
1
+ /** Canonical secret/bridge scrubbing (parity-tested by provider runtime and maintainer scripts). */
2
2
 
3
3
  function escapeRegExp(value) {
4
4
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5
5
  }
6
6
 
7
- export function resolveCursorSettingSources(raw) {
8
- const trimmed = raw?.trim();
9
- if (!trimmed) return ["all"];
10
- const normalized = trimmed.toLowerCase();
11
- if (["0", "false", "off", "none", "omit", "disabled"].includes(normalized)) return undefined;
12
- if (["1", "true", "on", "all"].includes(normalized)) return ["all"];
13
- return trimmed
14
- .split(",")
15
- .map((entry) => entry.trim())
16
- .filter(Boolean);
17
- }
18
-
19
7
  const BRIDGE_ENDPOINT_ROOT = "/cursor-pi-tool-bridge";
20
8
  const BRIDGE_ENDPOINT_TOKEN_PATTERN = "[^/\\s\"'<>]+";
21
9
  const BRIDGE_LOOPBACK_HOST_PATTERN = "127\\.0\\.0\\.1(?::\\d+)?";
@@ -0,0 +1,5 @@
1
+ export declare const CURSOR_SETTING_SOURCES_ENV: "PI_CURSOR_SETTING_SOURCES";
2
+
3
+ export declare function resolveCursorSettingSources(raw?: string): string[] | undefined;
4
+
5
+ export declare function serializeCursorSettingSources(settingSources: string[] | undefined): string;
@@ -0,0 +1,22 @@
1
+ /** Canonical Cursor settingSources parsing (parity-tested by provider runtime and maintainer scripts). */
2
+ export const CURSOR_SETTING_SOURCES_ENV = "PI_CURSOR_SETTING_SOURCES";
3
+
4
+ export function resolveCursorSettingSources(raw) {
5
+ const trimmed = raw?.trim();
6
+ if (!trimmed) return ["all"];
7
+ const normalized = trimmed.toLowerCase();
8
+ if (["0", "false", "off", "none", "omit", "disabled"].includes(normalized)) return undefined;
9
+ if (["1", "true", "on", "all"].includes(normalized)) return ["all"];
10
+ const sources = trimmed
11
+ .split(",")
12
+ .map((entry) => entry.trim())
13
+ .filter(Boolean);
14
+ if (sources.length === 0) return undefined;
15
+ return sources;
16
+ }
17
+
18
+ /** Serialize parsed settingSources for PI_CURSOR_SETTING_SOURCES (undefined => explicit none). */
19
+ export function serializeCursorSettingSources(settingSources) {
20
+ if (settingSources === undefined || settingSources.length === 0) return "none";
21
+ return settingSources.join(",");
22
+ }
package/src/context.ts CHANGED
@@ -2,7 +2,6 @@ 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 { getCursorPiBridgeContractText } from "./cursor-bridge-contract.js";
6
5
  import { getCursorReplayPromptLabel } from "./cursor-tool-names.js";
7
6
 
8
7
  export interface CursorPrompt {
@@ -14,6 +13,8 @@ export interface CursorPromptOptions {
14
13
  maxInputTokens?: number;
15
14
  charsPerToken?: number;
16
15
  imageTokenEstimate?: number;
16
+ /** Compact callable-surface summary; included on bootstrap prompts when set. */
17
+ toolManifest?: string;
17
18
  }
18
19
 
19
20
  export const CURSOR_APPROX_CHARS_PER_TOKEN = 4;
@@ -21,20 +22,25 @@ export const CURSOR_IMAGE_TOKEN_ESTIMATE = 1200;
21
22
  const SECTION_SEPARATOR = "\n\n";
22
23
 
23
24
  export function getCursorToolTailGuardText(): string {
24
- return "Tool boundary reminder: If a tool is needed, call an available Cursor SDK/MCP tool. Never print a tool card (for example Tool call/Shell/command) as assistant text.";
25
+ return [
26
+ "Shell: use an explicit `cd` to the repo path when running project commands; session cwd may not match paths in tool args.",
27
+ "Tool boundary reminder: If a tool is needed, call an available Cursor SDK/MCP tool. Never print a tool card (for example Tool call/Shell/command) as assistant text.",
28
+ ].join("\n");
25
29
  }
26
30
 
27
- function getCursorToolBoundaryText(): string {
28
- return [
31
+ function getCursorToolBoundaryText(options: { hasToolManifest?: boolean } = {}): string {
32
+ const lines = [
29
33
  "Cursor SDK tool boundary:",
30
- "You can call only tools actually exposed by Cursor SDK in this run. Pi tool names, replay tool names, and transcript tool names are context only, not callable capabilities.",
31
- getCursorPiBridgeContractText(),
32
- "If asked to list or exercise available tools, list and exercise Cursor SDK tools only; do not claim access to pi-side tools from the system prompt unless Cursor exposes an equivalent tool that runs.",
34
+ "Call only tools exposed by Cursor SDK in this run. Pi tool names, replay labels, and transcript names are context onlynot callable.",
35
+ "Bridged pi tools: call pi__* MCP names when exposed, not the pi card name in history. Replay activity is display-only.",
36
+ "Do not claim pi-side or WebSearch/WebFetch tools unless Cursor executes an equivalent tool.",
33
37
  "Use pi__cursor_ask_question for material choices if exposed.",
34
- "Web: use Cursor web/search/browser/MCP or say web search is not configured; do not claim WebSearch/WebFetch unless Cursor executes them.",
35
- "Replay: pi may display recorded Cursor tool activity as pi-style cards, but replay is display-only and not a capability to invoke.",
36
- "Images: only latest user images are sent; ask to reattach or describe prior images.",
37
- ].join("\n");
38
+ "Images: only the latest user message's images are sent as bytes; ask to reattach or describe prior images.",
39
+ ];
40
+ if (options.hasToolManifest) {
41
+ lines.push("See callable tool surfaces block below.");
42
+ }
43
+ return lines.join("\n");
38
44
  }
39
45
 
40
46
  function getCursorBootstrapTailSections(): string[] {
@@ -370,7 +376,10 @@ export function buildCursorIncrementalPrompt(context: Context, options: CursorPr
370
376
  }
371
377
 
372
378
  export function buildCursorPrompt(context: Context, options: CursorPromptOptions = {}): CursorPrompt {
373
- const sectionsBeforeMessages: string[] = [getCursorToolBoundaryText()];
379
+ const sectionsBeforeMessages: string[] = [getCursorToolBoundaryText({ hasToolManifest: Boolean(options.toolManifest) })];
380
+ if (options.toolManifest) {
381
+ sectionsBeforeMessages.push(options.toolManifest);
382
+ }
374
383
 
375
384
  if (context.systemPrompt) {
376
385
  sectionsBeforeMessages.push(`System instructions from pi:\n${sanitizeSystemPromptForCursor(context.systemPrompt)}`);
@@ -20,8 +20,6 @@ export function buildCursorPiBridgeMcpToolDescription(options: {
20
20
  }): string {
21
21
  return [
22
22
  options.piToolDescription,
23
- "",
24
- getCursorPiBridgeContractText(),
25
- `This run exposes real pi tool ${options.piToolName} as Cursor MCP tool ${options.mcpToolName}.`,
23
+ `Call MCP name ${options.mcpToolName} (pi tool: ${options.piToolName}). Full tool-surface rules are in the session bootstrap prompt.`,
26
24
  ].join("\n");
27
25
  }
@@ -5,6 +5,11 @@ import {
5
5
  DISCARDED_INCOMPLETE_TOOL_CALL_REASON,
6
6
  type DiscardedIncompleteStartedToolCallReason,
7
7
  } from "./cursor-sdk-event-debug.js";
8
+ import {
9
+ assembleCursorReplayActivityDetails,
10
+ parseCursorReplayToolDetails,
11
+ resolveIncompleteReplayActivitySourceToolName,
12
+ } from "./cursor-replay-tool-details.js";
8
13
  import { truncateArg, type CursorPiToolDisplay } from "./cursor-transcript-utils.js";
9
14
  import { classifyCursorToolVisibility } from "./cursor-tool-visibility.js";
10
15
 
@@ -87,6 +92,14 @@ export function buildIncompleteCursorToolDisplay(
87
92
  const headline = `${activityTitle} did not complete`;
88
93
  const reasonText = scrubSensitiveText(formatIncompleteCursorToolReasonText(reason), options.apiKey);
89
94
  const contentText = `${headline}\n${reasonText}`;
95
+ const details = assembleCursorReplayActivityDetails(
96
+ resolveIncompleteReplayActivitySourceToolName(visibility.normalizedName),
97
+ headline,
98
+ { summary: reasonText, expandedText: contentText },
99
+ contentText,
100
+ true,
101
+ reasonText,
102
+ );
90
103
  return {
91
104
  toolName: CURSOR_REPLAY_ACTIVITY_TOOL_NAME,
92
105
  args: {
@@ -97,17 +110,21 @@ export function buildIncompleteCursorToolDisplay(
97
110
  },
98
111
  result: {
99
112
  content: [{ type: "text", text: contentText }],
100
- details: {
101
- cursorToolName: visibility.normalizedName,
102
- title: headline,
103
- summary: reasonText,
104
- },
113
+ details,
105
114
  },
106
115
  isError: true,
107
116
  };
108
117
  }
109
118
 
110
119
  export function formatIncompleteCursorToolTrace(display: CursorPiToolDisplay): string {
120
+ const parsed = parseCursorReplayToolDetails(display.result.details);
121
+ if (parsed?.variant === "activity") {
122
+ const summary =
123
+ parsed.summary?.trim() ||
124
+ (typeof display.args.activitySummary === "string" && display.args.activitySummary.trim()) ||
125
+ formatIncompleteCursorToolReasonText(DISCARDED_INCOMPLETE_TOOL_CALL_REASON);
126
+ return `${truncateCursorDisplayLine(parsed.title)}: ${truncateCursorDisplayLine(summary)}\n`;
127
+ }
111
128
  const details = display.result.details;
112
129
  const detailRecord = details && typeof details === "object" ? (details as Record<string, unknown>) : undefined;
113
130
  const argsRecord = display.args;
@@ -13,12 +13,19 @@ import {
13
13
  NATIVE_CURSOR_TOOL_DISPLAY_ENV,
14
14
  readBooleanEnv,
15
15
  registeredNativeToolNames,
16
+ skippedNativeToolNames,
16
17
  } from "./cursor-native-tool-display-state.js";
17
18
  import { isCursorReplayToolName } from "./cursor-tool-names.js";
18
19
 
19
- const CORE_PI_TOOL_NAMES = new Set(["read", "bash", "edit", "write"]);
20
+ export const CURSOR_CORE_PI_REPLAY_TOOL_NAMES = ["read", "bash", "edit", "write"] as const;
21
+ const CORE_PI_TOOL_NAMES = new Set<string>(CURSOR_CORE_PI_REPLAY_TOOL_NAMES);
20
22
 
21
- type CursorNativeToolRegistryApi = Pick<ExtensionAPI, "getActiveTools" | "getAllTools" | "registerTool" | "setActiveTools">;
23
+ function isCursorCorePiReplayToolName(toolName: string): toolName is (typeof CURSOR_CORE_PI_REPLAY_TOOL_NAMES)[number] {
24
+ return CORE_PI_TOOL_NAMES.has(toolName);
25
+ }
26
+
27
+ type CursorNativeToolActivationApi = Pick<ExtensionAPI, "getActiveTools" | "setActiveTools">;
28
+ type CursorNativeToolRegistryApi = CursorNativeToolActivationApi & Pick<ExtensionAPI, "getAllTools" | "registerTool">;
22
29
 
23
30
  export interface CursorNativeToolDisplayExtensionApi extends CursorNativeToolRegistryApi {
24
31
  on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
@@ -34,7 +41,40 @@ function hasNonBuiltinTool(pi: Pick<ExtensionAPI, "getAllTools">, toolName: Nati
34
41
 
35
42
  type NativeRegistrationContext = { hasUI: boolean; ui: Pick<ExtensionContext["ui"], "notify">; model?: ExtensionContext["model"] };
36
43
 
37
- export function syncRegisteredNativeCursorToolsForModel(pi: Pick<ExtensionAPI, "getActiveTools" | "setActiveTools">, model: ExtensionContext["model"]): void {
44
+ function registerNativeCursorToolsFromSet(
45
+ pi: CursorNativeToolRegistryApi,
46
+ toolNames: readonly NativeCursorToolName[],
47
+ ): NativeCursorToolName[] {
48
+ const newlySkippedToolNames: NativeCursorToolName[] = [];
49
+ for (const toolName of toolNames) {
50
+ if (registeredNativeToolNames.has(toolName) || skippedNativeToolNames.has(toolName)) continue;
51
+ if (hasNonBuiltinTool(pi, toolName)) {
52
+ skippedNativeToolNames.add(toolName);
53
+ newlySkippedToolNames.push(toolName);
54
+ continue;
55
+ }
56
+ registerNativeCursorTool(pi, toolName);
57
+ registeredNativeToolNames.add(toolName);
58
+ }
59
+ return newlySkippedToolNames;
60
+ }
61
+
62
+ function notifySkippedNativeCursorToolsIfNeeded(ctx: NativeRegistrationContext, skippedToolNames: readonly NativeCursorToolName[]): void {
63
+ if (skippedToolNames.length === 0 || readBooleanEnv(NATIVE_CURSOR_TOOL_DISPLAY_ENV) !== true || !ctx.hasUI) return;
64
+ ctx.ui.notify(
65
+ `Cursor native tool replay skipped for ${skippedToolNames.join(", ")} because another extension already provides ${skippedToolNames.length === 1 ? "that tool" : "those tools"}. Cursor will use scrubbed activity transcripts for skipped tools.`,
66
+ "warning",
67
+ );
68
+ }
69
+
70
+ function hasAttemptedNativeCursorToolRegistration(): boolean {
71
+ return registeredNativeToolNames.size > 0 || skippedNativeToolNames.size > 0;
72
+ }
73
+
74
+ export function syncRegisteredNativeCursorToolsForModel(
75
+ pi: CursorNativeToolActivationApi,
76
+ model: ExtensionContext["model"],
77
+ ): void {
38
78
  if (registeredNativeToolNames.size === 0) return;
39
79
  const activeToolNames = new Set(pi.getActiveTools());
40
80
  let changed = false;
@@ -47,7 +87,7 @@ export function syncRegisteredNativeCursorToolsForModel(pi: Pick<ExtensionAPI, "
47
87
  }
48
88
  } else {
49
89
  for (const toolName of registeredNativeToolNames) {
50
- if (CORE_PI_TOOL_NAMES.has(toolName)) continue;
90
+ if (isCursorCorePiReplayToolName(toolName)) continue;
51
91
  if (!activeToolNames.delete(toolName)) continue;
52
92
  changed = true;
53
93
  }
@@ -55,45 +95,41 @@ export function syncRegisteredNativeCursorToolsForModel(pi: Pick<ExtensionAPI, "
55
95
  if (changed) pi.setActiveTools([...activeToolNames]);
56
96
  }
57
97
 
58
- function registerAvailableNativeCursorTools(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext): void {
98
+ function ensureNativeCursorToolsRegisteredForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext): void {
59
99
  if (!isCursorNativeToolRegistrationRequested()) {
60
100
  registeredNativeToolNames.clear();
101
+ skippedNativeToolNames.clear();
61
102
  return;
62
103
  }
104
+ if (!isCursorModel(ctx.model) || hasAttemptedNativeCursorToolRegistration()) return;
63
105
 
64
- const skippedToolNames: string[] = [];
65
- for (const toolName of NATIVE_CURSOR_TOOL_NAMES) {
66
- if (registeredNativeToolNames.has(toolName)) continue;
67
- if (hasNonBuiltinTool(pi, toolName)) {
68
- skippedToolNames.push(toolName);
69
- continue;
70
- }
71
- registerNativeCursorTool(pi, toolName);
72
- registeredNativeToolNames.add(toolName);
73
- }
74
-
75
- syncRegisteredNativeCursorToolsForModel(pi, ctx.model);
106
+ const nonCoreToolNames = NATIVE_CURSOR_TOOL_NAMES.filter((toolName) => !isCursorCorePiReplayToolName(toolName));
107
+ const skippedToolNames = [
108
+ ...registerNativeCursorToolsFromSet(pi, nonCoreToolNames),
109
+ ...registerNativeCursorToolsFromSet(pi, CURSOR_CORE_PI_REPLAY_TOOL_NAMES),
110
+ ];
111
+ notifySkippedNativeCursorToolsIfNeeded(ctx, skippedToolNames);
112
+ }
76
113
 
77
- if (skippedToolNames.length > 0 && readBooleanEnv(NATIVE_CURSOR_TOOL_DISPLAY_ENV) === true && ctx.hasUI) {
78
- ctx.ui.notify(
79
- `Cursor native tool replay skipped for ${skippedToolNames.join(", ")} because another extension already provides ${skippedToolNames.length === 1 ? "that tool" : "those tools"}. Cursor will use scrubbed activity transcripts for skipped tools.`,
80
- "warning",
81
- );
114
+ function ensureThenSyncNativeCursorToolsForModel(pi: CursorNativeToolRegistryApi, ctx: NativeRegistrationContext): void {
115
+ if (isCursorModel(ctx.model) && !hasAttemptedNativeCursorToolRegistration()) {
116
+ ensureNativeCursorToolsRegisteredForModel(pi, ctx);
82
117
  }
118
+ syncRegisteredNativeCursorToolsForModel(pi, ctx.model);
83
119
  }
84
120
 
85
121
  export function registerCursorNativeToolDisplay(pi: CursorNativeToolDisplayExtensionApi): void {
86
122
  pi.on("session_start", (_event, ctx) => {
87
- registerAvailableNativeCursorTools(pi, ctx);
123
+ ensureThenSyncNativeCursorToolsForModel(pi, ctx);
88
124
  });
89
125
  pi.on("before_agent_start", (_event, ctx) => {
90
- syncRegisteredNativeCursorToolsForModel(pi, ctx.model);
126
+ ensureThenSyncNativeCursorToolsForModel(pi, ctx);
91
127
  });
92
128
  pi.on("turn_start", (_event, ctx) => {
93
- syncRegisteredNativeCursorToolsForModel(pi, ctx.model);
129
+ ensureThenSyncNativeCursorToolsForModel(pi, ctx);
94
130
  });
95
- pi.on("model_select", (event) => {
96
- syncRegisteredNativeCursorToolsForModel(pi, event.model);
131
+ pi.on("model_select", (event, ctx) => {
132
+ ensureThenSyncNativeCursorToolsForModel(pi, { ...ctx, model: event.model });
97
133
  });
98
134
  }
99
135