engineering-behavior-observatory 0.2.1

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 (178) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +151 -0
  3. package/contracts/codex-app-server-0.150.1/manifest.json +25 -0
  4. package/contracts/codex-app-server-0.150.1/schema/InitializeParams.json +84 -0
  5. package/contracts/codex-app-server-0.150.1/schema/JSONRPCMessage.json +137 -0
  6. package/contracts/codex-app-server-0.150.1/schema/v2/ThreadReadParams.json +17 -0
  7. package/contracts/codex-app-server-0.150.1/schema/v2/ThreadTokenUsageUpdatedNotification.json +82 -0
  8. package/contracts/codex-app-server-0.150.1/schema/v2/TurnInterruptParams.json +17 -0
  9. package/contracts/codex-app-server-0.150.1/types/AskForApproval.ts +5 -0
  10. package/contracts/codex-app-server-0.150.1/types/ClientNotification.ts +5 -0
  11. package/contracts/codex-app-server-0.150.1/types/SandboxMode.ts +5 -0
  12. package/contracts/codex-app-server-0.150.1/types/ThreadReadParams.ts +9 -0
  13. package/contracts/codex-app-server-0.150.1/types/TokenUsageBreakdown.ts +5 -0
  14. package/contracts/codex-app-server-0.150.1/types/TurnInterruptParams.ts +5 -0
  15. package/contracts/codex-app-server-0.153.4/manifest.json +27 -0
  16. package/contracts/codex-app-server-0.153.4/schema/JSONRPCMessage.json +137 -0
  17. package/contracts/codex-app-server-0.153.4/schema/v1/InitializeParams.json +84 -0
  18. package/contracts/codex-app-server-0.153.4/schema/v2/ThreadReadParams.json +17 -0
  19. package/contracts/codex-app-server-0.153.4/schema/v2/ThreadStartParams.json +515 -0
  20. package/contracts/codex-app-server-0.153.4/schema/v2/ThreadTokenUsageUpdatedNotification.json +82 -0
  21. package/contracts/codex-app-server-0.153.4/schema/v2/TurnInterruptParams.json +17 -0
  22. package/contracts/codex-app-server-0.153.4/schema/v2/TurnStartParams.json +921 -0
  23. package/contracts/codex-app-server-0.153.4/types/AskForApproval.ts +5 -0
  24. package/contracts/codex-app-server-0.153.4/types/ClientNotification.ts +5 -0
  25. package/contracts/codex-app-server-0.153.4/types/SandboxMode.ts +5 -0
  26. package/contracts/codex-app-server-0.153.4/types/ThreadReadParams.ts +12 -0
  27. package/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.ts +5 -0
  28. package/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.ts +5 -0
  29. package/contracts/openhands-agent-server-v1.44.1.json +65 -0
  30. package/contracts/openhands-agent-server-v1.46.0.json +46 -0
  31. package/dist/contracts/codex-app-server-0.153.4/types/AskForApproval.d.ts +9 -0
  32. package/dist/contracts/codex-app-server-0.153.4/types/AskForApproval.js +2 -0
  33. package/dist/contracts/codex-app-server-0.153.4/types/ClientNotification.d.ts +3 -0
  34. package/dist/contracts/codex-app-server-0.153.4/types/ClientNotification.js +2 -0
  35. package/dist/contracts/codex-app-server-0.153.4/types/SandboxMode.d.ts +1 -0
  36. package/dist/contracts/codex-app-server-0.153.4/types/SandboxMode.js +2 -0
  37. package/dist/contracts/codex-app-server-0.153.4/types/ThreadReadParams.d.ts +10 -0
  38. package/dist/contracts/codex-app-server-0.153.4/types/ThreadReadParams.js +2 -0
  39. package/dist/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.d.ts +8 -0
  40. package/dist/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.js +2 -0
  41. package/dist/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.d.ts +4 -0
  42. package/dist/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.js +2 -0
  43. package/dist/src/agent-sdk-normalizer.d.ts +22 -0
  44. package/dist/src/agent-sdk-normalizer.js +995 -0
  45. package/dist/src/agent-sdk-run.d.ts +31 -0
  46. package/dist/src/agent-sdk-run.js +242 -0
  47. package/dist/src/agent-sdk-runner.d.ts +96 -0
  48. package/dist/src/agent-sdk-runner.js +407 -0
  49. package/dist/src/agent-sdk.d.ts +294 -0
  50. package/dist/src/agent-sdk.js +644 -0
  51. package/dist/src/aggregation.d.ts +183 -0
  52. package/dist/src/aggregation.js +513 -0
  53. package/dist/src/artifacts.d.ts +36 -0
  54. package/dist/src/artifacts.js +1372 -0
  55. package/dist/src/atlas-grafana.d.ts +377 -0
  56. package/dist/src/atlas-grafana.js +47 -0
  57. package/dist/src/atlas-html.d.ts +2 -0
  58. package/dist/src/atlas-html.js +34 -0
  59. package/dist/src/atlas.d.ts +130 -0
  60. package/dist/src/atlas.js +235 -0
  61. package/dist/src/behavior-assertions.d.ts +95 -0
  62. package/dist/src/behavior-assertions.js +154 -0
  63. package/dist/src/cli.d.ts +5 -0
  64. package/dist/src/cli.js +957 -0
  65. package/dist/src/codex-judge.d.ts +44 -0
  66. package/dist/src/codex-judge.js +214 -0
  67. package/dist/src/codex-run.d.ts +114 -0
  68. package/dist/src/codex-run.js +519 -0
  69. package/dist/src/codex.d.ts +187 -0
  70. package/dist/src/codex.js +1028 -0
  71. package/dist/src/contracts.d.ts +155 -0
  72. package/dist/src/contracts.js +866 -0
  73. package/dist/src/corpus.d.ts +67 -0
  74. package/dist/src/corpus.js +540 -0
  75. package/dist/src/cursor-sdk-runner.d.ts +81 -0
  76. package/dist/src/cursor-sdk-runner.js +362 -0
  77. package/dist/src/cursor-sdk.d.ts +81 -0
  78. package/dist/src/cursor-sdk.js +1078 -0
  79. package/dist/src/deepseek-adapter.d.ts +152 -0
  80. package/dist/src/deepseek-adapter.js +777 -0
  81. package/dist/src/exports.d.ts +69 -0
  82. package/dist/src/exports.js +966 -0
  83. package/dist/src/human-calibration.d.ts +167 -0
  84. package/dist/src/human-calibration.js +618 -0
  85. package/dist/src/index.d.ts +71 -0
  86. package/dist/src/index.js +38 -0
  87. package/dist/src/lifecycle.d.ts +196 -0
  88. package/dist/src/lifecycle.js +1889 -0
  89. package/dist/src/normalization-integrity.d.ts +128 -0
  90. package/dist/src/normalization-integrity.js +429 -0
  91. package/dist/src/openhands-run.d.ts +36 -0
  92. package/dist/src/openhands-run.js +415 -0
  93. package/dist/src/openhands.d.ts +130 -0
  94. package/dist/src/openhands.js +859 -0
  95. package/dist/src/pi.d.ts +203 -0
  96. package/dist/src/pi.js +1345 -0
  97. package/dist/src/process-protocol.d.ts +245 -0
  98. package/dist/src/process-protocol.js +1261 -0
  99. package/dist/src/retained-evidence.d.ts +12 -0
  100. package/dist/src/retained-evidence.js +201 -0
  101. package/dist/src/run-bundles.d.ts +193 -0
  102. package/dist/src/run-bundles.js +993 -0
  103. package/dist/src/scheduler.d.ts +109 -0
  104. package/dist/src/scheduler.js +1080 -0
  105. package/dist/src/semantic-judge.d.ts +194 -0
  106. package/dist/src/semantic-judge.js +875 -0
  107. package/dist/src/structural-observations.d.ts +124 -0
  108. package/dist/src/structural-observations.js +671 -0
  109. package/dist/src/task-packets.d.ts +115 -0
  110. package/dist/src/task-packets.js +683 -0
  111. package/dist/src/uniform-events.d.ts +120 -0
  112. package/dist/src/uniform-events.js +158 -0
  113. package/dist/src/verifiers.d.ts +116 -0
  114. package/dist/src/verifiers.js +819 -0
  115. package/dist/src/workspaces.d.ts +56 -0
  116. package/dist/src/workspaces.js +1314 -0
  117. package/docs/README.md +60 -0
  118. package/docs/development/README.md +71 -0
  119. package/docs/development/documentation-sync.md +58 -0
  120. package/docs/development/extension-contracts.md +307 -0
  121. package/docs/evaluation/README.md +48 -0
  122. package/docs/evaluation/aggregation.md +112 -0
  123. package/docs/evaluation/behavior-assertions.md +45 -0
  124. package/docs/evaluation/human-calibration.md +170 -0
  125. package/docs/evaluation/normalization-integrity.md +80 -0
  126. package/docs/evaluation/semantic-judge.md +188 -0
  127. package/docs/evaluation/structural-observations.md +84 -0
  128. package/docs/evaluation/uniform-events.md +131 -0
  129. package/docs/guides/atlas.md +144 -0
  130. package/docs/guides/evidence-and-sharing.md +100 -0
  131. package/docs/guides/operator-guide.md +424 -0
  132. package/docs/guides/quickstart.md +152 -0
  133. package/docs/guides/telemetry.md +66 -0
  134. package/docs/harnesses/README.md +31 -0
  135. package/docs/harnesses/claude-agent-sdk.md +61 -0
  136. package/docs/harnesses/codex-harness.md +174 -0
  137. package/docs/harnesses/cursor-sdk.md +149 -0
  138. package/docs/harnesses/deepseek-harness.md +134 -0
  139. package/docs/harnesses/openhands-agent-server.md +153 -0
  140. package/docs/harnesses/pi-sdk.md +66 -0
  141. package/docs/reference/README.md +19 -0
  142. package/docs/reference/agent-sdk-operational-runner.md +118 -0
  143. package/docs/reference/cli.md +114 -0
  144. package/docs/reference/contracts.md +222 -0
  145. package/docs/reference/run-bundle-contract.md +354 -0
  146. package/docs/reference/run-lifecycle.md +54 -0
  147. package/examples/cursor-sdk/README.md +11 -0
  148. package/examples/cursor-sdk/capture-profile.json +1 -0
  149. package/examples/cursor-sdk/harness.json +1 -0
  150. package/examples/cursor-sdk/model.json +1 -0
  151. package/examples/cursor-sdk/native-limits.json +1 -0
  152. package/examples/cursor-sdk/native-tool-policy.json +1 -0
  153. package/ontology/behavior-categories.v1.json +46 -0
  154. package/package.json +66 -0
  155. package/release/0.1.0/KNOWN_LIMITATIONS.md +35 -0
  156. package/release/0.1.0/README.md +46 -0
  157. package/release/0.1.0/reproducibility.json +61 -0
  158. package/release/0.2.0/KNOWN_LIMITATIONS.md +40 -0
  159. package/release/0.2.0/README.md +42 -0
  160. package/release/0.2.0/reproducibility.json +73 -0
  161. package/release/0.2.1/KNOWN_LIMITATIONS.md +18 -0
  162. package/release/0.2.1/README.md +63 -0
  163. package/release/0.2.1/reproducibility.json +73 -0
  164. package/release/README.md +13 -0
  165. package/schemas/aggregation.v1.json +293 -0
  166. package/schemas/behavior-assertions.v1.json +220 -0
  167. package/schemas/deepseek-runtime-composition.v1.schema.json +122 -0
  168. package/schemas/experiment.v1.schema.json +188 -0
  169. package/schemas/human-calibration.v1.json +336 -0
  170. package/schemas/normalization-integrity.v1.json +302 -0
  171. package/schemas/run-bundles/v1.json +1040 -0
  172. package/schemas/run-queue.v1.schema.json +206 -0
  173. package/schemas/semantic-judge.v1.json +380 -0
  174. package/schemas/structural-observations.v1.json +131 -0
  175. package/schemas/task-packet-freeze.v1.schema.json +106 -0
  176. package/schemas/task-packet.v1.schema.json +234 -0
  177. package/schemas/uniform-events/v1.json +289 -0
  178. package/scripts/atlas-grafana.sh +18 -0
