pi-cursor-sdk 0.2.0 → 0.3.6

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 (168) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +54 -50
  3. package/dist/bundled-context-windows.js +39 -0
  4. package/dist/context-window-cache.js +92 -0
  5. package/dist/context.js +338 -0
  6. package/dist/cursor-active-tools.js +3 -0
  7. package/dist/cursor-agent-message-web-tools.js +165 -0
  8. package/dist/cursor-agents-context-registration.js +17 -0
  9. package/dist/cursor-agents-context.js +113 -0
  10. package/dist/cursor-api-key.js +34 -0
  11. package/dist/cursor-bridge-contract.js +27 -0
  12. package/dist/cursor-cloud-lifecycle.js +650 -0
  13. package/dist/cursor-cloud-local-state.js +460 -0
  14. package/dist/cursor-cloud-options.js +145 -0
  15. package/dist/cursor-cloud-reporting.js +222 -0
  16. package/dist/cursor-compact-tool-summary.js +75 -0
  17. package/dist/cursor-config.js +613 -0
  18. package/dist/cursor-context-tools.js +4 -0
  19. package/dist/cursor-display-only-trace.js +11 -0
  20. package/dist/cursor-display-text.js +18 -0
  21. package/dist/cursor-durable-fs.js +53 -0
  22. package/dist/cursor-edit-diff.js +13 -0
  23. package/dist/cursor-env-boolean.js +19 -0
  24. package/dist/cursor-fallback-models.generated.js +7760 -0
  25. package/dist/cursor-fallback-warning.js +15 -0
  26. package/dist/cursor-http1.js +45 -0
  27. package/dist/cursor-incomplete-tool-visibility.js +83 -0
  28. package/dist/cursor-live-run-accounting.js +55 -0
  29. package/dist/cursor-live-run-coordinator.js +406 -0
  30. package/dist/cursor-mcp-timeout-override.js +116 -0
  31. package/dist/cursor-model-lifecycle.js +32 -0
  32. package/dist/cursor-model.js +5 -0
  33. package/dist/cursor-native-replay-routing.js +35 -0
  34. package/dist/cursor-native-replay-trace.js +28 -0
  35. package/dist/cursor-native-tool-display-registration.js +102 -0
  36. package/dist/cursor-native-tool-display-replay.js +428 -0
  37. package/dist/cursor-native-tool-display-state.js +70 -0
  38. package/dist/cursor-native-tool-display-tools.js +123 -0
  39. package/dist/cursor-native-tool-names.js +11 -0
  40. package/dist/cursor-partial-content-emitter.js +118 -0
  41. package/dist/cursor-pi-tool-bridge-abort.js +114 -0
  42. package/dist/cursor-pi-tool-bridge-constants.js +6 -0
  43. package/dist/cursor-pi-tool-bridge-diagnostics.js +125 -0
  44. package/dist/cursor-pi-tool-bridge-env.js +18 -0
  45. package/dist/cursor-pi-tool-bridge-mcp.js +100 -0
  46. package/dist/cursor-pi-tool-bridge-run.js +355 -0
  47. package/dist/cursor-pi-tool-bridge-server.js +163 -0
  48. package/dist/cursor-pi-tool-bridge-snapshot.js +63 -0
  49. package/dist/cursor-pi-tool-bridge-types.js +1 -0
  50. package/dist/cursor-pi-tool-bridge.js +123 -0
  51. package/dist/cursor-provider-errors.js +311 -0
  52. package/dist/cursor-provider-lazy.js +39 -0
  53. package/dist/cursor-provider-live-run-drain.js +412 -0
  54. package/dist/cursor-provider-overflow.js +90 -0
  55. package/dist/cursor-provider-run-finalizer.js +176 -0
  56. package/dist/cursor-provider-run-outcome.js +76 -0
  57. package/dist/cursor-provider-turn-coordinator.js +275 -0
  58. package/dist/cursor-provider-turn-display-router.js +158 -0
  59. package/dist/cursor-provider-turn-emit.js +35 -0
  60. package/dist/cursor-provider-turn-finalize.js +154 -0
  61. package/dist/cursor-provider-turn-lifecycle-emitter.js +126 -0
  62. package/dist/cursor-provider-turn-prepare.js +374 -0
  63. package/dist/cursor-provider-turn-runner.js +146 -0
  64. package/dist/cursor-provider-turn-sdk-normalizer.js +74 -0
  65. package/dist/cursor-provider-turn-send.js +138 -0
  66. package/dist/cursor-provider-turn-shell-output.js +117 -0
  67. package/dist/cursor-provider-turn-tool-ledger.js +99 -0
  68. package/dist/cursor-provider-turn-types.js +1 -0
  69. package/dist/cursor-provider.js +72 -0
  70. package/dist/cursor-question-tool.js +194 -0
  71. package/dist/cursor-record-utils.js +61 -0
  72. package/dist/cursor-replay-activity-builders.js +114 -0
  73. package/dist/cursor-replay-source-names.js +27 -0
  74. package/dist/cursor-replay-summary-args.js +105 -0
  75. package/dist/cursor-replay-tool-details.js +167 -0
  76. package/dist/cursor-ripgrep-path.js +27 -0
  77. package/dist/cursor-run-final-text.js +63 -0
  78. package/dist/cursor-runtime-state.js +380 -0
  79. package/dist/cursor-sdk-billed-usage.js +86 -0
  80. package/dist/cursor-sdk-event-debug-constants.js +33 -0
  81. package/dist/cursor-sdk-event-debug-session.js +107 -0
  82. package/dist/cursor-sdk-event-debug.js +461 -0
  83. package/dist/cursor-sdk-output-filter.js +1 -0
  84. package/dist/cursor-sdk-process-error-guard.js +221 -0
  85. package/dist/cursor-sdk-runtime.js +3 -0
  86. package/dist/cursor-sensitive-text.js +23 -0
  87. package/dist/cursor-session-agent-cleanup.js +311 -0
  88. package/dist/cursor-session-agent-lifecycle.js +32 -0
  89. package/dist/cursor-session-agent-lineage.js +103 -0
  90. package/dist/cursor-session-agent-resume.js +417 -0
  91. package/dist/cursor-session-agent.js +507 -0
  92. package/dist/cursor-session-compaction-prep.js +21 -0
  93. package/dist/cursor-session-scope.js +113 -0
  94. package/dist/cursor-session-send-policy.js +18 -0
  95. package/dist/cursor-session-store.js +105 -0
  96. package/dist/cursor-session-turn-queue.js +50 -0
  97. package/dist/cursor-setting-sources.js +15 -0
  98. package/dist/cursor-skill-tool.js +230 -0
  99. package/dist/cursor-state.js +519 -0
  100. package/dist/cursor-task-presentation.js +59 -0
  101. package/dist/cursor-tool-lifecycle.js +94 -0
  102. package/dist/cursor-tool-manifest.js +34 -0
  103. package/dist/cursor-tool-presentation-registry.js +305 -0
  104. package/dist/cursor-tool-result-display-readers.js +158 -0
  105. package/dist/cursor-tool-transcript.js +32 -0
  106. package/dist/cursor-tool-visibility.js +28 -0
  107. package/dist/cursor-transcript-tool-formatters.js +618 -0
  108. package/dist/cursor-transcript-tool-specs.js +284 -0
  109. package/dist/cursor-transcript-utils.js +170 -0
  110. package/dist/cursor-usage-accounting.js +166 -0
  111. package/dist/cursor-web-tool-activity.js +23 -0
  112. package/dist/cursor-web-tool-args.js +12 -0
  113. package/dist/index.js +82 -0
  114. package/dist/model-discovery.js +305 -0
  115. package/dist/model-list-cache.js +156 -0
  116. package/docs/cursor-dogfood-checklist.md +2 -1
  117. package/docs/cursor-live-smoke-checklist.md +13 -12
  118. package/docs/cursor-model-ux-spec.md +25 -6
  119. package/docs/cursor-native-tool-replay.md +13 -13
  120. package/docs/cursor-native-tool-visual-audit.md +6 -3
  121. package/docs/cursor-testing-lessons.md +7 -7
  122. package/docs/cursor-tool-surfaces.md +5 -5
  123. package/docs/platform-smoke.md +3 -3
  124. package/package.json +19 -7
  125. package/platform-smoke.config.mjs +1 -1
  126. package/scripts/build.mjs +126 -0
  127. package/scripts/cloud-runtime-smoke.mjs +3 -1
  128. package/scripts/debug-provider-events.mjs +3 -1
  129. package/scripts/debug-sdk-events.mjs +1 -1
  130. package/scripts/isolated-cursor-smoke.sh +5 -4
  131. package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +1 -1
  132. package/scripts/lib/cursor-visual-render.mjs +1 -1
  133. package/scripts/lib/ensure-built.mjs +16 -0
  134. package/scripts/lib/local-resume-smoke-harness.mjs +1 -1
  135. package/scripts/local-resume-smoke.mjs +3 -1
  136. package/scripts/platform-smoke/card-detect.mjs +1 -1
  137. package/scripts/platform-smoke/live-suite-runner.mjs +1 -1
  138. package/scripts/platform-smoke/render-ansi.mjs +1 -1
  139. package/scripts/platform-smoke/targets.mjs +2 -2
  140. package/scripts/prepare.mjs +102 -0
  141. package/scripts/steering-rpc-smoke.mjs +4 -2
  142. package/scripts/tmux-live-smoke.sh +11 -11
  143. package/scripts/visual-tui-smoke-self-test.mjs +4 -1
  144. package/scripts/visual-tui-smoke.mjs +51 -7
  145. package/src/bundled-context-windows.ts +4 -1
  146. package/src/cursor-agents-context-registration.ts +2 -2
  147. package/src/cursor-agents-context.ts +0 -2
  148. package/src/cursor-api-key.ts +5 -4
  149. package/src/cursor-cloud-reporting.ts +23 -2
  150. package/src/cursor-fallback-models.generated.ts +592 -204
  151. package/src/cursor-live-run-coordinator.ts +1 -0
  152. package/src/cursor-native-tool-display-registration.ts +10 -11
  153. package/src/cursor-provider-errors.ts +1 -1
  154. package/src/cursor-provider-lazy.ts +5 -4
  155. package/src/cursor-provider-live-run-drain.ts +1 -0
  156. package/src/cursor-provider-run-finalizer.ts +1 -0
  157. package/src/cursor-provider-turn-finalize.ts +12 -0
  158. package/src/cursor-provider-turn-types.ts +2 -0
  159. package/src/cursor-sdk-billed-usage.ts +107 -0
  160. package/src/cursor-sdk-process-error-guard.ts +6 -4
  161. package/src/cursor-session-agent-lifecycle.ts +8 -9
  162. package/src/cursor-session-agent-resume.ts +23 -0
  163. package/src/cursor-session-compaction-prep.ts +4 -0
  164. package/src/cursor-tool-manifest.ts +1 -1
  165. package/src/cursor-usage-accounting.ts +70 -9
  166. package/src/index.ts +1 -1
  167. package/tsconfig.build.json +8 -0
  168. package/tsconfig.json +16 -0
