pi-cursor-sdk 0.1.56 → 0.1.57

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 (85) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/README.md +100 -28
  3. package/docs/cursor-live-smoke-checklist.md +7 -7
  4. package/docs/cursor-model-ux-spec.md +45 -38
  5. package/docs/cursor-native-tool-replay.md +2 -2
  6. package/docs/cursor-native-tool-visual-audit.md +1 -1
  7. package/docs/cursor-testing-lessons.md +20 -7
  8. package/docs/cursor-tool-surfaces.md +13 -2
  9. package/docs/platform-smoke-implementation.md +220 -0
  10. package/docs/platform-smoke.md +183 -247
  11. package/package.json +38 -6
  12. package/platform-smoke.config.mjs +5 -1
  13. package/scripts/cloud-runtime-smoke.d.mts +3 -0
  14. package/scripts/cloud-runtime-smoke.mjs +502 -0
  15. package/scripts/debug-provider-events.mjs +7 -2
  16. package/scripts/isolated-cursor-smoke.sh +4 -6
  17. package/scripts/lib/cursor-child-process.d.mts +1 -0
  18. package/scripts/lib/cursor-child-process.mjs +137 -7
  19. package/scripts/lib/local-resume-smoke-harness.mjs +543 -0
  20. package/scripts/local-resume-cleanup-smoke.mjs +108 -0
  21. package/scripts/local-resume-smoke.d.mts +1 -0
  22. package/scripts/local-resume-smoke.mjs +642 -0
  23. package/scripts/platform-smoke/artifact-anchored-extract.d.mts +10 -0
  24. package/scripts/platform-smoke/artifact-anchored-extract.mjs +111 -0
  25. package/scripts/platform-smoke/artifact-bundle-chunk.mjs +57 -0
  26. package/scripts/platform-smoke/artifact-bundle-contract.mjs +34 -0
  27. package/scripts/platform-smoke/artifact-fs-safety.mjs +311 -0
  28. package/scripts/platform-smoke/artifact-openat-extract.c +335 -0
  29. package/scripts/platform-smoke/artifact-secrets.mjs +155 -0
  30. package/scripts/platform-smoke/artifacts.mjs +293 -65
  31. package/scripts/platform-smoke/card-detect.mjs +16 -4
  32. package/scripts/platform-smoke/crabbox-runner.mjs +45 -3
  33. package/scripts/platform-smoke/doctor.mjs +20 -10
  34. package/scripts/platform-smoke/live-suite-runner.mjs +18 -57
  35. package/scripts/platform-smoke/local-resume-runner.mjs +252 -0
  36. package/scripts/platform-smoke/local-resume-suites.d.mts +15 -0
  37. package/scripts/platform-smoke/local-resume-suites.mjs +104 -0
  38. package/scripts/platform-smoke/scenarios.mjs +16 -2
  39. package/scripts/platform-smoke/target-runtime.mjs +206 -0
  40. package/scripts/platform-smoke/targets.mjs +33 -141
  41. package/scripts/platform-smoke/visual-evidence.mjs +6 -7
  42. package/scripts/platform-smoke/wrapped-line-match.mjs +9 -0
  43. package/scripts/platform-smoke.mjs +40 -27
  44. package/scripts/refresh-cursor-model-snapshots.mjs +18 -6
  45. package/scripts/steering-rpc-smoke.mjs +12 -2
  46. package/scripts/tmux-live-smoke.sh +3 -5
  47. package/shared/cursor-cloud-lifecycle-constants.d.mts +3 -0
  48. package/shared/cursor-cloud-lifecycle-constants.mjs +7 -0
  49. package/shared/cursor-sensitive-text.mjs +7 -1
  50. package/src/context.ts +5 -2
  51. package/src/cursor-agents-context-registration.ts +7 -0
  52. package/src/cursor-agents-context.ts +3 -1
  53. package/src/cursor-api-key.ts +15 -1
  54. package/src/cursor-bridge-contract.ts +3 -0
  55. package/src/cursor-cloud-lifecycle.ts +733 -0
  56. package/src/cursor-cloud-options.ts +206 -0
  57. package/src/cursor-cloud-reporting.ts +246 -0
  58. package/src/cursor-config.ts +659 -0
  59. package/src/cursor-display-only-trace.ts +14 -0
  60. package/src/cursor-display-text.ts +8 -2
  61. package/src/cursor-durable-fs.ts +49 -0
  62. package/src/cursor-fallback-models.generated.ts +2045 -485
  63. package/src/cursor-live-run-accounting.ts +7 -1
  64. package/src/cursor-live-run-coordinator.ts +1 -0
  65. package/src/cursor-pi-tool-bridge-run.ts +14 -4
  66. package/src/cursor-provider-live-run-drain.ts +5 -0
  67. package/src/cursor-provider-run-finalizer.ts +36 -28
  68. package/src/cursor-provider-turn-finalize.ts +72 -6
  69. package/src/cursor-provider-turn-prepare.ts +228 -12
  70. package/src/cursor-provider-turn-runner.ts +41 -13
  71. package/src/cursor-provider-turn-send.ts +59 -16
  72. package/src/cursor-provider-turn-types.ts +44 -10
  73. package/src/cursor-runtime-state.ts +478 -0
  74. package/src/cursor-sdk-event-debug.ts +46 -6
  75. package/src/cursor-sdk-process-error-guard.ts +101 -30
  76. package/src/cursor-session-agent-cleanup.ts +328 -0
  77. package/src/cursor-session-agent-resume.ts +439 -0
  78. package/src/cursor-session-agent.ts +109 -13
  79. package/src/cursor-session-scope.ts +35 -2
  80. package/src/cursor-session-send-policy.ts +1 -1
  81. package/src/cursor-skill-tool.ts +30 -11
  82. package/src/cursor-state.ts +112 -69
  83. package/src/cursor-usage-accounting.ts +14 -4
  84. package/src/index.ts +11 -2
  85. package/src/model-discovery.ts +10 -56
