pi-cursor-sdk 0.2.0 → 0.3.7

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