@@ -0,0 +1,76 @@
1
+ import { selectCursorFinalText } from "./cursor-run-final-text.js";
2
+ import { formatCursorSdkAbortMessage, formatCursorSdkRunFailureDetail, resolveCursorSdkAbortCause, sanitizeCursorProviderError, } from "./cursor-provider-errors.js";
3
+ import { hasUsableText } from "./cursor-record-utils.js";
4
+ import { buildIncompleteCursorToolRunOutcome, } from "./cursor-incomplete-tool-visibility.js";
5
+ function hasCursorAssistantText(resultText, textDeltas, fallbackText) {
6
+ return (hasUsableText(typeof resultText === "string" ? resultText : undefined) ||
7
+ hasUsableText(textDeltas.join("")) ||
8
+ hasUsableText(fallbackText));
9
+ }
10
+ export function isCursorRunFinishedSuccessfully(outcome) {
11
+ return outcome.kind === "finished";
12
+ }
13
+ function buildCursorRunAbortMessage(signalAborted, sdkStatusCancelled) {
14
+ return formatCursorSdkAbortMessage(resolveCursorSdkAbortCause({
15
+ signalAborted,
16
+ sdkStatusCancelled,
17
+ }));
18
+ }
19
+ export function resolveCursorRunOutcome(params) {
20
+ const { waitResult, signalAborted } = params;
21
+ const sdkCancelled = waitResult.status === "cancelled";
22
+ const callerAborted = signalAborted === true;
23
+ if (callerAborted || sdkCancelled) {
24
+ const incompleteTools = buildIncompleteCursorToolRunOutcome({
25
+ status: "cancelled",
26
+ signalAborted: callerAborted,
27
+ assistantTextProduced: false,
28
+ });
29
+ return {
30
+ kind: "cancelled",
31
+ waitResult,
32
+ incompleteTools,
33
+ abortMessage: buildCursorRunAbortMessage(callerAborted, sdkCancelled),
34
+ };
35
+ }
36
+ if (waitResult.status === "error") {
37
+ const failureDetail = formatCursorSdkRunFailureDetail(waitResult, params.runResultFallback, params.runErrorFallback);
38
+ return {
39
+ kind: "error",
40
+ waitResult,
41
+ incompleteTools: buildIncompleteCursorToolRunOutcome({
42
+ status: "error",
43
+ assistantTextProduced: false,
44
+ }),
45
+ errorMessage: sanitizeCursorProviderError(failureDetail, params.resolvedApiKey ?? params.optionsApiKey, params.runtimeTarget),
46
+ };
47
+ }
48
+ const assistantTextProduced = hasCursorAssistantText(waitResult.result, params.textDeltas, params.planTextCandidate);
49
+ const incompleteTools = buildIncompleteCursorToolRunOutcome({
50
+ status: waitResult.status,
51
+ assistantTextProduced,
52
+ });
53
+ const finalText = selectCursorFinalText(waitResult.result, params.textDeltas, params.emittedText, params.planTextCandidate, params.selectFinalTextOptions);
54
+ return {
55
+ kind: "finished",
56
+ waitResult,
57
+ finalText,
58
+ incompleteTools,
59
+ assistantTextProduced,
60
+ };
61
+ }
62
+ export function classifyCursorRunEmission(outcome) {
63
+ switch (outcome.kind) {
64
+ case "finished":
65
+ return "finished";
66
+ case "cancelled":
67
+ return "cancelled";
68
+ case "error":
69
+ return "failed";
70
+ }
71
+ }
72
+ export function getCursorRunAbortMessage(outcome) {
73
+ if (outcome.kind === "cancelled")
74
+ return outcome.abortMessage;
75
+ return buildCursorRunAbortMessage(false, outcome.waitResult.status === "cancelled");
76
+ }
@@ -0,0 +1,275 @@
1
+ import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
2
+ import { truncateCursorDisplayLine } from "./cursor-display-text.js";
3
+ import { recordDiscardedIncompleteStartedToolCall, } from "./cursor-sdk-event-debug.js";
4
+ import { buildIncompleteCursorToolRunOutcome, resolveIncompleteCursorToolVisibility, } from "./cursor-incomplete-tool-visibility.js";
5
+ import { getToolName } from "./cursor-transcript-utils.js";
6
+ import { getNormalizedCursorToolName } from "./cursor-tool-visibility.js";
7
+ import { buildCursorPiToolDisplay } from "./cursor-tool-transcript.js";
8
+ import { getField } from "./cursor-record-utils.js";
9
+ import { CursorTurnDisplayRouter } from "./cursor-provider-turn-display-router.js";
10
+ import { createTurnCoordinatorContentEmitter, CursorToolLifecycleEmitter, } from "./cursor-provider-turn-lifecycle-emitter.js";
11
+ import { resolveCursorToolCompletion } from "./cursor-provider-turn-sdk-normalizer.js";
12
+ import { CursorShellOutputTracker, formatCursorShellOutputProgressText, getCursorShellOutputDelta, isCursorShellToolCall, } from "./cursor-provider-turn-shell-output.js";
13
+ import { CursorToolCompletionLedger, getToolFingerprint, } from "./cursor-provider-turn-tool-ledger.js";
14
+ import { readCursorSdkTurnUsageFromUpdate } from "./cursor-usage-accounting.js";
15
+ export class CursorSdkTurnCoordinator {
16
+ stream;
17
+ partial;
18
+ cwd;
19
+ resolvedApiKey;
20
+ liveRun;
21
+ useNativeToolReplay;
22
+ activeToolNames;
23
+ nativeReplayId;
24
+ textDeltas;
25
+ debugRecorder;
26
+ ledger = new CursorToolCompletionLedger();
27
+ shellOutput = new CursorShellOutputTracker();
28
+ displayRouter;
29
+ lifecycleEmitter;
30
+ contentEmitter;
31
+ sdkTurnUsage;
32
+ constructor(options) {
33
+ this.stream = options.stream;
34
+ this.partial = options.partial;
35
+ this.cwd = options.cwd;
36
+ this.resolvedApiKey = options.resolvedApiKey;
37
+ this.liveRun = options.liveRun;
38
+ this.useNativeToolReplay = options.useNativeToolReplay;
39
+ this.activeToolNames = options.activeToolNames;
40
+ this.nativeReplayId = options.nativeReplayId;
41
+ this.textDeltas = options.textDeltas;
42
+ this.debugRecorder = options.debugRecorder;
43
+ this.contentEmitter = createTurnCoordinatorContentEmitter(options.stream, options.partial);
44
+ this.displayRouter = new CursorTurnDisplayRouter({
45
+ cwd: options.cwd,
46
+ resolvedApiKey: options.resolvedApiKey,
47
+ liveRun: options.liveRun,
48
+ useNativeToolReplay: options.useNativeToolReplay,
49
+ activeToolNames: options.activeToolNames,
50
+ nativeReplayId: options.nativeReplayId,
51
+ contentEmitter: this.contentEmitter,
52
+ debugRecorder: options.debugRecorder,
53
+ });
54
+ this.lifecycleEmitter = new CursorToolLifecycleEmitter({
55
+ liveRun: options.liveRun,
56
+ resolvedApiKey: options.resolvedApiKey,
57
+ contentEmitter: this.contentEmitter,
58
+ debugRecorder: options.debugRecorder,
59
+ hasStartedToolCall: (callId) => this.ledger.hasStartedToolCall(callId),
60
+ isBridgeMcpToolCall: (toolCall) => options.liveRun?.bridgeRun?.isBridgeMcpToolCall(toolCall) ?? false,
61
+ });
62
+ }
63
+ get planTextCandidate() {
64
+ return this.displayRouter.planTextCandidate;
65
+ }
66
+ get replayStarted() {
67
+ return this.displayRouter.nativeToolReplayStarted;
68
+ }
69
+ get lastSdkTurnUsage() {
70
+ return this.sdkTurnUsage;
71
+ }
72
+ discardIncompleteStartedToolCalls(outcome = buildIncompleteCursorToolRunOutcome()) {
73
+ for (const [callId, toolCall] of this.ledger.startedToolCallEntries()) {
74
+ const toolName = getNormalizedCursorToolName(toolCall);
75
+ recordDiscardedIncompleteStartedToolCall(this.debugRecorder, process.env, {
76
+ toolName,
77
+ callId,
78
+ reason: outcome.reason,
79
+ });
80
+ const visibilityDecision = resolveIncompleteCursorToolVisibility(toolCall, outcome);
81
+ if (visibilityDecision !== "emit") {
82
+ this.displayRouter.recordIncompleteSkip(toolName, visibilityDecision === "debugOnly" && outcome.assistantTextProduced
83
+ ? "successful-run-text-produced"
84
+ : visibilityDecision);
85
+ continue;
86
+ }
87
+ const action = this.displayRouter.routeIncompleteStartedToolCall(toolCall, outcome.reason);
88
+ if (action)
89
+ this.displayRouter.emitDisplayAction(action);
90
+ }
91
+ this.ledger.clearStartedToolCalls();
92
+ this.shellOutput.clear();
93
+ this.lifecycleEmitter.clear();
94
+ }
95
+ closeTraceBlock() {
96
+ this.contentEmitter.closeThinking();
97
+ }
98
+ flushText(deltas) {
99
+ return this.contentEmitter.flushText(deltas);
100
+ }
101
+ handleDelta(update) {
102
+ const sdkTurnUsage = readCursorSdkTurnUsageFromUpdate(update);
103
+ if (sdkTurnUsage)
104
+ this.sdkTurnUsage = sdkTurnUsage;
105
+ if (this.liveRun && (update.type === "turn-ended" || sdkTurnUsage)) {
106
+ cursorLiveRuns.recordSdkTurnEnded(this.liveRun, sdkTurnUsage);
107
+ }
108
+ if (update.type === "text-delta") {
109
+ this.textDeltas.push(update.text);
110
+ if (this.liveRun) {
111
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "text-delta", text: update.text });
112
+ }
113
+ else {
114
+ this.contentEmitter.appendTextDelta(update.text);
115
+ }
116
+ return;
117
+ }
118
+ if (update.type === "thinking-delta") {
119
+ if (this.liveRun) {
120
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: update.text });
121
+ }
122
+ else {
123
+ this.contentEmitter.appendThinkingDelta(update.text);
124
+ }
125
+ return;
126
+ }
127
+ if (update.type === "thinking-completed") {
128
+ if (this.liveRun) {
129
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-completed" });
130
+ }
131
+ else {
132
+ this.contentEmitter.closeThinking();
133
+ }
134
+ return;
135
+ }
136
+ if (update.type === "partial-tool-call") {
137
+ this.lifecycleEmitter.maybeSchedule(update.callId, update.toolCall);
138
+ return;
139
+ }
140
+ if (update.type === "tool-call-started") {
141
+ if (this.liveRun?.bridgeRun?.isBridgeMcpToolCall(update.toolCall)) {
142
+ if (typeof update.callId === "string")
143
+ this.ledger.markBridgeStarted(update.callId);
144
+ }
145
+ else {
146
+ this.lifecycleEmitter.maybeSchedule(update.callId, update.toolCall);
147
+ this.ledger.registerStartedToolCall(update.callId, update.toolCall);
148
+ if (isCursorShellToolCall(update.toolCall) && typeof update.callId === "string") {
149
+ this.shellOutput.onShellToolStarted(update.callId);
150
+ }
151
+ }
152
+ return;
153
+ }
154
+ if (update.type === "tool-call-completed") {
155
+ const resolution = resolveCursorToolCompletion({
156
+ source: "delta",
157
+ callId: update.callId,
158
+ toolCall: update.toolCall,
159
+ startedToolCall: this.ledger.getStartedToolCall(update.callId),
160
+ liveRun: this.liveRun,
161
+ ledger: this.ledger,
162
+ shellOutput: this.shellOutput,
163
+ onClearStartedCallId: (callId) => {
164
+ this.lifecycleEmitter.cancel(callId);
165
+ this.shellOutput.onShellToolCleared(callId);
166
+ },
167
+ });
168
+ if (resolution.action === "ignore-bridge") {
169
+ this.displayRouter.recordIgnoreBridgeDecision(resolution.identity, getToolName(update.toolCall), "delta");
170
+ return;
171
+ }
172
+ this.handleCompletedToolCall(resolution.toolCall, {
173
+ identity: resolution.identity,
174
+ source: resolution.source,
175
+ });
176
+ if (resolution.matchedStartedCallId && resolution.matchedStartedCallId !== update.callId) {
177
+ this.ledger.recordCompletedIdentity(`cursor-tool:${resolution.matchedStartedCallId}`);
178
+ }
179
+ return;
180
+ }
181
+ if (update.type === "shell-output-delta") {
182
+ const delta = getCursorShellOutputDelta(update);
183
+ if (delta) {
184
+ const progress = this.shellOutput.appendShellOutputDelta(delta);
185
+ const progressText = progress ? formatCursorShellOutputProgressText(progress, this.resolvedApiKey) : undefined;
186
+ if (progressText) {
187
+ if (this.liveRun) {
188
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: progressText });
189
+ }
190
+ else {
191
+ this.contentEmitter.appendThinkingDelta(progressText);
192
+ }
193
+ }
194
+ }
195
+ return;
196
+ }
197
+ if (update.type === "summary") {
198
+ const summary = `Cursor summary: ${truncateCursorDisplayLine(update.summary)}\n`;
199
+ if (this.liveRun) {
200
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: summary });
201
+ }
202
+ else {
203
+ this.contentEmitter.appendThinkingDelta(summary);
204
+ }
205
+ }
206
+ }
207
+ handleStep(stepEnvelope) {
208
+ const stepType = getField(stepEnvelope, "type");
209
+ const step = getField(stepEnvelope, "message") ? stepEnvelope : undefined;
210
+ const rawStepToolCall = getField(step, "message");
211
+ if (stepType !== "toolCall")
212
+ return;
213
+ const toolCall = rawStepToolCall;
214
+ const stepId = getField(stepEnvelope, "id") ?? getField(toolCall, "id") ?? getField(toolCall, "callId");
215
+ if (!toolCall)
216
+ return;
217
+ const resolution = resolveCursorToolCompletion({
218
+ source: "step",
219
+ callId: stepId,
220
+ toolCall,
221
+ liveRun: this.liveRun,
222
+ ledger: this.ledger,
223
+ shellOutput: this.shellOutput,
224
+ onClearStartedCallId: (callId) => {
225
+ this.lifecycleEmitter.cancel(callId);
226
+ this.shellOutput.onShellToolCleared(callId);
227
+ },
228
+ });
229
+ if (resolution.action === "ignore-bridge") {
230
+ this.displayRouter.recordIgnoreBridgeDecision(resolution.identity, getToolName(toolCall), "step");
231
+ return;
232
+ }
233
+ this.handleCompletedToolCall(resolution.toolCall, {
234
+ identity: resolution.identity,
235
+ source: resolution.source,
236
+ });
237
+ if (resolution.matchedStartedCallId && resolution.matchedStartedCallId !== stepId) {
238
+ this.ledger.recordCompletedIdentity(`cursor-tool:${resolution.matchedStartedCallId}`);
239
+ }
240
+ }
241
+ handleTranscriptCompletedToolCalls(toolCalls) {
242
+ for (const { identity, toolCall } of toolCalls) {
243
+ this.handleCompletedToolCall(toolCall, { identity, source: "transcript" });
244
+ }
245
+ }
246
+ handleCompletedToolCall(toolCall, options = {}) {
247
+ const display = buildCursorPiToolDisplay(toolCall, { cwd: this.cwd });
248
+ const fingerprint = getToolFingerprint({
249
+ toolName: display.toolName,
250
+ args: display.args,
251
+ result: display.result,
252
+ });
253
+ const duplicateReason = this.ledger.shouldSkipDuplicateCompletion({
254
+ identity: options.identity,
255
+ source: options.source,
256
+ fingerprint,
257
+ });
258
+ if (duplicateReason) {
259
+ this.displayRouter.recordDuplicateSkip(display.toolName, {
260
+ identity: options.identity,
261
+ source: options.source,
262
+ reason: duplicateReason,
263
+ });
264
+ return;
265
+ }
266
+ this.ledger.recordCompletedTool({
267
+ identity: options.identity,
268
+ source: options.source,
269
+ fingerprint,
270
+ });
271
+ const action = this.displayRouter.routeCompletedToolCall(toolCall, options);
272
+ if (action)
273
+ this.displayRouter.emitDisplayAction(action);
274
+ }
275
+ }
@@ -0,0 +1,158 @@
1
+ import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
2
+ import { truncateCursorDisplayLine } from "./cursor-display-text.js";
3
+ import { formatInactiveCursorReplayTrace } from "./cursor-native-replay-trace.js";
4
+ import { resolveNativeReplayDisposition } from "./cursor-native-replay-routing.js";
5
+ import { buildIncompleteCursorToolDisplay, formatIncompleteCursorToolTrace, } from "./cursor-incomplete-tool-visibility.js";
6
+ import { scrubPiToolDisplay, scrubSensitiveText } from "./cursor-sensitive-text.js";
7
+ import { buildCursorPiToolDisplay, formatCursorToolTranscript, getCursorCreatePlanText, } from "./cursor-tool-transcript.js";
8
+ import { getToolName } from "./cursor-transcript-utils.js";
9
+ function formatCursorToolName(toolCall) {
10
+ return truncateCursorDisplayLine(getToolName(toolCall), 80) || "unknown";
11
+ }
12
+ export class CursorTurnDisplayRouter {
13
+ cwd;
14
+ resolvedApiKey;
15
+ liveRun;
16
+ useNativeToolReplay;
17
+ activeToolNames;
18
+ nativeReplayId;
19
+ contentEmitter;
20
+ debugRecorder;
21
+ nativeToolDisplayCounter = 0;
22
+ nativeToolReplayStarted = false;
23
+ planTextCandidate;
24
+ constructor(options) {
25
+ this.cwd = options.cwd;
26
+ this.resolvedApiKey = options.resolvedApiKey;
27
+ this.liveRun = options.liveRun;
28
+ this.useNativeToolReplay = options.useNativeToolReplay;
29
+ this.activeToolNames = options.activeToolNames;
30
+ this.nativeReplayId = options.nativeReplayId;
31
+ this.contentEmitter = options.contentEmitter;
32
+ this.debugRecorder = options.debugRecorder;
33
+ }
34
+ routeCompletedToolCall(toolCall, options = {}) {
35
+ const planText = getCursorCreatePlanText(toolCall);
36
+ if (planText)
37
+ this.planTextCandidate = scrubSensitiveText(planText, this.resolvedApiKey);
38
+ const transcript = scrubSensitiveText(formatCursorToolTranscript(toolCall, { cwd: this.cwd }), this.resolvedApiKey);
39
+ const display = buildCursorPiToolDisplay(toolCall, { cwd: this.cwd });
40
+ const disposition = resolveNativeReplayDisposition({
41
+ toolName: display.toolName,
42
+ useNativeToolReplay: this.useNativeToolReplay,
43
+ activeToolNames: this.activeToolNames,
44
+ hasLiveRun: this.liveRun !== undefined,
45
+ });
46
+ if (disposition === "queue_replay" && this.liveRun) {
47
+ this.nativeToolReplayStarted = true;
48
+ const id = `${this.nativeReplayId}-tool-${++this.nativeToolDisplayCounter}`;
49
+ const scrubbedDisplay = scrubPiToolDisplay(display, this.resolvedApiKey);
50
+ this.recordDisplayDecision({
51
+ action: "queue_replay",
52
+ disposition,
53
+ toolName: display.toolName,
54
+ identity: options.identity,
55
+ source: options.source,
56
+ transcript,
57
+ replayToolId: id,
58
+ });
59
+ return { kind: "queue_replay", tool: scrubbedDisplay, replayToolId: id, disposition };
60
+ }
61
+ const traceText = disposition === "inactive_trace"
62
+ ? formatInactiveCursorReplayTrace(scrubPiToolDisplay(display, this.resolvedApiKey))
63
+ : transcript || `Cursor tool: ${formatCursorToolName(toolCall)} completed`;
64
+ this.recordDisplayDecision({
65
+ action: "emit_trace",
66
+ disposition,
67
+ toolName: display.toolName,
68
+ identity: options.identity,
69
+ source: options.source,
70
+ transcript,
71
+ traceText,
72
+ });
73
+ return { kind: "emit_trace", traceText, disposition };
74
+ }
75
+ routeIncompleteStartedToolCall(toolCall, reason) {
76
+ const display = scrubPiToolDisplay(buildIncompleteCursorToolDisplay(toolCall, reason, { apiKey: this.resolvedApiKey }), this.resolvedApiKey);
77
+ const disposition = resolveNativeReplayDisposition({
78
+ toolName: display.toolName,
79
+ useNativeToolReplay: this.useNativeToolReplay,
80
+ activeToolNames: this.activeToolNames,
81
+ hasLiveRun: this.liveRun !== undefined,
82
+ });
83
+ if (disposition === "queue_replay" && this.liveRun && reason !== "abort") {
84
+ this.nativeToolReplayStarted = true;
85
+ const id = `${this.nativeReplayId}-tool-${++this.nativeToolDisplayCounter}`;
86
+ this.recordDisplayDecision({
87
+ action: "queue_replay",
88
+ disposition,
89
+ toolName: display.toolName,
90
+ source: "started",
91
+ reason: "incomplete-started-tool-call",
92
+ replayToolId: id,
93
+ });
94
+ return { kind: "queue_replay", tool: display, replayToolId: id, disposition };
95
+ }
96
+ const traceText = disposition === "inactive_trace"
97
+ ? formatInactiveCursorReplayTrace(display)
98
+ : formatIncompleteCursorToolTrace(display);
99
+ this.recordDisplayDecision({
100
+ action: "emit_trace",
101
+ disposition,
102
+ toolName: display.toolName,
103
+ source: "started",
104
+ reason: "incomplete-started-tool-call",
105
+ traceText,
106
+ });
107
+ return { kind: "emit_trace", traceText, disposition };
108
+ }
109
+ emitDisplayAction(action) {
110
+ if (action.kind === "queue_replay") {
111
+ if (!this.liveRun)
112
+ return;
113
+ cursorLiveRuns.queueEvent(this.liveRun, {
114
+ type: "tool",
115
+ tool: { ...action.tool, id: action.replayToolId },
116
+ });
117
+ return;
118
+ }
119
+ this.emitCursorToolTrace(action.traceText);
120
+ }
121
+ recordIgnoreBridgeDecision(identity, toolName, source) {
122
+ this.debugRecorder?.recordDisplayDecision({
123
+ action: "ignore-bridge",
124
+ toolName,
125
+ identity,
126
+ source,
127
+ });
128
+ }
129
+ recordDuplicateSkip(toolName, options) {
130
+ this.recordDisplayDecision({
131
+ action: "skip-duplicate",
132
+ toolName,
133
+ identity: options.identity,
134
+ source: options.source,
135
+ reason: options.reason,
136
+ });
137
+ }
138
+ recordIncompleteSkip(toolName, reason) {
139
+ this.recordDisplayDecision({
140
+ action: "skip-incomplete-fast-local",
141
+ toolName,
142
+ source: "started",
143
+ reason,
144
+ });
145
+ }
146
+ recordDisplayDecision(decision) {
147
+ this.debugRecorder?.recordDisplayDecision(decision);
148
+ }
149
+ emitCursorToolTrace(text) {
150
+ const traceText = text.endsWith("\n") ? text : `${text}\n`;
151
+ if (this.liveRun) {
152
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: traceText });
153
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-completed" });
154
+ return;
155
+ }
156
+ this.contentEmitter.appendThinkingBlock(traceText);
157
+ }
158
+ }
@@ -0,0 +1,35 @@
1
+ import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
2
+ import { cursorLiveRuns, drainCursorLiveRunTurn, flushPendingCursorLiveRunTraceEventsToStream, settleCursorLiveToolBatch, } from "./cursor-provider-live-run-drain.js";
3
+ import { buildIncompleteCursorToolRunOutcome, } from "./cursor-incomplete-tool-visibility.js";
4
+ export async function emitCursorLiveTurn(emitParams) {
5
+ const { params, prepared, sdkEventDebug, discardIncompleteTools } = emitParams;
6
+ if (prepared.runtime.kind !== "live")
7
+ throw new Error("emitCursorLiveTurn requires a live run");
8
+ const { liveRun, turnCoordinator } = prepared.runtime;
9
+ const { options, model } = params;
10
+ try {
11
+ await cursorLiveRuns.withRunLease(liveRun, options?.signal, async () => {
12
+ await cursorLiveRuns.waitForProgress(liveRun, options?.signal);
13
+ await settleCursorLiveToolBatch(liveRun);
14
+ turnCoordinator.closeTraceBlock();
15
+ await drainCursorLiveRunTurn(params.stream, params.partial, model, params.context, liveRun, 0, {
16
+ mode: "emit",
17
+ signal: options?.signal,
18
+ debugRecorder: sdkEventDebug,
19
+ });
20
+ });
21
+ }
22
+ catch (caught) {
23
+ if (caught instanceof CursorLiveRunAbortError) {
24
+ discardIncompleteTools({ status: "cancelled", signalAborted: true });
25
+ turnCoordinator.closeTraceBlock();
26
+ flushPendingCursorLiveRunTraceEventsToStream(params.stream, params.partial, liveRun, {
27
+ includeTracesBehindQueuedTools: true,
28
+ });
29
+ }
30
+ throw caught;
31
+ }
32
+ }
33
+ export function discardIncompleteToolsFromPrepared(prepared, outcome) {
34
+ prepared?.runtime.turnCoordinator.discardIncompleteStartedToolCalls(buildIncompleteCursorToolRunOutcome(outcome));
35
+ }