pi-cursor-sdk 0.1.18 → 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 +38 -0
- package/README.md +37 -0
- package/docs/cursor-live-smoke-checklist.md +3 -0
- package/docs/cursor-model-ux-spec.md +4 -3
- package/docs/cursor-native-tool-replay.md +96 -2
- package/docs/cursor-testing-lessons.md +234 -5
- package/package.json +8 -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/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 +118 -0
- package/src/cursor-live-run-coordinator.ts +18 -7
- 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 +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 +181 -62
- package/src/cursor-provider-turn-coordinator.ts +198 -32
- package/src/cursor-provider.ts +270 -83
- 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 +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
|
@@ -9,7 +9,22 @@ 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
|
+
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";
|
|
13
28
|
|
|
14
29
|
function formatCursorToolName(toolCall: unknown): string {
|
|
15
30
|
return truncateCursorDisplayLine(getToolName(toolCall), 80) || "unknown";
|
|
@@ -30,17 +45,6 @@ function isCursorShellToolCall(toolCall: unknown): boolean {
|
|
|
30
45
|
return normalizedName === "shell" || normalizedName === "run_terminal_cmd" || normalizedName === "terminal" || normalizedName === "bash";
|
|
31
46
|
}
|
|
32
47
|
|
|
33
|
-
function isCursorTaskToolCall(toolCall: unknown): boolean {
|
|
34
|
-
return getToolName(toolCall).replace(/\s+/g, " ").trim().toLowerCase() === "task";
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function extractCursorTaskProgressLabel(toolCall: unknown, apiKey?: string): string | undefined {
|
|
38
|
-
if (!isCursorTaskToolCall(toolCall)) return undefined;
|
|
39
|
-
const description = getString(getToolArgs(toolCall), "description");
|
|
40
|
-
if (!description?.trim()) return undefined;
|
|
41
|
-
return truncateCursorDisplayLine(scrubSensitiveText(description, apiKey));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
48
|
function getCursorShellOutputDelta(update: InteractionUpdate): CursorShellOutputDelta | undefined {
|
|
45
49
|
if (update.type !== "shell-output-delta") return undefined;
|
|
46
50
|
const event = getField(update, "event");
|
|
@@ -83,13 +87,15 @@ function mergeShellOutputDeltasIntoCursorToolCall(toolCall: unknown, deltas: Cur
|
|
|
83
87
|
};
|
|
84
88
|
}
|
|
85
89
|
|
|
90
|
+
type CursorToolDisplaySource = "started" | "fallback" | "transcript";
|
|
91
|
+
|
|
86
92
|
type ToolCompletionResolution =
|
|
87
93
|
| { action: "ignore-bridge"; identity?: string }
|
|
88
94
|
| {
|
|
89
95
|
action: "handle";
|
|
90
96
|
toolCall: unknown;
|
|
91
97
|
identity?: string;
|
|
92
|
-
source?:
|
|
98
|
+
source?: CursorToolDisplaySource;
|
|
93
99
|
matchedStartedCallId?: string;
|
|
94
100
|
};
|
|
95
101
|
|
|
@@ -103,6 +109,7 @@ export interface CursorSdkTurnCoordinatorOptions {
|
|
|
103
109
|
activeToolNames?: ReadonlySet<string>;
|
|
104
110
|
nativeReplayId: string;
|
|
105
111
|
textDeltas: string[];
|
|
112
|
+
debugRecorder?: CursorSdkEventDebugRecorder;
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
export class CursorSdkTurnCoordinator {
|
|
@@ -117,6 +124,7 @@ export class CursorSdkTurnCoordinator {
|
|
|
117
124
|
readonly textDeltas: string[];
|
|
118
125
|
|
|
119
126
|
private readonly contentEmitter: CursorPartialContentEmitter;
|
|
127
|
+
private readonly debugRecorder?: CursorSdkEventDebugRecorder;
|
|
120
128
|
private nativeToolDisplayCounter = 0;
|
|
121
129
|
private nativeToolReplayStarted = false;
|
|
122
130
|
private cursorPlanTextCandidate: string | undefined;
|
|
@@ -128,7 +136,8 @@ export class CursorSdkTurnCoordinator {
|
|
|
128
136
|
private readonly completedToolIdentities = new Set<string>();
|
|
129
137
|
private readonly completedStartedToolFingerprints = new Set<string>();
|
|
130
138
|
private readonly completedFallbackToolFingerprints = new Set<string>();
|
|
131
|
-
private readonly
|
|
139
|
+
private readonly emittedLifecycleCallIds = new Set<string>();
|
|
140
|
+
private readonly lifecycleTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
132
141
|
|
|
133
142
|
constructor(options: CursorSdkTurnCoordinatorOptions) {
|
|
134
143
|
this.stream = options.stream;
|
|
@@ -140,6 +149,7 @@ export class CursorSdkTurnCoordinator {
|
|
|
140
149
|
this.activeToolNames = options.activeToolNames;
|
|
141
150
|
this.nativeReplayId = options.nativeReplayId;
|
|
142
151
|
this.textDeltas = options.textDeltas;
|
|
152
|
+
this.debugRecorder = options.debugRecorder;
|
|
143
153
|
this.contentEmitter = new CursorPartialContentEmitter(options.stream, options.partial, undefined, false);
|
|
144
154
|
}
|
|
145
155
|
|
|
@@ -151,13 +161,26 @@ export class CursorSdkTurnCoordinator {
|
|
|
151
161
|
return this.nativeToolReplayStarted;
|
|
152
162
|
}
|
|
153
163
|
|
|
154
|
-
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
|
+
}
|
|
155
176
|
this.startedToolCalls.clear();
|
|
156
177
|
this.bridgeStartedToolCallIds.clear();
|
|
157
178
|
this.activeShellCallIds.clear();
|
|
158
179
|
this.ambiguousShellOutputCallIds.clear();
|
|
159
180
|
this.shellOutputDeltasByCallId.clear();
|
|
160
|
-
this.
|
|
181
|
+
this.emittedLifecycleCallIds.clear();
|
|
182
|
+
for (const timer of this.lifecycleTimers.values()) clearTimeout(timer);
|
|
183
|
+
this.lifecycleTimers.clear();
|
|
161
184
|
}
|
|
162
185
|
|
|
163
186
|
closeTraceBlock(): void {
|
|
@@ -195,14 +218,14 @@ export class CursorSdkTurnCoordinator {
|
|
|
195
218
|
return;
|
|
196
219
|
}
|
|
197
220
|
if (update.type === "partial-tool-call") {
|
|
198
|
-
this.
|
|
221
|
+
this.maybeScheduleCursorToolLifecycle(update.callId, update.toolCall);
|
|
199
222
|
return;
|
|
200
223
|
}
|
|
201
224
|
if (update.type === "tool-call-started") {
|
|
202
225
|
if (this.liveRun?.bridgeRun?.isBridgeMcpToolCall(update.toolCall)) {
|
|
203
226
|
if (typeof update.callId === "string") this.bridgeStartedToolCallIds.add(update.callId);
|
|
204
227
|
} else {
|
|
205
|
-
this.
|
|
228
|
+
this.maybeScheduleCursorToolLifecycle(update.callId, update.toolCall);
|
|
206
229
|
this.startedToolCalls.set(update.callId, update.toolCall);
|
|
207
230
|
if (isCursorShellToolCall(update.toolCall)) this.activeShellCallIds.add(update.callId);
|
|
208
231
|
}
|
|
@@ -215,7 +238,10 @@ export class CursorSdkTurnCoordinator {
|
|
|
215
238
|
toolCall: update.toolCall,
|
|
216
239
|
startedToolCall: this.startedToolCalls.get(update.callId),
|
|
217
240
|
});
|
|
218
|
-
if (resolution.action === "ignore-bridge")
|
|
241
|
+
if (resolution.action === "ignore-bridge") {
|
|
242
|
+
this.recordIgnoreBridgeDecision(resolution.identity, getToolName(update.toolCall), "delta");
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
219
245
|
this.handleCompletedToolCall(resolution.toolCall, {
|
|
220
246
|
identity: resolution.identity,
|
|
221
247
|
source: resolution.source,
|
|
@@ -251,7 +277,10 @@ export class CursorSdkTurnCoordinator {
|
|
|
251
277
|
callId: stepId,
|
|
252
278
|
toolCall,
|
|
253
279
|
});
|
|
254
|
-
if (resolution.action === "ignore-bridge")
|
|
280
|
+
if (resolution.action === "ignore-bridge") {
|
|
281
|
+
this.recordIgnoreBridgeDecision(resolution.identity, getToolName(toolCall), "step");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
255
284
|
this.handleCompletedToolCall(resolution.toolCall, {
|
|
256
285
|
identity: resolution.identity,
|
|
257
286
|
source: resolution.source,
|
|
@@ -319,20 +348,52 @@ export class CursorSdkTurnCoordinator {
|
|
|
319
348
|
};
|
|
320
349
|
}
|
|
321
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
|
+
|
|
322
357
|
private handleCompletedToolCall(
|
|
323
358
|
toolCall: unknown,
|
|
324
|
-
options: { identity?: string; source?:
|
|
359
|
+
options: { identity?: string; source?: CursorToolDisplaySource } = {},
|
|
325
360
|
): void {
|
|
326
361
|
const planText = getCursorCreatePlanText(toolCall);
|
|
327
362
|
if (planText) this.cursorPlanTextCandidate = scrubSensitiveText(planText, this.resolvedApiKey);
|
|
328
363
|
|
|
329
364
|
const transcript = scrubSensitiveText(formatCursorToolTranscript(toolCall, { cwd: this.cwd }), this.resolvedApiKey);
|
|
330
365
|
const display = buildCursorPiToolDisplay(toolCall, { cwd: this.cwd });
|
|
366
|
+
const toolName = display.toolName;
|
|
331
367
|
const fingerprint = this.getToolFingerprint({ toolName: display.toolName, args: display.args, result: display.result });
|
|
332
|
-
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
|
+
}
|
|
333
378
|
if (options.source === "started") {
|
|
334
|
-
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
|
+
}
|
|
335
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
|
+
});
|
|
336
397
|
return;
|
|
337
398
|
}
|
|
338
399
|
if (options.identity) this.completedToolIdentities.add(options.identity);
|
|
@@ -353,6 +414,15 @@ export class CursorSdkTurnCoordinator {
|
|
|
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 },
|
|
@@ -362,11 +432,84 @@ export class CursorSdkTurnCoordinator {
|
|
|
362
432
|
|
|
363
433
|
const traceText =
|
|
364
434
|
disposition === "inactive_trace"
|
|
365
|
-
? formatInactiveCursorReplayTrace(display)
|
|
435
|
+
? formatInactiveCursorReplayTrace(scrubPiToolDisplay(display, this.resolvedApiKey))
|
|
366
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
|
+
});
|
|
367
493
|
this.emitCursorToolTrace(traceText);
|
|
368
494
|
}
|
|
369
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);
|
|
511
|
+
}
|
|
512
|
+
|
|
370
513
|
private emitCursorToolTrace(text: string): void {
|
|
371
514
|
const traceText = text.endsWith("\n") ? text : `${text}\n`;
|
|
372
515
|
if (this.liveRun) {
|
|
@@ -377,17 +520,39 @@ export class CursorSdkTurnCoordinator {
|
|
|
377
520
|
this.contentEmitter.appendThinkingBlock(traceText);
|
|
378
521
|
}
|
|
379
522
|
|
|
380
|
-
private
|
|
381
|
-
if (typeof callId !== "string" || this.
|
|
523
|
+
private maybeScheduleCursorToolLifecycle(callId: unknown, toolCall: unknown): void {
|
|
524
|
+
if (typeof callId !== "string" || this.emittedLifecycleCallIds.has(callId)) return;
|
|
382
525
|
if (this.liveRun?.bridgeRun?.isBridgeMcpToolCall(toolCall)) return;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
this.
|
|
386
|
-
|
|
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);
|
|
387
537
|
}
|
|
388
538
|
|
|
389
|
-
private
|
|
390
|
-
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
|
+
});
|
|
391
556
|
if (this.liveRun) {
|
|
392
557
|
cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: progressText });
|
|
393
558
|
return;
|
|
@@ -408,6 +573,7 @@ export class CursorSdkTurnCoordinator {
|
|
|
408
573
|
}
|
|
409
574
|
|
|
410
575
|
private clearStartedToolCall(callId: string): void {
|
|
576
|
+
this.cancelCursorToolLifecycleTimer(callId);
|
|
411
577
|
this.startedToolCalls.delete(callId);
|
|
412
578
|
this.bridgeStartedToolCallIds.delete(callId);
|
|
413
579
|
this.activeShellCallIds.delete(callId);
|