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,644 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { HOOK_EVENTS, query, } from "@anthropic-ai/claude-agent-sdk";
5
+ import { BoundedDiagnosticCapture, openJsonlEvidenceWriter } from "./process-protocol.js";
6
+ const PACKAGE_NAME = "@anthropic-ai/claude-agent-sdk";
7
+ const TELEMETRY_SIGNALS = ["traces", "metrics", "logs"];
8
+ const TELEMETRY_EXPORTER_KEYS = ["OTEL_TRACES_EXPORTER", "OTEL_METRICS_EXPORTER", "OTEL_LOGS_EXPORTER"];
9
+ const DEFAULT_EXPORT_INTERVAL_MS = 1_000;
10
+ const MAX_HOOK_RECORD_BYTES = 8 * 1024 * 1024;
11
+ const DETAILED_BETA_HOOK_SPAN_REASON = "Optional detailed-beta hook spans are not required; hooks.jsonl is authoritative for hook occurrence.";
12
+ export async function openClaudeAgentSdkHookCapture(path, now = () => new Date().toISOString()) {
13
+ const writer = await openJsonlEvidenceWriter(path, { exclusive: true, maxLineBytes: MAX_HOOK_RECORD_BYTES });
14
+ let sequence = 0;
15
+ return {
16
+ path: writer.path,
17
+ hook: async (event, input, toolUseId, signal) => {
18
+ await writer.append({
19
+ schemaVersion: "ebo.claude-agent-hook/v1",
20
+ sequence: ++sequence,
21
+ callbackAt: now(),
22
+ hook: event,
23
+ sessionId: input.session_id,
24
+ transcriptPath: input.transcript_path,
25
+ cwd: input.cwd,
26
+ ...(input.prompt_id === undefined ? {} : { promptId: input.prompt_id }),
27
+ ...(toolUseId === undefined ? {} : { toolUseId }),
28
+ ...(input.agent_id === undefined ? {} : { agentId: input.agent_id }),
29
+ ...(input.agent_type === undefined ? {} : { agentType: input.agent_type }),
30
+ signalAborted: signal.aborted,
31
+ callbackOutput: {},
32
+ nativePayload: JSON.parse(JSON.stringify(input)),
33
+ });
34
+ },
35
+ flush: () => writer.flush(),
36
+ close: () => writer.close(),
37
+ };
38
+ }
39
+ const MAX_CAPTURE_DIAGNOSTICS = 32;
40
+ const KNOWN_MESSAGE_TYPES = new Set([
41
+ "assistant",
42
+ "auth_status",
43
+ "conversation_reset",
44
+ "prompt_suggestion",
45
+ "rate_limit_event",
46
+ "result",
47
+ "stream_event",
48
+ "system",
49
+ "tool_progress",
50
+ "tool_use_summary",
51
+ "user",
52
+ ]);
53
+ export async function openClaudeAgentSdkStreamCapture(path, sink, now = () => new Date().toISOString()) {
54
+ validateEvidenceSink(sink);
55
+ const writer = await openJsonlEvidenceWriter(path, { exclusive: true });
56
+ let sequence = 0;
57
+ let sessionId;
58
+ let sessionSource;
59
+ let result;
60
+ let diagnosticCount = 0;
61
+ let appendError;
62
+ const diagnostics = [];
63
+ return {
64
+ path: writer.path,
65
+ async message(message) {
66
+ const record = nativeMessageRecord(message, sequence + 1, now());
67
+ const value = message;
68
+ try {
69
+ await writer.append(record);
70
+ }
71
+ catch (error) {
72
+ appendError ??= error;
73
+ throw error;
74
+ }
75
+ sequence = record.sequence;
76
+ for (const diagnostic of record.diagnostics ?? []) {
77
+ diagnosticCount += 1;
78
+ if (diagnostics.length < MAX_CAPTURE_DIAGNOSTICS)
79
+ diagnostics.push(diagnostic);
80
+ }
81
+ if (record.sessionId !== undefined && sessionId === undefined) {
82
+ sessionId = record.sessionId;
83
+ sessionSource = "sdk-message.session_id";
84
+ }
85
+ if (record.nativeType === "result" && record.sessionId !== undefined && isRecord(value)
86
+ && typeof value.uuid === "string" && typeof value.subtype === "string") {
87
+ sessionId = record.sessionId;
88
+ sessionSource = "sdk-result.session_id";
89
+ result = { uuid: value.uuid, subtype: value.subtype, sessionId: record.sessionId };
90
+ }
91
+ await sink.message(message);
92
+ },
93
+ stderr: (data) => sink.stderr(data),
94
+ hook: (event, input, toolUseId, signal) => sink.hook(event, input, toolUseId, signal),
95
+ lifecycle: (event) => sink.lifecycle(event),
96
+ async flush() {
97
+ const results = await Promise.allSettled([writer.flush(), Promise.resolve().then(() => sink.flush?.())]);
98
+ const error = appendError ?? results.find((entry) => entry.status === "rejected")?.reason;
99
+ if (error !== undefined)
100
+ throw error;
101
+ },
102
+ close: () => writer.close(),
103
+ report: () => ({
104
+ schemaVersion: "ebo.agent-sdk-capture/v1",
105
+ status: result === undefined || appendError !== undefined ? "partial" : "complete",
106
+ artifact: {
107
+ path: writer.path,
108
+ kind: "session",
109
+ authority: "semantic",
110
+ mediaType: "application/x-ndjson",
111
+ sharingClass: "restricted",
112
+ },
113
+ capabilities: {
114
+ typedMessageStream: { status: "available", messageCount: sequence },
115
+ sessionIdentity: sessionId === undefined || sessionSource === undefined
116
+ ? { status: "missing", reason: "not-emitted" }
117
+ : { status: "available", sessionId, source: sessionSource },
118
+ },
119
+ ...(result === undefined ? {} : { result: { ...result } }),
120
+ diagnostics: {
121
+ count: diagnosticCount,
122
+ retained: diagnostics.map((diagnostic) => ({ ...diagnostic })),
123
+ truncated: diagnosticCount > diagnostics.length,
124
+ },
125
+ }),
126
+ };
127
+ }
128
+ export function probeClaudeAgentSdkCapabilities() {
129
+ const { sdkVersion, claudeCodeVersion } = installedVersions();
130
+ return {
131
+ sdkVersion,
132
+ claudeCodeVersion,
133
+ query: { status: "available" },
134
+ typedMessageStream: { status: "available" },
135
+ stderr: { status: "available" },
136
+ abort: { status: "available" },
137
+ hookLifecycleMessages: { status: "available" },
138
+ hooks: Object.fromEntries(HOOK_EVENTS.map((event) => [event, { status: "available", evidence: "callback" }])),
139
+ unsupportedHooks: [],
140
+ hookOccurrenceAuthority: "hooks.jsonl",
141
+ telemetry: {
142
+ metrics: { status: "available", stability: "stable" },
143
+ logs: { status: "available", stability: "stable" },
144
+ traces: { status: "available", stability: "beta" },
145
+ hookSpans: { status: "optional", stability: "detailed-beta" },
146
+ },
147
+ detailedBetaHookSpanTiming: { status: "unsupported", optional: true, reason: DETAILED_BETA_HOOK_SPAN_REASON },
148
+ missingEvidence: [{ capability: "detailed-beta-hook-span-timing", reason: DETAILED_BETA_HOOK_SPAN_REASON }],
149
+ };
150
+ }
151
+ export function buildClaudeAgentSdkEnvironment(input) {
152
+ const env = { ...input.inherited, ...input.overrides };
153
+ if (input.telemetry === undefined) {
154
+ rejectConsoleExporter(env);
155
+ return { env };
156
+ }
157
+ validateTelemetryConfiguration(input.telemetry);
158
+ const protocol = input.telemetry.protocol ?? "http/protobuf";
159
+ const exportIntervalMs = input.telemetry.exportIntervalMs ?? DEFAULT_EXPORT_INTERVAL_MS;
160
+ const attributes = {
161
+ "ebo.run.id": input.run.id,
162
+ "ebo.attempt.id": input.attempt.id,
163
+ "ebo.attempt.number": String(input.attempt.number),
164
+ };
165
+ const encodedAttributes = Object.entries(attributes)
166
+ .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
167
+ .join(",");
168
+ const inheritedResourceAttributes = env.OTEL_RESOURCE_ATTRIBUTES;
169
+ const content = {
170
+ userPrompts: input.telemetry.logUserPrompts ?? false,
171
+ toolDetails: input.telemetry.logToolDetails ?? false,
172
+ toolContent: input.telemetry.logToolContent ?? false,
173
+ rawApiBodies: input.telemetry.logRawApiBodies ?? false,
174
+ };
175
+ Object.assign(env, {
176
+ CLAUDE_CODE_ENABLE_TELEMETRY: "1",
177
+ CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
178
+ CLAUDE_CODE_OTEL_DIAG_STDERR: "1",
179
+ OTEL_TRACES_EXPORTER: "otlp",
180
+ OTEL_METRICS_EXPORTER: "otlp",
181
+ OTEL_LOGS_EXPORTER: "otlp",
182
+ OTEL_EXPORTER_OTLP_PROTOCOL: protocol,
183
+ OTEL_EXPORTER_OTLP_ENDPOINT: input.telemetry.endpoint,
184
+ OTEL_METRIC_EXPORT_INTERVAL: String(exportIntervalMs),
185
+ OTEL_LOGS_EXPORT_INTERVAL: String(exportIntervalMs),
186
+ OTEL_TRACES_EXPORT_INTERVAL: String(exportIntervalMs),
187
+ OTEL_LOG_USER_PROMPTS: content.userPrompts ? "1" : "0",
188
+ OTEL_LOG_TOOL_DETAILS: content.toolDetails ? "1" : "0",
189
+ OTEL_LOG_TOOL_CONTENT: content.toolContent ? "1" : "0",
190
+ OTEL_LOG_RAW_API_BODIES: content.rawApiBodies ? "1" : "0",
191
+ OTEL_RESOURCE_ATTRIBUTES: inheritedResourceAttributes === undefined || inheritedResourceAttributes === ""
192
+ ? encodedAttributes
193
+ : `${inheritedResourceAttributes},${encodedAttributes}`,
194
+ });
195
+ for (const signal of TELEMETRY_SIGNALS) {
196
+ delete env[`OTEL_EXPORTER_OTLP_${signal.toUpperCase()}_ENDPOINT`];
197
+ delete env[`OTEL_EXPORTER_OTLP_${signal.toUpperCase()}_PROTOCOL`];
198
+ }
199
+ if (input.telemetry.detailedHookSpans === undefined) {
200
+ delete env.ENABLE_BETA_TRACING_DETAILED;
201
+ delete env.BETA_TRACING_ENDPOINT;
202
+ }
203
+ else {
204
+ env.ENABLE_BETA_TRACING_DETAILED = "1";
205
+ env.BETA_TRACING_ENDPOINT = input.telemetry.detailedHookSpans.endpoint;
206
+ }
207
+ rejectConsoleExporter(env);
208
+ return {
209
+ env,
210
+ telemetry: {
211
+ effectiveSettings: {
212
+ endpoint: input.telemetry.endpoint,
213
+ protocol,
214
+ exporters: { traces: "otlp", metrics: "otlp", logs: "otlp" },
215
+ exportIntervalMs: { traces: exportIntervalMs, metrics: exportIntervalMs, logs: exportIntervalMs },
216
+ diagnostics: "stderr",
217
+ content,
218
+ secretHeadersConfigured: TELEMETRY_SIGNALS.some((signal) => env[`OTEL_EXPORTER_OTLP_${signal.toUpperCase()}_HEADERS`] !== undefined)
219
+ || env.OTEL_EXPORTER_OTLP_HEADERS !== undefined,
220
+ },
221
+ correlation: {
222
+ type: "resource-attributes",
223
+ attributes,
224
+ inheritedResourceAttributesPreserved: inheritedResourceAttributes !== undefined && inheritedResourceAttributes !== "",
225
+ },
226
+ traces: { status: "beta" },
227
+ hookSpans: input.telemetry.detailedHookSpans === undefined
228
+ ? { status: "disabled" }
229
+ : { status: "enabled", stability: "detailed-beta", endpoint: input.telemetry.detailedHookSpans.endpoint },
230
+ receipt: { status: "not-checked", signals: [], reason: "no-receipt-check" },
231
+ },
232
+ };
233
+ }
234
+ export async function executeClaudeAgentSdk(context, configuration, sink, queryFunction = query) {
235
+ let queryHandle;
236
+ let messageCount = 0;
237
+ let lastResult;
238
+ const stderr = new BoundedDiagnosticCapture();
239
+ let evidence;
240
+ const controller = new AbortController();
241
+ let abortEvent = Promise.resolve();
242
+ let abortLifecycleError;
243
+ let closeError;
244
+ let closed = false;
245
+ let receiptChecked = false;
246
+ const close = () => {
247
+ if (queryHandle === undefined || closed)
248
+ return;
249
+ closed = true;
250
+ try {
251
+ queryHandle.close();
252
+ }
253
+ catch (error) {
254
+ closeError = errorMessage(error);
255
+ }
256
+ };
257
+ const abort = () => {
258
+ if (controller.signal.aborted)
259
+ return;
260
+ controller.abort(context.signal.reason);
261
+ abortEvent = emitLifecycle(sink, {
262
+ type: "abort-requested",
263
+ at: new Date().toISOString(),
264
+ ...errorReason(context.signal.reason),
265
+ }).catch((error) => {
266
+ abortLifecycleError = errorMessage(error);
267
+ });
268
+ close();
269
+ };
270
+ context.registerShutdown(() => {
271
+ close();
272
+ if (closeError !== undefined)
273
+ throw new Error(closeError);
274
+ });
275
+ if (context.signal.aborted)
276
+ abort();
277
+ else
278
+ context.signal.addEventListener("abort", abort, { once: true });
279
+ try {
280
+ validateEvidenceSink(sink);
281
+ validateConfiguration(configuration, context);
282
+ const capabilities = probeClaudeAgentSdkCapabilities();
283
+ const environment = buildClaudeAgentSdkEnvironment({
284
+ inherited: process.env,
285
+ overrides: configuration.env,
286
+ telemetry: configuration.telemetry,
287
+ run: context.run,
288
+ attempt: context.attempt,
289
+ });
290
+ evidence = executorEvidence(configuration, context, capabilities, environment.telemetry);
291
+ const hookWarningCapture = new BoundedDiagnosticCapture();
292
+ const hooks = passiveHooks(sink, (warning) => {
293
+ evidence.captureWarnings.count += 1;
294
+ hookWarningCapture.write(Buffer.from(`${JSON.stringify(warning)}\n`));
295
+ const diagnostic = hookWarningCapture.result();
296
+ evidence.captureWarnings.diagnostic = diagnostic.text;
297
+ evidence.captureWarnings.sizeBytes = diagnostic.sizeBytes;
298
+ evidence.captureWarnings.truncated = diagnostic.truncated;
299
+ });
300
+ const options = {
301
+ abortController: controller,
302
+ cwd: context.workspace.path,
303
+ env: environment.env,
304
+ model: configuration.model,
305
+ tools: [...configuration.tools],
306
+ allowedTools: [...(configuration.allowedTools ?? [])],
307
+ disallowedTools: [...(configuration.disallowedTools ?? [])],
308
+ permissionMode: configuration.permissionMode,
309
+ ...(configuration.allowDangerouslySkipPermissions === undefined
310
+ ? {}
311
+ : { allowDangerouslySkipPermissions: configuration.allowDangerouslySkipPermissions }),
312
+ ...(configuration.maxTurns === undefined ? {} : { maxTurns: configuration.maxTurns }),
313
+ ...(configuration.maxBudgetUsd === undefined ? {} : { maxBudgetUsd: configuration.maxBudgetUsd }),
314
+ hooks,
315
+ includeHookEvents: true,
316
+ stderr: captureStderr,
317
+ };
318
+ await emitLifecycle(sink, { type: "started", at: new Date().toISOString() });
319
+ queryHandle = queryFunction({ prompt: configuration.prompt, options });
320
+ if (controller.signal.aborted)
321
+ close();
322
+ for await (const message of queryHandle) {
323
+ messageCount += 1;
324
+ await sink.message?.(message);
325
+ if (message.type === "result")
326
+ lastResult = message;
327
+ }
328
+ close();
329
+ if (closeError !== undefined)
330
+ throw new Error(closeError);
331
+ await checkTelemetryReceipt();
332
+ await abortEvent;
333
+ if (abortLifecycleError !== undefined)
334
+ throw new Error(abortLifecycleError);
335
+ if (context.signal.aborted || controller.signal.aborted && lastResult === undefined) {
336
+ const reason = "Claude Agent SDK execution was aborted.";
337
+ await emitLifecycle(sink, { type: "failed", at: new Date().toISOString(), messageCount, error: reason });
338
+ return withCaptureError({ status: "interrupted", reason, evidence }, evidence);
339
+ }
340
+ const result = classifyResult(lastResult, diagnosticText(stderr), evidence);
341
+ await emitLifecycle(sink, result.status === "completed"
342
+ ? { type: "completed", at: new Date().toISOString(), messageCount }
343
+ : { type: "failed", at: new Date().toISOString(), messageCount, error: result.error ?? result.reason ?? "Claude Agent SDK failed." });
344
+ return withCaptureError(result, evidence);
345
+ }
346
+ catch (error) {
347
+ close();
348
+ await checkTelemetryReceipt();
349
+ await abortEvent;
350
+ const diagnostic = joinDiagnostics(errorMessage(error), closeError, diagnosticText(stderr));
351
+ if (context.signal.aborted || controller.signal.aborted) {
352
+ await emitLifecycle(sink, { type: "failed", at: new Date().toISOString(), messageCount, error: diagnostic });
353
+ return withCaptureError({ status: "interrupted", reason: "Claude Agent SDK execution was aborted.", error: diagnostic, ...(evidence === undefined ? {} : { evidence }) }, evidence);
354
+ }
355
+ if (lastResult !== undefined && evidence !== undefined && (lastResult.is_error || lastResult.subtype !== "success")) {
356
+ const result = classifyResult(lastResult, diagnostic, evidence);
357
+ await emitLifecycle(sink, { type: "failed", at: new Date().toISOString(), messageCount, error: result.error ?? diagnostic });
358
+ return withCaptureError(result, evidence);
359
+ }
360
+ await emitLifecycle(sink, { type: "failed", at: new Date().toISOString(), messageCount, error: diagnostic });
361
+ return withCaptureError({ status: "failed", failureClass: "infrastructure", reason: "Claude Agent SDK execution failed.", error: diagnostic, ...(evidence === undefined ? {} : { evidence }) }, evidence);
362
+ }
363
+ finally {
364
+ context.signal.removeEventListener("abort", abort);
365
+ }
366
+ function captureStderr(data) {
367
+ stderr.write(Buffer.from(data));
368
+ sink.stderr?.(data);
369
+ }
370
+ async function checkTelemetryReceipt() {
371
+ if (receiptChecked || configuration.telemetry === undefined || evidence?.telemetry === undefined)
372
+ return;
373
+ receiptChecked = true;
374
+ evidence.telemetry.receipt = await checkTelemetryReceiptSafely(configuration.telemetry, evidence.telemetry.correlation);
375
+ }
376
+ }
377
+ function passiveHooks(sink, captureWarning) {
378
+ const hooks = {};
379
+ for (const event of HOOK_EVENTS) {
380
+ hooks[event] = [{
381
+ hooks: [async (input, toolUseId, { signal }) => {
382
+ try {
383
+ await sink.hook?.(event, input, toolUseId, signal);
384
+ }
385
+ catch (error) {
386
+ captureWarning({
387
+ type: "hook-capture-warning",
388
+ at: new Date().toISOString(),
389
+ hook: event,
390
+ sessionId: input.session_id,
391
+ ...(toolUseId === undefined ? {} : { toolUseId }),
392
+ message: errorMessage(error),
393
+ });
394
+ }
395
+ return {};
396
+ }],
397
+ }];
398
+ }
399
+ return hooks;
400
+ }
401
+ function withCaptureError(result, evidence) {
402
+ const count = evidence?.captureWarnings.count ?? 0;
403
+ return count === 0
404
+ ? result
405
+ : { ...result, captureError: `Claude Agent SDK hook capture reported ${count} warning${count === 1 ? "" : "s"}.` };
406
+ }
407
+ function classifyResult(result, stderr, evidence) {
408
+ if (result === undefined) {
409
+ return {
410
+ status: "failed",
411
+ failureClass: "infrastructure",
412
+ reason: "Claude Agent SDK stream ended without a result message.",
413
+ error: joinDiagnostics("Claude Agent SDK stream ended without a result message.", stderr),
414
+ evidence,
415
+ };
416
+ }
417
+ evidence.usage = {
418
+ source: "sdk-result",
419
+ totalCostUsd: result.total_cost_usd,
420
+ numTurns: result.num_turns,
421
+ mainLoop: structuredClone(result.usage),
422
+ byModel: structuredClone(result.modelUsage),
423
+ };
424
+ const completionEvidence = {
425
+ type: "sdk-result",
426
+ subtype: result.subtype,
427
+ sessionId: result.session_id,
428
+ uuid: result.uuid,
429
+ };
430
+ if (result.subtype === "success" && !result.is_error) {
431
+ return { status: "completed", completionEvidence, evidence };
432
+ }
433
+ const sdkError = result.subtype === "success" ? result.result : result.errors.join("\n");
434
+ const error = joinDiagnostics(sdkError || `Claude Agent SDK returned ${result.subtype}.`, stderr);
435
+ if (result.subtype === "error_max_turns" || result.subtype === "error_max_budget_usd") {
436
+ return { status: "stopped", stopReason: "budget", reason: `Claude Agent SDK reached ${result.subtype}.`, error, completionEvidence, evidence };
437
+ }
438
+ return {
439
+ status: "failed",
440
+ failureClass: "infrastructure",
441
+ reason: result.subtype === "success"
442
+ ? "Claude Agent SDK returned an API error."
443
+ : `Claude Agent SDK returned ${result.subtype}.`,
444
+ error,
445
+ completionEvidence,
446
+ evidence,
447
+ };
448
+ }
449
+ function executorEvidence(configuration, context, capabilities, telemetry) {
450
+ return {
451
+ runtime: [
452
+ { source: "anthropic", name: "agent-sdk", version: capabilities.sdkVersion },
453
+ { source: "anthropic", name: "agent-cli", version: capabilities.claudeCodeVersion },
454
+ ],
455
+ effectiveConfiguration: {
456
+ model: configuration.model,
457
+ tools: [...configuration.tools],
458
+ allowedTools: [...(configuration.allowedTools ?? [])],
459
+ disallowedTools: [...(configuration.disallowedTools ?? [])],
460
+ permissionMode: configuration.permissionMode,
461
+ environment: { parentPreserved: true, overridesApplied: Object.keys(configuration.env ?? {}).length > 0 },
462
+ budget: {
463
+ ...(context.budgetMs === undefined ? {} : { wallClockMs: context.budgetMs }),
464
+ ...(configuration.maxTurns === undefined ? {} : { maxTurns: configuration.maxTurns }),
465
+ ...(configuration.maxBudgetUsd === undefined ? {} : { maxBudgetUsd: configuration.maxBudgetUsd }),
466
+ },
467
+ workingDirectory: "attempt-workspace",
468
+ },
469
+ capabilities,
470
+ ...(telemetry === undefined ? {} : { telemetry }),
471
+ captureWarnings: { count: 0, diagnostic: "", sizeBytes: 0, truncated: false },
472
+ };
473
+ }
474
+ function validateConfiguration(configuration, context) {
475
+ if (!isRecord(configuration))
476
+ throw new Error("Claude Agent SDK configuration must be an object.");
477
+ assertText(configuration.prompt, "prompt");
478
+ assertText(configuration.model, "model");
479
+ assertStringList(configuration.tools, "tools");
480
+ if (configuration.allowedTools !== undefined)
481
+ assertStringList(configuration.allowedTools, "allowedTools");
482
+ if (configuration.disallowedTools !== undefined)
483
+ assertStringList(configuration.disallowedTools, "disallowedTools");
484
+ if (!["default", "acceptEdits", "bypassPermissions", "plan", "dontAsk", "auto"].includes(configuration.permissionMode)) {
485
+ throw new Error("Claude Agent SDK permissionMode is invalid.");
486
+ }
487
+ if (configuration.permissionMode === "bypassPermissions" && configuration.allowDangerouslySkipPermissions !== true) {
488
+ throw new Error("bypassPermissions requires allowDangerouslySkipPermissions=true.");
489
+ }
490
+ if (configuration.maxTurns !== undefined && (!Number.isSafeInteger(configuration.maxTurns) || configuration.maxTurns < 1)) {
491
+ throw new Error("Claude Agent SDK maxTurns must be a positive safe integer.");
492
+ }
493
+ if (configuration.maxBudgetUsd !== undefined && (!Number.isFinite(configuration.maxBudgetUsd) || configuration.maxBudgetUsd <= 0)) {
494
+ throw new Error("Claude Agent SDK maxBudgetUsd must be a positive finite number.");
495
+ }
496
+ if (configuration.env !== undefined) {
497
+ if (!isRecord(configuration.env))
498
+ throw new Error("Claude Agent SDK env must be an object.");
499
+ for (const value of Object.values(configuration.env)) {
500
+ if (value !== undefined && typeof value !== "string")
501
+ throw new Error("Claude Agent SDK env values must be strings or undefined.");
502
+ }
503
+ }
504
+ if (configuration.telemetry !== undefined && !isRecord(configuration.telemetry)) {
505
+ throw new Error("Claude Agent SDK telemetry must be an object.");
506
+ }
507
+ if (context.workspace?.status !== "ready" || context.workspace.path === undefined || context.workspace.path.trim() === "") {
508
+ throw new Error("Claude Agent SDK requires a ready attempt workspace path.");
509
+ }
510
+ }
511
+ function validateTelemetryConfiguration(configuration) {
512
+ if (!isRecord(configuration))
513
+ throw new Error("Claude Agent SDK telemetry must be an object.");
514
+ assertTelemetryEndpoint(configuration.endpoint, "endpoint");
515
+ if (configuration.protocol !== undefined && !["http/protobuf", "http/json", "grpc"].includes(configuration.protocol)) {
516
+ throw new Error("Claude Agent SDK telemetry protocol is invalid.");
517
+ }
518
+ if (configuration.exportIntervalMs !== undefined
519
+ && (!Number.isSafeInteger(configuration.exportIntervalMs) || configuration.exportIntervalMs < 1)) {
520
+ throw new Error("Claude Agent SDK telemetry exportIntervalMs must be a positive safe integer.");
521
+ }
522
+ for (const field of ["logUserPrompts", "logToolDetails", "logToolContent", "logRawApiBodies"]) {
523
+ if (configuration[field] !== undefined && typeof configuration[field] !== "boolean") {
524
+ throw new Error(`Claude Agent SDK telemetry ${field} must be a boolean.`);
525
+ }
526
+ }
527
+ if (configuration.detailedHookSpans !== undefined) {
528
+ if (!isRecord(configuration.detailedHookSpans))
529
+ throw new Error("Claude Agent SDK detailedHookSpans must be an object.");
530
+ assertTelemetryEndpoint(configuration.detailedHookSpans.endpoint, "detailedHookSpans.endpoint");
531
+ }
532
+ if (configuration.checkReceipt !== undefined && typeof configuration.checkReceipt !== "function") {
533
+ throw new Error("Claude Agent SDK telemetry checkReceipt must be a function.");
534
+ }
535
+ }
536
+ function assertTelemetryEndpoint(value, field) {
537
+ assertText(value, `telemetry ${field}`);
538
+ let endpoint;
539
+ try {
540
+ endpoint = new URL(value);
541
+ }
542
+ catch {
543
+ throw new Error(`Claude Agent SDK telemetry ${field} must be an absolute URL.`);
544
+ }
545
+ if (!["http:", "https:"].includes(endpoint.protocol)
546
+ || endpoint.username !== "" || endpoint.password !== "" || endpoint.search !== "" || endpoint.hash !== "") {
547
+ throw new Error(`Claude Agent SDK telemetry ${field} must be an HTTP(S) URL without credentials, query, or fragment.`);
548
+ }
549
+ }
550
+ function rejectConsoleExporter(env) {
551
+ for (const key of TELEMETRY_EXPORTER_KEYS) {
552
+ if ((env[key] ?? "").split(",").some((exporter) => exporter.trim().toLowerCase() === "console")) {
553
+ throw new Error(`Claude Agent SDK ${key} cannot use the console exporter because stdout is the SDK protocol channel.`);
554
+ }
555
+ }
556
+ }
557
+ async function checkTelemetryReceiptSafely(configuration, correlation) {
558
+ if (configuration.checkReceipt === undefined)
559
+ return { status: "not-checked", signals: [], reason: "no-receipt-check" };
560
+ let receipt;
561
+ try {
562
+ receipt = await configuration.checkReceipt(correlation);
563
+ }
564
+ catch {
565
+ return { status: "missing", signals: [], reason: "receipt-check-failed" };
566
+ }
567
+ if (!isRecord(receipt) || !Array.isArray(receipt.signals)
568
+ || receipt.signals.some((signal) => !TELEMETRY_SIGNALS.includes(signal))) {
569
+ return { status: "missing", signals: [], reason: "receipt-check-failed" };
570
+ }
571
+ const signals = [...new Set(receipt.signals)];
572
+ if (receipt.status === "received") {
573
+ return TELEMETRY_SIGNALS.every((signal) => signals.includes(signal))
574
+ ? { status: "received", signals }
575
+ : { status: "missing", signals, reason: "partial-receipt" };
576
+ }
577
+ if (receipt.status === "missing"
578
+ && ["not-received", "collector-unreachable", "process-interrupted", "partial-receipt", "receipt-check-failed"].includes(String(receipt.reason))) {
579
+ return { status: "missing", signals, reason: receipt.reason };
580
+ }
581
+ return { status: "missing", signals, reason: "receipt-check-failed" };
582
+ }
583
+ function validateEvidenceSink(sink) {
584
+ if (!isRecord(sink) || [sink.message, sink.stderr, sink.hook, sink.lifecycle].some((callback) => typeof callback !== "function")) {
585
+ throw new Error("Claude Agent SDK evidence sink requires message, stderr, hook, and lifecycle callbacks.");
586
+ }
587
+ }
588
+ function nativeMessageRecord(message, sequence, capturedAt) {
589
+ const value = message;
590
+ const nativeType = isRecord(value) && typeof value.type === "string" && value.type !== "" ? value.type : "unknown";
591
+ const nativeSubtype = isRecord(value) && typeof value.subtype === "string" && value.subtype !== "" ? value.subtype : undefined;
592
+ const sessionId = isRecord(value) && typeof value.session_id === "string" && value.session_id !== "" ? value.session_id : undefined;
593
+ const diagnostic = KNOWN_MESSAGE_TYPES.has(nativeType)
594
+ ? undefined
595
+ : { sequence, code: "unknown-native-type", nativeType: nativeType.slice(0, 256) };
596
+ return {
597
+ schemaVersion: "ebo.agent-sdk-message/v1",
598
+ sequence,
599
+ capturedAt,
600
+ nativeType,
601
+ ...(nativeSubtype === undefined ? {} : { nativeSubtype }),
602
+ ...(sessionId === undefined ? {} : { sessionId }),
603
+ message,
604
+ ...(diagnostic === undefined ? {} : { diagnostics: [diagnostic] }),
605
+ };
606
+ }
607
+ function installedVersions() {
608
+ const entry = fileURLToPath(import.meta.resolve(PACKAGE_NAME));
609
+ const metadata = JSON.parse(readFileSync(join(dirname(entry), "package.json"), "utf8"));
610
+ if (!isRecord(metadata) || typeof metadata.version !== "string" || typeof metadata.claudeCodeVersion !== "string") {
611
+ throw new Error("Installed Claude Agent SDK package metadata is incomplete.");
612
+ }
613
+ return { sdkVersion: metadata.version, claudeCodeVersion: metadata.claudeCodeVersion };
614
+ }
615
+ function emitLifecycle(sink, event) {
616
+ return Promise.resolve(sink.lifecycle?.(event));
617
+ }
618
+ function assertText(value, field) {
619
+ if (typeof value !== "string" || value.trim() === "")
620
+ throw new Error(`Claude Agent SDK ${field} must be a nonempty string.`);
621
+ }
622
+ function assertStringList(value, field) {
623
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.trim() === "")) {
624
+ throw new Error(`Claude Agent SDK ${field} must contain only nonempty strings.`);
625
+ }
626
+ }
627
+ function joinDiagnostics(...values) {
628
+ return values.filter((value) => value !== undefined && value !== "").join("\n");
629
+ }
630
+ function diagnosticText(capture) {
631
+ const result = capture.result();
632
+ return result.truncated
633
+ ? `${result.text}\n[stderr truncated: received ${result.sizeBytes} bytes, retained ${result.maxBytes}]`
634
+ : result.text;
635
+ }
636
+ function errorReason(value) {
637
+ return value === undefined ? {} : { reason: errorMessage(value) };
638
+ }
639
+ function errorMessage(error) {
640
+ return error instanceof Error ? error.message : String(error);
641
+ }
642
+ function isRecord(value) {
643
+ return typeof value === "object" && value !== null && !Array.isArray(value);
644
+ }