@@ -38,7 +38,13 @@ export function takeCursorLiveSdkTurnUsage(state: CursorLiveRunAccountingState):
38
38
  sdkTurnUsage?: CursorSdkTurnUsage;
39
39
  } {
40
40
  const { sdkTurnUsage, ...nextState } = state;
41
- return { state: { ...nextState, sdkTurnEnded: false }, sdkTurnUsage };
41
+ return {
42
+ state: {
43
+ ...nextState,
44
+ sdkTurnEnded: false,
45
+ },
46
+ sdkTurnUsage,
47
+ };
42
48
  }
43
49
 
44
50
  function asToolResultMessage(message: Message): ToolResultMessage | undefined {
@@ -48,6 +48,7 @@ export interface CursorLiveRun {
48
48
  emittedText: string;
49
49
  recordedToolDisplayIds: string[];
50
50
  finalText?: string;
51
+ resumeNotice?: string;
51
52
  done: boolean;
52
53
  cancelled: boolean;
53
54
  disposed: boolean;
@@ -150,6 +150,16 @@ export class CursorPiToolBridgeRunImpl implements CursorPiToolBridgeRun {
150
150
  this.debugRecorder = recorder;
151
151
  }
152
152
 
153
+ private recordBridgeRaw(
154
+ payload: Parameters<NonNullable<CursorPiToolBridgeRunOptions["debugRecorder"]>["recordBridgeRaw"]>[0],
155
+ ): void {
156
+ try {
157
+ this.debugRecorder?.recordBridgeRaw(payload);
158
+ } catch {
159
+ // Debug capture must never block or strand a bridge call.
160
+ }
161
+ }
162
+
153
163
  async resolveToolResults(toolResults: readonly ToolResultMessage[]): Promise<void> {
154
164
  let resolvedCount = 0;
155
165
  for (const toolResult of toolResults) {
@@ -298,11 +308,11 @@ export class CursorPiToolBridgeRunImpl implements CursorPiToolBridgeRun {
298
308
  }
299
309
  this.queuedRequests.push(request);
300
310
  this.emitRequestQueuedDiagnostic(request);
301
- this.debugRecorder?.recordBridgeRaw({ kind: "queued", request });
311
+ this.recordBridgeRaw({ kind: "queued", request });
302
312
  return;
303
313
  }
304
314
  this.emitRequestQueuedDiagnostic(request);
305
- this.debugRecorder?.recordBridgeRaw({ kind: "queued", request });
315
+ this.recordBridgeRaw({ kind: "queued", request });
306
316
  this.dispatchPendingToolRequest(pending, this.onToolRequest);
307
317
  });
308
318
  }
