pi-cursor-sdk 0.1.17 → 0.1.19
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 +62 -0
- package/README.md +38 -1
- package/docs/cursor-live-smoke-checklist.md +22 -2
- package/docs/cursor-model-ux-spec.md +5 -4
- package/docs/cursor-native-tool-replay.md +96 -2
- package/docs/cursor-testing-lessons.md +428 -0
- package/package.json +11 -2
- package/scripts/debug-provider-events.mjs +403 -0
- package/scripts/debug-sdk-events.mjs +413 -0
- package/scripts/isolated-cursor-smoke.sh +226 -0
- package/scripts/lib/cursor-probe-utils.mjs +52 -0
- package/scripts/lib/cursor-sdk-output-filter.mjs +86 -0
- package/scripts/validate-smoke-jsonl.mjs +86 -7
- package/src/context.ts +45 -32
- package/src/cursor-agent-message-web-tools.ts +172 -0
- package/src/cursor-agents-context.ts +176 -0
- package/src/cursor-context-tools.ts +6 -0
- package/src/cursor-display-text.ts +10 -0
- package/src/cursor-incomplete-tool-visibility.ts +118 -0
- package/src/cursor-live-run-coordinator.ts +18 -7
- package/src/cursor-model.ts +12 -0
- package/src/cursor-native-replay-routing.ts +48 -0
- package/src/cursor-native-replay-trace.ts +29 -0
- package/src/cursor-native-tool-display-registration.ts +14 -7
- package/src/cursor-native-tool-display-replay.ts +63 -5
- package/src/cursor-native-tool-display-tools.ts +20 -0
- package/src/cursor-pi-tool-bridge-diagnostics.ts +11 -1
- package/src/cursor-pi-tool-bridge-run.ts +16 -1
- package/src/cursor-pi-tool-bridge-types.ts +3 -0
- package/src/cursor-provider-errors.ts +96 -0
- package/src/cursor-provider-live-run-drain.ts +208 -63
- package/src/cursor-provider-turn-coordinator.ts +217 -47
- package/src/cursor-provider.ts +275 -83
- package/src/cursor-question-tool.ts +10 -5
- package/src/cursor-sdk-abort-error-guard.ts +109 -0
- package/src/cursor-sdk-event-debug-constants.ts +40 -0
- package/src/cursor-sdk-event-debug-session.ts +163 -0
- package/src/cursor-sdk-event-debug.ts +597 -0
- package/src/cursor-sensitive-text.ts +27 -7
- package/src/cursor-session-agent.ts +25 -3
- package/src/cursor-session-send-policy.ts +43 -0
- package/src/cursor-setting-sources.ts +29 -0
- package/src/cursor-state.ts +1 -5
- package/src/cursor-tool-lifecycle.ts +111 -0
- package/src/cursor-tool-names.ts +12 -0
- package/src/cursor-tool-transcript.ts +4 -2
- package/src/cursor-transcript-tool-formatters.ts +228 -5
- package/src/cursor-transcript-tool-specs.ts +113 -14
- package/src/cursor-transcript-utils.ts +12 -0
- package/src/cursor-web-tool-activity.ts +84 -0
- package/src/index.ts +4 -1
|
@@ -2,24 +2,32 @@ import type { AssistantMessage, AssistantMessageEventStream } from "@earendil-wo
|
|
|
2
2
|
import type { InteractionUpdate } from "@cursor/sdk";
|
|
3
3
|
import type { CursorLiveRun } from "./cursor-live-run-coordinator.js";
|
|
4
4
|
import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
|
|
5
|
-
import {
|
|
5
|
+
import { formatInactiveCursorReplayTrace } from "./cursor-native-replay-trace.js";
|
|
6
|
+
import { resolveNativeReplayDisposition } from "./cursor-native-replay-routing.js";
|
|
7
|
+
import { truncateCursorDisplayLine } from "./cursor-display-text.js";
|
|
6
8
|
import { CursorPartialContentEmitter } from "./cursor-partial-content-emitter.js";
|
|
7
9
|
import { asRecord, getField, hasUsableText } from "./cursor-record-utils.js";
|
|
8
10
|
import { scrubPiToolDisplay, scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
9
11
|
import { buildCursorPiToolDisplay, formatCursorToolTranscript, getCursorCreatePlanText, mergeCursorToolCalls } from "./cursor-tool-transcript.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
12
|
+
import {
|
|
13
|
+
recordDiscardedIncompleteStartedToolCall,
|
|
14
|
+
type CursorSdkEventDebugRecorder,
|
|
15
|
+
DISCARDED_INCOMPLETE_TOOL_CALL_REASON,
|
|
16
|
+
} from "./cursor-sdk-event-debug.js";
|
|
17
|
+
import {
|
|
18
|
+
buildIncompleteCursorToolDisplay,
|
|
19
|
+
formatIncompleteCursorToolTrace,
|
|
20
|
+
type IncompleteCursorToolDiscardReason,
|
|
21
|
+
} from "./cursor-incomplete-tool-visibility.js";
|
|
22
|
+
import { getToolName, normalizeToolName } from "./cursor-transcript-utils.js";
|
|
23
|
+
import {
|
|
24
|
+
CURSOR_TOOL_LIFECYCLE_DEFER_MS,
|
|
25
|
+
formatCursorToolLifecycleProgressText,
|
|
26
|
+
isCursorToolLifecycleEligible,
|
|
27
|
+
} from "./cursor-tool-lifecycle.js";
|
|
20
28
|
|
|
21
29
|
function formatCursorToolName(toolCall: unknown): string {
|
|
22
|
-
return
|
|
30
|
+
return truncateCursorDisplayLine(getToolName(toolCall), 80) || "unknown";
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
interface CursorShellOutputDelta {
|
|
@@ -37,17 +45,6 @@ function isCursorShellToolCall(toolCall: unknown): boolean {
|
|
|
37
45
|
return normalizedName === "shell" || normalizedName === "run_terminal_cmd" || normalizedName === "terminal" || normalizedName === "bash";
|
|
38
46
|
}
|
|
39
47
|
|
|
40
|
-
function isCursorTaskToolCall(toolCall: unknown): boolean {
|
|
41
|
-
return getToolName(toolCall).replace(/\s+/g, " ").trim().toLowerCase() === "task";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function extractCursorTaskProgressLabel(toolCall: unknown, apiKey?: string): string | undefined {
|
|
45
|
-
if (!isCursorTaskToolCall(toolCall)) return undefined;
|
|
46
|
-
const description = getString(getToolArgs(toolCall), "description");
|
|
47
|
-
if (!description?.trim()) return undefined;
|
|
48
|
-
return truncateSingleLine(scrubSensitiveText(description, apiKey));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
48
|
function getCursorShellOutputDelta(update: InteractionUpdate): CursorShellOutputDelta | undefined {
|
|
52
49
|
if (update.type !== "shell-output-delta") return undefined;
|
|
53
50
|
const event = getField(update, "event");
|
|
@@ -90,13 +87,15 @@ function mergeShellOutputDeltasIntoCursorToolCall(toolCall: unknown, deltas: Cur
|
|
|
90
87
|
};
|
|
91
88
|
}
|
|
92
89
|
|
|
90
|
+
type CursorToolDisplaySource = "started" | "fallback" | "transcript";
|
|
91
|
+
|
|
93
92
|
type ToolCompletionResolution =
|
|
94
93
|
| { action: "ignore-bridge"; identity?: string }
|
|
95
94
|
| {
|
|
96
95
|
action: "handle";
|
|
97
96
|
toolCall: unknown;
|
|
98
97
|
identity?: string;
|
|
99
|
-
source?:
|
|
98
|
+
source?: CursorToolDisplaySource;
|
|
100
99
|
matchedStartedCallId?: string;
|
|
101
100
|
};
|
|
102
101
|
|
|
@@ -107,8 +106,10 @@ export interface CursorSdkTurnCoordinatorOptions {
|
|
|
107
106
|
resolvedApiKey?: string;
|
|
108
107
|
liveRun?: CursorLiveRun;
|
|
109
108
|
useNativeToolReplay: boolean;
|
|
109
|
+
activeToolNames?: ReadonlySet<string>;
|
|
110
110
|
nativeReplayId: string;
|
|
111
111
|
textDeltas: string[];
|
|
112
|
+
debugRecorder?: CursorSdkEventDebugRecorder;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
export class CursorSdkTurnCoordinator {
|
|
@@ -118,10 +119,12 @@ export class CursorSdkTurnCoordinator {
|
|
|
118
119
|
readonly resolvedApiKey?: string;
|
|
119
120
|
readonly liveRun?: CursorLiveRun;
|
|
120
121
|
readonly useNativeToolReplay: boolean;
|
|
122
|
+
readonly activeToolNames?: ReadonlySet<string>;
|
|
121
123
|
readonly nativeReplayId: string;
|
|
122
124
|
readonly textDeltas: string[];
|
|
123
125
|
|
|
124
126
|
private readonly contentEmitter: CursorPartialContentEmitter;
|
|
127
|
+
private readonly debugRecorder?: CursorSdkEventDebugRecorder;
|
|
125
128
|
private nativeToolDisplayCounter = 0;
|
|
126
129
|
private nativeToolReplayStarted = false;
|
|
127
130
|
private cursorPlanTextCandidate: string | undefined;
|
|
@@ -133,7 +136,8 @@ export class CursorSdkTurnCoordinator {
|
|
|
133
136
|
private readonly completedToolIdentities = new Set<string>();
|
|
134
137
|
private readonly completedStartedToolFingerprints = new Set<string>();
|
|
135
138
|
private readonly completedFallbackToolFingerprints = new Set<string>();
|
|
136
|
-
private readonly
|
|
139
|
+
private readonly emittedLifecycleCallIds = new Set<string>();
|
|
140
|
+
private readonly lifecycleTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
137
141
|
|
|
138
142
|
constructor(options: CursorSdkTurnCoordinatorOptions) {
|
|
139
143
|
this.stream = options.stream;
|
|
@@ -142,8 +146,10 @@ export class CursorSdkTurnCoordinator {
|
|
|
142
146
|
this.resolvedApiKey = options.resolvedApiKey;
|
|
143
147
|
this.liveRun = options.liveRun;
|
|
144
148
|
this.useNativeToolReplay = options.useNativeToolReplay;
|
|
149
|
+
this.activeToolNames = options.activeToolNames;
|
|
145
150
|
this.nativeReplayId = options.nativeReplayId;
|
|
146
151
|
this.textDeltas = options.textDeltas;
|
|
152
|
+
this.debugRecorder = options.debugRecorder;
|
|
147
153
|
this.contentEmitter = new CursorPartialContentEmitter(options.stream, options.partial, undefined, false);
|
|
148
154
|
}
|
|
149
155
|
|
|
@@ -155,13 +161,26 @@ export class CursorSdkTurnCoordinator {
|
|
|
155
161
|
return this.nativeToolReplayStarted;
|
|
156
162
|
}
|
|
157
163
|
|
|
158
|
-
discardIncompleteStartedToolCalls(
|
|
164
|
+
discardIncompleteStartedToolCalls(
|
|
165
|
+
reason: IncompleteCursorToolDiscardReason = DISCARDED_INCOMPLETE_TOOL_CALL_REASON,
|
|
166
|
+
): void {
|
|
167
|
+
for (const [callId, toolCall] of this.startedToolCalls) {
|
|
168
|
+
if (typeof callId !== "string") continue;
|
|
169
|
+
recordDiscardedIncompleteStartedToolCall(this.debugRecorder, process.env, {
|
|
170
|
+
toolName: normalizeToolName(getToolName(toolCall)),
|
|
171
|
+
callId,
|
|
172
|
+
reason,
|
|
173
|
+
});
|
|
174
|
+
this.emitIncompleteStartedToolCall(toolCall, reason);
|
|
175
|
+
}
|
|
159
176
|
this.startedToolCalls.clear();
|
|
160
177
|
this.bridgeStartedToolCallIds.clear();
|
|
161
178
|
this.activeShellCallIds.clear();
|
|
162
179
|
this.ambiguousShellOutputCallIds.clear();
|
|
163
180
|
this.shellOutputDeltasByCallId.clear();
|
|
164
|
-
this.
|
|
181
|
+
this.emittedLifecycleCallIds.clear();
|
|
182
|
+
for (const timer of this.lifecycleTimers.values()) clearTimeout(timer);
|
|
183
|
+
this.lifecycleTimers.clear();
|
|
165
184
|
}
|
|
166
185
|
|
|
167
186
|
closeTraceBlock(): void {
|
|
@@ -199,14 +218,14 @@ export class CursorSdkTurnCoordinator {
|
|
|
199
218
|
return;
|
|
200
219
|
}
|
|
201
220
|
if (update.type === "partial-tool-call") {
|
|
202
|
-
this.
|
|
221
|
+
this.maybeScheduleCursorToolLifecycle(update.callId, update.toolCall);
|
|
203
222
|
return;
|
|
204
223
|
}
|
|
205
224
|
if (update.type === "tool-call-started") {
|
|
206
225
|
if (this.liveRun?.bridgeRun?.isBridgeMcpToolCall(update.toolCall)) {
|
|
207
226
|
if (typeof update.callId === "string") this.bridgeStartedToolCallIds.add(update.callId);
|
|
208
227
|
} else {
|
|
209
|
-
this.
|
|
228
|
+
this.maybeScheduleCursorToolLifecycle(update.callId, update.toolCall);
|
|
210
229
|
this.startedToolCalls.set(update.callId, update.toolCall);
|
|
211
230
|
if (isCursorShellToolCall(update.toolCall)) this.activeShellCallIds.add(update.callId);
|
|
212
231
|
}
|
|
@@ -219,7 +238,10 @@ export class CursorSdkTurnCoordinator {
|
|
|
219
238
|
toolCall: update.toolCall,
|
|
220
239
|
startedToolCall: this.startedToolCalls.get(update.callId),
|
|
221
240
|
});
|
|
222
|
-
if (resolution.action === "ignore-bridge")
|
|
241
|
+
if (resolution.action === "ignore-bridge") {
|
|
242
|
+
this.recordIgnoreBridgeDecision(resolution.identity, getToolName(update.toolCall), "delta");
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
223
245
|
this.handleCompletedToolCall(resolution.toolCall, {
|
|
224
246
|
identity: resolution.identity,
|
|
225
247
|
source: resolution.source,
|
|
@@ -232,7 +254,7 @@ export class CursorSdkTurnCoordinator {
|
|
|
232
254
|
return;
|
|
233
255
|
}
|
|
234
256
|
if (update.type === "summary") {
|
|
235
|
-
const summary = `Cursor summary: ${
|
|
257
|
+
const summary = `Cursor summary: ${truncateCursorDisplayLine(update.summary)}\n`;
|
|
236
258
|
if (this.liveRun) {
|
|
237
259
|
cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: summary });
|
|
238
260
|
} else {
|
|
@@ -255,7 +277,10 @@ export class CursorSdkTurnCoordinator {
|
|
|
255
277
|
callId: stepId,
|
|
256
278
|
toolCall,
|
|
257
279
|
});
|
|
258
|
-
if (resolution.action === "ignore-bridge")
|
|
280
|
+
if (resolution.action === "ignore-bridge") {
|
|
281
|
+
this.recordIgnoreBridgeDecision(resolution.identity, getToolName(toolCall), "step");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
259
284
|
this.handleCompletedToolCall(resolution.toolCall, {
|
|
260
285
|
identity: resolution.identity,
|
|
261
286
|
source: resolution.source,
|
|
@@ -323,20 +348,52 @@ export class CursorSdkTurnCoordinator {
|
|
|
323
348
|
};
|
|
324
349
|
}
|
|
325
350
|
|
|
351
|
+
handleTranscriptCompletedToolCalls(toolCalls: readonly { identity: string; toolCall: unknown }[]): void {
|
|
352
|
+
for (const { identity, toolCall } of toolCalls) {
|
|
353
|
+
this.handleCompletedToolCall(toolCall, { identity, source: "transcript" });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
326
357
|
private handleCompletedToolCall(
|
|
327
358
|
toolCall: unknown,
|
|
328
|
-
options: { identity?: string; source?:
|
|
359
|
+
options: { identity?: string; source?: CursorToolDisplaySource } = {},
|
|
329
360
|
): void {
|
|
330
361
|
const planText = getCursorCreatePlanText(toolCall);
|
|
331
362
|
if (planText) this.cursorPlanTextCandidate = scrubSensitiveText(planText, this.resolvedApiKey);
|
|
332
363
|
|
|
333
364
|
const transcript = scrubSensitiveText(formatCursorToolTranscript(toolCall, { cwd: this.cwd }), this.resolvedApiKey);
|
|
334
365
|
const display = buildCursorPiToolDisplay(toolCall, { cwd: this.cwd });
|
|
366
|
+
const toolName = display.toolName;
|
|
335
367
|
const fingerprint = this.getToolFingerprint({ toolName: display.toolName, args: display.args, result: display.result });
|
|
336
|
-
if (options.identity && this.completedToolIdentities.has(options.identity))
|
|
368
|
+
if (options.identity && this.completedToolIdentities.has(options.identity)) {
|
|
369
|
+
this.recordDisplayDecision({
|
|
370
|
+
action: "skip-duplicate",
|
|
371
|
+
toolName,
|
|
372
|
+
identity: options.identity,
|
|
373
|
+
source: options.source,
|
|
374
|
+
reason: "identity-already-completed",
|
|
375
|
+
});
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
337
378
|
if (options.source === "started") {
|
|
338
|
-
if (this.completedFallbackToolFingerprints.has(fingerprint))
|
|
379
|
+
if (this.completedFallbackToolFingerprints.has(fingerprint)) {
|
|
380
|
+
this.recordDisplayDecision({
|
|
381
|
+
action: "skip-duplicate",
|
|
382
|
+
toolName,
|
|
383
|
+
identity: options.identity,
|
|
384
|
+
source: options.source,
|
|
385
|
+
reason: "fallback-fingerprint-already-completed",
|
|
386
|
+
});
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
339
389
|
} else if (this.completedStartedToolFingerprints.has(fingerprint) || this.completedFallbackToolFingerprints.has(fingerprint)) {
|
|
390
|
+
this.recordDisplayDecision({
|
|
391
|
+
action: "skip-duplicate",
|
|
392
|
+
toolName,
|
|
393
|
+
identity: options.identity,
|
|
394
|
+
source: options.source,
|
|
395
|
+
reason: "fingerprint-already-completed",
|
|
396
|
+
});
|
|
340
397
|
return;
|
|
341
398
|
}
|
|
342
399
|
if (options.identity) this.completedToolIdentities.add(options.identity);
|
|
@@ -346,13 +403,26 @@ export class CursorSdkTurnCoordinator {
|
|
|
346
403
|
this.completedFallbackToolFingerprints.add(fingerprint);
|
|
347
404
|
}
|
|
348
405
|
|
|
349
|
-
const
|
|
350
|
-
|
|
406
|
+
const disposition = resolveNativeReplayDisposition({
|
|
407
|
+
toolName: display.toolName,
|
|
408
|
+
useNativeToolReplay: this.useNativeToolReplay,
|
|
409
|
+
activeToolNames: this.activeToolNames,
|
|
410
|
+
hasLiveRun: this.liveRun !== undefined,
|
|
411
|
+
});
|
|
351
412
|
|
|
352
|
-
if (
|
|
413
|
+
if (disposition === "queue_replay" && this.liveRun) {
|
|
353
414
|
this.nativeToolReplayStarted = true;
|
|
354
415
|
const id = `${this.nativeReplayId}-tool-${++this.nativeToolDisplayCounter}`;
|
|
355
416
|
const scrubbedDisplay = scrubPiToolDisplay(display, this.resolvedApiKey);
|
|
417
|
+
this.recordDisplayDecision({
|
|
418
|
+
action: "queue_replay",
|
|
419
|
+
disposition,
|
|
420
|
+
toolName,
|
|
421
|
+
identity: options.identity,
|
|
422
|
+
source: options.source,
|
|
423
|
+
transcript,
|
|
424
|
+
replayToolId: id,
|
|
425
|
+
});
|
|
356
426
|
cursorLiveRuns.queueEvent(this.liveRun, {
|
|
357
427
|
type: "tool",
|
|
358
428
|
tool: { ...scrubbedDisplay, id },
|
|
@@ -360,7 +430,84 @@ export class CursorSdkTurnCoordinator {
|
|
|
360
430
|
return;
|
|
361
431
|
}
|
|
362
432
|
|
|
363
|
-
|
|
433
|
+
const traceText =
|
|
434
|
+
disposition === "inactive_trace"
|
|
435
|
+
? formatInactiveCursorReplayTrace(scrubPiToolDisplay(display, this.resolvedApiKey))
|
|
436
|
+
: transcript || `Cursor tool: ${formatCursorToolName(toolCall)} completed`;
|
|
437
|
+
this.recordDisplayDecision({
|
|
438
|
+
action: "emit_trace",
|
|
439
|
+
disposition,
|
|
440
|
+
toolName,
|
|
441
|
+
identity: options.identity,
|
|
442
|
+
source: options.source,
|
|
443
|
+
transcript,
|
|
444
|
+
traceText,
|
|
445
|
+
});
|
|
446
|
+
this.emitCursorToolTrace(traceText);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private emitIncompleteStartedToolCall(toolCall: unknown, reason: IncompleteCursorToolDiscardReason): void {
|
|
450
|
+
const display = scrubPiToolDisplay(
|
|
451
|
+
buildIncompleteCursorToolDisplay(toolCall, reason, { apiKey: this.resolvedApiKey }),
|
|
452
|
+
this.resolvedApiKey,
|
|
453
|
+
);
|
|
454
|
+
const toolName = display.toolName;
|
|
455
|
+
const disposition = resolveNativeReplayDisposition({
|
|
456
|
+
toolName,
|
|
457
|
+
useNativeToolReplay: this.useNativeToolReplay,
|
|
458
|
+
activeToolNames: this.activeToolNames,
|
|
459
|
+
hasLiveRun: this.liveRun !== undefined,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Aborted live runs emit trace visibility only; do not synthesize a toolUse replay turn.
|
|
463
|
+
if (disposition === "queue_replay" && this.liveRun && reason !== "abort") {
|
|
464
|
+
this.nativeToolReplayStarted = true;
|
|
465
|
+
const id = `${this.nativeReplayId}-tool-${++this.nativeToolDisplayCounter}`;
|
|
466
|
+
this.recordDisplayDecision({
|
|
467
|
+
action: "queue_replay",
|
|
468
|
+
disposition,
|
|
469
|
+
toolName,
|
|
470
|
+
source: "started",
|
|
471
|
+
reason: "incomplete-started-tool-call",
|
|
472
|
+
replayToolId: id,
|
|
473
|
+
});
|
|
474
|
+
cursorLiveRuns.queueEvent(this.liveRun, {
|
|
475
|
+
type: "tool",
|
|
476
|
+
tool: { ...display, id },
|
|
477
|
+
});
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const traceText =
|
|
482
|
+
disposition === "inactive_trace"
|
|
483
|
+
? formatInactiveCursorReplayTrace(display)
|
|
484
|
+
: formatIncompleteCursorToolTrace(display);
|
|
485
|
+
this.recordDisplayDecision({
|
|
486
|
+
action: "emit_trace",
|
|
487
|
+
disposition,
|
|
488
|
+
toolName,
|
|
489
|
+
source: "started",
|
|
490
|
+
reason: "incomplete-started-tool-call",
|
|
491
|
+
traceText,
|
|
492
|
+
});
|
|
493
|
+
this.emitCursorToolTrace(traceText);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
private recordIgnoreBridgeDecision(
|
|
497
|
+
identity: string | undefined,
|
|
498
|
+
toolName: string,
|
|
499
|
+
source: "delta" | "step",
|
|
500
|
+
): void {
|
|
501
|
+
this.debugRecorder?.recordDisplayDecision({
|
|
502
|
+
action: "ignore-bridge",
|
|
503
|
+
toolName,
|
|
504
|
+
identity,
|
|
505
|
+
source,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
private recordDisplayDecision(decision: Parameters<CursorSdkEventDebugRecorder["recordDisplayDecision"]>[0]): void {
|
|
510
|
+
this.debugRecorder?.recordDisplayDecision(decision);
|
|
364
511
|
}
|
|
365
512
|
|
|
366
513
|
private emitCursorToolTrace(text: string): void {
|
|
@@ -373,17 +520,39 @@ export class CursorSdkTurnCoordinator {
|
|
|
373
520
|
this.contentEmitter.appendThinkingBlock(traceText);
|
|
374
521
|
}
|
|
375
522
|
|
|
376
|
-
private
|
|
377
|
-
if (typeof callId !== "string" || this.
|
|
523
|
+
private maybeScheduleCursorToolLifecycle(callId: unknown, toolCall: unknown): void {
|
|
524
|
+
if (typeof callId !== "string" || this.emittedLifecycleCallIds.has(callId)) return;
|
|
378
525
|
if (this.liveRun?.bridgeRun?.isBridgeMcpToolCall(toolCall)) return;
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
this.
|
|
382
|
-
|
|
526
|
+
if (!isCursorToolLifecycleEligible(toolCall)) return;
|
|
527
|
+
|
|
528
|
+
this.cancelCursorToolLifecycleTimer(callId);
|
|
529
|
+
const timer = setTimeout(() => {
|
|
530
|
+
this.lifecycleTimers.delete(callId);
|
|
531
|
+
if (!this.startedToolCalls.has(callId)) return;
|
|
532
|
+
if (this.emittedLifecycleCallIds.has(callId)) return;
|
|
533
|
+
this.emitCursorToolLifecycle(callId, toolCall);
|
|
534
|
+
}, CURSOR_TOOL_LIFECYCLE_DEFER_MS);
|
|
535
|
+
timer.unref?.();
|
|
536
|
+
this.lifecycleTimers.set(callId, timer);
|
|
383
537
|
}
|
|
384
538
|
|
|
385
|
-
private
|
|
386
|
-
const
|
|
539
|
+
private cancelCursorToolLifecycleTimer(callId: string): void {
|
|
540
|
+
const timer = this.lifecycleTimers.get(callId);
|
|
541
|
+
if (!timer) return;
|
|
542
|
+
clearTimeout(timer);
|
|
543
|
+
this.lifecycleTimers.delete(callId);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private emitCursorToolLifecycle(callId: string, toolCall: unknown): void {
|
|
547
|
+
const progressText = formatCursorToolLifecycleProgressText(toolCall, this.resolvedApiKey);
|
|
548
|
+
if (!progressText) return;
|
|
549
|
+
this.emittedLifecycleCallIds.add(callId);
|
|
550
|
+
this.debugRecorder?.recordCoordinatorEvent("tool_lifecycle", {
|
|
551
|
+
callId,
|
|
552
|
+
toolName: normalizeToolName(getToolName(toolCall)),
|
|
553
|
+
progressText,
|
|
554
|
+
liveRun: this.liveRun !== undefined,
|
|
555
|
+
});
|
|
387
556
|
if (this.liveRun) {
|
|
388
557
|
cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: progressText });
|
|
389
558
|
return;
|
|
@@ -404,6 +573,7 @@ export class CursorSdkTurnCoordinator {
|
|
|
404
573
|
}
|
|
405
574
|
|
|
406
575
|
private clearStartedToolCall(callId: string): void {
|
|
576
|
+
this.cancelCursorToolLifecycleTimer(callId);
|
|
407
577
|
this.startedToolCalls.delete(callId);
|
|
408
578
|
this.bridgeStartedToolCallIds.delete(callId);
|
|
409
579
|
this.activeShellCallIds.delete(callId);
|