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,407 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { lstatSync } from "node:fs";
3
+ import { cp, mkdtemp, rm } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
6
+ import { assertNoDuplicateJsonKeys, digestMetadata, validateArtifact, validateRunManifestEvidence } from "./artifacts.js";
7
+ import { captureClaudeAgentSdkRun } from "./agent-sdk-run.js";
8
+ import { CLAUDE_AGENT_SDK_HARNESS } from "./agent-sdk-normalizer.js";
9
+ import { probeClaudeAgentSdkCapabilities, } from "./agent-sdk.js";
10
+ import { isSafeArtifactRelativePath, resolveBundleConfiguration } from "./contracts.js";
11
+ import { readBoundedFile, readRunQueue } from "./scheduler.js";
12
+ import { assertTaskPacketAdmitted, formatErrors } from "./task-packets.js";
13
+ import { executeVerifier } from "./verifiers.js";
14
+ import { cleanupWorkspace, materializeWorkspace } from "./workspaces.js";
15
+ export const AGENT_SDK_CONFIG_SCHEMA_VERSION = "ebo.agent-sdk-config/v1";
16
+ const PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan", "dontAsk", "auto"];
17
+ const TELEMETRY_PROTOCOLS = ["http/protobuf", "http/json", "grpc"];
18
+ // Matches the workspace attempt-ID contract: one safe path component, no
19
+ // separators, no leading dot, so a supplied ID cannot escape the output root.
20
+ const ATTEMPT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
21
+ /**
22
+ * Execute exactly one frozen run-queue entry through the Claude Agent SDK and
23
+ * retain its run bundle under `<outputRoot>/<runId>/<attemptId>`. Preflight
24
+ * failures throw before any attempt evidence exists; once capture starts, the
25
+ * existing lifecycle retains complete or partial evidence without retrying.
26
+ */
27
+ export async function runAgentSdkQueueEntry(options) {
28
+ const bundleRoot = resolve(options.bundleRoot);
29
+ const queue = readRunQueue(options.queuePath, undefined, { bundleRoot });
30
+ const matches = queue.entries.filter((candidate) => candidate.runId === options.runId);
31
+ if (matches.length === 0)
32
+ throw new Error(`Run "${options.runId}" is not in the queue.`);
33
+ if (matches.length > 1)
34
+ throw new Error(`Run "${options.runId}" matches more than one queue entry.`);
35
+ const entry = matches[0];
36
+ const model = resolveAgentSdkConfigurationRecord(bundleRoot, entry.configuration.model, "model");
37
+ resolveAgentSdkConfigurationRecord(bundleRoot, entry.configuration.harness, "harness");
38
+ const limits = resolveAgentSdkConfigurationRecord(bundleRoot, entry.configuration.nativeLimits, "native-limits");
39
+ const toolPolicy = resolveAgentSdkConfigurationRecord(bundleRoot, entry.configuration.nativeToolPolicy, "native-tool-policy");
40
+ const captureProfile = resolveAgentSdkConfigurationRecord(bundleRoot, queue.captureProfile, "capture-profile");
41
+ if (model.model !== entry.model.id) {
42
+ throw new Error(`Model configuration "${model.model}" does not match the queue entry model ID "${entry.model.id}".`);
43
+ }
44
+ const inspection = assertTaskPacketAdmitted(bundleRoot, entry.task.packetRef.locator);
45
+ const packet = inspection.packet;
46
+ if (inspection.packetDigest === null || inspection.packetDigest.value !== entry.task.packetRef.digest.value
47
+ || inspection.packetDigest.algorithm !== entry.task.packetRef.digest.algorithm) {
48
+ throw new Error(`Task packet "${entry.task.packetRef.locator}" changed from its frozen queue reference.`);
49
+ }
50
+ const verifierReference = packet.assessmentMode === "verified" ? packet.restricted.verifier : undefined;
51
+ const verifierFormat = verifierReference?.locator.toLowerCase().endsWith(".mjs") ? "module" : "commonjs";
52
+ const attemptId = options.attemptId ?? randomUUID();
53
+ if (!ATTEMPT_ID_PATTERN.test(attemptId)) {
54
+ throw new Error("Attempt ID must be one safe path component.");
55
+ }
56
+ const outputRoot = resolve(options.outputRoot);
57
+ const attemptBundleRoot = join(outputRoot, entry.runId, attemptId);
58
+ const fromOutputRoot = relative(outputRoot, attemptBundleRoot);
59
+ if (fromOutputRoot === "" || isAbsolute(fromOutputRoot)
60
+ || fromOutputRoot === ".." || fromOutputRoot.startsWith(`..${sep}`)) {
61
+ throw new Error("Attempt destination escapes the selected output root.");
62
+ }
63
+ if (pathExists(attemptBundleRoot)) {
64
+ throw new Error(`Attempt destination "${attemptBundleRoot}" already exists and is never replaced.`);
65
+ }
66
+ const capabilities = probeClaudeAgentSdkCapabilities();
67
+ const configuration = buildSdkConfiguration(packet.agentInput.prompt, model, limits, toolPolicy, captureProfile, options.checkTelemetryReceipt);
68
+ const definition = buildRunBundleDefinition(queue.captureProfile, entry, packet, attemptBundleRoot, attemptId, capabilities, verifierFormat);
69
+ // The workspace directory name stays decoupled from the attempt identity:
70
+ // the native SDK encodes its working-directory path into retained session
71
+ // evidence, and an embedded attempt ID would leak a source correlation value
72
+ // past path redaction during portable export.
73
+ const workspace = await materializeWorkspace({
74
+ bundleRoot,
75
+ packetLocator: entry.task.packetRef.locator,
76
+ freezeLocator: entry.task.freezeLocator,
77
+ ...(options.workspaceRoot === undefined ? {} : { workspaceParent: options.workspaceRoot }),
78
+ });
79
+ if (workspace.status !== "ready") {
80
+ throw new Error(`Workspace materialization failed before the attempt started: ${workspace.error ?? "unknown failure"}`);
81
+ }
82
+ let baselineRoot;
83
+ let captureStarted = false;
84
+ try {
85
+ baselineRoot = await mkdtemp(join(tmpdir(), "ebo-agent-sdk-baseline-"));
86
+ const startingWorkspacePath = join(baselineRoot, "workspace");
87
+ await cp(workspace.path, startingWorkspacePath, { recursive: true, preserveTimestamps: true, force: false });
88
+ const result = await captureClaudeAgentSdkRun({
89
+ definition,
90
+ startingWorkspacePath,
91
+ workspace: {
92
+ setup: () => {
93
+ captureStarted = true;
94
+ return { status: "ready", path: workspace.path, artifactId: "workspace", retained: true };
95
+ },
96
+ cleanup: async () => {
97
+ await cleanupWorkspace(workspace);
98
+ },
99
+ },
100
+ configuration,
101
+ ...(captureProfile.workspaceOutcome === undefined ? {} : {
102
+ workspaceOutcomeExcludedDirectoryNames: captureProfile.workspaceOutcome.excludeDirectoryNames,
103
+ ...(captureProfile.workspaceOutcome.respectGitignore === undefined ? {} : {
104
+ workspaceOutcomeRespectsGitignore: captureProfile.workspaceOutcome.respectGitignore,
105
+ }),
106
+ ...(captureProfile.workspaceOutcome.omitEmptyDirectories === undefined ? {} : {
107
+ workspaceOutcomeOmitsEmptyDirectories: captureProfile.workspaceOutcome.omitEmptyDirectories,
108
+ }),
109
+ }),
110
+ ...(verifierReference === undefined ? {} : {
111
+ verifier: (context, outcome, projectedWorkspacePath) => executeVerifier({
112
+ bundleId: definition.bundleId,
113
+ verifierRoot: bundleRoot,
114
+ verifier: verifierReference,
115
+ workspacePath: projectedWorkspacePath,
116
+ workspaceFingerprint: outcome.fingerprint,
117
+ workspace: {
118
+ artifactId: outcome.descriptor.id,
119
+ digest: outcome.descriptor.digest,
120
+ fingerprint: outcome.fingerprint,
121
+ },
122
+ artifactRoot: attemptBundleRoot,
123
+ moduleFormat: verifierFormat,
124
+ signal: context.signal,
125
+ }),
126
+ }),
127
+ maxWallClockMs: queue.coordinatorBudget.maxWallClockMs,
128
+ ...(options.query === undefined ? {} : { query: options.query }),
129
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
130
+ });
131
+ const manifest = reopenFinalManifest(attemptBundleRoot);
132
+ return {
133
+ runId: entry.runId,
134
+ attemptId,
135
+ bundlePath: attemptBundleRoot,
136
+ terminal: structuredClone(manifest.terminal),
137
+ classification: result.attempt.classification.kind,
138
+ captureQualification: result.qualification.status,
139
+ assessmentMode: packet.assessmentMode,
140
+ ...(manifest.run.native?.sessionId === undefined ? {} : { sessionId: manifest.run.native.sessionId }),
141
+ ...(manifest.run.native?.traceId === undefined ? {} : { traceId: manifest.run.native.traceId }),
142
+ ...(workspace.state === "ready"
143
+ ? { retainedWorkspacePath: workspace.path }
144
+ : {}),
145
+ };
146
+ }
147
+ finally {
148
+ if (baselineRoot !== undefined)
149
+ await rm(baselineRoot, { recursive: true, force: true });
150
+ // Once execution starts, the capture layer cleans up only after retaining
151
+ // an outcome. Preserve the original work if packaging or finalization fails.
152
+ if (!captureStarted && workspace.state === "ready")
153
+ await cleanupWorkspace(workspace).catch(() => undefined);
154
+ }
155
+ }
156
+ /** Resolve one digest-pinned `ebo.agent-sdk-config/v1` record and validate it before SDK launch. */
157
+ export function resolveAgentSdkConfigurationRecord(bundleRoot, reference, kind) {
158
+ const bytes = resolveBundleConfiguration(bundleRoot, reference);
159
+ let text;
160
+ try {
161
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
162
+ }
163
+ catch {
164
+ throw configurationError(reference, "is not valid UTF-8");
165
+ }
166
+ assertNoDuplicateJsonKeys(text);
167
+ let value;
168
+ try {
169
+ value = JSON.parse(text);
170
+ }
171
+ catch (error) {
172
+ throw configurationError(reference, `is not valid JSON: ${errorMessage(error)}`);
173
+ }
174
+ if (!isRecord(value))
175
+ throw configurationError(reference, "must be a JSON object");
176
+ if (value.schemaVersion !== AGENT_SDK_CONFIG_SCHEMA_VERSION) {
177
+ throw configurationError(reference, `must declare schemaVersion ${AGENT_SDK_CONFIG_SCHEMA_VERSION}`);
178
+ }
179
+ if (value.kind !== kind) {
180
+ throw configurationError(reference, `has kind "${String(value.kind)}" instead of the required "${kind}"`);
181
+ }
182
+ validateConfigurationRecord(value, kind, reference);
183
+ return value;
184
+ }
185
+ function validateConfigurationRecord(record, kind, reference) {
186
+ switch (kind) {
187
+ case "model":
188
+ assertRecordKeys(record, ["model"], ["model"], reference);
189
+ assertText(record.model, "model", reference);
190
+ return;
191
+ case "harness":
192
+ assertRecordKeys(record, ["adapter"], ["adapter"], reference);
193
+ if (record.adapter !== "claude-agent-sdk") {
194
+ throw configurationError(reference, 'must declare the "claude-agent-sdk" adapter');
195
+ }
196
+ return;
197
+ case "native-limits":
198
+ assertRecordKeys(record, ["maxTurns", "maxBudgetUsd"], [], reference);
199
+ if (record.maxTurns !== undefined && (!Number.isSafeInteger(record.maxTurns) || record.maxTurns < 1)) {
200
+ throw configurationError(reference, "maxTurns must be a positive safe integer");
201
+ }
202
+ if (record.maxBudgetUsd !== undefined
203
+ && (typeof record.maxBudgetUsd !== "number" || !Number.isFinite(record.maxBudgetUsd) || record.maxBudgetUsd <= 0)) {
204
+ throw configurationError(reference, "maxBudgetUsd must be a positive finite number");
205
+ }
206
+ return;
207
+ case "native-tool-policy": {
208
+ assertRecordKeys(record, ["tools", "allowedTools", "disallowedTools", "permissionMode", "allowDangerouslySkipPermissions"], ["tools", "permissionMode"], reference);
209
+ assertToolList(record.tools, "tools", reference);
210
+ if (record.allowedTools !== undefined)
211
+ assertToolList(record.allowedTools, "allowedTools", reference);
212
+ if (record.disallowedTools !== undefined)
213
+ assertToolList(record.disallowedTools, "disallowedTools", reference);
214
+ if (!PERMISSION_MODES.includes(record.permissionMode)) {
215
+ throw configurationError(reference, "permissionMode is invalid");
216
+ }
217
+ if (record.allowDangerouslySkipPermissions !== undefined && typeof record.allowDangerouslySkipPermissions !== "boolean") {
218
+ throw configurationError(reference, "allowDangerouslySkipPermissions must be a boolean");
219
+ }
220
+ if (record.permissionMode === "bypassPermissions" && record.allowDangerouslySkipPermissions !== true) {
221
+ throw configurationError(reference, "bypassPermissions requires allowDangerouslySkipPermissions=true");
222
+ }
223
+ return;
224
+ }
225
+ case "capture-profile": {
226
+ assertRecordKeys(record, ["telemetry", "workspaceOutcome"], [], reference);
227
+ if (record.workspaceOutcome !== undefined) {
228
+ if (!isRecord(record.workspaceOutcome))
229
+ throw configurationError(reference, "workspaceOutcome must be an object");
230
+ assertKeys(record.workspaceOutcome, ["excludeDirectoryNames", "respectGitignore", "omitEmptyDirectories"], ["excludeDirectoryNames"], reference);
231
+ assertToolList(record.workspaceOutcome.excludeDirectoryNames, "workspaceOutcome excludeDirectoryNames", reference);
232
+ if (record.workspaceOutcome.excludeDirectoryNames.some((name) => name.includes("/") || !isSafeArtifactRelativePath(name))) {
233
+ throw configurationError(reference, "workspaceOutcome excludeDirectoryNames must contain safe path segments");
234
+ }
235
+ if (record.workspaceOutcome.respectGitignore !== undefined && typeof record.workspaceOutcome.respectGitignore !== "boolean") {
236
+ throw configurationError(reference, "workspaceOutcome respectGitignore must be a boolean");
237
+ }
238
+ if (record.workspaceOutcome.omitEmptyDirectories !== undefined && typeof record.workspaceOutcome.omitEmptyDirectories !== "boolean") {
239
+ throw configurationError(reference, "workspaceOutcome omitEmptyDirectories must be a boolean");
240
+ }
241
+ }
242
+ if (record.telemetry === undefined)
243
+ return;
244
+ if (!isRecord(record.telemetry))
245
+ throw configurationError(reference, "telemetry must be an object");
246
+ const telemetry = record.telemetry;
247
+ assertKeys(telemetry, ["endpoint", "protocol", "exportIntervalMs", "logUserPrompts", "logToolDetails", "logToolContent", "logRawApiBodies"], ["endpoint"], reference);
248
+ assertTelemetryEndpoint(telemetry.endpoint, reference);
249
+ if (telemetry.protocol !== undefined && !TELEMETRY_PROTOCOLS.includes(telemetry.protocol)) {
250
+ throw configurationError(reference, "telemetry protocol is invalid");
251
+ }
252
+ if (telemetry.exportIntervalMs !== undefined
253
+ && (!Number.isSafeInteger(telemetry.exportIntervalMs) || telemetry.exportIntervalMs < 1)) {
254
+ throw configurationError(reference, "telemetry exportIntervalMs must be a positive safe integer");
255
+ }
256
+ for (const flag of ["logUserPrompts", "logToolDetails", "logToolContent", "logRawApiBodies"]) {
257
+ if (telemetry[flag] !== undefined && typeof telemetry[flag] !== "boolean") {
258
+ throw configurationError(reference, `telemetry ${flag} must be a boolean`);
259
+ }
260
+ }
261
+ return;
262
+ }
263
+ }
264
+ }
265
+ function buildSdkConfiguration(prompt, model, limits, toolPolicy, captureProfile, checkReceipt) {
266
+ const telemetry = captureProfile.telemetry;
267
+ return {
268
+ prompt,
269
+ model: model.model,
270
+ tools: [...toolPolicy.tools],
271
+ allowedTools: [...(toolPolicy.allowedTools ?? [])],
272
+ disallowedTools: [...(toolPolicy.disallowedTools ?? [])],
273
+ permissionMode: toolPolicy.permissionMode,
274
+ ...(toolPolicy.allowDangerouslySkipPermissions === undefined
275
+ ? {}
276
+ : { allowDangerouslySkipPermissions: toolPolicy.allowDangerouslySkipPermissions }),
277
+ ...(limits.maxTurns === undefined ? {} : { maxTurns: limits.maxTurns }),
278
+ ...(limits.maxBudgetUsd === undefined ? {} : { maxBudgetUsd: limits.maxBudgetUsd }),
279
+ ...(telemetry === undefined ? {} : {
280
+ telemetry: {
281
+ endpoint: telemetry.endpoint,
282
+ ...(telemetry.protocol === undefined ? {} : { protocol: telemetry.protocol }),
283
+ ...(telemetry.exportIntervalMs === undefined ? {} : { exportIntervalMs: telemetry.exportIntervalMs }),
284
+ ...(telemetry.logUserPrompts === undefined ? {} : { logUserPrompts: telemetry.logUserPrompts }),
285
+ ...(telemetry.logToolDetails === undefined ? {} : { logToolDetails: telemetry.logToolDetails }),
286
+ ...(telemetry.logToolContent === undefined ? {} : { logToolContent: telemetry.logToolContent }),
287
+ ...(telemetry.logRawApiBodies === undefined ? {} : { logRawApiBodies: telemetry.logRawApiBodies }),
288
+ ...(checkReceipt === undefined ? {} : { checkReceipt }),
289
+ },
290
+ }),
291
+ };
292
+ }
293
+ function buildRunBundleDefinition(captureProfile, entry, packet, attemptBundleRoot, attemptId, capabilities, verifierFormat) {
294
+ return {
295
+ bundleRoot: attemptBundleRoot,
296
+ bundleId: `bundle-${attemptId}`,
297
+ run: {
298
+ id: entry.runId,
299
+ trial: { index: entry.trial.index },
300
+ assessmentMode: packet.assessmentMode,
301
+ task: { id: entry.task.id, digest: `sha256:${entry.task.packetRef.digest.value}` },
302
+ fixture: {
303
+ id: packet.agentInput.fixture.source.locator,
304
+ digest: `sha256:${packet.agentInput.fixture.source.digest.value}`,
305
+ },
306
+ model: { provider: "anthropic", id: entry.model.id, configurationDigest: `sha256:${entry.configuration.model.digest.value}` },
307
+ harness: { id: entry.harness.id, version: capabilities.sdkVersion, configurationDigest: `sha256:${entry.configuration.harness.digest.value}` },
308
+ runtime: [
309
+ ...(entry.harness.id === CLAUDE_AGENT_SDK_HARNESS ? [] : [{ source: "EBO", name: entry.harness.id, version: capabilities.sdkVersion }]),
310
+ { source: "anthropic", name: CLAUDE_AGENT_SDK_HARNESS, version: capabilities.sdkVersion },
311
+ { source: "anthropic", name: "agent-cli", version: capabilities.claudeCodeVersion },
312
+ ],
313
+ ...(packet.assessmentMode === "verified" ? {
314
+ verifier: {
315
+ locator: packet.restricted.verifier.locator,
316
+ digest: `sha256:${packet.restricted.verifier.digest.value}`,
317
+ format: verifierFormat,
318
+ },
319
+ } : {}),
320
+ },
321
+ attempt: { id: attemptId, number: 1 },
322
+ configuration: {
323
+ digest: `sha256:${digestMetadata({
324
+ model: entry.configuration.model,
325
+ harness: entry.configuration.harness,
326
+ captureProfile,
327
+ }).value}`,
328
+ captureProfileDigest: `sha256:${captureProfile.digest.value}`,
329
+ budgetDigest: `sha256:${entry.configuration.nativeLimits.digest.value}`,
330
+ toolPolicyDigest: `sha256:${entry.configuration.nativeToolPolicy.digest.value}`,
331
+ },
332
+ };
333
+ }
334
+ function reopenFinalManifest(bundleRoot) {
335
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(readBoundedFile(join(bundleRoot, "manifest.json"), "Run manifest"));
336
+ assertNoDuplicateJsonKeys(text);
337
+ const manifest = JSON.parse(text);
338
+ const errors = [
339
+ ...validateArtifact("manifest.json", manifest),
340
+ ...validateRunManifestEvidence("manifest.json", manifest, bundleRoot),
341
+ ];
342
+ if (errors.length > 0)
343
+ throw new Error(`Retained run manifest failed validation:\n${formatErrors(errors)}`);
344
+ return manifest;
345
+ }
346
+ /** Allow the schemaVersion/kind envelope only on top-level configuration records. */
347
+ function assertRecordKeys(record, allowed, required, reference) {
348
+ assertKeys(record, ["schemaVersion", "kind", ...allowed], required, reference);
349
+ }
350
+ function assertKeys(record, allowed, required, reference) {
351
+ const permitted = new Set(allowed);
352
+ for (const key of Object.keys(record)) {
353
+ if (!permitted.has(key))
354
+ throw configurationError(reference, `contains an unknown field "${key}"`);
355
+ }
356
+ for (const key of required) {
357
+ if (record[key] === undefined)
358
+ throw configurationError(reference, `is missing the required field "${key}"`);
359
+ }
360
+ }
361
+ function assertText(value, field, reference) {
362
+ if (typeof value !== "string" || value.trim() === "") {
363
+ throw configurationError(reference, `${field} must be a nonempty string`);
364
+ }
365
+ }
366
+ function assertToolList(value, field, reference) {
367
+ if (!Array.isArray(value) || value.some((tool) => typeof tool !== "string" || tool.trim() === "")) {
368
+ throw configurationError(reference, `${field} must be an array of nonempty strings`);
369
+ }
370
+ if (new Set(value).size !== value.length) {
371
+ throw configurationError(reference, `${field} must not repeat tool names`);
372
+ }
373
+ }
374
+ function assertTelemetryEndpoint(value, reference) {
375
+ assertText(value, "telemetry endpoint", reference);
376
+ let endpoint;
377
+ try {
378
+ endpoint = new URL(value);
379
+ }
380
+ catch {
381
+ throw configurationError(reference, "telemetry endpoint must be an absolute URL");
382
+ }
383
+ if (!["http:", "https:"].includes(endpoint.protocol)
384
+ || endpoint.username !== "" || endpoint.password !== "" || endpoint.search !== "" || endpoint.hash !== "") {
385
+ throw configurationError(reference, "telemetry endpoint must be an HTTP(S) URL without credentials, query, or fragment");
386
+ }
387
+ }
388
+ function pathExists(path) {
389
+ try {
390
+ lstatSync(path);
391
+ return true;
392
+ }
393
+ catch (error) {
394
+ if (error.code === "ENOENT")
395
+ return false;
396
+ throw error;
397
+ }
398
+ }
399
+ function configurationError(reference, detail) {
400
+ return new Error(`Agent SDK configuration "${reference.locator}" ${detail}.`);
401
+ }
402
+ function errorMessage(error) {
403
+ return error instanceof Error ? error.message : String(error);
404
+ }
405
+ function isRecord(value) {
406
+ return typeof value === "object" && value !== null && !Array.isArray(value);
407
+ }