pi-cursor-sdk 0.1.18 → 0.1.20
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 +58 -0
- package/README.md +59 -1
- package/docs/cursor-live-smoke-checklist.md +4 -1
- package/docs/cursor-model-ux-spec.md +7 -5
- package/docs/cursor-native-tool-replay.md +99 -3
- package/docs/cursor-testing-lessons.md +234 -5
- package/package.json +10 -2
- package/scripts/debug-provider-events.mjs +403 -0
- package/scripts/debug-sdk-events.mjs +413 -0
- package/scripts/lib/cursor-probe-utils.mjs +52 -0
- package/scripts/lib/cursor-sdk-output-filter.mjs +86 -0
- package/scripts/probe-mcp-coldstart.mjs +244 -0
- package/scripts/validate-smoke-jsonl.mjs +27 -3
- 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-incomplete-tool-visibility.ts +124 -0
- package/src/cursor-live-run-coordinator.ts +18 -7
- package/src/cursor-mcp-timeout-override.ts +66 -11
- package/src/cursor-model.ts +12 -0
- package/src/cursor-native-tool-display-registration.ts +1 -4
- package/src/cursor-native-tool-display-replay.ts +65 -6
- 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 +181 -62
- package/src/cursor-provider-turn-coordinator.ts +220 -33
- package/src/cursor-provider.ts +302 -93
- package/src/cursor-question-tool.ts +1 -4
- 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 +602 -0
- package/src/cursor-sensitive-text.ts +27 -7
- package/src/cursor-session-agent.ts +279 -82
- 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 +85 -0
- package/src/cursor-tool-names.ts +39 -0
- package/src/cursor-tool-transcript.ts +4 -2
- package/src/cursor-tool-visibility.ts +63 -0
- package/src/cursor-transcript-tool-formatters.ts +228 -5
- package/src/cursor-transcript-tool-specs.ts +135 -24
- package/src/cursor-transcript-utils.ts +12 -0
- package/src/cursor-web-tool-activity.ts +84 -0
- package/src/index.ts +4 -1
|
@@ -9,7 +9,26 @@ import { CursorPartialContentEmitter } from "./cursor-partial-content-emitter.js
|
|
|
9
9
|
import { asRecord, getField, hasUsableText } from "./cursor-record-utils.js";
|
|
10
10
|
import { scrubPiToolDisplay, scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
11
11
|
import { buildCursorPiToolDisplay, formatCursorToolTranscript, getCursorCreatePlanText, mergeCursorToolCalls } from "./cursor-tool-transcript.js";
|
|
12
|
-
import {
|
|
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
|
+
buildIncompleteCursorToolRunOutcome,
|
|
20
|
+
formatIncompleteCursorToolTrace,
|
|
21
|
+
resolveIncompleteCursorToolVisibility,
|
|
22
|
+
type IncompleteCursorToolRunOutcome,
|
|
23
|
+
type IncompleteCursorToolDiscardReason,
|
|
24
|
+
} from "./cursor-incomplete-tool-visibility.js";
|
|
25
|
+
import { getToolName } from "./cursor-transcript-utils.js";
|
|
26
|
+
import {
|
|
27
|
+
CURSOR_TOOL_LIFECYCLE_DEFER_MS,
|
|
28
|
+
formatCursorToolLifecycleProgressText,
|
|
29
|
+
isCursorToolLifecycleEligible,
|
|
30
|
+
} from "./cursor-tool-lifecycle.js";
|
|
31
|
+
import { classifyCursorToolVisibility } from "./cursor-tool-visibility.js";
|
|
13
32
|
|
|
14
33
|
function formatCursorToolName(toolCall: unknown): string {
|
|
15
34
|
return truncateCursorDisplayLine(getToolName(toolCall), 80) || "unknown";
|
|
@@ -25,20 +44,12 @@ interface CursorShellOutputDeltas {
|
|
|
25
44
|
stderr: string[];
|
|
26
45
|
}
|
|
27
46
|
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
return normalizedName === "shell" || normalizedName === "run_terminal_cmd" || normalizedName === "terminal" || normalizedName === "bash";
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function isCursorTaskToolCall(toolCall: unknown): boolean {
|
|
34
|
-
return getToolName(toolCall).replace(/\s+/g, " ").trim().toLowerCase() === "task";
|
|
47
|
+
function getNormalizedCursorToolName(toolCall: unknown): string {
|
|
48
|
+
return classifyCursorToolVisibility(toolCall).normalizedName;
|
|
35
49
|
}
|
|
36
50
|
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
const description = getString(getToolArgs(toolCall), "description");
|
|
40
|
-
if (!description?.trim()) return undefined;
|
|
41
|
-
return truncateCursorDisplayLine(scrubSensitiveText(description, apiKey));
|
|
51
|
+
function isCursorShellToolCall(toolCall: unknown): boolean {
|
|
52
|
+
return classifyCursorToolVisibility(toolCall).normalizedKey === "shell";
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
function getCursorShellOutputDelta(update: InteractionUpdate): CursorShellOutputDelta | undefined {
|
|
@@ -83,13 +94,15 @@ function mergeShellOutputDeltasIntoCursorToolCall(toolCall: unknown, deltas: Cur
|
|
|
83
94
|
};
|
|
84
95
|
}
|
|
85
96
|
|
|
97
|
+
type CursorToolDisplaySource = "started" | "fallback" | "transcript";
|
|
98
|
+
|
|
86
99
|
type ToolCompletionResolution =
|
|
87
100
|
| { action: "ignore-bridge"; identity?: string }
|
|
88
101
|
| {
|
|
89
102
|
action: "handle";
|
|
90
103
|
toolCall: unknown;
|
|
91
104
|
identity?: string;
|
|
92
|
-
source?:
|
|
105
|
+
source?: CursorToolDisplaySource;
|
|
93
106
|
matchedStartedCallId?: string;
|
|
94
107
|
};
|
|
95
108
|
|
|
@@ -103,6 +116,7 @@ export interface CursorSdkTurnCoordinatorOptions {
|
|
|
103
116
|
activeToolNames?: ReadonlySet<string>;
|
|
104
117
|
nativeReplayId: string;
|
|
105
118
|
textDeltas: string[];
|
|
119
|
+
debugRecorder?: CursorSdkEventDebugRecorder;
|
|
106
120
|
}
|
|
107
121
|
|
|
108
122
|
export class CursorSdkTurnCoordinator {
|
|
@@ -117,6 +131,7 @@ export class CursorSdkTurnCoordinator {
|
|
|
117
131
|
readonly textDeltas: string[];
|
|
118
132
|
|
|
119
133
|
private readonly contentEmitter: CursorPartialContentEmitter;
|
|
134
|
+
private readonly debugRecorder?: CursorSdkEventDebugRecorder;
|
|
120
135
|
private nativeToolDisplayCounter = 0;
|
|
121
136
|
private nativeToolReplayStarted = false;
|
|
122
137
|
private cursorPlanTextCandidate: string | undefined;
|
|
@@ -128,7 +143,8 @@ export class CursorSdkTurnCoordinator {
|
|
|
128
143
|
private readonly completedToolIdentities = new Set<string>();
|
|
129
144
|
private readonly completedStartedToolFingerprints = new Set<string>();
|
|
130
145
|
private readonly completedFallbackToolFingerprints = new Set<string>();
|
|
131
|
-
private readonly
|
|
146
|
+
private readonly emittedLifecycleCallIds = new Set<string>();
|
|
147
|
+
private readonly lifecycleTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
132
148
|
|
|
133
149
|
constructor(options: CursorSdkTurnCoordinatorOptions) {
|
|
134
150
|
this.stream = options.stream;
|
|
@@ -140,6 +156,7 @@ export class CursorSdkTurnCoordinator {
|
|
|
140
156
|
this.activeToolNames = options.activeToolNames;
|
|
141
157
|
this.nativeReplayId = options.nativeReplayId;
|
|
142
158
|
this.textDeltas = options.textDeltas;
|
|
159
|
+
this.debugRecorder = options.debugRecorder;
|
|
143
160
|
this.contentEmitter = new CursorPartialContentEmitter(options.stream, options.partial, undefined, false);
|
|
144
161
|
}
|
|
145
162
|
|
|
@@ -151,13 +168,40 @@ export class CursorSdkTurnCoordinator {
|
|
|
151
168
|
return this.nativeToolReplayStarted;
|
|
152
169
|
}
|
|
153
170
|
|
|
154
|
-
discardIncompleteStartedToolCalls(
|
|
171
|
+
discardIncompleteStartedToolCalls(
|
|
172
|
+
outcome: IncompleteCursorToolRunOutcome = buildIncompleteCursorToolRunOutcome(),
|
|
173
|
+
): void {
|
|
174
|
+
for (const [callId, toolCall] of this.startedToolCalls) {
|
|
175
|
+
if (typeof callId !== "string") continue;
|
|
176
|
+
const toolName = getNormalizedCursorToolName(toolCall);
|
|
177
|
+
recordDiscardedIncompleteStartedToolCall(this.debugRecorder, process.env, {
|
|
178
|
+
toolName,
|
|
179
|
+
callId,
|
|
180
|
+
reason: outcome.reason,
|
|
181
|
+
});
|
|
182
|
+
const visibilityDecision = resolveIncompleteCursorToolVisibility(toolCall, outcome);
|
|
183
|
+
if (visibilityDecision !== "emit") {
|
|
184
|
+
this.recordDisplayDecision({
|
|
185
|
+
action: "skip-incomplete-fast-local",
|
|
186
|
+
toolName,
|
|
187
|
+
source: "started",
|
|
188
|
+
reason:
|
|
189
|
+
visibilityDecision === "debugOnly" && outcome.assistantTextProduced
|
|
190
|
+
? "successful-run-text-produced"
|
|
191
|
+
: visibilityDecision,
|
|
192
|
+
});
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
this.emitIncompleteStartedToolCall(toolCall, outcome.reason);
|
|
196
|
+
}
|
|
155
197
|
this.startedToolCalls.clear();
|
|
156
198
|
this.bridgeStartedToolCallIds.clear();
|
|
157
199
|
this.activeShellCallIds.clear();
|
|
158
200
|
this.ambiguousShellOutputCallIds.clear();
|
|
159
201
|
this.shellOutputDeltasByCallId.clear();
|
|
160
|
-
this.
|
|
202
|
+
this.emittedLifecycleCallIds.clear();
|
|
203
|
+
for (const timer of this.lifecycleTimers.values()) clearTimeout(timer);
|
|
204
|
+
this.lifecycleTimers.clear();
|
|
161
205
|
}
|
|
162
206
|
|
|
163
207
|
closeTraceBlock(): void {
|
|
@@ -195,14 +239,14 @@ export class CursorSdkTurnCoordinator {
|
|
|
195
239
|
return;
|
|
196
240
|
}
|
|
197
241
|
if (update.type === "partial-tool-call") {
|
|
198
|
-
this.
|
|
242
|
+
this.maybeScheduleCursorToolLifecycle(update.callId, update.toolCall);
|
|
199
243
|
return;
|
|
200
244
|
}
|
|
201
245
|
if (update.type === "tool-call-started") {
|
|
202
246
|
if (this.liveRun?.bridgeRun?.isBridgeMcpToolCall(update.toolCall)) {
|
|
203
247
|
if (typeof update.callId === "string") this.bridgeStartedToolCallIds.add(update.callId);
|
|
204
248
|
} else {
|
|
205
|
-
this.
|
|
249
|
+
this.maybeScheduleCursorToolLifecycle(update.callId, update.toolCall);
|
|
206
250
|
this.startedToolCalls.set(update.callId, update.toolCall);
|
|
207
251
|
if (isCursorShellToolCall(update.toolCall)) this.activeShellCallIds.add(update.callId);
|
|
208
252
|
}
|
|
@@ -215,7 +259,10 @@ export class CursorSdkTurnCoordinator {
|
|
|
215
259
|
toolCall: update.toolCall,
|
|
216
260
|
startedToolCall: this.startedToolCalls.get(update.callId),
|
|
217
261
|
});
|
|
218
|
-
if (resolution.action === "ignore-bridge")
|
|
262
|
+
if (resolution.action === "ignore-bridge") {
|
|
263
|
+
this.recordIgnoreBridgeDecision(resolution.identity, getToolName(update.toolCall), "delta");
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
219
266
|
this.handleCompletedToolCall(resolution.toolCall, {
|
|
220
267
|
identity: resolution.identity,
|
|
221
268
|
source: resolution.source,
|
|
@@ -251,7 +298,10 @@ export class CursorSdkTurnCoordinator {
|
|
|
251
298
|
callId: stepId,
|
|
252
299
|
toolCall,
|
|
253
300
|
});
|
|
254
|
-
if (resolution.action === "ignore-bridge")
|
|
301
|
+
if (resolution.action === "ignore-bridge") {
|
|
302
|
+
this.recordIgnoreBridgeDecision(resolution.identity, getToolName(toolCall), "step");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
255
305
|
this.handleCompletedToolCall(resolution.toolCall, {
|
|
256
306
|
identity: resolution.identity,
|
|
257
307
|
source: resolution.source,
|
|
@@ -319,20 +369,52 @@ export class CursorSdkTurnCoordinator {
|
|
|
319
369
|
};
|
|
320
370
|
}
|
|
321
371
|
|
|
372
|
+
handleTranscriptCompletedToolCalls(toolCalls: readonly { identity: string; toolCall: unknown }[]): void {
|
|
373
|
+
for (const { identity, toolCall } of toolCalls) {
|
|
374
|
+
this.handleCompletedToolCall(toolCall, { identity, source: "transcript" });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
322
378
|
private handleCompletedToolCall(
|
|
323
379
|
toolCall: unknown,
|
|
324
|
-
options: { identity?: string; source?:
|
|
380
|
+
options: { identity?: string; source?: CursorToolDisplaySource } = {},
|
|
325
381
|
): void {
|
|
326
382
|
const planText = getCursorCreatePlanText(toolCall);
|
|
327
383
|
if (planText) this.cursorPlanTextCandidate = scrubSensitiveText(planText, this.resolvedApiKey);
|
|
328
384
|
|
|
329
385
|
const transcript = scrubSensitiveText(formatCursorToolTranscript(toolCall, { cwd: this.cwd }), this.resolvedApiKey);
|
|
330
386
|
const display = buildCursorPiToolDisplay(toolCall, { cwd: this.cwd });
|
|
387
|
+
const toolName = display.toolName;
|
|
331
388
|
const fingerprint = this.getToolFingerprint({ toolName: display.toolName, args: display.args, result: display.result });
|
|
332
|
-
if (options.identity && this.completedToolIdentities.has(options.identity))
|
|
389
|
+
if (options.identity && this.completedToolIdentities.has(options.identity)) {
|
|
390
|
+
this.recordDisplayDecision({
|
|
391
|
+
action: "skip-duplicate",
|
|
392
|
+
toolName,
|
|
393
|
+
identity: options.identity,
|
|
394
|
+
source: options.source,
|
|
395
|
+
reason: "identity-already-completed",
|
|
396
|
+
});
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
333
399
|
if (options.source === "started") {
|
|
334
|
-
if (this.completedFallbackToolFingerprints.has(fingerprint))
|
|
400
|
+
if (this.completedFallbackToolFingerprints.has(fingerprint)) {
|
|
401
|
+
this.recordDisplayDecision({
|
|
402
|
+
action: "skip-duplicate",
|
|
403
|
+
toolName,
|
|
404
|
+
identity: options.identity,
|
|
405
|
+
source: options.source,
|
|
406
|
+
reason: "fallback-fingerprint-already-completed",
|
|
407
|
+
});
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
335
410
|
} else if (this.completedStartedToolFingerprints.has(fingerprint) || this.completedFallbackToolFingerprints.has(fingerprint)) {
|
|
411
|
+
this.recordDisplayDecision({
|
|
412
|
+
action: "skip-duplicate",
|
|
413
|
+
toolName,
|
|
414
|
+
identity: options.identity,
|
|
415
|
+
source: options.source,
|
|
416
|
+
reason: "fingerprint-already-completed",
|
|
417
|
+
});
|
|
336
418
|
return;
|
|
337
419
|
}
|
|
338
420
|
if (options.identity) this.completedToolIdentities.add(options.identity);
|
|
@@ -353,6 +435,15 @@ export class CursorSdkTurnCoordinator {
|
|
|
353
435
|
this.nativeToolReplayStarted = true;
|
|
354
436
|
const id = `${this.nativeReplayId}-tool-${++this.nativeToolDisplayCounter}`;
|
|
355
437
|
const scrubbedDisplay = scrubPiToolDisplay(display, this.resolvedApiKey);
|
|
438
|
+
this.recordDisplayDecision({
|
|
439
|
+
action: "queue_replay",
|
|
440
|
+
disposition,
|
|
441
|
+
toolName,
|
|
442
|
+
identity: options.identity,
|
|
443
|
+
source: options.source,
|
|
444
|
+
transcript,
|
|
445
|
+
replayToolId: id,
|
|
446
|
+
});
|
|
356
447
|
cursorLiveRuns.queueEvent(this.liveRun, {
|
|
357
448
|
type: "tool",
|
|
358
449
|
tool: { ...scrubbedDisplay, id },
|
|
@@ -362,11 +453,84 @@ export class CursorSdkTurnCoordinator {
|
|
|
362
453
|
|
|
363
454
|
const traceText =
|
|
364
455
|
disposition === "inactive_trace"
|
|
365
|
-
? formatInactiveCursorReplayTrace(display)
|
|
456
|
+
? formatInactiveCursorReplayTrace(scrubPiToolDisplay(display, this.resolvedApiKey))
|
|
366
457
|
: transcript || `Cursor tool: ${formatCursorToolName(toolCall)} completed`;
|
|
458
|
+
this.recordDisplayDecision({
|
|
459
|
+
action: "emit_trace",
|
|
460
|
+
disposition,
|
|
461
|
+
toolName,
|
|
462
|
+
identity: options.identity,
|
|
463
|
+
source: options.source,
|
|
464
|
+
transcript,
|
|
465
|
+
traceText,
|
|
466
|
+
});
|
|
367
467
|
this.emitCursorToolTrace(traceText);
|
|
368
468
|
}
|
|
369
469
|
|
|
470
|
+
private emitIncompleteStartedToolCall(toolCall: unknown, reason: IncompleteCursorToolDiscardReason): void {
|
|
471
|
+
const display = scrubPiToolDisplay(
|
|
472
|
+
buildIncompleteCursorToolDisplay(toolCall, reason, { apiKey: this.resolvedApiKey }),
|
|
473
|
+
this.resolvedApiKey,
|
|
474
|
+
);
|
|
475
|
+
const toolName = display.toolName;
|
|
476
|
+
const disposition = resolveNativeReplayDisposition({
|
|
477
|
+
toolName,
|
|
478
|
+
useNativeToolReplay: this.useNativeToolReplay,
|
|
479
|
+
activeToolNames: this.activeToolNames,
|
|
480
|
+
hasLiveRun: this.liveRun !== undefined,
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Aborted live runs emit trace visibility only; do not synthesize a toolUse replay turn.
|
|
484
|
+
if (disposition === "queue_replay" && this.liveRun && reason !== "abort") {
|
|
485
|
+
this.nativeToolReplayStarted = true;
|
|
486
|
+
const id = `${this.nativeReplayId}-tool-${++this.nativeToolDisplayCounter}`;
|
|
487
|
+
this.recordDisplayDecision({
|
|
488
|
+
action: "queue_replay",
|
|
489
|
+
disposition,
|
|
490
|
+
toolName,
|
|
491
|
+
source: "started",
|
|
492
|
+
reason: "incomplete-started-tool-call",
|
|
493
|
+
replayToolId: id,
|
|
494
|
+
});
|
|
495
|
+
cursorLiveRuns.queueEvent(this.liveRun, {
|
|
496
|
+
type: "tool",
|
|
497
|
+
tool: { ...display, id },
|
|
498
|
+
});
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const traceText =
|
|
503
|
+
disposition === "inactive_trace"
|
|
504
|
+
? formatInactiveCursorReplayTrace(display)
|
|
505
|
+
: formatIncompleteCursorToolTrace(display);
|
|
506
|
+
this.recordDisplayDecision({
|
|
507
|
+
action: "emit_trace",
|
|
508
|
+
disposition,
|
|
509
|
+
toolName,
|
|
510
|
+
source: "started",
|
|
511
|
+
reason: "incomplete-started-tool-call",
|
|
512
|
+
traceText,
|
|
513
|
+
});
|
|
514
|
+
this.emitCursorToolTrace(traceText);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private recordIgnoreBridgeDecision(
|
|
518
|
+
identity: string | undefined,
|
|
519
|
+
toolName: string,
|
|
520
|
+
source: "delta" | "step",
|
|
521
|
+
): void {
|
|
522
|
+
this.debugRecorder?.recordDisplayDecision({
|
|
523
|
+
action: "ignore-bridge",
|
|
524
|
+
toolName,
|
|
525
|
+
identity,
|
|
526
|
+
source,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
private recordDisplayDecision(decision: Parameters<CursorSdkEventDebugRecorder["recordDisplayDecision"]>[0]): void {
|
|
531
|
+
this.debugRecorder?.recordDisplayDecision(decision);
|
|
532
|
+
}
|
|
533
|
+
|
|
370
534
|
private emitCursorToolTrace(text: string): void {
|
|
371
535
|
const traceText = text.endsWith("\n") ? text : `${text}\n`;
|
|
372
536
|
if (this.liveRun) {
|
|
@@ -377,17 +541,39 @@ export class CursorSdkTurnCoordinator {
|
|
|
377
541
|
this.contentEmitter.appendThinkingBlock(traceText);
|
|
378
542
|
}
|
|
379
543
|
|
|
380
|
-
private
|
|
381
|
-
if (typeof callId !== "string" || this.
|
|
544
|
+
private maybeScheduleCursorToolLifecycle(callId: unknown, toolCall: unknown): void {
|
|
545
|
+
if (typeof callId !== "string" || this.emittedLifecycleCallIds.has(callId)) return;
|
|
382
546
|
if (this.liveRun?.bridgeRun?.isBridgeMcpToolCall(toolCall)) return;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
this.
|
|
386
|
-
|
|
547
|
+
if (!isCursorToolLifecycleEligible(toolCall)) return;
|
|
548
|
+
|
|
549
|
+
this.cancelCursorToolLifecycleTimer(callId);
|
|
550
|
+
const timer = setTimeout(() => {
|
|
551
|
+
this.lifecycleTimers.delete(callId);
|
|
552
|
+
if (!this.startedToolCalls.has(callId)) return;
|
|
553
|
+
if (this.emittedLifecycleCallIds.has(callId)) return;
|
|
554
|
+
this.emitCursorToolLifecycle(callId, toolCall);
|
|
555
|
+
}, CURSOR_TOOL_LIFECYCLE_DEFER_MS);
|
|
556
|
+
timer.unref?.();
|
|
557
|
+
this.lifecycleTimers.set(callId, timer);
|
|
387
558
|
}
|
|
388
559
|
|
|
389
|
-
private
|
|
390
|
-
const
|
|
560
|
+
private cancelCursorToolLifecycleTimer(callId: string): void {
|
|
561
|
+
const timer = this.lifecycleTimers.get(callId);
|
|
562
|
+
if (!timer) return;
|
|
563
|
+
clearTimeout(timer);
|
|
564
|
+
this.lifecycleTimers.delete(callId);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private emitCursorToolLifecycle(callId: string, toolCall: unknown): void {
|
|
568
|
+
const progressText = formatCursorToolLifecycleProgressText(toolCall, this.resolvedApiKey);
|
|
569
|
+
if (!progressText) return;
|
|
570
|
+
this.emittedLifecycleCallIds.add(callId);
|
|
571
|
+
this.debugRecorder?.recordCoordinatorEvent("tool_lifecycle", {
|
|
572
|
+
callId,
|
|
573
|
+
toolName: getNormalizedCursorToolName(toolCall),
|
|
574
|
+
progressText,
|
|
575
|
+
liveRun: this.liveRun !== undefined,
|
|
576
|
+
});
|
|
391
577
|
if (this.liveRun) {
|
|
392
578
|
cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: progressText });
|
|
393
579
|
return;
|
|
@@ -408,6 +594,7 @@ export class CursorSdkTurnCoordinator {
|
|
|
408
594
|
}
|
|
409
595
|
|
|
410
596
|
private clearStartedToolCall(callId: string): void {
|
|
597
|
+
this.cancelCursorToolLifecycleTimer(callId);
|
|
411
598
|
this.startedToolCalls.delete(callId);
|
|
412
599
|
this.bridgeStartedToolCallIds.delete(callId);
|
|
413
600
|
this.activeShellCallIds.delete(callId);
|