@@ -0,0 +1,1078 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync } from "node:fs";
3
+ import { lstat, mkdir, open, stat } from "node:fs/promises";
4
+ import { dirname, join, parse, resolve } from "node:path";
5
+ import { Agent, JSONL_LOCAL_AGENT_STORE_FILES, JsonlLocalAgentStore, } from "@cursor/sdk";
6
+ import { assertNoDuplicateJsonKeys, digestMetadata } from "./artifacts.js";
7
+ import { createAttemptIdentity, createRunIdentity, executeRunAttempt, } from "./lifecycle.js";
8
+ import { createCapturedNativeEvidenceResolver, describeNormalizedDataset, validateNormalizedDataset, } from "./normalization-integrity.js";
9
+ import { createAgentSdkNativeEvidenceResolver, readQualifiedRunCapture, } from "./agent-sdk-normalizer.js";
10
+ import { createRunBundleAssembler, qualifyRunBundle, } from "./run-bundles.js";
11
+ import {} from "./uniform-events.js";
12
+ import { readBoundedFile } from "./scheduler.js";
13
+ export const CURSOR_SDK_VERSION = "1.0.31";
14
+ export const CURSOR_SDK_HARNESS = "cursor-sdk";
15
+ export const CURSOR_SDK_ADAPTER_ID = "ebo-cursor-sdk-v1";
16
+ export const CURSOR_SDK_ADAPTER_VERSION = "0.1.0";
17
+ export const CURSOR_SDK_DEFAULT_SHUTDOWN_GRACE_MS = 2_000;
18
+ const CURSOR_NATIVE_SCHEMA_VERSION = "ebo.cursor-native-record/v1";
19
+ const MAX_NATIVE_RECORD_BYTES = 16 * 1024 * 1024;
20
+ const STREAM_TYPES = ["assistant", "request", "status", "system", "task", "thinking", "tool_call", "usage", "user"];
21
+ const NATIVE_TYPES = [
22
+ "configuration", "agent-created", "run-created", "delta", "step", "terminal", "history", "billing", "error", "cleanup",
23
+ ...STREAM_TYPES.map((type) => `stream:${type}`),
24
+ "stream:unknown", "store:agent", "store:run", "store:run-event", "store:checkpoint", "store:unknown",
25
+ ];
26
+ const CURSOR_ENVIRONMENT_KEYS = new Set([
27
+ "ALL_PROXY", "CURSOR_API_KEY", "HOME", "HTTPS_PROXY", "HTTP_PROXY", "LANG", "LOGNAME", "NODE_EXTRA_CA_CERTS",
28
+ "NO_PROXY", "PATH", "SHELL", "SSL_CERT_DIR", "SSL_CERT_FILE", "TEMP", "TERM", "TMP", "TMPDIR", "TZ", "USER",
29
+ ]);
30
+ let cursorEnvironmentActive = false;
31
+ export const CURSOR_SDK_CAPABILITIES = {
32
+ schemaVersion: "ebo.adapter-capability-profile/v1",
33
+ adapterId: CURSOR_SDK_ADAPTER_ID,
34
+ harness: CURSOR_SDK_HARNESS,
35
+ nativeTypes: NATIVE_TYPES,
36
+ families: {
37
+ message: { status: "available", detail: "SDK stream user and assistant messages are authoritative for message projection." },
38
+ "model-request": { status: "unsupported", detail: "The SDK does not expose source-native inference request boundaries." },
39
+ tool: { status: "available", detail: "SDK stream tool_call records are authoritative; detailed callbacks and store history remain overlap evidence." },
40
+ context: { status: "partial", detail: "Detailed summary and nested-task callback records remain native and are not semantically reconstructed." },
41
+ permission: { status: "unsupported", detail: "The public SDK does not expose individual local approval decisions." },
42
+ delegation: { status: "partial", detail: "Task tool records are retained as tool operations without inventing subagent lifecycle boundaries." },
43
+ artifact: { status: "unsupported", detail: "Tool names express intent, not verified mutations; retained workspace outcome remains authoritative." },
44
+ validation: { status: "unsupported", detail: "No validation is inferred from shell command text." },
45
+ runtime: { status: "available", detail: "Per-turn stream usage is projected as increments; cumulative run and billing readbacks remain separate." },
46
+ outcome: { status: "available", detail: "run.wait() is authoritative; stream EOF is never treated as completion." },
47
+ },
48
+ evidence: {
49
+ nativeOrder: { status: "partial", detail: "Serialized callback/stream receipt order is retained and labeled as adapter receipt order." },
50
+ nativeTime: { status: "partial", detail: "Adapter receipt timestamps are retained separately; absent source timestamps remain unknown." },
51
+ parentage: { status: "partial", detail: "Stable agent, run, and tool-call identities are retained where exposed." },
52
+ content: { status: "partial", detail: "Content stays in restricted native records and normalized events carry references only." },
53
+ },
54
+ };
55
+ /** Execute one SDK-owned local attempt and retain all source channels before projection. */
56
+ export async function captureCursorSdkRun(options) {
57
+ requireText(options.apiKey, "Cursor API key");
58
+ requireText(options.prompt, "Cursor prompt");
59
+ if (options.definition.run.model.id !== options.configuration.model.id) {
60
+ throw new Error("The declared model must match the Cursor SDK model configuration.");
61
+ }
62
+ if (options.definition.run.harness.id !== CURSOR_SDK_HARNESS) {
63
+ throw new Error(`Cursor capture requires harness ${CURSOR_SDK_HARNESS}.`);
64
+ }
65
+ if (options.definition.run.harness.version !== CURSOR_SDK_VERSION) {
66
+ throw new Error(`Cursor capture requires pinned SDK ${CURSOR_SDK_VERSION}.`);
67
+ }
68
+ const assembler = await createRunBundleAssembler(options.definition);
69
+ const writer = await CursorNativeWriter.open(join(assembler.bundleRoot, "native", "session.jsonl"), options.configuration.maxNativeRecordBytes ?? MAX_NATIVE_RECORD_BYTES, options.now);
70
+ const storeRoot = join(assembler.bundleRoot, "native", "store");
71
+ const store = new JsonlLocalAgentStore(storeRoot);
72
+ let workspace;
73
+ let workspaceOutcome;
74
+ let workspacePromise;
75
+ let workspaceCaptureError;
76
+ let verifierResult;
77
+ let verifierError;
78
+ let agentId;
79
+ let nativeRunId;
80
+ let terminalResult;
81
+ let streamRecords = 0;
82
+ let deltaRecords = 0;
83
+ let stepRecords = 0;
84
+ let historyStatus = "not-started";
85
+ let billingStatus = "not-started";
86
+ const errors = [];
87
+ const captureWorkspace = async (context) => {
88
+ if (workspaceOutcome !== undefined)
89
+ return workspaceOutcome;
90
+ workspacePromise ??= (async () => {
91
+ if (workspace?.status !== "ready" || workspace.path === undefined || workspace.artifactId === undefined) {
92
+ throw new Error("Cursor SDK capture requires a retained ready workspace before outcome packaging.");
93
+ }
94
+ workspaceOutcome = await assembler.captureWorkspaceOutcome({
95
+ startPath: options.startingWorkspacePath,
96
+ finalPath: workspace.path,
97
+ id: workspace.artifactId,
98
+ ...(options.workspaceOutcomeExcludedDirectoryNames === undefined ? {} : { excludeDirectoryNames: options.workspaceOutcomeExcludedDirectoryNames }),
99
+ ...(options.workspaceOutcomeRespectsGitignore === undefined ? {} : { respectGitignore: options.workspaceOutcomeRespectsGitignore }),
100
+ ...(options.workspaceOutcomeOmitsEmptyDirectories === undefined ? {} : { omitEmptyDirectories: options.workspaceOutcomeOmitsEmptyDirectories }),
101
+ }, context === undefined || options.verifier === undefined ? undefined : async (projectedPath, outcome) => {
102
+ try {
103
+ verifierResult = await options.verifier(context, outcome, projectedPath);
104
+ }
105
+ catch (error) {
106
+ verifierError = error;
107
+ }
108
+ });
109
+ return workspaceOutcome;
110
+ })().catch((error) => {
111
+ workspaceCaptureError = errorMessage(error);
112
+ throw error;
113
+ });
114
+ return workspacePromise;
115
+ };
116
+ const coordinatedWorkspace = {
117
+ setup: async (context) => {
118
+ workspace = await options.workspace.setup(context);
119
+ return workspace;
120
+ },
121
+ cleanup: async (context) => {
122
+ if (workspace?.status === "ready")
123
+ await captureWorkspace();
124
+ await options.workspace.cleanup?.(context);
125
+ },
126
+ };
127
+ const run = createRunIdentity({
128
+ id: options.definition.run.id,
129
+ taskId: options.definition.run.task.id,
130
+ modelId: options.definition.run.model.id,
131
+ harnessId: options.definition.run.harness.id,
132
+ });
133
+ const attemptIdentity = createAttemptIdentity(options.definition.run.id, options.definition.attempt.number, options.definition.attempt.id, options.definition.attempt.retryOf);
134
+ let attempt;
135
+ let recorderCloseError;
136
+ try {
137
+ attempt = await executeRunAttempt({
138
+ run,
139
+ assessmentMode: options.definition.run.assessmentMode,
140
+ attempt: attemptIdentity,
141
+ workspace: coordinatedWorkspace,
142
+ harness: async (context) => {
143
+ if (context.workspace?.path === undefined)
144
+ throw new Error("Cursor SDK attempt has no materialized workspace.");
145
+ const result = await executeCursorSdk({
146
+ workspacePath: context.workspace.path,
147
+ prompt: options.prompt,
148
+ apiKey: options.apiKey,
149
+ configuration: options.configuration,
150
+ store,
151
+ storeRoot,
152
+ writer,
153
+ signal: context.signal,
154
+ registerShutdown: context.registerShutdown,
155
+ agentFactory: options.agentFactory ?? ((agentOptions) => Agent.create(agentOptions)),
156
+ setAgentId: (value) => { agentId = value; },
157
+ setRunId: (value) => { nativeRunId = value; },
158
+ setTerminal: (value) => { terminalResult = value; },
159
+ onStream: () => { streamRecords += 1; },
160
+ onDelta: () => { deltaRecords += 1; },
161
+ onStep: () => { stepRecords += 1; },
162
+ setHistoryStatus: (value) => { historyStatus = value; },
163
+ setBillingStatus: (value) => { billingStatus = value; },
164
+ errors,
165
+ shutdownGraceMs: options.shutdownGraceMs ?? CURSOR_SDK_DEFAULT_SHUTDOWN_GRACE_MS,
166
+ });
167
+ return result;
168
+ },
169
+ ...(options.verifier === undefined ? {} : {
170
+ verifier: async (context) => {
171
+ await captureWorkspace(context);
172
+ if (verifierError !== undefined)
173
+ throw verifierError;
174
+ if (verifierResult === undefined)
175
+ throw new Error("Cursor verifier did not return a result.");
176
+ await assembler.writeJsonArtifact({
177
+ id: "verifier", source: "ebo-verifier", kind: "verifier", mediaType: "application/json",
178
+ sharingClass: "restricted", relativePath: "verifier.json",
179
+ }, verifierResult);
180
+ return { status: verifierResult.status, ...(verifierResult.error === undefined ? {} : { error: verifierResult.error }), evidence: verifierResult };
181
+ },
182
+ }),
183
+ evidence: writer,
184
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
185
+ ...(options.maxWallClockMs === undefined ? {} : { maxWallClockMs: options.maxWallClockMs }),
186
+ shutdownGraceMs: options.shutdownGraceMs ?? CURSOR_SDK_DEFAULT_SHUTDOWN_GRACE_MS,
187
+ });
188
+ }
189
+ finally {
190
+ await writer.close().catch((error) => {
191
+ recorderCloseError = `recorder-close: ${errorMessage(error)}`;
192
+ errors.push(recorderCloseError);
193
+ });
194
+ }
195
+ if (recorderCloseError !== undefined)
196
+ attempt = withCaptureIncomplete(attempt, recorderCloseError);
197
+ if (workspace?.status === "ready")
198
+ await captureWorkspace().catch(() => undefined);
199
+ const sessionPath = join(assembler.bundleRoot, "native", "session.jsonl");
200
+ if (await nonempty(sessionPath)) {
201
+ await assembler.registerArtifact({
202
+ id: "cursor-session", source: CURSOR_SDK_HARNESS, kind: "session", mediaType: "application/x-ndjson",
203
+ sharingClass: "restricted", relativePath: "native/session.jsonl",
204
+ ...(agentId === undefined ? {} : { nativeReference: { type: "session", id: agentId } }),
205
+ });
206
+ }
207
+ const storeFiles = [];
208
+ for (const [name, file] of Object.entries(JSONL_LOCAL_AGENT_STORE_FILES)) {
209
+ const path = join(storeRoot, file);
210
+ if (!await nonempty(path))
211
+ continue;
212
+ const relativePath = `native/store/${file}`;
213
+ storeFiles.push(relativePath);
214
+ await assembler.registerArtifact({
215
+ id: `cursor-store-${name}`, source: "cursor-sdk-native-store", kind: "session", mediaType: "application/x-ndjson",
216
+ sharingClass: "restricted", relativePath,
217
+ });
218
+ }
219
+ const captureFailure = errors.some((error) => !error.startsWith("billing:"));
220
+ const missingEvidence = [
221
+ { kind: "telemetry", reason: "unsupported", affects: ["timing-resource"], detail: "@cursor/sdk 1.0.31 exposes no SDK-local per-run OTLP configuration or receipt API." },
222
+ ...(captureFailure ? [{ kind: "session", reason: "not-collected", affects: ["semantic"], detail: errors.join("; ").slice(0, 4096) }] : []),
223
+ ...(terminalResult === undefined ? [{ kind: "session-completion", reason: attempt.terminal.state === "interrupted" ? "process-interrupted" : "not-emitted", affects: ["semantic"] }] : []),
224
+ ...(workspaceOutcome === undefined && workspaceCaptureError !== undefined ? [{ kind: "workspace", reason: "not-collected", affects: ["outcome"], detail: workspaceCaptureError.slice(0, 4096) }] : []),
225
+ ];
226
+ const terminal = structuredClone(attempt.terminal);
227
+ if (workspaceOutcome === undefined)
228
+ delete terminal.workspaceArtifactId;
229
+ const qualificationOptions = { startingWorkspacePath: options.startingWorkspacePath, semanticEvidenceKinds: ["session"] };
230
+ const manifest = await assembler.finalize({ terminal, missingEvidence, qualification: qualificationOptions });
231
+ const qualification = await qualifyRunBundle(assembler.bundleRoot, qualificationOptions);
232
+ return {
233
+ attempt,
234
+ manifest,
235
+ qualification,
236
+ capture: {
237
+ sdkVersion: CURSOR_SDK_VERSION,
238
+ ...(agentId === undefined ? {} : { agentId }),
239
+ ...(nativeRunId === undefined ? {} : { runId: nativeRunId }),
240
+ ...(terminalResult === undefined ? {} : { terminal: structuredClone(terminalResult) }),
241
+ streamRecords,
242
+ deltaRecords,
243
+ stepRecords,
244
+ historyStatus,
245
+ billingStatus,
246
+ storeFiles,
247
+ errors,
248
+ },
249
+ };
250
+ }
251
+ async function executeCursorSdk(options) {
252
+ let agent;
253
+ let run;
254
+ let result;
255
+ let executionError;
256
+ let captureError;
257
+ let cleanupError;
258
+ let callbacksOpen = true;
259
+ let disposePromise;
260
+ const environment = restrictCursorProcessEnvironment(options.apiKey);
261
+ const disposeAgent = async () => {
262
+ if (agent === undefined)
263
+ return;
264
+ disposePromise ??= settleWithin(agent[Symbol.asyncDispose](), options.shutdownGraceMs, "Cursor agent disposal");
265
+ await disposePromise;
266
+ };
267
+ options.registerShutdown(async () => {
268
+ callbacksOpen = false;
269
+ const outcomes = await Promise.allSettled([
270
+ ...(run === undefined ? [] : [settleWithin(run.cancel(), options.shutdownGraceMs, "Cursor run cancellation")]),
271
+ disposeAgent(),
272
+ ]);
273
+ const failed = outcomes.find((outcome) => outcome.status === "rejected");
274
+ if (failed !== undefined)
275
+ throw failed.reason;
276
+ });
277
+ try {
278
+ await assertCursorWorkspaceIsolation(options.workspacePath);
279
+ await options.writer.record("configuration", {
280
+ sdkVersion: CURSOR_SDK_VERSION,
281
+ runtimeIdentity: { status: "not-exposed", detail: "The public SDK does not expose a separately versioned local agent runtime." },
282
+ model: options.configuration.model,
283
+ toolPolicy: options.configuration.toolPolicy,
284
+ workingDirectory: options.workspacePath,
285
+ workingDirectoryPolicy: "isolated-local.cwd",
286
+ environmentKeys: environment.keys,
287
+ telemetry: { sdkLocalOtlp: "unsupported", enterpriseTeamExport: "not-checked" },
288
+ });
289
+ if (options.signal.aborted)
290
+ throw new Error("Cursor agent creation was interrupted.");
291
+ const agentPromise = options.agentFactory({
292
+ apiKey: options.apiKey,
293
+ model: structuredClone(options.configuration.model),
294
+ tools: [...options.configuration.toolPolicy.tools],
295
+ ...(options.configuration.toolPolicy.disallowedTools === undefined ? {} : { disallowedTools: [...options.configuration.toolPolicy.disallowedTools] }),
296
+ local: {
297
+ cwd: options.workspacePath,
298
+ store: options.store,
299
+ settingSources: [],
300
+ sandboxOptions: { enabled: options.configuration.toolPolicy.sandbox.enabled },
301
+ autoReview: false,
302
+ enableAgentRetries: false,
303
+ },
304
+ });
305
+ agent = await abortable(agentPromise, options.signal, "Cursor agent creation", async (lateAgent) => {
306
+ await settleWithin(lateAgent[Symbol.asyncDispose](), options.shutdownGraceMs, "Late Cursor agent disposal").catch(() => undefined);
307
+ });
308
+ requireText(agent.agentId, "Cursor agent ID");
309
+ options.setAgentId(agent.agentId);
310
+ await options.writer.record("agent-created", { agentId: agent.agentId, model: agent.model }, { agentId: agent.agentId, sessionId: agent.agentId });
311
+ if (options.signal.aborted)
312
+ throw new Error("Cursor run creation was interrupted.");
313
+ const sendPromise = agent.send(options.prompt, {
314
+ onDelta: async ({ update }) => {
315
+ if (!callbacksOpen)
316
+ return;
317
+ try {
318
+ const snapshot = snapshotJson(update);
319
+ options.onDelta();
320
+ await options.writer.record("delta", { update: snapshot }, { agentId: agent.agentId, runId: run?.id, sessionId: agent.agentId });
321
+ }
322
+ catch (error) {
323
+ callbacksOpen = false;
324
+ captureError ??= `delta-recorder: ${errorMessage(error)}`;
325
+ options.errors.push(captureError);
326
+ }
327
+ },
328
+ onStep: async ({ step }) => {
329
+ if (!callbacksOpen)
330
+ return;
331
+ try {
332
+ const snapshot = snapshotJson(step);
333
+ options.onStep();
334
+ await options.writer.record("step", { step: snapshot }, { agentId: agent.agentId, runId: run?.id, sessionId: agent.agentId });
335
+ }
336
+ catch (error) {
337
+ callbacksOpen = false;
338
+ captureError ??= `step-recorder: ${errorMessage(error)}`;
339
+ options.errors.push(captureError);
340
+ }
341
+ },
342
+ });
343
+ run = await abortable(sendPromise, options.signal, "Cursor run creation", async (lateRun) => {
344
+ await settleWithin(lateRun.cancel(), options.shutdownGraceMs, "Late Cursor run cancellation").catch(() => undefined);
345
+ });
346
+ requireText(run.id, "Cursor run ID");
347
+ if (run.agentId !== agent.agentId)
348
+ throw new Error("Cursor run identity differs from its owned agent.");
349
+ if (!sameModelSelection(run.model, options.configuration.model)) {
350
+ throw new Error("Cursor run model selection differs from the requested model and parameters.");
351
+ }
352
+ options.setRunId(run.id);
353
+ await options.writer.record("run-created", { runId: run.id, agentId: run.agentId, model: run.model, createdAt: run.createdAt }, ids(agent, run));
354
+ if (options.signal.aborted) {
355
+ await settleWithin(run.cancel(), options.shutdownGraceMs, "Cursor post-creation cancellation");
356
+ }
357
+ const stream = (async () => {
358
+ try {
359
+ for await (const message of run.stream()) {
360
+ if (!callbacksOpen)
361
+ break;
362
+ assertMessageIdentity(message, agent.agentId, run.id);
363
+ options.onStream();
364
+ await options.writer.record("stream", { message: snapshotJson(message) }, ids(agent, run));
365
+ }
366
+ }
367
+ catch (error) {
368
+ captureError ??= `stream: ${errorMessage(error)}`;
369
+ options.errors.push(captureError);
370
+ await options.writer.record("error", { stage: "stream", message: errorMessage(error) }, ids(agent, run)).catch(() => undefined);
371
+ }
372
+ })();
373
+ try {
374
+ result = await abortable(run.wait(), options.signal, "Cursor terminal wait", async () => undefined);
375
+ options.setTerminal(result);
376
+ assertTerminalIdentity(result, agent.agentId, run, options.configuration.model);
377
+ }
378
+ catch (error) {
379
+ executionError = `wait: ${errorMessage(error)}`;
380
+ options.errors.push(executionError);
381
+ await options.writer.record("error", { stage: "wait", message: errorMessage(error) }, ids(agent, run)).catch(() => undefined);
382
+ }
383
+ if (result !== undefined && executionError === undefined) {
384
+ try {
385
+ await options.writer.record("terminal", { result: snapshotJson(result) }, ids(agent, run));
386
+ }
387
+ catch (error) {
388
+ callbacksOpen = false;
389
+ captureError ??= `terminal-recorder: ${errorMessage(error)}`;
390
+ options.errors.push(captureError);
391
+ }
392
+ }
393
+ await abortable(stream, options.signal, "Cursor stream drain", async () => undefined);
394
+ try {
395
+ const conversation = await abortable(run.conversation(), options.signal, "Cursor conversation readback", async () => undefined);
396
+ options.setHistoryStatus("captured");
397
+ await options.writer.record("history", { conversation: snapshotJson(conversation) }, ids(agent, run));
398
+ }
399
+ catch (error) {
400
+ options.setHistoryStatus("failed");
401
+ captureError ??= `history: ${errorMessage(error)}`;
402
+ options.errors.push(`history: ${errorMessage(error)}`);
403
+ await options.writer.record("error", { stage: "history", message: errorMessage(error) }, ids(agent, run)).catch(() => undefined);
404
+ }
405
+ try {
406
+ const usage = await abortable(agent.getUsage(), options.signal, "Cursor billing readback", async () => undefined);
407
+ options.setBillingStatus("captured");
408
+ await options.writer.record("billing", { scope: "agent", retrievedAt: new Date().toISOString(), usage: snapshotJson(usage) }, ids(agent, run));
409
+ }
410
+ catch (error) {
411
+ options.setBillingStatus("unavailable");
412
+ options.errors.push(`billing: ${errorMessage(error)}`);
413
+ await options.writer.record("error", { stage: "billing", message: errorMessage(error) }, ids(agent, run)).catch(() => undefined);
414
+ }
415
+ try {
416
+ await validateStoreIdentity(options.store, agent.agentId, run.id, options.configuration.model);
417
+ }
418
+ catch (error) {
419
+ captureError ??= `store: ${errorMessage(error)}`;
420
+ options.errors.push(captureError);
421
+ await options.writer.record("error", { stage: "store", message: errorMessage(error) }, ids(agent, run)).catch(() => undefined);
422
+ }
423
+ }
424
+ catch (error) {
425
+ executionError ??= errorMessage(error);
426
+ options.errors.push(`execution: ${executionError}`);
427
+ await options.writer.record("error", { stage: "execution", message: executionError }, {
428
+ ...(agent === undefined ? {} : { agentId: agent.agentId, sessionId: agent.agentId }),
429
+ ...(run === undefined ? {} : { runId: run.id }),
430
+ }).catch(() => undefined);
431
+ }
432
+ finally {
433
+ callbacksOpen = false;
434
+ if (agent !== undefined) {
435
+ try {
436
+ await disposeAgent();
437
+ await options.writer.record("cleanup", { status: "completed" }, {
438
+ agentId: agent.agentId, sessionId: agent.agentId, ...(run === undefined ? {} : { runId: run.id }),
439
+ });
440
+ }
441
+ catch (error) {
442
+ cleanupError = errorMessage(error);
443
+ options.errors.push(`cleanup: ${cleanupError}`);
444
+ await options.writer.record("cleanup", { status: "failed", error: cleanupError }, {
445
+ agentId: agent.agentId, sessionId: agent.agentId, ...(run === undefined ? {} : { runId: run.id }),
446
+ }).catch(() => undefined);
447
+ }
448
+ }
449
+ environment.restore();
450
+ }
451
+ if (agent !== undefined && run !== undefined) {
452
+ try {
453
+ await validateStoreFiles(options.storeRoot, agent.agentId, run.id, options.configuration.model, options.configuration.maxNativeRecordBytes ?? MAX_NATIVE_RECORD_BYTES);
454
+ }
455
+ catch (error) {
456
+ captureError ??= `store: ${errorMessage(error)}`;
457
+ options.errors.push(captureError);
458
+ await options.writer.record("error", { stage: "store", message: errorMessage(error) }, ids(agent, run)).catch(() => undefined);
459
+ }
460
+ }
461
+ captureError ??= options.writer.error;
462
+ if (captureError !== undefined && !options.errors.includes(captureError))
463
+ options.errors.push(captureError);
464
+ if (executionError !== undefined || cleanupError !== undefined || result?.status === "error") {
465
+ return {
466
+ status: "failed",
467
+ failureClass: "infrastructure",
468
+ reason: executionError ?? cleanupError ?? result?.error?.message ?? "Cursor run failed.",
469
+ ...(captureError === undefined ? {} : { captureError }),
470
+ ...(result === undefined ? {} : { completionEvidence: result }),
471
+ };
472
+ }
473
+ if (result?.status === "cancelled" || options.signal.aborted) {
474
+ return { status: "interrupted", ...(captureError === undefined ? {} : { captureError }), ...(result === undefined ? {} : { completionEvidence: result }) };
475
+ }
476
+ if (result?.status !== "finished") {
477
+ return { status: "failed", failureClass: "infrastructure", reason: "Cursor run did not produce owned terminal evidence.", ...(captureError === undefined ? {} : { captureError }) };
478
+ }
479
+ return { status: "completed", ...(captureError === undefined ? {} : { captureError }), completionEvidence: result };
480
+ }
481
+ async function abortable(promise, signal, label, onLateValue) {
482
+ if (signal.aborted) {
483
+ void promise.then(onLateValue, () => undefined);
484
+ throw new Error(`${label} was interrupted.`);
485
+ }
486
+ return await new Promise((resolvePromise, rejectPromise) => {
487
+ let settled = false;
488
+ const onAbort = () => {
489
+ if (settled)
490
+ return;
491
+ settled = true;
492
+ signal.removeEventListener("abort", onAbort);
493
+ rejectPromise(new Error(`${label} was interrupted.`));
494
+ };
495
+ signal.addEventListener("abort", onAbort, { once: true });
496
+ void promise.then((value) => {
497
+ if (settled) {
498
+ void Promise.resolve(onLateValue(value)).catch(() => undefined);
499
+ return;
500
+ }
501
+ settled = true;
502
+ signal.removeEventListener("abort", onAbort);
503
+ resolvePromise(value);
504
+ }, (error) => {
505
+ if (settled)
506
+ return;
507
+ settled = true;
508
+ signal.removeEventListener("abort", onAbort);
509
+ rejectPromise(error);
510
+ });
511
+ });
512
+ }
513
+ async function settleWithin(promise, timeoutMs, label) {
514
+ let timer;
515
+ try {
516
+ await Promise.race([
517
+ promise,
518
+ new Promise((_resolve, reject) => {
519
+ timer = setTimeout(() => reject(new Error(`${label} exceeded ${String(timeoutMs)}ms.`)), timeoutMs);
520
+ timer.unref?.();
521
+ }),
522
+ ]);
523
+ }
524
+ finally {
525
+ if (timer !== undefined)
526
+ clearTimeout(timer);
527
+ }
528
+ }
529
+ /** Prevent Cursor from promoting an ancestor checkout above the materialized workspace. */
530
+ export async function assertCursorWorkspaceIsolation(workspacePath) {
531
+ const workspace = resolve(workspacePath);
532
+ let current = workspace;
533
+ const root = parse(workspace).root;
534
+ while (current !== root) {
535
+ try {
536
+ await lstat(join(current, ".git"));
537
+ throw new Error("Cursor workspace must not be nested in another Git checkout; choose --workspace-root outside that checkout.");
538
+ }
539
+ catch (error) {
540
+ if (error.code !== "ENOENT")
541
+ throw error;
542
+ }
543
+ current = dirname(current);
544
+ }
545
+ }
546
+ function restrictCursorProcessEnvironment(apiKey) {
547
+ if (cursorEnvironmentActive)
548
+ throw new Error("Concurrent in-process Cursor captures are not supported while the SDK child environment is restricted.");
549
+ const previous = { ...process.env };
550
+ cursorEnvironmentActive = true;
551
+ for (const key of Object.keys(process.env)) {
552
+ if (!CURSOR_ENVIRONMENT_KEYS.has(key) && !key.startsWith("LC_"))
553
+ delete process.env[key];
554
+ }
555
+ process.env.CURSOR_API_KEY = apiKey;
556
+ let restored = false;
557
+ return {
558
+ keys: Object.keys(process.env).sort(),
559
+ restore: () => {
560
+ if (restored)
561
+ return;
562
+ for (const key of Object.keys(process.env))
563
+ delete process.env[key];
564
+ Object.assign(process.env, previous);
565
+ restored = true;
566
+ cursorEnvironmentActive = false;
567
+ },
568
+ };
569
+ }
570
+ /** Read and normalize one retained, capture-qualified Cursor SDK bundle. */
571
+ export async function createCursorSdkBehaviorEvidence(bundleRoot) {
572
+ const manifest = JSON.parse(readBoundedFile(join(resolve(bundleRoot), "manifest.json"), "Run manifest").toString("utf8"));
573
+ if (manifest.run.harness.id !== CURSOR_SDK_HARNESS || manifest.run.harness.version !== CURSOR_SDK_VERSION
574
+ || !manifest.run.runtime.some(({ source, name, version }) => source === "cursor" && name === "cursor-sdk" && version === CURSOR_SDK_VERSION)) {
575
+ throw new Error(`Unsupported retained Cursor SDK runtime ${manifest.run.harness.version}.`);
576
+ }
577
+ const outcomeCapture = await readQualifiedRunCapture(bundleRoot);
578
+ const manifestRecord = outcomeCapture.records.find(({ record }) => record.kind === "manifest")?.record.document;
579
+ const capture = {
580
+ runId: outcomeCapture.runId,
581
+ attemptId: outcomeCapture.attemptId,
582
+ qualification: outcomeCapture.qualification,
583
+ records: outcomeCapture.records.filter(({ record }) => record.kind === "session").map(({ reference, record }) => {
584
+ assertCursorRecord(record.document, reference);
585
+ return { reference, record: record.document };
586
+ }),
587
+ };
588
+ assertRetainedCursorIdentity(manifest, capture.records.map(({ record }) => record));
589
+ const normalization = normalizeCursorSdkCapture(capture);
590
+ const dataset = describeNormalizedDataset({
591
+ capture,
592
+ normalization,
593
+ capabilityProfile: CURSOR_SDK_CAPABILITIES,
594
+ adapterVersion: CURSOR_SDK_ADAPTER_VERSION,
595
+ nativeType: cursorNativeType,
596
+ });
597
+ const resolver = createCapturedNativeEvidenceResolver(capture, createAgentSdkNativeEvidenceResolver(outcomeCapture));
598
+ const coverage = await validateNormalizedDataset(dataset, resolver);
599
+ if (manifestRecord === undefined)
600
+ throw new Error("Retained Cursor bundle omits terminal outcome metadata.");
601
+ return { capture, outcomeCapture, dataset, resolver, coverage };
602
+ }
603
+ function assertRetainedCursorIdentity(manifest, records) {
604
+ const envelopes = records.filter((record) => record.schemaVersion === CURSOR_NATIVE_SCHEMA_VERSION);
605
+ const byChannel = (channel) => envelopes.filter((record) => record.channel === channel);
606
+ const configurations = byChannel("configuration");
607
+ const agents = byChannel("agent-created");
608
+ const runs = byChannel("run-created");
609
+ const terminals = byChannel("terminal");
610
+ if (configurations.length !== 1 || agents.length !== 1 || runs.length !== 1 || terminals.length !== 1) {
611
+ throw new Error("Retained Cursor capture requires one configuration, agent, run, and terminal record.");
612
+ }
613
+ const sessionId = manifest.run.native?.sessionId;
614
+ const agentId = text(agents[0].agentId) ?? text(asRecord(agents[0].payload)?.agentId);
615
+ const runId = text(runs[0].runId) ?? text(asRecord(runs[0].payload)?.runId);
616
+ if (sessionId === undefined || agentId !== sessionId || runId === undefined) {
617
+ throw new Error("Retained Cursor agent/run identity differs from the run manifest.");
618
+ }
619
+ for (const record of envelopes) {
620
+ if (record.agentId !== undefined && record.agentId !== agentId
621
+ || record.sessionId !== undefined && record.sessionId !== sessionId
622
+ || record.runId !== undefined && record.runId !== runId) {
623
+ throw new Error("Retained Cursor native envelope contains a foreign agent/run identity.");
624
+ }
625
+ }
626
+ const configuration = asRecord(configurations[0].payload);
627
+ const configuredModel = configuration?.model;
628
+ if (configuration?.sdkVersion !== CURSOR_SDK_VERSION || modelId(configuredModel) !== manifest.run.model.id
629
+ || configuration?.workingDirectoryPolicy !== "isolated-local.cwd"
630
+ || !Array.isArray(configuration?.environmentKeys) || configuration.environmentKeys.some((key) => typeof key !== "string")) {
631
+ throw new Error("Retained Cursor configuration differs from the effective run manifest or policy.");
632
+ }
633
+ const runPayload = asRecord(runs[0].payload);
634
+ const terminal = asRecord(asRecord(terminals[0].payload)?.result);
635
+ if (runPayload?.runId !== runId || runPayload.agentId !== agentId || !sameModelSelection(runPayload.model, configuredModel)
636
+ || terminal?.id !== runId || !sameModelSelection(terminal.model, configuredModel)
637
+ || !["finished", "cancelled", "error"].includes(String(terminal.status))) {
638
+ throw new Error("Retained Cursor terminal/model identity differs from the owned run.");
639
+ }
640
+ for (const record of byChannel("stream")) {
641
+ const message = asRecord(asRecord(record.payload)?.message);
642
+ if (message?.agent_id !== agentId || message.run_id !== runId) {
643
+ throw new Error("Retained Cursor stream identity differs from the owned run.");
644
+ }
645
+ }
646
+ const storeAgents = records.filter((record) => cursorNativeType(record) === "store:agent");
647
+ const storeRuns = records.filter((record) => cursorNativeType(record) === "store:run");
648
+ const storeEvents = records.filter((record) => cursorNativeType(record) === "store:run-event");
649
+ const storeCheckpoints = records.filter((record) => cursorNativeType(record) === "store:checkpoint");
650
+ if (storeAgents.length !== 1 || storeAgents[0].agentId !== agentId
651
+ || storeRuns.length !== 1 || storeRuns[0].agentId !== agentId || storeRuns[0].runId !== runId
652
+ || !sameModelSelection(storeRuns[0].model, configuredModel)
653
+ || storeEvents.some((record) => record.runId !== runId)
654
+ || storeCheckpoints.some((record) => record.agentId !== agentId)) {
655
+ throw new Error("Retained Cursor native store identity differs from the owned agent/run/model.");
656
+ }
657
+ assertCheckpointCompleteness(storeAgents[0], storeRuns[0], storeCheckpoints, "Retained Cursor native store");
658
+ }
659
+ export function normalizeCursorSdkCapture(input) {
660
+ const events = [];
661
+ const unmapped = [];
662
+ for (const captured of input.records) {
663
+ const mapped = mapCursorRecord(input, captured.reference, captured.record);
664
+ events.push(...mapped);
665
+ if (mapped.length === 0)
666
+ unmapped.push({ reference: captured.reference, reason: unmappedCursorReason(captured.record) });
667
+ }
668
+ return { events, unmapped };
669
+ }
670
+ function mapCursorRecord(input, reference, record) {
671
+ if (record.schemaVersion !== CURSOR_NATIVE_SCHEMA_VERSION)
672
+ return [];
673
+ const channel = record.channel;
674
+ const payload = asRecord(record.payload);
675
+ const sequence = safeInteger(record.sequence);
676
+ if (typeof channel !== "string" || payload === undefined || sequence === undefined)
677
+ return [];
678
+ const nativeType = cursorNativeType(record);
679
+ const base = (family, phase, discriminator) => ({
680
+ schemaVersion: "ebo.uniform-event/v1",
681
+ id: stableEventId(input, reference, discriminator),
682
+ runId: input.runId,
683
+ attemptId: input.attemptId,
684
+ source: { harness: CURSOR_SDK_HARNESS, nativeType, nativeReference: reference },
685
+ nativeOrder: { status: "known", value: sequence, domain: "cursor-sdk-receipt" },
686
+ nativeTime: { status: "unknown", reason: "Record time is adapter receipt time, not a source-native timestamp." },
687
+ actor: { kind: "harness", ...(text(record.agentId) === undefined ? {} : { id: text(record.agentId) }) },
688
+ family,
689
+ phase,
690
+ scope: text(record.runId) === undefined ? { kind: "session", ...(text(record.sessionId) === undefined ? {} : { id: text(record.sessionId) }) } : { kind: "turn", id: text(record.runId) },
691
+ relations: { parent: { status: "unknown", reason: "No source-native event parent is exposed for this record." }, known: [] },
692
+ attributes: compactAttributes({ timestampSemantics: "adapter-receipt", agentId: text(record.agentId), sessionId: text(record.sessionId), runId: text(record.runId) }),
693
+ content: { status: "unknown", reason: "Native content remains in the restricted record." },
694
+ });
695
+ if (channel === "stream") {
696
+ const message = asRecord(payload.message);
697
+ const type = text(message?.type);
698
+ if (message === undefined || type === undefined)
699
+ return [];
700
+ if (type === "assistant" || type === "user") {
701
+ const event = base("message", "instant", type);
702
+ event.actor = { kind: type === "user" ? "user" : "model", ...(type === "assistant" && text(record.agentId) !== undefined ? { id: text(record.agentId) } : {}) };
703
+ event.content = knownContent(contentReference(reference, "", `${type}-message`));
704
+ return [event];
705
+ }
706
+ if (type === "tool_call") {
707
+ const callId = text(message.call_id);
708
+ const status = text(message.status);
709
+ const phase = status === "running" ? "before" : "after";
710
+ const failed = status === "error" || asRecord(message.result)?.status === "error";
711
+ const event = base("tool", phase, `tool:${callId ?? sequence}:${status ?? "unknown"}`);
712
+ event.actor = { kind: "tool", ...(text(message.name) === undefined ? {} : { id: text(message.name) }) };
713
+ event.scope = { kind: "operation", ...(callId === undefined ? {} : { id: callId }) };
714
+ event.attributes = compactAttributes({
715
+ ...event.attributes,
716
+ operationId: callId,
717
+ toolName: text(message.name),
718
+ toolStatus: status,
719
+ inputDigest: message.args === undefined ? undefined : `sha256:${digestMetadata(message.args).value}`,
720
+ failed: failed ? true : undefined,
721
+ });
722
+ const content = [];
723
+ if (message.args !== undefined)
724
+ content.push(contentReference(reference, "", "tool-input"));
725
+ if (message.result !== undefined)
726
+ content.push(contentReference(reference, "", failed ? "tool-error" : "tool-result"));
727
+ if (content.length > 0)
728
+ event.content = knownContent(...content);
729
+ return [event];
730
+ }
731
+ if (type === "usage") {
732
+ const usage = asRecord(message.usage);
733
+ if (usage === undefined)
734
+ return [];
735
+ const event = base("runtime", "instant", `usage:${sequence}`);
736
+ event.attributes = compactAttributes({
737
+ ...event.attributes,
738
+ resourceSemantics: "increment",
739
+ inputTokens: nonnegativeInteger(usage.inputTokens),
740
+ outputTokens: nonnegativeInteger(usage.outputTokens),
741
+ cacheReadInputTokens: nonnegativeInteger(usage.cacheReadTokens),
742
+ cacheCreationInputTokens: nonnegativeInteger(usage.cacheWriteTokens),
743
+ totalTokens: nonnegativeInteger(usage.totalTokens),
744
+ reasoningTokens: nonnegativeInteger(usage.reasoningTokens),
745
+ });
746
+ return [event];
747
+ }
748
+ if (type === "status" || type === "system") {
749
+ const event = base("runtime", "instant", `${type}:${sequence}`);
750
+ event.attributes = compactAttributes({ ...event.attributes, status: text(message.status), model: modelId(message.model) });
751
+ return [event];
752
+ }
753
+ return [];
754
+ }
755
+ if (channel === "terminal") {
756
+ const result = asRecord(payload.result);
757
+ if (result === undefined)
758
+ return [];
759
+ const event = base("outcome", "after", "terminal");
760
+ event.attributes = compactAttributes({
761
+ ...event.attributes,
762
+ status: text(result.status),
763
+ model: modelId(result.model),
764
+ durationMs: nonnegativeNumber(result.durationMs),
765
+ resourceSemantics: nonnegativeNumber(result.durationMs) === undefined ? undefined : "cumulative-final",
766
+ });
767
+ return [event];
768
+ }
769
+ return [];
770
+ }
771
+ class CursorNativeWriter {
772
+ path;
773
+ file;
774
+ maxRecordBytes;
775
+ now;
776
+ pending = Promise.resolve();
777
+ sequence = 0;
778
+ failure;
779
+ constructor(path, file, maxRecordBytes, now) {
780
+ this.path = path;
781
+ this.file = file;
782
+ this.maxRecordBytes = maxRecordBytes;
783
+ this.now = now;
784
+ }
785
+ static async open(path, maxRecordBytes, now) {
786
+ if (!Number.isSafeInteger(maxRecordBytes) || maxRecordBytes < 1)
787
+ throw new Error("Cursor native record limit must be a positive safe integer.");
788
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
789
+ const file = await open(path, "wx", 0o600);
790
+ return new CursorNativeWriter(path, file, maxRecordBytes, now ?? (() => new Date().toISOString()));
791
+ }
792
+ get error() {
793
+ return this.failure;
794
+ }
795
+ async record(channel, payload, identity = {}) {
796
+ if (this.failure !== undefined)
797
+ throw new Error(this.failure);
798
+ const record = {
799
+ schemaVersion: CURSOR_NATIVE_SCHEMA_VERSION,
800
+ sequence: ++this.sequence,
801
+ receivedAt: this.now(),
802
+ channel,
803
+ ...identity,
804
+ payload: snapshotJson(payload),
805
+ };
806
+ const line = `${JSON.stringify(record)}\n`;
807
+ if (Buffer.byteLength(line) > this.maxRecordBytes) {
808
+ this.failure ??= `Cursor ${channel} record exceeds the configured byte limit.`;
809
+ throw new Error(this.failure);
810
+ }
811
+ const write = this.pending.then(async () => {
812
+ await this.file.write(line);
813
+ });
814
+ this.pending = write.catch((error) => {
815
+ this.failure ??= `recorder: ${errorMessage(error)}`;
816
+ });
817
+ await write;
818
+ }
819
+ async flush() {
820
+ await this.pending;
821
+ if (this.failure !== undefined)
822
+ throw new Error(this.failure);
823
+ await this.file.sync();
824
+ }
825
+ async close() {
826
+ let failure;
827
+ try {
828
+ await this.flush();
829
+ }
830
+ catch (error) {
831
+ failure = error;
832
+ }
833
+ await this.file.close();
834
+ if (failure !== undefined)
835
+ throw failure;
836
+ }
837
+ }
838
+ function withCaptureIncomplete(attempt, error) {
839
+ if (attempt.classification.kind === "capture-incomplete")
840
+ return attempt;
841
+ const classification = {
842
+ kind: "capture-incomplete",
843
+ terminal: structuredClone(attempt.terminal),
844
+ reason: error,
845
+ source: "capture",
846
+ underlying: attempt.classification.kind,
847
+ ...(attempt.classification.source === undefined ? {} : { underlyingSource: attempt.classification.source }),
848
+ };
849
+ return {
850
+ ...attempt,
851
+ classification,
852
+ record: {
853
+ ...attempt.record,
854
+ classification: structuredClone(classification),
855
+ capture: { status: "incomplete", error },
856
+ partial: true,
857
+ },
858
+ };
859
+ }
860
+ async function validateStoreIdentity(store, agentId, runId, expectedModel) {
861
+ const [agent, run, agents, runs] = await Promise.all([
862
+ store.agents.get({ agentId }),
863
+ store.runs.get({ agentId, runId }),
864
+ store.agents.list({ filter: { limit: 2 } }),
865
+ store.runs.list({ filter: { limit: 2 } }),
866
+ ]);
867
+ if (agent?.agentId !== agentId)
868
+ throw new Error("Cursor native store omits or mismatches the owned agent identity.");
869
+ if (run?.agentId !== agentId || run.runId !== runId || !sameModelSelection(run.model, expectedModel)) {
870
+ throw new Error("Cursor native store omits or mismatches the owned run/model identity.");
871
+ }
872
+ if (agents.items.length !== 1 || agents.items[0]?.agentId !== agentId
873
+ || runs.items.length !== 1 || runs.items[0]?.agentId !== agentId || runs.items[0]?.runId !== runId
874
+ || !sameModelSelection(runs.items[0]?.model, expectedModel)) {
875
+ throw new Error("Cursor native store contains a foreign agent or run.");
876
+ }
877
+ }
878
+ async function validateStoreFiles(storeRoot, agentId, runId, expectedModel, maxRecordBytes) {
879
+ const documents = (name) => {
880
+ const path = join(storeRoot, JSONL_LOCAL_AGENT_STORE_FILES[name]);
881
+ if (!existsSync(path))
882
+ return [];
883
+ const source = readBoundedFile(path, `Cursor ${name} store`).toString("utf8");
884
+ return source.split(/\r?\n/u).filter(Boolean).map((line, index) => {
885
+ if (Buffer.byteLength(`${line}\n`) > maxRecordBytes) {
886
+ throw new Error(`Cursor ${name} store line ${String(index + 1)} exceeds the configured native record byte limit.`);
887
+ }
888
+ assertNoDuplicateJsonKeys(line);
889
+ const value = JSON.parse(line);
890
+ if (!isRecord(value))
891
+ throw new Error(`Cursor ${name} store line ${String(index + 1)} is not an object.`);
892
+ assertCursorRecord(value, { artifactId: `cursor-store-${name}`, recordLocator: `line:${String(index + 1)}` });
893
+ const expectedType = {
894
+ agents: "store:agent",
895
+ runs: "store:run",
896
+ runEvents: "store:run-event",
897
+ checkpoints: "store:checkpoint",
898
+ }[name];
899
+ if (cursorNativeType(value) !== expectedType) {
900
+ throw new Error(`Cursor ${name} store line ${String(index + 1)} has the wrong record shape.`);
901
+ }
902
+ return value;
903
+ });
904
+ };
905
+ const agents = documents("agents");
906
+ const runs = documents("runs");
907
+ const runEvents = documents("runEvents");
908
+ const checkpoints = documents("checkpoints");
909
+ if (agents.length !== 1 || agents[0].agentId !== agentId
910
+ || runs.length !== 1 || runs[0].agentId !== agentId || runs[0].runId !== runId
911
+ || !sameModelSelection(runs[0].model, expectedModel)
912
+ || runEvents.some((record) => record.runId !== runId)
913
+ || checkpoints.some((record) => record.agentId !== agentId)) {
914
+ throw new Error("Cursor native store contains evidence outside the owned agent/run.");
915
+ }
916
+ assertCheckpointCompleteness(agents[0], runs[0], checkpoints, "Cursor native store");
917
+ }
918
+ function assertCheckpointCompleteness(agent, run, checkpoints, label) {
919
+ const reference = (value, field) => {
920
+ if (value === undefined || value === null)
921
+ return undefined;
922
+ const record = asRecord(value);
923
+ if (record?.schemaVersion !== 1 || typeof record.rootBlobId !== "string" || record.rootBlobId.trim() === "") {
924
+ throw new Error(`${label} ${field} is malformed.`);
925
+ }
926
+ return record.rootBlobId;
927
+ };
928
+ const required = [
929
+ reference(agent.latestCheckpoint, "agent latest checkpoint"),
930
+ reference(run.startCheckpointRef, "run start checkpoint"),
931
+ reference(run.latestCheckpointRef, "run latest checkpoint"),
932
+ ].filter((value) => value !== undefined);
933
+ const retained = new Set(checkpoints.map((checkpoint) => checkpoint.blobId));
934
+ if (required.some((blobId) => !retained.has(blobId))) {
935
+ throw new Error(`${label} omits a referenced checkpoint blob.`);
936
+ }
937
+ }
938
+ function assertMessageIdentity(message, agentId, runId) {
939
+ if (message.agent_id !== agentId || message.run_id !== runId) {
940
+ throw new Error("Cursor stream record identity differs from its owned agent/run.");
941
+ }
942
+ }
943
+ function assertTerminalIdentity(result, agentId, run, expectedModel) {
944
+ if (result.id !== run.id || run.agentId !== agentId)
945
+ throw new Error("Cursor terminal identity differs from its owned agent/run.");
946
+ if (!sameModelSelection(run.model, expectedModel) || !sameModelSelection(result.model, expectedModel)) {
947
+ throw new Error("Cursor terminal model selection differs from the requested model and parameters.");
948
+ }
949
+ }
950
+ function ids(agent, run) {
951
+ return { agentId: agent.agentId, runId: run.id, sessionId: agent.agentId };
952
+ }
953
+ function assertCursorRecord(value, reference) {
954
+ if (!isRecord(value))
955
+ throw new Error(`Invalid retained Cursor record at ${reference.artifactId}:${reference.recordLocator}.`);
956
+ if (value.schemaVersion === CURSOR_NATIVE_SCHEMA_VERSION) {
957
+ const expectedLine = Number(reference.recordLocator.match(/^line:(\d+)$/u)?.[1]);
958
+ if (!Number.isSafeInteger(value.sequence) || value.sequence !== expectedLine || value.sequence < 1 || typeof value.channel !== "string"
959
+ || typeof value.receivedAt !== "string" || !isRecord(value.payload)) {
960
+ throw new Error(`Invalid retained Cursor envelope at ${reference.artifactId}:${reference.recordLocator}.`);
961
+ }
962
+ return;
963
+ }
964
+ const type = cursorNativeType(value);
965
+ if (type === "store:agent" && typeof value.agentId === "string" && typeof value.cwd === "string"
966
+ && typeof value.status === "string" && Number.isFinite(value.createdAt) && Number.isFinite(value.updatedAt))
967
+ return;
968
+ if (type === "store:run" && typeof value.agentId === "string" && typeof value.runId === "string"
969
+ && Number.isSafeInteger(value.turnNumber) && typeof value.status === "string" && Number.isFinite(value.createdAt) && Number.isFinite(value.updatedAt))
970
+ return;
971
+ if (type === "store:run-event" && typeof value.runId === "string" && Number.isSafeInteger(value.seq)
972
+ && typeof value.offset === "string" && typeof value.eventType === "string" && typeof value.createdAt === "string")
973
+ return;
974
+ if (type === "store:checkpoint" && typeof value.agentId === "string" && typeof value.blobId === "string" && typeof value.dataBase64 === "string")
975
+ return;
976
+ throw new Error(`Invalid retained Cursor store record at ${reference.artifactId}:${reference.recordLocator}.`);
977
+ }
978
+ function cursorNativeType(record) {
979
+ if (record.schemaVersion === CURSOR_NATIVE_SCHEMA_VERSION) {
980
+ if (record.channel === "stream") {
981
+ const type = text(asRecord(asRecord(record.payload)?.message)?.type);
982
+ return STREAM_TYPES.includes(type ?? "") ? `stream:${type}` : "stream:unknown";
983
+ }
984
+ return typeof record.channel === "string" && NATIVE_TYPES.includes(record.channel)
985
+ ? record.channel
986
+ : "error";
987
+ }
988
+ if (typeof record.dataBase64 === "string" && typeof record.blobId === "string")
989
+ return "store:checkpoint";
990
+ if (typeof record.eventType === "string" && Number.isSafeInteger(record.seq))
991
+ return "store:run-event";
992
+ if (Number.isSafeInteger(record.turnNumber) && typeof record.runId === "string")
993
+ return "store:run";
994
+ if (typeof record.cwd === "string" && typeof record.agentId === "string")
995
+ return "store:agent";
996
+ return "store:unknown";
997
+ }
998
+ function unmappedCursorReason(record) {
999
+ const type = cursorNativeType(record);
1000
+ if (type === "delta" || type === "step")
1001
+ return "Detailed callback evidence overlaps the authoritative stream projection and remains native.";
1002
+ if (type === "history" || type.startsWith("store:"))
1003
+ return "Durable native history remains authoritative evidence and is not counted as a second operation/message source.";
1004
+ if (type === "billing")
1005
+ return "Eventually consistent billing readback remains separate from per-turn token observations.";
1006
+ if (type === "stream:thinking")
1007
+ return "Hidden reasoning remains restricted native evidence and is never projected as semantic content.";
1008
+ return "Cursor native record is retained explicitly without a supported semantic mapping.";
1009
+ }
1010
+ function contentReference(reference, pointer, role) {
1011
+ return { nativeReference: pointer === "" ? structuredClone(reference) : { artifactId: reference.artifactId, recordLocator: `${reference.recordLocator}#${pointer}` }, role };
1012
+ }
1013
+ function knownContent(...value) {
1014
+ return { status: "known", value };
1015
+ }
1016
+ function stableEventId(input, reference, discriminator) {
1017
+ return `cursor-${createHash("sha256").update(JSON.stringify([input.runId, input.attemptId, reference, discriminator])).digest("hex").slice(0, 32)}`;
1018
+ }
1019
+ function compactAttributes(values) {
1020
+ return Object.fromEntries(Object.entries(values).filter((entry) => entry[1] !== undefined));
1021
+ }
1022
+ function snapshotJson(value) {
1023
+ const text = JSON.stringify(value);
1024
+ if (text === undefined)
1025
+ throw new Error("Cursor SDK emitted a non-JSON value.");
1026
+ return JSON.parse(text);
1027
+ }
1028
+ function modelId(value) {
1029
+ return typeof value === "string" ? value : text(asRecord(value)?.id);
1030
+ }
1031
+ function sameModelSelection(value, expected) {
1032
+ const normalize = (candidate) => {
1033
+ if (typeof candidate === "string")
1034
+ return { id: candidate, params: [] };
1035
+ const record = asRecord(candidate);
1036
+ if (record === undefined || typeof record.id !== "string" || !Array.isArray(record.params)) {
1037
+ return record !== undefined && typeof record.id === "string" && record.params === undefined
1038
+ ? { id: record.id, params: [] }
1039
+ : undefined;
1040
+ }
1041
+ if (record.params.some((entry) => !isRecord(entry) || typeof entry.id !== "string" || typeof entry.value !== "string"))
1042
+ return undefined;
1043
+ return {
1044
+ id: record.id,
1045
+ params: record.params.map(({ id, value }) => ({ id, value }))
1046
+ .sort((left, right) => left.id.localeCompare(right.id) || left.value.localeCompare(right.value)),
1047
+ };
1048
+ };
1049
+ return JSON.stringify(normalize(value)) === JSON.stringify(normalize(expected));
1050
+ }
1051
+ function nonnegativeInteger(value) {
1052
+ return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
1053
+ }
1054
+ function nonnegativeNumber(value) {
1055
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
1056
+ }
1057
+ function safeInteger(value) {
1058
+ return Number.isSafeInteger(value) ? value : undefined;
1059
+ }
1060
+ function text(value) {
1061
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
1062
+ }
1063
+ function asRecord(value) {
1064
+ return isRecord(value) ? value : undefined;
1065
+ }
1066
+ function isRecord(value) {
1067
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1068
+ }
1069
+ function requireText(value, label) {
1070
+ if (value.trim() === "")
1071
+ throw new Error(`${label} must be nonempty.`);
1072
+ }
1073
+ function errorMessage(error) {
1074
+ return error instanceof Error ? error.message : String(error);
1075
+ }
1076
+ async function nonempty(path) {
1077
+ return ((await stat(path).catch(() => undefined))?.size ?? 0) > 0;
1078
+ }