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,415 @@
1
+ import { writeArtifactAtomically } from "./artifacts.js";
2
+ import { captureOpenHandsAgentServer, normalizeOpenHandsCapture, OPENHANDS_AGENT_SERVER_VERSION, OPENHANDS_DEFAULT_MAX_RESPONSE_BYTES, OPENHANDS_MAX_RESPONSE_BYTES, } from "./openhands.js";
3
+ import { createAttemptIdentity, createRunIdentity, executeRunAttempt, } from "./lifecycle.js";
4
+ import { createRunBundleAssembler, qualifyRunBundle, } from "./run-bundles.js";
5
+ import { validateUniformEvents } from "./uniform-events.js";
6
+ import { openJsonlEvidenceWriter } from "./process-protocol.js";
7
+ /** Execute and retain one caller-configured OpenHands Agent Server attempt. */
8
+ export async function captureOpenHandsAgentServerRun(options) {
9
+ if (options.definition.run.harness.id !== "openhands-agent-server") {
10
+ throw new Error(`OpenHands run harness must be openhands-agent-server; received ${options.definition.run.harness.id}.`);
11
+ }
12
+ if (options.definition.run.model.id !== options.configuration.model) {
13
+ throw new Error("The declared model must match the OpenHands run configuration.");
14
+ }
15
+ const requestModel = openHandsRequestModel(options.configuration.startConversation);
16
+ if (requestModel !== options.configuration.model) {
17
+ throw new Error(`The actual conversation request model must match ${options.configuration.model}; received ${String(requestModel)}.`);
18
+ }
19
+ const definition = withPinnedRuntime(options.definition);
20
+ const assembler = await createRunBundleAssembler(definition);
21
+ const sessionWriter = await openJsonlEvidenceWriter(`${assembler.bundleRoot}/session.jsonl`, {
22
+ exclusive: true,
23
+ maxLineBytes: Math.min(Math.max(1, options.configuration.maxResponseBytes ?? OPENHANDS_DEFAULT_MAX_RESPONSE_BYTES), OPENHANDS_MAX_RESPONSE_BYTES) + 64 * 1024,
24
+ });
25
+ let workspace;
26
+ let workspaceOutcome;
27
+ let workspaceOutcomePromise;
28
+ let workspaceCaptureError;
29
+ let verifierResult;
30
+ let capture;
31
+ let nativeEvidenceWritten = false;
32
+ let captureSettled = false;
33
+ let progressiveSessionId;
34
+ let progressiveWriteError;
35
+ let acceptingRecords = true;
36
+ const progressiveRecords = [];
37
+ const workspaceOutcomeExcludedDirectoryNames = [...new Set([
38
+ ".git",
39
+ ...(options.workspaceOutcomeExcludedDirectoryNames ?? []).filter((name) => name !== ".git"),
40
+ ])];
41
+ const qualificationOptions = {
42
+ startingWorkspacePath: options.startingWorkspacePath,
43
+ workspaceOutcomeExcludedDirectoryNames,
44
+ ...(options.workspaceOutcomeRespectsGitignore === undefined ? {} : {
45
+ workspaceOutcomeRespectsGitignore: options.workspaceOutcomeRespectsGitignore,
46
+ }),
47
+ ...(options.workspaceOutcomeOmitsEmptyDirectories === undefined ? {} : {
48
+ workspaceOutcomeOmitsEmptyDirectories: options.workspaceOutcomeOmitsEmptyDirectories,
49
+ }),
50
+ };
51
+ const captureWorkspace = async (verifierContext) => {
52
+ if (workspaceOutcome !== undefined)
53
+ return workspaceOutcome;
54
+ if (workspace?.status !== "ready" || workspace.path === undefined || workspace.artifactId === undefined) {
55
+ throw new Error("OpenHands capture requires a retained ready workspace before outcome packaging.");
56
+ }
57
+ workspaceOutcomePromise ??= assembler.captureWorkspaceOutcome({
58
+ startPath: options.startingWorkspacePath,
59
+ finalPath: workspace.path,
60
+ id: workspace.artifactId,
61
+ excludeDirectoryNames: workspaceOutcomeExcludedDirectoryNames,
62
+ ...(options.workspaceOutcomeRespectsGitignore === undefined ? {} : {
63
+ respectGitignore: options.workspaceOutcomeRespectsGitignore,
64
+ }),
65
+ ...(options.workspaceOutcomeOmitsEmptyDirectories === undefined ? {} : {
66
+ omitEmptyDirectories: options.workspaceOutcomeOmitsEmptyDirectories,
67
+ }),
68
+ }, verifierContext === undefined || options.verifier === undefined
69
+ ? undefined
70
+ : async (projectedPath, outcome) => {
71
+ workspaceOutcome = outcome;
72
+ verifierResult = await options.verifier(verifierContext, outcome, projectedPath);
73
+ });
74
+ workspaceOutcome = await workspaceOutcomePromise;
75
+ return workspaceOutcome;
76
+ };
77
+ const writeNativeEvidence = async () => {
78
+ if (nativeEvidenceWritten)
79
+ return;
80
+ await sessionWriter.flush();
81
+ if (progressiveWriteError !== undefined)
82
+ throw new Error(progressiveWriteError);
83
+ if (!captureSettled || sessionWriter.count === 0)
84
+ return;
85
+ await sessionWriter.close();
86
+ await assembler.registerArtifact({
87
+ id: "session",
88
+ source: "openhands-agent-server",
89
+ kind: "session",
90
+ mediaType: "application/x-ndjson",
91
+ sharingClass: "restricted",
92
+ relativePath: "session.jsonl",
93
+ ...(progressiveSessionId === undefined ? {} : {
94
+ nativeReference: { type: "session", id: progressiveSessionId },
95
+ }),
96
+ });
97
+ if (capture === undefined) {
98
+ nativeEvidenceWritten = true;
99
+ return;
100
+ }
101
+ const hooks = capture.records.filter(({ record }) => record.payload.kind === "HookExecutionEvent");
102
+ if (hooks.length > 0) {
103
+ const hookBytes = Buffer.from(`${hooks.map(({ record }) => JSON.stringify({
104
+ session_id: capture.conversationId,
105
+ hook_event_name: record.payload.hook_event_type,
106
+ native_record: record,
107
+ })).join("\n")}\n`);
108
+ await writeArtifactAtomically(assembler.bundleRoot, "hooks.jsonl", hookBytes, undefined, { overwrite: false });
109
+ await assembler.registerArtifact({
110
+ id: "hooks",
111
+ source: "openhands-agent-server",
112
+ kind: "hook",
113
+ mediaType: "application/x-ndjson",
114
+ sharingClass: "restricted",
115
+ relativePath: "hooks.jsonl",
116
+ });
117
+ }
118
+ nativeEvidenceWritten = true;
119
+ };
120
+ const coordinatedWorkspace = {
121
+ setup: async (context) => {
122
+ workspace = await options.workspace.setup(context);
123
+ return workspace;
124
+ },
125
+ cleanup: async (context) => {
126
+ try {
127
+ if (workspace?.status === "ready")
128
+ await captureWorkspace();
129
+ }
130
+ catch (error) {
131
+ if (workspaceOutcome === undefined) {
132
+ workspaceCaptureError = error instanceof Error ? error.message : String(error);
133
+ throw error;
134
+ }
135
+ }
136
+ await options.workspace.cleanup?.(context);
137
+ },
138
+ };
139
+ const run = createRunIdentity({
140
+ id: definition.run.id,
141
+ taskId: definition.run.task.id,
142
+ modelId: definition.run.model.id,
143
+ harnessId: definition.run.harness.id,
144
+ });
145
+ const attemptIdentity = createAttemptIdentity(definition.run.id, definition.attempt.number, definition.attempt.id, definition.attempt.retryOf);
146
+ const attempt = await executeRunAttempt({
147
+ run,
148
+ assessmentMode: definition.run.assessmentMode,
149
+ attempt: attemptIdentity,
150
+ workspace: coordinatedWorkspace,
151
+ harness: async ({ signal, registerShutdown }) => {
152
+ if (workspace?.status !== "ready" || workspace.path === undefined) {
153
+ throw new Error("OpenHands run requires a ready workspace path.");
154
+ }
155
+ const { serverWorkspacePath, ...configuration } = options.configuration;
156
+ registerShutdown(async () => {
157
+ if (progressiveSessionId === undefined)
158
+ return;
159
+ await cleanupOpenHandsConversation(configuration, progressiveSessionId, options.shutdownGraceMs ?? 250);
160
+ });
161
+ try {
162
+ capture = await captureOpenHandsAgentServer({
163
+ ...configuration,
164
+ runId: definition.run.id,
165
+ attemptId: definition.attempt.id,
166
+ signal,
167
+ onRecord: (captured) => {
168
+ if (!acceptingRecords)
169
+ return;
170
+ if (captured.record.session_id !== undefined)
171
+ progressiveSessionId = captured.record.session_id;
172
+ progressiveRecords.push(captured);
173
+ void sessionWriter.append(captured.record).catch((error) => {
174
+ progressiveWriteError ??= error instanceof Error ? error.message : String(error);
175
+ });
176
+ },
177
+ startConversation: {
178
+ ...options.configuration.startConversation,
179
+ workspace: { type: "local", working_dir: serverWorkspacePath ?? workspace.path },
180
+ },
181
+ });
182
+ }
183
+ finally {
184
+ captureSettled = true;
185
+ }
186
+ if (capture.captureError !== undefined) {
187
+ return {
188
+ status: "failed",
189
+ failureClass: "infrastructure",
190
+ reason: capture.captureError,
191
+ captureError: capture.captureError,
192
+ evidence: capture,
193
+ };
194
+ }
195
+ const status = capture.finalConversation?.execution_status;
196
+ return status === "finished"
197
+ ? {
198
+ status: "completed",
199
+ completionEvidence: capture.finalConversation,
200
+ evidence: capture,
201
+ ...(capture.reconciliation.finalReadError === undefined ? {} : {
202
+ captureError: capture.reconciliation.finalReadError,
203
+ }),
204
+ }
205
+ : { status: "failed", failureClass: "infrastructure", reason: `OpenHands conversation ended as ${String(status)}.`, evidence: capture };
206
+ },
207
+ ...(options.verifier === undefined ? {} : {
208
+ verifier: async (context) => {
209
+ await captureWorkspace(context);
210
+ if (verifierResult === undefined)
211
+ throw new Error("OpenHands verifier did not return a result.");
212
+ await assembler.writeJsonArtifact({
213
+ id: "verifier",
214
+ source: "ebo-verifier",
215
+ kind: "verifier",
216
+ mediaType: "application/json",
217
+ sharingClass: "restricted",
218
+ relativePath: "verifier.json",
219
+ }, verifierResult);
220
+ return {
221
+ status: verifierResult.status,
222
+ ...(verifierResult.error === undefined ? {} : { error: verifierResult.error }),
223
+ evidence: verifierResult,
224
+ };
225
+ },
226
+ }),
227
+ evidence: { flush: writeNativeEvidence },
228
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
229
+ ...(options.maxWallClockMs === undefined ? {} : { maxWallClockMs: options.maxWallClockMs }),
230
+ ...(options.harnessBudgetMs === undefined ? {} : { harnessBudgetMs: options.harnessBudgetMs }),
231
+ ...(options.shutdownGraceMs === undefined ? {} : { shutdownGraceMs: options.shutdownGraceMs }),
232
+ });
233
+ if (capture === undefined) {
234
+ acceptingRecords = false;
235
+ await sessionWriter.close();
236
+ captureSettled = true;
237
+ if (progressiveSessionId !== undefined && sessionWriter.count > 0) {
238
+ capture = interruptedCapture(definition.run.id, definition.attempt.id, progressiveSessionId, progressiveRecords.slice(0, sessionWriter.count));
239
+ }
240
+ }
241
+ if (capture === undefined) {
242
+ await writeNativeEvidence();
243
+ const terminal = structuredClone(attempt.terminal);
244
+ if (workspaceOutcome === undefined)
245
+ delete terminal.workspaceArtifactId;
246
+ const manifest = await assembler.finalize({
247
+ terminal,
248
+ missingEvidence: [
249
+ {
250
+ kind: sessionWriter.count === 0 ? "session" : "session-identity",
251
+ reason: "not-collected",
252
+ affects: ["semantic"],
253
+ detail: "The attempt ended before the OpenHands harness produced an identified native session.",
254
+ },
255
+ {
256
+ kind: "telemetry",
257
+ reason: "unsupported",
258
+ affects: ["timing-resource"],
259
+ detail: "The pinned Agent Server REST/WebSocket boundary does not expose native OpenTelemetry records.",
260
+ },
261
+ ],
262
+ qualification: qualificationOptions,
263
+ });
264
+ const qualification = await qualifyRunBundle(assembler.bundleRoot, qualificationOptions);
265
+ return {
266
+ attempt,
267
+ manifest,
268
+ qualification,
269
+ ...(workspace?.status === "ready" && workspaceOutcome === undefined
270
+ ? { retainedWorkspacePath: workspace.path }
271
+ : {}),
272
+ };
273
+ }
274
+ await writeNativeEvidence();
275
+ const terminal = structuredClone(attempt.terminal);
276
+ if (workspaceOutcome === undefined)
277
+ delete terminal.workspaceArtifactId;
278
+ const missingEvidence = [
279
+ {
280
+ kind: "telemetry",
281
+ reason: "unsupported",
282
+ affects: ["timing-resource"],
283
+ detail: "The pinned Agent Server REST/WebSocket boundary does not expose native OpenTelemetry records.",
284
+ },
285
+ {
286
+ kind: "event-log-completeness",
287
+ reason: "not-checked",
288
+ affects: ["semantic"],
289
+ detail: capture.eventLogCompleteness.reason,
290
+ },
291
+ ...(capture.captureError === undefined ? [] : [{
292
+ kind: "agent-server-control",
293
+ reason: "not-collected",
294
+ affects: ["semantic"],
295
+ detail: capture.captureError,
296
+ }]),
297
+ ...(capture.reconciliation.finalReadError === undefined ? [] : [{
298
+ kind: "final-rest-events",
299
+ reason: "not-collected",
300
+ affects: ["semantic"],
301
+ detail: capture.reconciliation.finalReadError,
302
+ }]),
303
+ ...(capture.reconciliation.transportGaps.length === 0 ? [] : [{
304
+ kind: "websocket-events",
305
+ reason: "not-collected",
306
+ affects: ["semantic"],
307
+ detail: `Transport gaps: ${capture.reconciliation.transportGaps.join(", ")}`,
308
+ }]),
309
+ ...(workspaceOutcome === undefined && workspaceCaptureError !== undefined ? [{
310
+ kind: "workspace",
311
+ reason: "not-collected",
312
+ affects: ["outcome"],
313
+ detail: workspaceCaptureError.slice(0, 4096),
314
+ }] : []),
315
+ ];
316
+ const manifest = await assembler.finalize({ terminal, missingEvidence, qualification: qualificationOptions });
317
+ const qualification = await qualifyRunBundle(assembler.bundleRoot, qualificationOptions);
318
+ const retainedWorkspacePath = workspace?.status === "ready" && workspaceOutcome === undefined
319
+ ? workspace.path
320
+ : undefined;
321
+ if (!["qualified", "qualified-with-gaps"].includes(qualification.status)) {
322
+ return {
323
+ attempt,
324
+ manifest,
325
+ qualification,
326
+ capture,
327
+ ...(retainedWorkspacePath === undefined ? {} : { retainedWorkspacePath }),
328
+ };
329
+ }
330
+ try {
331
+ const normalized = await normalizeOpenHandsCapture(capture);
332
+ await validateUniformEvents(normalized.events, {
333
+ resolve: (reference) => resolvesCaptureReference(capture, reference),
334
+ });
335
+ return { attempt, manifest, qualification, capture, normalized };
336
+ }
337
+ catch (error) {
338
+ return {
339
+ attempt,
340
+ manifest,
341
+ qualification,
342
+ capture,
343
+ normalizationError: (error instanceof Error ? error.message : String(error)).slice(0, 4096),
344
+ };
345
+ }
346
+ }
347
+ function resolvesCaptureReference(capture, reference) {
348
+ return capture.records.some(({ reference: base }) => base.artifactId === reference.artifactId
349
+ && (reference.recordLocator === base.recordLocator || reference.recordLocator.startsWith(`${base.recordLocator}#`)));
350
+ }
351
+ function interruptedCapture(runId, attemptId, conversationId, records) {
352
+ const events = (channel) => records
353
+ .filter(({ record }) => record.channel === channel)
354
+ .map(({ record }) => record.payload);
355
+ const ids = (values) => [...new Set(values.flatMap(({ id }) => typeof id === "string" && id !== "" ? [id] : []))];
356
+ const streamedEventIds = ids(events("websocket-event"));
357
+ const finalEventIds = ids(events("rest-event"));
358
+ const streamedSet = new Set(streamedEventIds);
359
+ const finalSet = new Set(finalEventIds);
360
+ return {
361
+ runId,
362
+ attemptId,
363
+ qualification: "qualified-with-gaps",
364
+ records: structuredClone(records),
365
+ conversationId,
366
+ serverInfo: structuredClone(events("server-info")[0] ?? {}),
367
+ captureError: "OpenHands capture was aborted before returning.",
368
+ reconciliation: {
369
+ status: "partial",
370
+ streamedEventIds,
371
+ finalEventIds,
372
+ streamedOnlyEventIds: streamedEventIds.filter((id) => !finalSet.has(id)),
373
+ finalOnlyEventIds: finalEventIds.filter((id) => !streamedSet.has(id)),
374
+ transportGaps: ["capture-aborted"],
375
+ },
376
+ eventLogCompleteness: {
377
+ status: "unknown",
378
+ reason: "REST/WebSocket reconciliation cannot prove complete in-process EventLog delivery.",
379
+ },
380
+ };
381
+ }
382
+ function withPinnedRuntime(definition) {
383
+ const runtime = definition.run.runtime.filter(({ name }) => name !== "openhands-agent-server");
384
+ runtime.push({ source: "OpenHands", name: "openhands-agent-server", version: OPENHANDS_AGENT_SERVER_VERSION });
385
+ return {
386
+ ...structuredClone(definition),
387
+ run: {
388
+ ...structuredClone(definition.run),
389
+ harness: { ...structuredClone(definition.run.harness), version: OPENHANDS_AGENT_SERVER_VERSION },
390
+ runtime,
391
+ },
392
+ };
393
+ }
394
+ function openHandsRequestModel(startConversation) {
395
+ for (const candidate of [startConversation.agent, startConversation.agent_settings]) {
396
+ if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate))
397
+ continue;
398
+ const llm = candidate.llm;
399
+ if (typeof llm !== "object" || llm === null || Array.isArray(llm))
400
+ continue;
401
+ const model = llm.model;
402
+ if (typeof model === "string" && model !== "")
403
+ return model;
404
+ }
405
+ return undefined;
406
+ }
407
+ async function cleanupOpenHandsConversation(configuration, conversationId, timeoutMs) {
408
+ const headers = new Headers();
409
+ if (configuration.sessionApiKey !== undefined)
410
+ headers.set("x-session-api-key", configuration.sessionApiKey);
411
+ const response = await (configuration.fetch ?? globalThis.fetch)(new URL(`/api/conversations/${encodeURIComponent(conversationId)}`, configuration.baseUrl), { method: "DELETE", headers, signal: AbortSignal.timeout(Math.max(1, timeoutMs)), redirect: "manual" });
412
+ if (!response.ok && response.status !== 404) {
413
+ throw new Error(`OpenHands conversation cleanup failed with ${response.status}.`);
414
+ }
415
+ }
@@ -0,0 +1,130 @@
1
+ import type { AdapterCapabilityProfile, CapturedNativeRecord, HarnessAdapter, NormalizationInput, NormalizationResult, QualifiedNativeCapture } from "./uniform-events.js";
2
+ export declare const OPENHANDS_AGENT_SERVER_VERSION = "1.46.0";
3
+ export declare const OPENHANDS_TYPESCRIPT_CLIENT_VERSION = "1.39.0";
4
+ export declare const OPENHANDS_DEFAULT_MAX_RESPONSE_BYTES: number;
5
+ export declare const OPENHANDS_MAX_RESPONSE_BYTES: number;
6
+ export declare const OPENHANDS_DEFAULT_MAX_CAPTURE_BYTES: number;
7
+ export declare const OPENHANDS_MAX_CAPTURE_BYTES: number;
8
+ export declare const OPENHANDS_AGENT_SERVER_CAPABILITIES: {
9
+ readonly schemaVersion: "ebo.adapter-capability-profile/v1";
10
+ readonly adapterId: "openhands-agent-server-v1.46.0";
11
+ readonly harness: "openhands-agent-server";
12
+ readonly nativeTypes: readonly ["MessageEvent", "SystemPromptEvent", "ActionEvent", "ObservationEvent", "AgentErrorEvent", "UserRejectObservation", "CondensationRequest", "Condensation", "CondensationSummaryEvent", "ConversationErrorEvent", "ServerErrorEvent", "HookExecutionEvent", "ConversationStateUpdateEvent", "InterruptEvent", "PauseEvent", "conversation-final"];
13
+ readonly families: {
14
+ readonly message: {
15
+ readonly status: "available";
16
+ };
17
+ readonly "model-request": {
18
+ readonly status: "unsupported";
19
+ readonly detail: "The pinned public event boundary does not expose model requests.";
20
+ };
21
+ readonly tool: {
22
+ readonly status: "available";
23
+ };
24
+ readonly context: {
25
+ readonly status: "available";
26
+ };
27
+ readonly permission: {
28
+ readonly status: "unsupported";
29
+ readonly detail: "Hook blocking is retained natively but is not projected as a permission event.";
30
+ };
31
+ readonly delegation: {
32
+ readonly status: "unsupported";
33
+ readonly detail: "Nested delegate actions remain source-specific tool evidence.";
34
+ };
35
+ readonly artifact: {
36
+ readonly status: "unsupported";
37
+ readonly detail: "Workspace outcome is packaged outside the Agent Server event normalizer.";
38
+ };
39
+ readonly validation: {
40
+ readonly status: "unsupported";
41
+ readonly detail: "Verifier evidence is packaged outside the Agent Server event normalizer.";
42
+ };
43
+ readonly runtime: {
44
+ readonly status: "available";
45
+ };
46
+ readonly outcome: {
47
+ readonly status: "partial";
48
+ readonly detail: "Final conversation state is available when the run reaches the final REST read.";
49
+ };
50
+ };
51
+ readonly evidence: {
52
+ readonly nativeOrder: {
53
+ readonly status: "partial";
54
+ readonly detail: "REST page and WebSocket receipt order remain separate source domains.";
55
+ };
56
+ readonly nativeTime: {
57
+ readonly status: "partial";
58
+ readonly detail: "Event timestamps are available; control records may omit native time.";
59
+ };
60
+ readonly parentage: {
61
+ readonly status: "partial";
62
+ readonly detail: "Event parent_id and selected action associations are projected when their targets are captured.";
63
+ };
64
+ readonly content: {
65
+ readonly status: "partial";
66
+ readonly detail: "Supported content stays in native records and is referenced rather than copied.";
67
+ };
68
+ };
69
+ };
70
+ /** Retained evidence keeps the identity of the runtime that produced it. */
71
+ export declare function openHandsCapabilityProfile(version: string): AdapterCapabilityProfile;
72
+ export interface OpenHandsWebSocket {
73
+ readonly readyState: number;
74
+ addEventListener(type: string, listener: (event: {
75
+ data?: unknown;
76
+ }) => void): void;
77
+ removeEventListener(type: string, listener: (event: {
78
+ data?: unknown;
79
+ }) => void): void;
80
+ send(data: string): void;
81
+ close(): void;
82
+ }
83
+ export type OpenHandsNativeRecord = {
84
+ schemaVersion: "ebo.openhands-native-record/v1";
85
+ session_id?: string;
86
+ channel: "server-info" | "conversation-create-response" | "conversation-created" | "websocket-status" | "websocket-event" | "conversation-final" | "rest-event" | "capture-error" | "cleanup";
87
+ sequence: number;
88
+ channelSequence?: number;
89
+ payload: Record<string, unknown>;
90
+ };
91
+ export type OpenHandsCaptureRequest = {
92
+ runId: string;
93
+ attemptId: string;
94
+ baseUrl: string;
95
+ startConversation: Record<string, unknown>;
96
+ message: Record<string, unknown>;
97
+ sessionApiKey?: string;
98
+ fetch?: typeof globalThis.fetch;
99
+ webSocket?: (url: string) => OpenHandsWebSocket;
100
+ pollIntervalMs?: number;
101
+ timeoutMs?: number;
102
+ maxReconnects?: number;
103
+ reconnectDelayMs?: number;
104
+ maxResponseBytes?: number;
105
+ maxCaptureBytes?: number;
106
+ signal?: AbortSignal;
107
+ onRecord?: (record: CapturedNativeRecord<OpenHandsNativeRecord>) => void;
108
+ };
109
+ export type OpenHandsCapture = QualifiedNativeCapture<OpenHandsNativeRecord> & {
110
+ conversationId?: string;
111
+ serverInfo: Record<string, unknown>;
112
+ finalConversation?: Record<string, unknown>;
113
+ captureError?: string;
114
+ reconciliation: {
115
+ status: "matched" | "mismatch" | "partial";
116
+ streamedEventIds: string[];
117
+ finalEventIds: string[];
118
+ streamedOnlyEventIds: string[];
119
+ finalOnlyEventIds: string[];
120
+ transportGaps: string[];
121
+ finalReadError?: string;
122
+ };
123
+ eventLogCompleteness: {
124
+ status: "unknown";
125
+ reason: string;
126
+ };
127
+ };
128
+ export declare function createOpenHandsHarnessAdapter(): HarnessAdapter<OpenHandsCaptureRequest, OpenHandsNativeRecord>;
129
+ export declare function captureOpenHandsAgentServer(request: OpenHandsCaptureRequest): Promise<OpenHandsCapture>;
130
+ export declare function normalizeOpenHandsCapture(capture: NormalizationInput<OpenHandsNativeRecord>): Promise<NormalizationResult>;