@@ -331,7 +341,7 @@ export class CursorPiToolBridgeRunImpl implements CursorPiToolBridgeRun {
331
341
  pending.settled = true;
332
342
  this.removePending(pending);
333
343
  this.emitRequestResolvedDiagnostic(pending.request, result.isError === true);
334
- this.debugRecorder?.recordBridgeRaw({ kind: "resolved", request: pending.request, result });
344
+ this.recordBridgeRaw({ kind: "resolved", request: pending.request, result });
335
345
  pending.resolve(result);
336
346
  }
337
347
 
@@ -340,7 +350,7 @@ export class CursorPiToolBridgeRunImpl implements CursorPiToolBridgeRun {
340
350
  pending.settled = true;
341
351
  this.removePending(pending);
342
352
  this.emitRequestRejectedDiagnostic(pending.request, kind);
343
- this.debugRecorder?.recordBridgeRaw({
353
+ this.recordBridgeRaw({
344
354
  kind: "rejected",
345
355
  request: pending.request,
346
356
  error: error.message,
@@ -22,6 +22,7 @@ import { type CursorPiBridgeToolRequest } from "./cursor-pi-tool-bridge.js";
22
22
  import { resetSessionCursorAgent } from "./cursor-session-agent.js";
23
23
  import { applyCursorUsage } from "./cursor-usage-accounting.js";
24
24
  import { CursorPartialContentEmitter } from "./cursor-partial-content-emitter.js";
25
+ import { emitDisplayOnlyTraceBlock } from "./cursor-display-only-trace.js";
25
26
  import { trimCurrentTurnAlreadyEmittedCursorText } from "./cursor-run-final-text.js";
26
27
  import { formatCursorSdkAbortMessage, resolveCursorSdkAbortCause } from "./cursor-provider-errors.js";
27
28
  import { formatInactiveCursorReplayTrace } from "./cursor-native-replay-trace.js";
@@ -379,6 +380,10 @@ export async function drainCursorLiveRunTurn(
379
380
  applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
380
381
  turn: cursorLiveRuns.takeSdkTurnUsage(run),
381
382
  });
383
+ if (run.resumeNotice) {
384
+ emitDisplayOnlyTraceBlock(stream, partial, run.resumeNotice);
385
+ run.resumeNotice = undefined;
386
+ }
382
387
  partial.stopReason = "stop";
383
388
  stream.push({ type: "done", reason: "stop", message: partial });
384
389
  await cursorLiveRuns.release(run);
@@ -1,5 +1,5 @@
1
1
  import type { AssistantMessage } from "@earendil-works/pi-ai/compat";
2
- import { abandonSessionCursorAgent, cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
2
+ import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
3
3
  import {
4
4
  classifyCursorRunEmission,
5
5
  getCursorRunAbortMessage,
@@ -23,27 +23,32 @@ import type {
23
23
  CursorProviderTurnRunnerParams,
24
24
  CursorProviderTurnSend,
25
25
  CursorProviderTurnSendResult,
26
+ LiveCursorProviderTurnRuntime,
27
+ LocalCursorProviderTurnPrepareResult,
26
28
  } from "./cursor-provider-turn-types.js";
27
29
  import { applyCursorUsage } from "./cursor-usage-accounting.js";
28
30
  import { hasUsableText } from "./cursor-record-utils.js";
31
+ import { emitDisplayOnlyTraceBlock } from "./cursor-display-only-trace.js";
29
32
  export type CursorTurnTerminalEvent =
30
33
  | {
31
34
  kind: "direct";
32
35
  prepared: CursorProviderTurnPrepareResult;
33
36
  outcome: CursorRunOutcome;
37
+ displayOnlyTraceBlock?: string;
34
38
  }
35
39
  | { kind: "error"; prepared: CursorProviderTurnPrepareResult | undefined; error: unknown };
36
40
 
37
41
  function applyLiveRunOutcome(
38
42
  outcome: CursorRunOutcome,
39
- prepared: CursorProviderTurnPrepareResult,
43
+ prepared: LocalCursorProviderTurnPrepareResult & { runtime: LiveCursorProviderTurnRuntime },
40
44
  context: CursorProviderTurnRunnerParams["context"],
41
45
  ): void {
42
- if (prepared.runtime.kind !== "live" || prepared.runtime.liveRun.disposed) return;
46
+ if (prepared.runtime.liveRun.disposed) return;
43
47
  const { liveRun } = prepared.runtime;
44
48
  switch (classifyCursorRunEmission(outcome)) {
45
49
  case "finished":
46
- prepared.sessionAgentLease.commitSend(context, prepared.meta.bootstrap);
50
+ prepared.lifecycle.commitSend(context, prepared.meta.bootstrap);
51
+ if (prepared.meta.resumeNotice) liveRun.resumeNotice = prepared.meta.resumeNotice;
47
52
  cursorLiveRuns.markFinished(liveRun, outcome.kind === "finished" ? outcome.finalText : "");
48
53
  break;
49
54
  case "cancelled":
@@ -69,7 +74,7 @@ export interface CursorRunFinalizerParams {
69
74
 
70
75
  export interface StartCursorLiveRunCompletionParams {
71
76
  send: CursorProviderTurnSend;
72
- prepared: CursorProviderTurnPrepareResult;
77
+ prepared: LocalCursorProviderTurnPrepareResult & { runtime: LiveCursorProviderTurnRuntime };
73
78
  modelId: string;
74
79
  discardIncompleteTools: (outcome: IncompleteCursorToolRunOutcomeInput) => void;
75
80
  }
@@ -84,7 +89,6 @@ export class CursorRunFinalizer {
84
89
  const sdkEventDebug = this.params.sdkEventDebug();
85
90
  const { send, prepared, modelId, discardIncompleteTools } = startParams;
86
91
  const { run, cursorAgentMessageOffset } = send;
87
- if (prepared.runtime.kind !== "live") throw new Error("startLiveRunCompletion requires a live run");
88
92
  const { liveRun } = prepared.runtime;
89
93
  const waitCompletion = awaitFinalizeCursorRunOutcome({
90
94
  run,
@@ -100,30 +104,30 @@ export class CursorRunFinalizer {
100
104
  cacheContextWindow: true,
101
105
  contextWindowAgentId: liveRun.agent.agentId,
102
106
  })
103
- .then(async (outcome) => {
104
- applyLiveRunOutcome(outcome, prepared, runnerParams.context);
107
+ .then(async (finalized) => {
108
+ applyLiveRunOutcome(finalized.outcome, prepared, runnerParams.context);
105
109
  })
106
- .catch(async (error: unknown) => {
107
- sdkEventDebug?.recordWaitResult({ status: "error", error: String(error) });
108
- sdkEventDebug?.recordError("run_wait", error);
109
- discardIncompleteTools({ status: "error" });
110
- await sdkEventDebug?.captureRunArtifacts(run);
111
- if (liveRun.disposed) return;
112
- cursorLiveRuns.markError(
113
- liveRun,
114
- sanitizeCursorProviderError(error, this.params.resolvedApiKey() ?? runnerParams.options?.apiKey),
115
- );
110
+ .catch((error: unknown) => {
111
+ this.safeCleanup(() => discardIncompleteTools({ status: "error" }));
112
+ if (!liveRun.disposed) {
113
+ cursorLiveRuns.markError(
114
+ liveRun,
115
+ sanitizeCursorProviderError(error, this.params.resolvedApiKey() ?? runnerParams.options?.apiKey),
116
+ );
117
+ }
118
+ this.safeCleanup(() => sdkEventDebug?.recordWaitResult({ status: "error", error: String(error) }));
119
+ this.safeCleanup(() => sdkEventDebug?.recordError("run_wait", error));
116
120
  });
117
- // Mark the pooled agent busy as soon as the SDK run exists so auto-compaction summarization
121
+ // Mark the pooled local agent busy as soon as the SDK run exists so auto-compaction summarization
118
122
  // (and other concurrent acquires) wait for run.wait() instead of hitting AgentBusyError.
119
- prepared.sessionAgentLease.trackRunCompletion(waitCompletion);
123
+ prepared.lifecycle.trackRunCompletion(waitCompletion);
120
124
  return { waitCompletion, prepared };
121
125
  }
122
126
 
123
127
  async applyTerminalEvent(event: CursorTurnTerminalEvent): Promise<void> {
124
128
  if (this.terminalApplied) return;
125
129
  if (event.kind === "direct") {
126
- await this.applyDirectOutcome(event.prepared, event.outcome);
130
+ await this.applyDirectOutcome(event.prepared, event.outcome, event.displayOnlyTraceBlock);
127
131
  this.terminalApplied = true;
128
132
  return;
129
133
  }
@@ -151,6 +155,7 @@ export class CursorRunFinalizer {
151
155
  .catch(() => {});
152
156
  return;
153
157
  }
158
+ await prepared?.lifecycle.dispose().catch(() => {});
154
159
  await this.finalizeSdkEventDebugBestEffort();
155
160
  this.safeCleanup(() => this.params.sdkProcessErrorGuard.dispose());
156
161
  }
@@ -158,45 +163,48 @@ export class CursorRunFinalizer {
158
163
  private async applyDirectOutcome(
159
164
  prepared: CursorProviderTurnPrepareResult,
160
165
  outcome: CursorRunOutcome,
166
+ displayOnlyTraceBlock: string | undefined,
161
167
  ): Promise<void> {
162
168
  const { stream, partial, model, context } = this.params.runnerParams;
163
169
  prepared.runtime.turnCoordinator.closeTraceBlock();
164
170
  switch (classifyCursorRunEmission(outcome)) {
165
171
  case "cancelled":
166
- await abandonSessionCursorAgent(prepared.sessionAgentScopeKey);
172
+ await prepared.lifecycle.abandon();
167
173
  this.pushTerminalError(partial, "aborted", getCursorRunAbortMessage(outcome));
168
174
  break;
169
175
  case "failed":
170
- await abandonSessionCursorAgent(prepared.sessionAgentScopeKey);
176
+ await prepared.lifecycle.abandon();
171
177
  this.pushTerminalError(partial, "error", outcome.kind === "error" ? outcome.errorMessage : "Cursor SDK run failed.");
172
178
  break;
173
179
  case "finished":
174
- prepared.sessionAgentLease.commitSend(context, prepared.meta.bootstrap);
180
+ prepared.lifecycle.commitSend(context, prepared.meta.bootstrap);
175
181
  prepared.runtime.turnCoordinator.flushText(
176
182
  outcome.kind === "finished" && hasUsableText(outcome.finalText) ? [outcome.finalText] : [],
177
183
  );
178
184
  applyCursorUsage(partial, model, context, prepared.meta.promptInputTokens, {
179
185
  turn: prepared.runtime.turnCoordinator.lastSdkTurnUsage,
180
186
  });
187
+ if (prepared.meta.resumeNotice) emitDisplayOnlyTraceBlock(stream, partial, prepared.meta.resumeNotice);
188
+ if (displayOnlyTraceBlock) emitDisplayOnlyTraceBlock(stream, partial, displayOnlyTraceBlock);
181
189
  stream.push({ type: "done", reason: "stop", message: partial });
182
190
  break;
183
191
  }
184
192
  }
185
193
 
186
194
  private async applyErrorOutcome(prepared: CursorProviderTurnPrepareResult | undefined, error: unknown): Promise<void> {
187
- this.params.sdkEventDebug()?.recordError("provider_stream", error);
188
- prepared?.runtime.turnCoordinator.discardIncompleteStartedToolCalls(
195
+ this.safeCleanup(() => prepared?.runtime.turnCoordinator.discardIncompleteStartedToolCalls(
189
196
  buildIncompleteCursorToolRunOutcome({
190
197
  status: error instanceof CursorLiveRunAbortError ? "cancelled" : "error",
191
198
  signalAborted: error instanceof CursorLiveRunAbortError,
192
199
  }),
193
- );
200
+ ));
194
201
  const activeLiveRun = prepared?.runtime.liveRun;
195
202
  if (activeLiveRun && !activeLiveRun.disposed) {
196
203
  await cursorLiveRuns.release(activeLiveRun);
197
204
  } else {
198
- await abandonSessionCursorAgent(prepared?.sessionAgentScopeKey);
205
+ await prepared?.lifecycle.abandon();
199
206
  }
207
+ this.safeCleanup(() => this.params.sdkEventDebug()?.recordError("provider_stream", error));
200
208
  if (error instanceof CursorLiveRunAbortError) {
201
209
  this.params.sdkProcessErrorGuard.suppressAbortErrors();
202
210
  this.pushTerminalError(this.params.runnerParams.partial, "aborted", this.abortMessage());
@@ -1,6 +1,13 @@
1
1
  import type { RunError, SDKAgent } from "@cursor/sdk";
2
2
  import { loadCursorTranscriptWebToolCallsAfterOffset } from "./cursor-agent-message-web-tools.js";
3
+ import {
4
+ collectCursorCloudRunReport,
5
+ formatCursorCloudRunReport,
6
+ type CursorCloudRunReport,
7
+ } from "./cursor-cloud-reporting.js";
8
+ import { recordCursorCloudLifecycleRun } from "./cursor-cloud-lifecycle.js";
3
9
  import { getCheckpointContextWindow, saveCachedContextWindow } from "./context-window-cache.js";
10
+ import { scrubSensitiveText } from "./cursor-sensitive-text.js";
4
11
  import type { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
5
12
  import type { CursorSdkTurnCoordinator } from "./cursor-provider-turn-coordinator.js";
6
13
  import {
@@ -78,6 +85,22 @@ async function replayCursorTranscriptWebToolCalls(
78
85
  }
79
86
  }
80
87
 
88
+ function scrubCursorCloudReportingError(error: unknown, apiKey: string | undefined): Error {
89
+ return new Error(scrubSensitiveText(error instanceof Error ? error.message : String(error), apiKey));
90
+ }
91
+
92
+ function recordCursorCloudReportingError(
93
+ sdkEventDebug: CursorSdkEventDebugSink | undefined,
94
+ error: unknown,
95
+ apiKey: string | undefined,
96
+ ): void {
97
+ try {
98
+ sdkEventDebug?.recordError("cloud_run_report", scrubCursorCloudReportingError(error, apiKey));
99
+ } catch {
100
+ // Debug reporting must never affect provider execution.
101
+ }
102
+ }
103
+
81
104
  export interface AwaitFinalizeCursorRunOutcomeParams {
82
105
  run: Awaited<ReturnType<SDKAgent["send"]>>;
83
106
  prepared: CursorProviderTurnPrepareResult;
@@ -95,10 +118,15 @@ export interface AwaitFinalizeCursorRunOutcomeParams {
95
118
  contextWindowAgentId?: string;
96
119
  }
97
120
 
121
+ export interface FinalizedCursorRunOutcome {
122
+ outcome: CursorRunOutcome;
123
+ displayOnlyTraceBlock?: string;
124
+ }
125
+
98
126
  /** Single wait/finalize path for SDK runs: wait, debug capture, transcript replay, incomplete tools, artifacts, context cache. */
99
- export async function awaitFinalizeCursorRunOutcome(params: AwaitFinalizeCursorRunOutcomeParams): Promise<CursorRunOutcome> {
127
+ export async function awaitFinalizeCursorRunOutcome(params: AwaitFinalizeCursorRunOutcomeParams): Promise<FinalizedCursorRunOutcome> {
128
+ const apiKey = params.resolvedApiKey ?? params.optionsApiKey;
100
129
  const waitResult = params.waitResult ?? (await params.run.wait());
101
- params.sdkEventDebug?.recordWaitResult(waitResult);
102
130
  const outcome = buildCursorRunOutcomeFromWait({
103
131
  waitResult,
104
132
  prepared: params.prepared,
@@ -108,7 +136,41 @@ export async function awaitFinalizeCursorRunOutcome(params: AwaitFinalizeCursorR
108
136
  resolvedApiKey: params.resolvedApiKey,
109
137
  optionsApiKey: params.optionsApiKey,
110
138
  });
111
- if (isCursorRunFinishedSuccessfully(outcome)) {
139
+ let displayOnlyTraceBlock: string | undefined;
140
+ if (params.prepared.runtimeTarget === "cloud" && isCursorRunFinishedSuccessfully(outcome)) {
141
+ let report: CursorCloudRunReport = { agentId: params.run.agentId, runId: params.run.id, branches: [] };
142
+ try {
143
+ report = await collectCursorCloudRunReport({
144
+ agent: params.prepared.agent,
145
+ run: params.run,
146
+ waitResult,
147
+ apiKey,
148
+ });
149
+ } catch (error) {
150
+ recordCursorCloudReportingError(params.sdkEventDebug, error, apiKey);
151
+ }
152
+ try {
153
+ recordCursorCloudLifecycleRun(report, { apiKey });
154
+ } catch (error) {
155
+ recordCursorCloudReportingError(params.sdkEventDebug, error, apiKey);
156
+ }
157
+ try {
158
+ params.sdkEventDebug?.recordProviderEvent("cloud_run_report", report);
159
+ } catch (error) {
160
+ recordCursorCloudReportingError(params.sdkEventDebug, error, apiKey);
161
+ }
162
+ try {
163
+ displayOnlyTraceBlock = formatCursorCloudRunReport(report, { apiKey });
164
+ } catch (error) {
165
+ recordCursorCloudReportingError(params.sdkEventDebug, error, apiKey);
166
+ }
167
+ }
168
+ try {
169
+ params.sdkEventDebug?.recordWaitResult(waitResult);
170
+ } catch {
171
+ // Debug reporting must never affect provider execution.
172
+ }
173
+ if (params.prepared.runtimeTarget === "local" && isCursorRunFinishedSuccessfully(outcome)) {
112
174
  await replayCursorTranscriptWebToolCalls(
113
175
  params.run.agentId,
114
176
  params.prepared.cwd,
@@ -118,9 +180,13 @@ export async function awaitFinalizeCursorRunOutcome(params: AwaitFinalizeCursorR
118
180
  );
119
181
  }
120
182
  params.prepared.runtime.turnCoordinator.discardIncompleteStartedToolCalls(outcome.incompleteTools);
121
- await params.sdkEventDebug?.captureRunArtifacts(params.run);
122
- if (params.cacheContextWindow !== false) {
183
+ try {
184
+ await params.sdkEventDebug?.captureRunArtifacts(params.run);
185
+ } catch {
186
+ // Debug artifact failures must never affect provider execution.
187
+ }
188
+ if (params.prepared.runtimeTarget === "local" && params.cacheContextWindow !== false) {
123
189
  await cacheSdkContextWindow(params.contextWindowAgentId ?? params.run.agentId, params.modelId, params.prepared.cwd);
124
190
  }
125
- return outcome;
191
+ return { outcome, displayOnlyTraceBlock };
126
192
  }