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,15 @@
1
+ import { isCursorModel } from "./cursor-model.js";
2
+ import { registerCursorModelLifecycle } from "./cursor-model-lifecycle.js";
3
+ import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
4
+ export function registerCursorFallbackIssueWarning(pi, issue) {
5
+ const warnedSessionScopeKeys = new Set();
6
+ registerCursorModelLifecycle(pi, (ctx) => {
7
+ if (!isCursorModel(ctx.model) || !ctx.hasUI)
8
+ return;
9
+ const scopeKey = getCursorSessionScopeKey();
10
+ if (warnedSessionScopeKeys.has(scopeKey))
11
+ return;
12
+ warnedSessionScopeKeys.add(scopeKey);
13
+ ctx.ui.notify(issue.message, "warning");
14
+ });
15
+ }
@@ -0,0 +1,45 @@
1
+ import { asRecord } from "./cursor-record-utils.js";
2
+ export const CURSOR_HTTP1_ENTRY_TYPE = "cursor-http1-state";
3
+ let sessionCursorHttp1Enabled;
4
+ let globalPreferenceAuthoritative = false;
5
+ let configuredCursor;
6
+ export function isCursorHttp1EntryData(value) {
7
+ return typeof asRecord(value)?.enabled === "boolean";
8
+ }
9
+ export function getStoredCursorHttp1Enabled() {
10
+ return sessionCursorHttp1Enabled;
11
+ }
12
+ export function setStoredCursorHttp1Enabled(enabled) {
13
+ sessionCursorHttp1Enabled = enabled;
14
+ }
15
+ export function getResolvedSessionCursorHttp1Enabled() {
16
+ return globalPreferenceAuthoritative ? undefined : sessionCursorHttp1Enabled;
17
+ }
18
+ export function setCursorHttp1GlobalPreferenceAuthoritative(authoritative) {
19
+ globalPreferenceAuthoritative = authoritative;
20
+ }
21
+ export function clearCursorSdkHttp1() {
22
+ if (configuredCursor === undefined)
23
+ return;
24
+ configuredCursor.configure({ local: { useHttp1ForAgent: null } });
25
+ configuredCursor = undefined;
26
+ }
27
+ export function configureCursorSdkHttp1(sdk, setting) {
28
+ if (setting.source !== "builtin") {
29
+ sdk.Cursor.configure({ local: { useHttp1ForAgent: setting.value } });
30
+ configuredCursor = sdk.Cursor;
31
+ return setting.value;
32
+ }
33
+ if (configuredCursor === sdk.Cursor)
34
+ clearCursorSdkHttp1();
35
+ else
36
+ configuredCursor = undefined;
37
+ return undefined;
38
+ }
39
+ export const __testUtils = {
40
+ reset() {
41
+ sessionCursorHttp1Enabled = undefined;
42
+ globalPreferenceAuthoritative = false;
43
+ configuredCursor = undefined;
44
+ },
45
+ };
@@ -0,0 +1,83 @@
1
+ import { CURSOR_REPLAY_ACTIVITY_TOOL_NAME, getCursorToolActivityTitle } from "./cursor-tool-presentation-registry.js";
2
+ import { truncateCursorDisplayLine } from "./cursor-display-text.js";
3
+ import { scrubSensitiveText } from "./cursor-sensitive-text.js";
4
+ import { DISCARDED_INCOMPLETE_TOOL_CALL_REASON, } from "./cursor-sdk-event-debug.js";
5
+ import { assembleCursorReplayActivityDetails, parseCursorReplayToolDetails, resolveIncompleteReplayActivitySourceToolName, } from "./cursor-replay-tool-details.js";
6
+ import { asRecord } from "./cursor-record-utils.js";
7
+ import { classifyCursorToolVisibility } from "./cursor-tool-visibility.js";
8
+ export function buildIncompleteCursorToolRunOutcome(outcome = {}) {
9
+ return {
10
+ reason: outcome.reason ??
11
+ (outcome.status === "cancelled" || outcome.signalAborted
12
+ ? "abort"
13
+ : outcome.status === "error"
14
+ ? "sdk-failure"
15
+ : DISCARDED_INCOMPLETE_TOOL_CALL_REASON),
16
+ assistantTextProduced: outcome.assistantTextProduced ?? false,
17
+ };
18
+ }
19
+ export function resolveIncompleteCursorToolVisibility(toolCall, outcome) {
20
+ const visibility = classifyCursorToolVisibility(toolCall);
21
+ if (outcome.reason === DISCARDED_INCOMPLETE_TOOL_CALL_REASON &&
22
+ outcome.assistantTextProduced &&
23
+ visibility.fastLocalDiscovery) {
24
+ return "debugOnly";
25
+ }
26
+ return "emit";
27
+ }
28
+ export function formatIncompleteCursorToolReasonText(reason) {
29
+ switch (reason) {
30
+ case DISCARDED_INCOMPLETE_TOOL_CALL_REASON:
31
+ return "missing completion";
32
+ case "abort":
33
+ return "aborted";
34
+ case "sdk-failure":
35
+ return "SDK run failed";
36
+ case "run-drain":
37
+ return "run ended during drain";
38
+ }
39
+ }
40
+ export function getIncompleteCursorToolActivityTitle(toolCall) {
41
+ const visibility = classifyCursorToolVisibility(toolCall);
42
+ return visibility.incompleteTitle ?? getCursorToolActivityTitle(visibility.displayName);
43
+ }
44
+ export function buildIncompleteCursorToolDisplay(toolCall, reason, options = {}) {
45
+ const visibility = classifyCursorToolVisibility(toolCall);
46
+ const activityTitle = getIncompleteCursorToolActivityTitle(toolCall);
47
+ const headline = `${activityTitle} did not complete`;
48
+ const reasonText = scrubSensitiveText(formatIncompleteCursorToolReasonText(reason), options.apiKey);
49
+ const contentText = `${headline}\n${reasonText}`;
50
+ const details = assembleCursorReplayActivityDetails(resolveIncompleteReplayActivitySourceToolName(visibility.normalizedName), headline, { summary: reasonText, expandedText: contentText }, contentText, true, reasonText);
51
+ return {
52
+ toolName: CURSOR_REPLAY_ACTIVITY_TOOL_NAME,
53
+ args: {
54
+ activityTitle,
55
+ activitySummary: reasonText,
56
+ incomplete: true,
57
+ },
58
+ result: {
59
+ content: [{ type: "text", text: contentText }],
60
+ details,
61
+ },
62
+ isError: true,
63
+ };
64
+ }
65
+ export function formatIncompleteCursorToolTrace(display) {
66
+ const parsed = parseCursorReplayToolDetails(display.result.details);
67
+ if (parsed?.variant === "activity") {
68
+ const summary = parsed.summary?.trim() ||
69
+ (typeof display.args.activitySummary === "string" && display.args.activitySummary.trim()) ||
70
+ formatIncompleteCursorToolReasonText(DISCARDED_INCOMPLETE_TOOL_CALL_REASON);
71
+ return `${truncateCursorDisplayLine(parsed.title)}: ${truncateCursorDisplayLine(summary)}\n`;
72
+ }
73
+ const detailRecord = asRecord(display.result.details);
74
+ const argsRecord = display.args;
75
+ const title = (typeof detailRecord?.title === "string" && detailRecord.title.trim()) ||
76
+ (typeof argsRecord.activityTitle === "string" && argsRecord.activityTitle.trim()
77
+ ? `${argsRecord.activityTitle} did not complete`
78
+ : "Cursor tool did not complete");
79
+ const summary = (typeof detailRecord?.summary === "string" && detailRecord.summary.trim()) ||
80
+ (typeof argsRecord.activitySummary === "string" && argsRecord.activitySummary.trim()) ||
81
+ formatIncompleteCursorToolReasonText(DISCARDED_INCOMPLETE_TOOL_CALL_REASON);
82
+ return `${truncateCursorDisplayLine(title)}: ${truncateCursorDisplayLine(summary)}\n`;
83
+ }
@@ -0,0 +1,55 @@
1
+ import { CURSOR_APPROX_CHARS_PER_TOKEN, estimateCursorPromptMessageTokens } from "./context.js";
2
+ export function createCursorLiveRunAccountingState(promptInputTokens) {
3
+ return {
4
+ promptInputTokens,
5
+ promptInputTokensReported: false,
6
+ consumedToolResultIds: new Set(),
7
+ sdkTurnEnded: false,
8
+ };
9
+ }
10
+ export function recordCursorLiveSdkTurnEnded(state, sdkTurnUsage) {
11
+ return { ...state, sdkTurnEnded: true, sdkTurnUsage };
12
+ }
13
+ export function takeCursorLiveSdkTurnUsage(state) {
14
+ const { sdkTurnUsage, ...nextState } = state;
15
+ return {
16
+ state: {
17
+ ...nextState,
18
+ sdkTurnEnded: false,
19
+ },
20
+ sdkTurnUsage,
21
+ };
22
+ }
23
+ function asToolResultMessage(message) {
24
+ return message.role === "toolResult" ? message : undefined;
25
+ }
26
+ export function consumeCursorLiveToolResults(state, context, isMatchingToolResult) {
27
+ const consumedToolResultIds = new Set(state.consumedToolResultIds);
28
+ const toolResults = [];
29
+ let toolResultInputTokens = 0;
30
+ for (const message of context.messages) {
31
+ const toolResult = asToolResultMessage(message);
32
+ if (!toolResult)
33
+ continue;
34
+ if (consumedToolResultIds.has(toolResult.toolCallId))
35
+ continue;
36
+ if (!isMatchingToolResult(toolResult))
37
+ continue;
38
+ consumedToolResultIds.add(toolResult.toolCallId);
39
+ toolResults.push(toolResult);
40
+ toolResultInputTokens += estimateCursorPromptMessageTokens(toolResult, { charsPerToken: CURSOR_APPROX_CHARS_PER_TOKEN });
41
+ }
42
+ return {
43
+ state: { ...state, consumedToolResultIds },
44
+ toolResults,
45
+ toolResultInputTokens,
46
+ toolCallIds: toolResults.map((toolResult) => toolResult.toolCallId),
47
+ };
48
+ }
49
+ export function takeCursorLiveTurnInputTokens(state, toolResultInputTokens) {
50
+ const promptInputTokens = state.promptInputTokensReported ? 0 : state.promptInputTokens;
51
+ return {
52
+ state: state.promptInputTokensReported ? state : { ...state, promptInputTokensReported: true },
53
+ sessionInputTokens: promptInputTokens + toolResultInputTokens,
54
+ };
55
+ }
@@ -0,0 +1,409 @@
1
+ import { getCursorConversationMessages } from "./cursor-pi-context.js";
2
+ import { consumeCursorLiveToolResults, createCursorLiveRunAccountingState, recordCursorLiveSdkTurnEnded, takeCursorLiveSdkTurnUsage, takeCursorLiveTurnInputTokens, } from "./cursor-live-run-accounting.js";
3
+ import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
4
+ import { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
5
+ export class CursorLiveRunAbortError extends Error {
6
+ constructor() {
7
+ super("aborted");
8
+ this.name = "CursorLiveRunAbortError";
9
+ }
10
+ }
11
+ function isPendingBridgeToolRequest(run, request) {
12
+ const bridgeRun = [run.bridgeRun, run.sessionBridgeRun].find((candidate) => candidate?.id === request.runId);
13
+ return bridgeRun?.hasPendingPiToolCallId(request.piToolCallId) === true;
14
+ }
15
+ async function cancelCursorLiveSdkRun(run) {
16
+ if (!run.sdkRun)
17
+ return;
18
+ const guard = installCursorSdkProcessErrorGuard();
19
+ guard.suppressAbortErrors();
20
+ try {
21
+ await run.sdkRun.cancel();
22
+ }
23
+ finally {
24
+ guard.dispose();
25
+ }
26
+ }
27
+ export function hasTrailingUserMessagesAfterToolResults(context) {
28
+ const messages = getCursorConversationMessages(context);
29
+ let index = messages.length - 1;
30
+ let sawTrailingUser = false;
31
+ while (index >= 0 && messages[index]?.role === "user") {
32
+ sawTrailingUser = true;
33
+ index -= 1;
34
+ }
35
+ if (!sawTrailingUser)
36
+ return false;
37
+ let sawToolResult = false;
38
+ while (index >= 0 && messages[index]?.role === "toolResult") {
39
+ sawToolResult = true;
40
+ index -= 1;
41
+ }
42
+ return sawToolResult;
43
+ }
44
+ export function matchesCursorLiveRunToolResult(run, message, getReplayId) {
45
+ const replayId = getReplayId(message.toolCallId);
46
+ if (replayId)
47
+ return replayId === run.id;
48
+ return run.bridgeRun?.hasPendingPiToolCallId(message.toolCallId) ?? false;
49
+ }
50
+ function isSuccessfulCursorLiveRun(run) {
51
+ return run.done && !run.cancelled && !run.errorMessage;
52
+ }
53
+ export function createCursorLiveRunCoordinator(deps) {
54
+ const pendingRuns = new Map();
55
+ const pendingRunIdsByScopeKey = new Map();
56
+ const privateStates = new WeakMap();
57
+ const getScopeKey = deps.getScopeKey ?? getCursorSessionScopeKey;
58
+ function getPrivateState(run) {
59
+ let state = privateStates.get(run);
60
+ if (!state) {
61
+ state = {
62
+ waiters: new Set(),
63
+ idleDisposeRequested: false,
64
+ leased: false,
65
+ leaseQueue: [],
66
+ };
67
+ privateStates.set(run, state);
68
+ }
69
+ return state;
70
+ }
71
+ function getUndisposed(runId) {
72
+ if (!runId)
73
+ return undefined;
74
+ const run = pendingRuns.get(runId);
75
+ if (!run || run.disposed)
76
+ return undefined;
77
+ return run;
78
+ }
79
+ function clearIdleDisposeTimer(run) {
80
+ const state = getPrivateState(run);
81
+ if (!state.idleDisposeTimer)
82
+ return;
83
+ clearTimeout(state.idleDisposeTimer);
84
+ state.idleDisposeTimer = undefined;
85
+ }
86
+ function notifyProgress(run) {
87
+ const state = getPrivateState(run);
88
+ const waiters = [...state.waiters];
89
+ state.waiters.clear();
90
+ for (const waiter of waiters) {
91
+ if (waiter.onAbort)
92
+ waiter.signal?.removeEventListener("abort", waiter.onAbort);
93
+ waiter.resolve();
94
+ }
95
+ }
96
+ function removeLeaseWaiter(state, waiter) {
97
+ const index = state.leaseQueue.indexOf(waiter);
98
+ if (index >= 0)
99
+ state.leaseQueue.splice(index, 1);
100
+ }
101
+ function grantNextLeaseOrUnlock(run) {
102
+ const state = getPrivateState(run);
103
+ while (state.leaseQueue.length > 0) {
104
+ const next = state.leaseQueue.shift();
105
+ if (!next)
106
+ continue;
107
+ if (next.onAbort)
108
+ next.signal?.removeEventListener("abort", next.onAbort);
109
+ next.resolve();
110
+ return;
111
+ }
112
+ state.leased = false;
113
+ if (state.idleDisposeRequested && !run.disposed) {
114
+ coordinator.requestIdleDispose(run);
115
+ }
116
+ }
117
+ async function acquireLease(run, signal) {
118
+ if (signal?.aborted)
119
+ throw new CursorLiveRunAbortError();
120
+ const state = getPrivateState(run);
121
+ if (!state.leased) {
122
+ state.leased = true;
123
+ return;
124
+ }
125
+ await new Promise((resolve, reject) => {
126
+ const waiter = { resolve, reject, signal };
127
+ const onAbort = () => {
128
+ removeLeaseWaiter(state, waiter);
129
+ reject(new CursorLiveRunAbortError());
130
+ };
131
+ waiter.onAbort = onAbort;
132
+ state.leaseQueue.push(waiter);
133
+ if (signal?.aborted) {
134
+ onAbort();
135
+ return;
136
+ }
137
+ signal?.addEventListener("abort", onAbort, { once: true });
138
+ });
139
+ }
140
+ function unregister(run) {
141
+ pendingRuns.delete(run.id);
142
+ const scopeKey = run.sessionAgentScopeKey;
143
+ if (pendingRunIdsByScopeKey.get(scopeKey) === run.id) {
144
+ pendingRunIdsByScopeKey.delete(scopeKey);
145
+ }
146
+ }
147
+ const coordinator = {
148
+ start(params) {
149
+ const sessionAgentScopeKey = params.sessionAgentScopeKey ?? getScopeKey();
150
+ const run = {
151
+ id: params.id,
152
+ agent: params.agent,
153
+ bridgeRun: params.bridgeRun,
154
+ sessionBridgeRun: params.sessionBridgeRun,
155
+ sessionAgentScopeKey,
156
+ accounting: createCursorLiveRunAccountingState(params.promptInputTokens),
157
+ pendingEvents: [],
158
+ textDeltas: params.textDeltas ?? [],
159
+ emittedText: "",
160
+ recordedToolDisplayIds: [],
161
+ done: false,
162
+ cancelled: false,
163
+ disposed: false,
164
+ chainUserInputAfterCompletion: false,
165
+ debugRecorder: params.debugRecorder,
166
+ };
167
+ privateStates.set(run, {
168
+ waiters: new Set(),
169
+ idleDisposeRequested: false,
170
+ leased: false,
171
+ leaseQueue: [],
172
+ });
173
+ pendingRuns.set(run.id, run);
174
+ pendingRunIdsByScopeKey.set(sessionAgentScopeKey, run.id);
175
+ return run;
176
+ },
177
+ attachSdkRun(run, sdkRun) {
178
+ if (run.disposed)
179
+ return;
180
+ run.sdkRun = sdkRun;
181
+ },
182
+ markFinished(run, finalText) {
183
+ if (run.disposed)
184
+ return;
185
+ run.finalText = finalText;
186
+ run.cancelled = false;
187
+ run.done = true;
188
+ notifyProgress(run);
189
+ coordinator.requestIdleDispose(run);
190
+ },
191
+ markCancelled(run, abortMessage) {
192
+ if (run.disposed)
193
+ return;
194
+ run.cancelled = true;
195
+ run.abortMessage = abortMessage;
196
+ run.done = true;
197
+ notifyProgress(run);
198
+ coordinator.requestIdleDispose(run);
199
+ },
200
+ markError(run, errorMessage) {
201
+ if (run.disposed)
202
+ return;
203
+ run.errorMessage = errorMessage;
204
+ run.done = true;
205
+ notifyProgress(run);
206
+ coordinator.requestIdleDispose(run);
207
+ },
208
+ recordSdkTurnEnded(run, usage) {
209
+ if (run.disposed)
210
+ return;
211
+ if (run.ignoreFutureSdkTurnUsage) {
212
+ run.accounting = { ...run.accounting, sdkTurnEnded: false, sdkTurnUsage: undefined };
213
+ notifyProgress(run);
214
+ return;
215
+ }
216
+ run.accounting = recordCursorLiveSdkTurnEnded(run.accounting, usage);
217
+ notifyProgress(run);
218
+ },
219
+ ignoreFutureSdkTurnUsage(run) {
220
+ if (!run.disposed)
221
+ run.ignoreFutureSdkTurnUsage = true;
222
+ },
223
+ hasSdkTurnEnded(run) {
224
+ return run.accounting.sdkTurnEnded;
225
+ },
226
+ queueEvent(run, event) {
227
+ if (run.disposed)
228
+ return;
229
+ run.pendingEvents.push(event);
230
+ run.debugRecorder?.recordLiveRunEvent(event);
231
+ notifyProgress(run);
232
+ },
233
+ peekEvent(run) {
234
+ return run.pendingEvents[0];
235
+ },
236
+ shiftEvent(run) {
237
+ return run.pendingEvents.shift();
238
+ },
239
+ collectNativeToolBatch(run) {
240
+ const tools = [];
241
+ while (run.pendingEvents[0]?.type === "tool") {
242
+ const event = run.pendingEvents.shift();
243
+ if (event?.type === "tool")
244
+ tools.push(event.tool);
245
+ }
246
+ return tools;
247
+ },
248
+ collectBridgeToolBatch(run) {
249
+ const requests = [];
250
+ while (run.pendingEvents[0]?.type === "bridge-tool") {
251
+ const event = run.pendingEvents.shift();
252
+ if (event?.type === "bridge-tool" && isPendingBridgeToolRequest(run, event.request)) {
253
+ requests.push(event.request);
254
+ }
255
+ }
256
+ return requests;
257
+ },
258
+ consumeToolResults(run, context, getReplayId) {
259
+ const consumed = consumeCursorLiveToolResults(run.accounting, context, (toolResult) => matchesCursorLiveRunToolResult(run, toolResult, getReplayId));
260
+ run.accounting = consumed.state;
261
+ return consumed;
262
+ },
263
+ takeTurnInputTokens(run, toolResultInputTokens) {
264
+ const taken = takeCursorLiveTurnInputTokens(run.accounting, toolResultInputTokens);
265
+ run.accounting = taken.state;
266
+ return taken.sessionInputTokens;
267
+ },
268
+ takeSdkTurnUsage(run) {
269
+ const taken = takeCursorLiveSdkTurnUsage(run.accounting);
270
+ run.accounting = taken.state;
271
+ return taken.sdkTurnUsage;
272
+ },
273
+ getPendingFromContext(context, getReplayId) {
274
+ const messages = getCursorConversationMessages(context);
275
+ let index = messages.length - 1;
276
+ while (index >= 0 && messages[index]?.role === "user") {
277
+ index -= 1;
278
+ }
279
+ for (; index >= 0; index -= 1) {
280
+ const message = messages[index];
281
+ if (message.role !== "toolResult")
282
+ break;
283
+ const replayId = getReplayId(message.toolCallId);
284
+ if (replayId) {
285
+ const replayRun = getUndisposed(replayId);
286
+ if (replayRun)
287
+ return replayRun;
288
+ }
289
+ for (const run of pendingRuns.values()) {
290
+ if (run.disposed)
291
+ continue;
292
+ if (run.bridgeRun?.hasPendingPiToolCallId(message.toolCallId))
293
+ return run;
294
+ }
295
+ }
296
+ return undefined;
297
+ },
298
+ getActiveForScope(scopeKey = getScopeKey()) {
299
+ return getUndisposed(pendingRunIdsByScopeKey.get(scopeKey));
300
+ },
301
+ isReady(run) {
302
+ return run.disposed || run.pendingEvents.length > 0 || run.done || run.cancelled || run.errorMessage !== undefined;
303
+ },
304
+ async waitForProgress(run, signal) {
305
+ if (signal?.aborted)
306
+ throw new CursorLiveRunAbortError();
307
+ if (coordinator.isReady(run))
308
+ return;
309
+ await new Promise((resolve, reject) => {
310
+ const state = getPrivateState(run);
311
+ const waiter = { resolve, reject, signal };
312
+ const cleanup = () => {
313
+ state.waiters.delete(waiter);
314
+ if (waiter.onAbort)
315
+ signal?.removeEventListener("abort", waiter.onAbort);
316
+ };
317
+ const onAbort = () => {
318
+ cleanup();
319
+ reject(new CursorLiveRunAbortError());
320
+ };
321
+ waiter.onAbort = onAbort;
322
+ waiter.resolve = () => {
323
+ cleanup();
324
+ resolve();
325
+ };
326
+ state.waiters.add(waiter);
327
+ if (signal?.aborted) {
328
+ onAbort();
329
+ return;
330
+ }
331
+ signal?.addEventListener("abort", onAbort, { once: true });
332
+ });
333
+ },
334
+ async withRunLease(run, signal, body) {
335
+ await acquireLease(run, signal);
336
+ clearIdleDisposeTimer(run);
337
+ try {
338
+ if (signal?.aborted)
339
+ throw new CursorLiveRunAbortError();
340
+ return await body();
341
+ }
342
+ finally {
343
+ grantNextLeaseOrUnlock(run);
344
+ }
345
+ },
346
+ requestIdleDispose(run) {
347
+ if (run.disposed)
348
+ return;
349
+ const state = getPrivateState(run);
350
+ clearIdleDisposeTimer(run);
351
+ state.idleDisposeRequested = true;
352
+ if (state.leased || state.leaseQueue.length > 0)
353
+ return;
354
+ state.idleDisposeRequested = false;
355
+ state.idleDisposeTimer = setTimeout(() => {
356
+ void coordinator.release(run).catch(() => {
357
+ // Idle dispose must not leave release failures as unhandled rejections.
358
+ });
359
+ }, deps.getIdleDisposeMs());
360
+ state.idleDisposeTimer.unref?.();
361
+ },
362
+ async release(run) {
363
+ const state = getPrivateState(run);
364
+ if (state.releasing)
365
+ return state.releasing;
366
+ state.releasing = (async () => {
367
+ if (run.disposed)
368
+ return;
369
+ const abandoned = !isSuccessfulCursorLiveRun(run);
370
+ run.disposed = true;
371
+ unregister(run);
372
+ clearIdleDisposeTimer(run);
373
+ state.idleDisposeRequested = false;
374
+ notifyProgress(run);
375
+ run.bridgeRun?.cancel("Cursor live run released");
376
+ for (const toolDisplayId of run.recordedToolDisplayIds)
377
+ deps.deleteNativeToolDisplay(toolDisplayId);
378
+ run.recordedToolDisplayIds = [];
379
+ if (run.sessionBridgeRun) {
380
+ run.sessionBridgeRun.setOnToolRequest(undefined);
381
+ }
382
+ if (run.bridgeRun && run.bridgeRun !== run.sessionBridgeRun) {
383
+ try {
384
+ await run.bridgeRun.dispose();
385
+ }
386
+ catch {
387
+ // bridge disposal failure should not mask the provider result
388
+ }
389
+ }
390
+ if (abandoned) {
391
+ if (!run.done) {
392
+ try {
393
+ await cancelCursorLiveSdkRun(run);
394
+ }
395
+ catch {
396
+ // cancellation failure should not block session-agent abandonment
397
+ }
398
+ }
399
+ await deps.abandonSessionAgent(run.sessionAgentScopeKey);
400
+ }
401
+ })();
402
+ return state.releasing;
403
+ },
404
+ count() {
405
+ return pendingRuns.size;
406
+ },
407
+ };
408
+ return coordinator;
409
+ }