agentv 4.42.4 → 4.43.0-next.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 (43) hide show
  1. package/README.md +2 -2
  2. package/dist/{artifact-writer-7PXSB6LM.js → artifact-writer-STR7E5UO.js} +4 -4
  3. package/dist/{chunk-RBOP5X36.js → chunk-5NDSGGMJ.js} +2605 -171
  4. package/dist/chunk-5NDSGGMJ.js.map +1 -0
  5. package/dist/{chunk-6SQDBXT7.js → chunk-E2NL57MZ.js} +2 -2
  6. package/dist/chunk-E2NL57MZ.js.map +1 -0
  7. package/dist/{chunk-ISPJY2AX.js → chunk-IFQXBQSY.js} +2454 -415
  8. package/dist/chunk-IFQXBQSY.js.map +1 -0
  9. package/dist/{chunk-ZPFM52HV.js → chunk-RFJM2UBD.js} +2321 -630
  10. package/dist/chunk-RFJM2UBD.js.map +1 -0
  11. package/dist/{chunk-N6U3GXMF.js → chunk-ROLNQBYU.js} +620 -342
  12. package/dist/chunk-ROLNQBYU.js.map +1 -0
  13. package/dist/cli.js +5 -5
  14. package/dist/dashboard/assets/index-1Hvy8sBy.js +119 -0
  15. package/dist/dashboard/assets/{index-uXJsSG83.js → index-DitffAxS.js} +1 -1
  16. package/dist/dashboard/assets/index-J5sPcQO3.css +1 -0
  17. package/dist/dashboard/index.html +2 -2
  18. package/dist/{dist-VQWTETNY.js → dist-FTTL5IOY.js} +67 -3
  19. package/dist/index.js +9 -5
  20. package/dist/{interactive-TNRJEFA2.js → interactive-RE4DXH4A.js} +5 -5
  21. package/dist/skills/agentv-bench/SKILL.md +2 -2
  22. package/dist/skills/agentv-bench/agents/executor.md +1 -1
  23. package/dist/skills/agentv-bench/agents/grader.md +1 -1
  24. package/dist/skills/agentv-bench/references/autoresearch.md +4 -4
  25. package/dist/skills/agentv-bench/references/eval-yaml-spec.md +2 -1
  26. package/dist/skills/agentv-bench/references/schemas.md +1 -1
  27. package/dist/skills/agentv-bench/references/subagent-pipeline.md +4 -4
  28. package/dist/skills/agentv-eval-writer/SKILL.md +23 -16
  29. package/dist/skills/agentv-eval-writer/references/config-schema.json +114 -0
  30. package/dist/skills/agentv-eval-writer/references/eval-schema.json +192 -0
  31. package/dist/{ts-eval-loader-PYEU2PMP-YLXIQTEQ.js → ts-eval-loader-HQDMXQQV-LBW3N3W6.js} +2 -2
  32. package/package.json +6 -5
  33. package/dist/chunk-6SQDBXT7.js.map +0 -1
  34. package/dist/chunk-ISPJY2AX.js.map +0 -1
  35. package/dist/chunk-N6U3GXMF.js.map +0 -1
  36. package/dist/chunk-RBOP5X36.js.map +0 -1
  37. package/dist/chunk-ZPFM52HV.js.map +0 -1
  38. package/dist/dashboard/assets/index-BwBJwHp4.js +0 -118
  39. package/dist/dashboard/assets/index-C5v6_zhR.css +0 -1
  40. /package/dist/{artifact-writer-7PXSB6LM.js.map → artifact-writer-STR7E5UO.js.map} +0 -0
  41. /package/dist/{dist-VQWTETNY.js.map → dist-FTTL5IOY.js.map} +0 -0
  42. /package/dist/{interactive-TNRJEFA2.js.map → interactive-RE4DXH4A.js.map} +0 -0
  43. /package/dist/{ts-eval-loader-PYEU2PMP-YLXIQTEQ.js.map → ts-eval-loader-HQDMXQQV-LBW3N3W6.js.map} +0 -0
@@ -1,6 +1,9 @@
1
1
  import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
2
2
  import {
3
+ AGENTV_RESULTS_ARTIFACTS_REF,
4
+ AGENTV_RESULTS_PRIMARY_REF,
3
5
  AGENT_PROVIDER_KINDS,
6
+ EXECUTION_TRACE_SCHEMA_VERSION,
4
7
  buildPromptInputs,
5
8
  external_exports,
6
9
  extractLastAssistantContent,
@@ -18,7 +21,7 @@ import {
18
21
  readTranscriptJsonl,
19
22
  releaseSharedWorkspaceSetup,
20
23
  resolveRepoCloneUrl
21
- } from "./chunk-ZPFM52HV.js";
24
+ } from "./chunk-RFJM2UBD.js";
22
25
 
23
26
  // ../../packages/core/dist/index.js
24
27
  import { readFileSync } from "node:fs";
@@ -29,6 +32,7 @@ import micromatch from "micromatch";
29
32
  import { readFile } from "node:fs/promises";
30
33
  import path3 from "node:path";
31
34
  import { execFile, spawn } from "node:child_process";
35
+ import { createHash } from "node:crypto";
32
36
  import {
33
37
  existsSync,
34
38
  mkdirSync,
@@ -51,6 +55,1429 @@ import path5 from "node:path";
51
55
  import { readdir as readdir3, stat as stat3 } from "node:fs/promises";
52
56
  import { homedir as homedir2 } from "node:os";
53
57
  import path6 from "node:path";
58
+ var TRACE_SESSION_SCHEMA_VERSION = "agentv.dashboard.trace_session.v1";
59
+ function isRecord(value) {
60
+ return typeof value === "object" && value !== null && !Array.isArray(value);
61
+ }
62
+ function asRecord(value) {
63
+ return isRecord(value) ? value : void 0;
64
+ }
65
+ function asArray(value) {
66
+ return Array.isArray(value) ? value : [];
67
+ }
68
+ function stringValue(value) {
69
+ return typeof value === "string" && value.length > 0 ? value : void 0;
70
+ }
71
+ function finiteNumber(value) {
72
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
73
+ }
74
+ function finiteInteger(value) {
75
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
76
+ }
77
+ function boolValue(value) {
78
+ return typeof value === "boolean" ? value : void 0;
79
+ }
80
+ function dropUndefined(value) {
81
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
82
+ }
83
+ function compactRecord(value) {
84
+ const compacted = dropUndefined(value);
85
+ return Object.keys(compacted).length > 0 ? compacted : void 0;
86
+ }
87
+ function nonEmptyArray(value) {
88
+ return value && value.length > 0 ? value : void 0;
89
+ }
90
+ function unixNanoToIso(value) {
91
+ if (!value) {
92
+ return void 0;
93
+ }
94
+ try {
95
+ return new Date(Number(BigInt(value) / 1000000n)).toISOString();
96
+ } catch {
97
+ return void 0;
98
+ }
99
+ }
100
+ function durationMsFromNanos(start, end) {
101
+ if (!start || !end) {
102
+ return void 0;
103
+ }
104
+ try {
105
+ const startNanos = BigInt(start);
106
+ const endNanos = BigInt(end);
107
+ if (endNanos < startNanos) {
108
+ return void 0;
109
+ }
110
+ return Number(endNanos - startNanos) / 1e6;
111
+ } catch {
112
+ return void 0;
113
+ }
114
+ }
115
+ function numberFromAttributes(attributes, keys) {
116
+ for (const key of keys) {
117
+ const value = finiteNumber(attributes[key]);
118
+ if (value !== void 0) {
119
+ return value;
120
+ }
121
+ }
122
+ return void 0;
123
+ }
124
+ function tokenUsageFromAttributes(attributes) {
125
+ if (!attributes) {
126
+ return void 0;
127
+ }
128
+ const nested = asRecord(attributes.token_usage);
129
+ const usage = compactRecord({
130
+ input: finiteNumber(nested?.input) ?? numberFromAttributes(attributes, [
131
+ "gen_ai.usage.input_tokens",
132
+ "llm.token_count.prompt",
133
+ "input_tokens"
134
+ ]),
135
+ output: finiteNumber(nested?.output) ?? numberFromAttributes(attributes, [
136
+ "gen_ai.usage.output_tokens",
137
+ "llm.token_count.completion",
138
+ "output_tokens"
139
+ ]),
140
+ reasoning: finiteNumber(nested?.reasoning) ?? numberFromAttributes(attributes, [
141
+ "gen_ai.usage.reasoning.output_tokens",
142
+ "reasoning_tokens"
143
+ ]),
144
+ cached: finiteNumber(nested?.cached) ?? numberFromAttributes(attributes, ["gen_ai.usage.cache_read.input_tokens", "cached_tokens"]),
145
+ total: finiteNumber(nested?.total) ?? numberFromAttributes(attributes, ["total_tokens"])
146
+ });
147
+ return usage;
148
+ }
149
+ function isExternalTraceKey(key) {
150
+ return key === "external_trace" || key.startsWith("external_trace_") || key.startsWith("external_trace.");
151
+ }
152
+ function isCredentialLikeKey(key) {
153
+ const normalized = key.toLowerCase();
154
+ if (normalized === "token_usage" || normalized.endsWith("_tokens") || normalized.endsWith(".tokens") || normalized.includes("usage.")) {
155
+ return false;
156
+ }
157
+ return /(^|[._-])(api[._-]?key|authorization|bearer|password|secret|private[._-]?key|access[._-]?token|auth[._-]?token|client[._-]?secret|id[._-]?token|refresh[._-]?token|session[._-]?token|token)($|[._-])/.test(
158
+ normalized
159
+ );
160
+ }
161
+ function sanitizeAttributeMap(value) {
162
+ if (!value) {
163
+ return void 0;
164
+ }
165
+ const entries = Object.entries(value).flatMap(([key, entry]) => {
166
+ if (isExternalTraceKey(key) || isCredentialLikeKey(key)) {
167
+ return [];
168
+ }
169
+ if (isRecord(entry)) {
170
+ const nested = sanitizeAttributeMap(entry);
171
+ return nested ? [[key, nested]] : [];
172
+ }
173
+ return [[key, entry]];
174
+ });
175
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
176
+ }
177
+ function spanStatusFromValue(value) {
178
+ const record = asRecord(value);
179
+ if (!record) {
180
+ return void 0;
181
+ }
182
+ return compactRecord({
183
+ code: stringValue(record.code) ?? (typeof record.code === "number" ? String(record.code) : void 0),
184
+ message: stringValue(record.message)
185
+ });
186
+ }
187
+ function eventKind(name, attributes) {
188
+ const lowerName = name.toLowerCase();
189
+ if (lowerName.includes("score") || numberFromAttributes(attributes ?? {}, [
190
+ "score",
191
+ "agentv.score",
192
+ "agentv.grader.score",
193
+ "gen_ai.evaluation.score.value",
194
+ "openinference.evaluation.score"
195
+ ]) !== void 0) {
196
+ return "score";
197
+ }
198
+ if (lowerName.includes("annotation") || stringValue(attributes?.text) !== void 0 || stringValue(attributes?.annotation) !== void 0 || stringValue(attributes?.["agentv.annotation.text"]) !== void 0) {
199
+ return "annotation";
200
+ }
201
+ if (lowerName === "exception") {
202
+ return "exception";
203
+ }
204
+ return "event";
205
+ }
206
+ function scoreFromEvent(attributes) {
207
+ if (!attributes) {
208
+ return void 0;
209
+ }
210
+ return finiteNumber(attributes.score) ?? finiteNumber(attributes["agentv.score"]) ?? finiteNumber(attributes["agentv.grader.score"]) ?? finiteNumber(attributes["gen_ai.evaluation.score.value"]) ?? finiteNumber(attributes["openinference.evaluation.score"]);
211
+ }
212
+ function textFromEvent(attributes) {
213
+ if (!attributes) {
214
+ return void 0;
215
+ }
216
+ return stringValue(attributes.text) ?? stringValue(attributes.annotation) ?? stringValue(attributes["agentv.annotation.text"]) ?? stringValue(attributes["gen_ai.evaluation.explanation"]) ?? stringValue(attributes["gen_ai.evaluation.score.label"]) ?? stringValue(attributes["exception.message"]);
217
+ }
218
+ function passedFromEvent(attributes) {
219
+ if (!attributes) {
220
+ return void 0;
221
+ }
222
+ return boolValue(attributes.passed) ?? boolValue(attributes["agentv.annotation.passed"]) ?? boolValue(attributes["agentv.grader.passed"]) ?? boolValue(attributes["gen_ai.evaluation.passed"]);
223
+ }
224
+ function eventId(spanId, index, attributes) {
225
+ return stringValue(attributes?.event_id) ?? stringValue(attributes?.["agentv.event_id"]) ?? `${spanId}:event:${index}`;
226
+ }
227
+ function projectSpanEvent(spanId, event, index) {
228
+ const record = asRecord(event);
229
+ if (!record) {
230
+ return void 0;
231
+ }
232
+ const name = stringValue(record.name);
233
+ if (!name) {
234
+ return void 0;
235
+ }
236
+ const attributes = asRecord(record.attributes);
237
+ const safeAttributes = sanitizeAttributeMap(attributes);
238
+ return dropUndefined({
239
+ event_id: eventId(spanId, index, attributes),
240
+ span_id: spanId,
241
+ name,
242
+ kind: eventKind(name, attributes),
243
+ time_unix_nano: stringValue(record.time_unix_nano),
244
+ timestamp: unixNanoToIso(stringValue(record.time_unix_nano)),
245
+ score: scoreFromEvent(attributes),
246
+ text: textFromEvent(attributes),
247
+ passed: passedFromEvent(attributes),
248
+ attributes: safeAttributes
249
+ });
250
+ }
251
+ function projectSpan(span, index) {
252
+ const record = asRecord(span);
253
+ if (!record) {
254
+ return void 0;
255
+ }
256
+ const spanId = stringValue(record.span_id) ?? `span-${index}`;
257
+ const traceId = stringValue(record.trace_id);
258
+ const parentSpanId = record.parent_span_id === null ? null : stringValue(record.parent_span_id);
259
+ const attributes = asRecord(record.attributes);
260
+ const safeAttributes = sanitizeAttributeMap(attributes);
261
+ const startTimeUnixNano = stringValue(record.start_time_unix_nano);
262
+ const endTimeUnixNano = stringValue(record.end_time_unix_nano);
263
+ const events = asArray(record.events).map((event, eventIndex) => projectSpanEvent(spanId, event, eventIndex)).filter((event) => event !== void 0);
264
+ return dropUndefined({
265
+ id: spanId,
266
+ trace_id: traceId,
267
+ span_id: spanId,
268
+ parent_span_id: parentSpanId,
269
+ name: stringValue(record.name) ?? spanId,
270
+ kind: stringValue(record.kind),
271
+ status: spanStatusFromValue(record.status),
272
+ start_time_unix_nano: startTimeUnixNano,
273
+ end_time_unix_nano: endTimeUnixNano,
274
+ start_time: unixNanoToIso(startTimeUnixNano),
275
+ end_time: unixNanoToIso(endTimeUnixNano),
276
+ duration_ms: durationMsFromNanos(startTimeUnixNano, endTimeUnixNano),
277
+ token_usage: tokenUsageFromAttributes(attributes),
278
+ resource_attributes: sanitizeAttributeMap(asRecord(record.resource_attributes)),
279
+ attributes: safeAttributes,
280
+ events: events.length > 0 ? events : void 0
281
+ });
282
+ }
283
+ function projectScores(scores) {
284
+ const projected = [];
285
+ for (const score of asArray(scores)) {
286
+ const record = asRecord(score);
287
+ const name = stringValue(record?.name);
288
+ const value = finiteNumber(record?.score);
289
+ if (!record || !name || value === void 0) {
290
+ continue;
291
+ }
292
+ projected.push(
293
+ dropUndefined({
294
+ name,
295
+ type: stringValue(record.type),
296
+ score: value,
297
+ weight: finiteNumber(record.weight),
298
+ verdict: stringValue(record.verdict),
299
+ source: stringValue(record.source),
300
+ evaluated_at: stringValue(record.evaluated_at),
301
+ target_span_id: stringValue(record.target_span_id),
302
+ evidence: asRecord(record.evidence)
303
+ })
304
+ );
305
+ }
306
+ return projected.length > 0 ? projected : void 0;
307
+ }
308
+ var EXTERNAL_TRACE_KEYS = [
309
+ "provider",
310
+ "source",
311
+ "endpoint",
312
+ "profile",
313
+ "project",
314
+ "project_id",
315
+ "session_id",
316
+ "session_node_id",
317
+ "trace_id",
318
+ "trace_node_id",
319
+ "span_id",
320
+ "span_node_id",
321
+ "traceparent",
322
+ "tracestate",
323
+ "ui_url",
324
+ "run_id",
325
+ "test_id",
326
+ "target"
327
+ ];
328
+ function sanitizeUrl(value) {
329
+ const raw = stringValue(value);
330
+ if (!raw) {
331
+ return void 0;
332
+ }
333
+ try {
334
+ const url = new URL(raw);
335
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) {
336
+ return void 0;
337
+ }
338
+ url.search = "";
339
+ url.hash = "";
340
+ return url.toString();
341
+ } catch {
342
+ return void 0;
343
+ }
344
+ }
345
+ function sanitizeExternalTrace(value) {
346
+ const record = asRecord(value);
347
+ if (!record) {
348
+ return void 0;
349
+ }
350
+ const sanitized = compactRecord({
351
+ provider: stringValue(record.provider),
352
+ source: stringValue(record.source),
353
+ endpoint: sanitizeUrl(record.endpoint),
354
+ profile: stringValue(record.profile),
355
+ project: stringValue(record.project),
356
+ project_id: stringValue(record.project_id) ?? stringValue(record.projectId),
357
+ session_id: stringValue(record.session_id) ?? stringValue(record.session),
358
+ session_node_id: stringValue(record.session_node_id) ?? stringValue(record.session_node) ?? stringValue(record.node_id),
359
+ trace_id: stringValue(record.trace_id) ?? stringValue(record.trace),
360
+ trace_node_id: stringValue(record.trace_node_id) ?? stringValue(record.trace_node),
361
+ span_id: stringValue(record.span_id) ?? stringValue(record.span),
362
+ span_node_id: stringValue(record.span_node_id) ?? stringValue(record.span_node),
363
+ traceparent: stringValue(record.traceparent),
364
+ tracestate: stringValue(record.tracestate),
365
+ ui_url: sanitizeUrl(record.ui_url ?? record.url ?? record.href),
366
+ run_id: stringValue(record.run_id),
367
+ test_id: stringValue(record.test_id),
368
+ target: stringValue(record.target)
369
+ });
370
+ return sanitized && EXTERNAL_TRACE_KEYS.some((key) => sanitized[key] !== void 0) ? sanitized : void 0;
371
+ }
372
+ function externalTraceFromFlatMetadata(metadata) {
373
+ if (!metadata) {
374
+ return void 0;
375
+ }
376
+ return sanitizeExternalTrace({
377
+ provider: metadata.external_trace_provider ?? metadata["external_trace.provider"],
378
+ source: metadata.external_trace_source ?? metadata["external_trace.source"],
379
+ endpoint: metadata.external_trace_endpoint ?? metadata["external_trace.endpoint"],
380
+ profile: metadata.external_trace_profile ?? metadata["external_trace.profile"],
381
+ project: metadata.external_trace_project ?? metadata["external_trace.project"],
382
+ project_id: metadata.external_trace_project_id ?? metadata["external_trace.project_id"],
383
+ session_id: metadata.external_trace_session_id ?? metadata.external_trace_session ?? metadata["external_trace.session_id"] ?? metadata["external_trace.session"],
384
+ session_node_id: metadata.external_trace_session_node_id ?? metadata.external_trace_node_id ?? metadata["external_trace.session_node_id"] ?? metadata["external_trace.node_id"],
385
+ trace_id: metadata.external_trace_trace_id ?? metadata.external_trace_trace ?? metadata["external_trace.trace_id"] ?? metadata["external_trace.trace"],
386
+ trace_node_id: metadata.external_trace_trace_node_id ?? metadata["external_trace.trace_node_id"],
387
+ span_id: metadata.external_trace_span_id ?? metadata.external_trace_span ?? metadata["external_trace.span_id"] ?? metadata["external_trace.span"],
388
+ span_node_id: metadata.external_trace_span_node_id ?? metadata["external_trace.span_node_id"],
389
+ traceparent: metadata.external_trace_traceparent ?? metadata["external_trace.traceparent"],
390
+ tracestate: metadata.external_trace_tracestate ?? metadata["external_trace.tracestate"],
391
+ ui_url: metadata.external_trace_ui_url ?? metadata.external_trace_url ?? metadata["external_trace.ui_url"] ?? metadata["external_trace.url"],
392
+ run_id: metadata.external_trace_run_id ?? metadata["external_trace.run_id"],
393
+ test_id: metadata.external_trace_test_id ?? metadata["external_trace.test_id"],
394
+ target: metadata.external_trace_target ?? metadata["external_trace.target"]
395
+ });
396
+ }
397
+ function sanitizeMetadataValue(value) {
398
+ if (Array.isArray(value)) {
399
+ const sanitized = value.map(sanitizeMetadataValue).filter((entry) => entry !== void 0);
400
+ return sanitized.length > 0 ? sanitized : void 0;
401
+ }
402
+ if (isRecord(value)) {
403
+ return sanitizeMetadata(value);
404
+ }
405
+ return value;
406
+ }
407
+ function sanitizeMetadata(value) {
408
+ if (!value) {
409
+ return void 0;
410
+ }
411
+ const entries = Object.entries(value).flatMap(([key, entry]) => {
412
+ if (isExternalTraceKey(key) || isCredentialLikeKey(key)) {
413
+ return [];
414
+ }
415
+ const sanitized = sanitizeMetadataValue(entry);
416
+ return sanitized !== void 0 ? [[key, sanitized]] : [];
417
+ });
418
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
419
+ }
420
+ function sourceFromEnvelope(source, artifactPath) {
421
+ if (!source && !artifactPath) {
422
+ return void 0;
423
+ }
424
+ return compactRecord({
425
+ kind: stringValue(source?.kind),
426
+ path: stringValue(source?.path),
427
+ provider: stringValue(source?.provider),
428
+ format: stringValue(source?.format),
429
+ version: stringValue(source?.version),
430
+ artifact_path: artifactPath,
431
+ metadata: sanitizeMetadata(asRecord(source?.metadata))
432
+ });
433
+ }
434
+ function safeArtifactPath(value) {
435
+ const raw = stringValue(value);
436
+ if (!raw || raw.includes("\0")) {
437
+ return void 0;
438
+ }
439
+ const normalized = raw.replace(/\\/g, "/");
440
+ if (normalized.startsWith("/") || normalized.startsWith("//")) {
441
+ return void 0;
442
+ }
443
+ if (/^[a-z][a-z0-9+.-]*:/i.test(normalized)) {
444
+ return void 0;
445
+ }
446
+ if (normalized.split("/").includes("..")) {
447
+ return void 0;
448
+ }
449
+ return normalized;
450
+ }
451
+ function projectArtifactLinks(artifacts) {
452
+ const record = asRecord(artifacts);
453
+ if (!record) {
454
+ return void 0;
455
+ }
456
+ const links = Object.entries(record).flatMap(([name, value]) => {
457
+ if (!stringValue(name) || isCredentialLikeKey(name)) {
458
+ return [];
459
+ }
460
+ const artifactPath = safeArtifactPath(value);
461
+ return artifactPath ? [{ name, path: artifactPath }] : [];
462
+ }).sort((first, second) => first.name.localeCompare(second.name));
463
+ return links.length > 0 ? links : void 0;
464
+ }
465
+ function projectSourceRef(value) {
466
+ const record = asRecord(value);
467
+ if (!record) {
468
+ return void 0;
469
+ }
470
+ return compactRecord({
471
+ event_id: stringValue(record.event_id),
472
+ message_id: stringValue(record.message_id),
473
+ span_id: stringValue(record.span_id),
474
+ trace_id: stringValue(record.trace_id),
475
+ raw_kind: stringValue(record.raw_kind),
476
+ path: safeArtifactPath(record.path),
477
+ line: finiteInteger(record.line),
478
+ metadata: sanitizeMetadata(asRecord(record.metadata))
479
+ });
480
+ }
481
+ function projectConversionWarnings(warnings) {
482
+ const projected = [];
483
+ for (const warning of asArray(warnings)) {
484
+ const record = asRecord(warning);
485
+ const code = stringValue(record?.code);
486
+ const message = stringValue(record?.message);
487
+ if (!record || !code || !message) {
488
+ continue;
489
+ }
490
+ projected.push(
491
+ dropUndefined({
492
+ code,
493
+ severity: stringValue(record.severity),
494
+ span_id: stringValue(record.span_id),
495
+ source_ref: projectSourceRef(record.source_ref),
496
+ message,
497
+ details: sanitizeMetadata(asRecord(record.details))
498
+ })
499
+ );
500
+ }
501
+ return projected.length > 0 ? projected : void 0;
502
+ }
503
+ function externalTraceFromEnvelope(envelope) {
504
+ const source = asRecord(envelope.source);
505
+ const sourceMetadata = asRecord(source?.metadata);
506
+ const trace = asRecord(envelope.trace);
507
+ const rootSpanId = stringValue(trace?.root_span_id);
508
+ const rootSpan = asArray(trace?.spans).map(asRecord).find((span) => stringValue(span?.span_id) === rootSpanId);
509
+ const rootAttributes = asRecord(rootSpan?.attributes);
510
+ return sanitizeExternalTrace(envelope.external_trace) ?? sanitizeExternalTrace(sourceMetadata?.external_trace) ?? externalTraceFromFlatMetadata(sourceMetadata) ?? externalTraceFromFlatMetadata(rootAttributes);
511
+ }
512
+ function traceEnvelopeToTraceSessionResponse(input, options = {}) {
513
+ const envelope = asRecord(input) ?? {};
514
+ const evaluation = asRecord(envelope.eval);
515
+ const trace = asRecord(envelope.trace);
516
+ const traceResource = asRecord(trace?.resource);
517
+ const spans = asArray(trace?.spans).map(projectSpan).filter((span) => span !== void 0);
518
+ const events = spans.flatMap((span) => span.events ?? []);
519
+ return dropUndefined({
520
+ schema_version: TRACE_SESSION_SCHEMA_VERSION,
521
+ artifact_id: stringValue(envelope.artifact_id),
522
+ created_at: stringValue(envelope.created_at),
523
+ run_id: options.runId ?? stringValue(evaluation?.run_id),
524
+ test_id: stringValue(evaluation?.test_id),
525
+ suite: stringValue(evaluation?.suite),
526
+ target: stringValue(evaluation?.target),
527
+ trace_id: stringValue(trace?.trace_id),
528
+ root_span_id: stringValue(trace?.root_span_id),
529
+ source: sourceFromEnvelope(asRecord(envelope.source), options.artifactPath),
530
+ external_trace: externalTraceFromEnvelope(envelope),
531
+ artifact_links: projectArtifactLinks(envelope.artifacts),
532
+ resource_attributes: sanitizeAttributeMap(asRecord(traceResource?.attributes)),
533
+ conversion_warnings: projectConversionWarnings(envelope.conversion_warnings),
534
+ spans,
535
+ events,
536
+ scores: projectScores(envelope.scores)
537
+ });
538
+ }
539
+ function buildTraceSpanTree(spans) {
540
+ const nodes = [];
541
+ const firstNodeBySpanId = /* @__PURE__ */ new Map();
542
+ const spanIdCounts = /* @__PURE__ */ new Map();
543
+ spans.forEach((span, index) => {
544
+ const rawSpanId = stringValue(span.span_id);
545
+ const spanId = rawSpanId ?? `missing-span-${index}`;
546
+ const occurrence = (spanIdCounts.get(spanId) ?? 0) + 1;
547
+ spanIdCounts.set(spanId, occurrence);
548
+ const node = {
549
+ id: occurrence === 1 ? spanId : `${spanId}#${occurrence}`,
550
+ spanId,
551
+ parentSpanId: span.parent_span_id,
552
+ span,
553
+ children: [],
554
+ diagnostics: rawSpanId ? void 0 : [
555
+ {
556
+ code: "missing_span_id",
557
+ message: "Span was missing span_id and was assigned a stable node id.",
558
+ node_id: spanId
559
+ }
560
+ ]
561
+ };
562
+ if (occurrence > 1) {
563
+ addNodeDiagnostic(node, {
564
+ code: "duplicate_span_id",
565
+ message: "Duplicate span_id was preserved with a collision-free node id.",
566
+ span_id: spanId,
567
+ node_id: node.id
568
+ });
569
+ }
570
+ if (!firstNodeBySpanId.has(spanId)) {
571
+ firstNodeBySpanId.set(spanId, node);
572
+ }
573
+ nodes.push(node);
574
+ });
575
+ const parentByNodeId = /* @__PURE__ */ new Map();
576
+ for (const node of nodes) {
577
+ const parentSpanId = typeof node.parentSpanId === "string" && node.parentSpanId.length > 0 ? node.parentSpanId : void 0;
578
+ if (!parentSpanId) {
579
+ continue;
580
+ }
581
+ if (parentSpanId === node.spanId) {
582
+ addNodeDiagnostic(node, {
583
+ code: "self_parent",
584
+ message: "Span parent_span_id points to itself; span was promoted to a root.",
585
+ span_id: node.spanId,
586
+ node_id: node.id,
587
+ parent_span_id: parentSpanId
588
+ });
589
+ continue;
590
+ }
591
+ const parent = firstNodeBySpanId.get(parentSpanId);
592
+ if (!parent) {
593
+ addNodeDiagnostic(node, {
594
+ code: "missing_parent",
595
+ message: "Span parent_span_id was not present in this trace; span was promoted to a root.",
596
+ span_id: node.spanId,
597
+ node_id: node.id,
598
+ parent_span_id: parentSpanId
599
+ });
600
+ continue;
601
+ }
602
+ parentByNodeId.set(node.id, parent);
603
+ }
604
+ const cyclicNodes = [];
605
+ for (const node of nodes) {
606
+ if (hasAncestorCycle(node, parentByNodeId)) {
607
+ cyclicNodes.push(node);
608
+ }
609
+ }
610
+ for (const node of cyclicNodes) {
611
+ parentByNodeId.delete(node.id);
612
+ addNodeDiagnostic(node, {
613
+ code: "cycle",
614
+ message: "Span parent chain contains a cycle; span was promoted to a root.",
615
+ span_id: node.spanId,
616
+ node_id: node.id,
617
+ parent_span_id: typeof node.parentSpanId === "string" ? node.parentSpanId : void 0
618
+ });
619
+ }
620
+ const roots = [];
621
+ for (const node of nodes) {
622
+ const parent = parentByNodeId.get(node.id);
623
+ if (parent) {
624
+ parent.children.push(node);
625
+ } else {
626
+ roots.push(node);
627
+ }
628
+ }
629
+ sortTraceSpanNodes(roots);
630
+ return roots;
631
+ }
632
+ function addNodeDiagnostic(node, diagnostic) {
633
+ node.diagnostics = [...node.diagnostics ?? [], diagnostic];
634
+ }
635
+ function hasAncestorCycle(node, parentByNodeId) {
636
+ const seen = /* @__PURE__ */ new Set();
637
+ let cursor = parentByNodeId.get(node.id);
638
+ while (cursor) {
639
+ if (cursor.id === node.id || seen.has(cursor.id)) {
640
+ return true;
641
+ }
642
+ seen.add(cursor.id);
643
+ cursor = parentByNodeId.get(cursor.id);
644
+ }
645
+ return false;
646
+ }
647
+ function compareUnixNanoValue(first, second) {
648
+ if (first === second) {
649
+ return 0;
650
+ }
651
+ if (!first) {
652
+ return 1;
653
+ }
654
+ if (!second) {
655
+ return -1;
656
+ }
657
+ try {
658
+ const firstValue = BigInt(first);
659
+ const secondValue = BigInt(second);
660
+ return firstValue < secondValue ? -1 : firstValue > secondValue ? 1 : 0;
661
+ } catch {
662
+ return first.localeCompare(second);
663
+ }
664
+ }
665
+ function compareTraceSpanNodes(first, second) {
666
+ const byStart = compareUnixNanoValue(
667
+ first.span.start_time_unix_nano,
668
+ second.span.start_time_unix_nano
669
+ );
670
+ if (byStart !== 0) {
671
+ return byStart;
672
+ }
673
+ if (first.spanId === second.parentSpanId) {
674
+ return -1;
675
+ }
676
+ if (second.spanId === first.parentSpanId) {
677
+ return 1;
678
+ }
679
+ const bySpanId = first.spanId.localeCompare(second.spanId);
680
+ return bySpanId !== 0 ? bySpanId : first.id.localeCompare(second.id);
681
+ }
682
+ function sortTraceSpanNodes(nodes) {
683
+ nodes.sort(compareTraceSpanNodes);
684
+ for (const node of nodes) {
685
+ node.children.sort(compareTraceSpanNodes);
686
+ if (node.children.length > 0) {
687
+ sortTraceSpanNodes(node.children);
688
+ }
689
+ node.diagnostics = nonEmptyArray(node.diagnostics);
690
+ }
691
+ }
692
+ var TRACE_ENVELOPE_FORMAT = "otlp_openinference_spans";
693
+ var OTLP_ROOT_FIELDS = /* @__PURE__ */ new Set(["resourceSpans"]);
694
+ var OTLP_RESOURCE_SPAN_FIELDS = /* @__PURE__ */ new Set(["resource", "scopeSpans", "schemaUrl"]);
695
+ var OTLP_RESOURCE_FIELDS = /* @__PURE__ */ new Set(["attributes", "droppedAttributesCount"]);
696
+ var OTLP_SCOPE_SPAN_FIELDS = /* @__PURE__ */ new Set(["scope", "spans", "schemaUrl"]);
697
+ var OTLP_SCOPE_FIELDS = /* @__PURE__ */ new Set(["name", "version", "attributes", "droppedAttributesCount"]);
698
+ var OTLP_SPAN_FIELDS = /* @__PURE__ */ new Set([
699
+ "traceId",
700
+ "spanId",
701
+ "parentSpanId",
702
+ "traceState",
703
+ "name",
704
+ "kind",
705
+ "startTimeUnixNano",
706
+ "endTimeUnixNano",
707
+ "attributes",
708
+ "droppedAttributesCount",
709
+ "events",
710
+ "droppedEventsCount",
711
+ "links",
712
+ "droppedLinksCount",
713
+ "status"
714
+ ]);
715
+ var OTLP_EVENT_FIELDS = /* @__PURE__ */ new Set(["timeUnixNano", "name", "attributes", "droppedAttributesCount"]);
716
+ var OTLP_STATUS_FIELDS = /* @__PURE__ */ new Set(["code", "message"]);
717
+ var OTLP_ATTRIBUTE_FIELDS = /* @__PURE__ */ new Set(["key", "value"]);
718
+ var OTLP_ANY_VALUE_FIELDS = /* @__PURE__ */ new Set([
719
+ "stringValue",
720
+ "boolValue",
721
+ "intValue",
722
+ "doubleValue",
723
+ "arrayValue",
724
+ "kvlistValue",
725
+ "bytesValue"
726
+ ]);
727
+ function normalizeTraceArtifactToTraceSessionResponse(input, options = {}) {
728
+ const artifact = asRecord2(input);
729
+ if (!artifact) {
730
+ return {
731
+ status: "unsupported",
732
+ message: "Trace artifact is not a JSON object."
733
+ };
734
+ }
735
+ if (stringValue2(artifact.schema_version) === EXECUTION_TRACE_SCHEMA_VERSION) {
736
+ return normalizeAgentVTraceEnvelope(artifact, options);
737
+ }
738
+ if (Object.hasOwn(artifact, "resourceSpans")) {
739
+ return normalizeOtlpJson(artifact, options);
740
+ }
741
+ return {
742
+ status: "unsupported",
743
+ message: "Trace artifact is not an agentv.trace.v1 envelope or OTLP JSON resourceSpans body."
744
+ };
745
+ }
746
+ function normalizeAgentVTraceEnvelope(artifact, options) {
747
+ const trace = asRecord2(artifact.trace);
748
+ if (!Array.isArray(trace?.spans)) {
749
+ return {
750
+ status: "unsupported",
751
+ message: "Trace artifact is not an agentv.trace.v1 envelope with trace.spans."
752
+ };
753
+ }
754
+ const traceSession = traceEnvelopeToTraceSessionResponse(artifact, options);
755
+ return {
756
+ status: "ok",
757
+ format: "agentv_trace_v1",
758
+ traceSession,
759
+ warnings: traceSession.conversion_warnings
760
+ };
761
+ }
762
+ function normalizeOtlpJson(artifact, options) {
763
+ const warnings = [];
764
+ warnUnknownFields(artifact, OTLP_ROOT_FIELDS, warnings, {
765
+ rawKind: "otlp_json",
766
+ path: options.artifactPath
767
+ });
768
+ const resourceSpans = Array.isArray(artifact.resourceSpans) ? artifact.resourceSpans : [];
769
+ if (!Array.isArray(artifact.resourceSpans)) {
770
+ addWarning(warnings, "malformed_otlp_resource_spans", "OTLP resourceSpans must be an array.", {
771
+ rawKind: "otlp_json",
772
+ path: options.artifactPath
773
+ });
774
+ }
775
+ const spans = [];
776
+ const resourceAttributeSets = [];
777
+ let scopeSpanCount = 0;
778
+ let firstScope;
779
+ resourceSpans.forEach((resourceSpan, resourceSpanIndex) => {
780
+ const resourceRecord = asRecord2(resourceSpan);
781
+ if (!resourceRecord) {
782
+ addWarning(
783
+ warnings,
784
+ "malformed_otlp_resource_span",
785
+ "OTLP resourceSpans entry must be an object and was skipped.",
786
+ {
787
+ rawKind: "otlp_resource_span",
788
+ path: options.artifactPath,
789
+ details: { resource_span_index: resourceSpanIndex }
790
+ }
791
+ );
792
+ return;
793
+ }
794
+ warnUnknownFields(resourceRecord, OTLP_RESOURCE_SPAN_FIELDS, warnings, {
795
+ rawKind: "otlp_resource_span",
796
+ path: options.artifactPath,
797
+ details: { resource_span_index: resourceSpanIndex }
798
+ });
799
+ const resource = asRecord2(resourceRecord.resource);
800
+ if (resource) {
801
+ warnUnknownFields(resource, OTLP_RESOURCE_FIELDS, warnings, {
802
+ rawKind: "otlp_resource",
803
+ path: options.artifactPath,
804
+ details: { resource_span_index: resourceSpanIndex }
805
+ });
806
+ }
807
+ const resourceAttributes2 = parseOtlpAttributes(resource?.attributes, warnings, {
808
+ rawKind: "otlp_resource_attributes",
809
+ path: options.artifactPath,
810
+ details: { resource_span_index: resourceSpanIndex }
811
+ });
812
+ if (resourceAttributes2 && Object.keys(resourceAttributes2).length > 0) {
813
+ resourceAttributeSets.push(resourceAttributes2);
814
+ }
815
+ const scopeSpans = Array.isArray(resourceRecord.scopeSpans) ? resourceRecord.scopeSpans : [];
816
+ if (!Array.isArray(resourceRecord.scopeSpans)) {
817
+ addWarning(
818
+ warnings,
819
+ "malformed_otlp_scope_spans",
820
+ "OTLP resourceSpans.scopeSpans must be an array and was skipped.",
821
+ {
822
+ rawKind: "otlp_resource_span",
823
+ path: options.artifactPath,
824
+ details: { resource_span_index: resourceSpanIndex }
825
+ }
826
+ );
827
+ }
828
+ scopeSpans.forEach((scopeSpan, scopeSpanIndex) => {
829
+ const scopeSpanRecord = asRecord2(scopeSpan);
830
+ if (!scopeSpanRecord) {
831
+ addWarning(
832
+ warnings,
833
+ "malformed_otlp_scope_span",
834
+ "OTLP scopeSpans entry must be an object and was skipped.",
835
+ {
836
+ rawKind: "otlp_scope_span",
837
+ path: options.artifactPath,
838
+ details: { resource_span_index: resourceSpanIndex, scope_span_index: scopeSpanIndex }
839
+ }
840
+ );
841
+ return;
842
+ }
843
+ scopeSpanCount += 1;
844
+ warnUnknownFields(scopeSpanRecord, OTLP_SCOPE_SPAN_FIELDS, warnings, {
845
+ rawKind: "otlp_scope_span",
846
+ path: options.artifactPath,
847
+ details: { resource_span_index: resourceSpanIndex, scope_span_index: scopeSpanIndex }
848
+ });
849
+ const scope = normalizeOtlpScope(scopeSpanRecord.scope, warnings, {
850
+ rawKind: "otlp_scope",
851
+ path: options.artifactPath,
852
+ details: { resource_span_index: resourceSpanIndex, scope_span_index: scopeSpanIndex }
853
+ });
854
+ firstScope ??= scope;
855
+ const otlpSpans = Array.isArray(scopeSpanRecord.spans) ? scopeSpanRecord.spans : [];
856
+ if (!Array.isArray(scopeSpanRecord.spans)) {
857
+ addWarning(
858
+ warnings,
859
+ "malformed_otlp_spans",
860
+ "OTLP scopeSpans.spans must be an array and was skipped.",
861
+ {
862
+ rawKind: "otlp_scope_span",
863
+ path: options.artifactPath,
864
+ details: { resource_span_index: resourceSpanIndex, scope_span_index: scopeSpanIndex }
865
+ }
866
+ );
867
+ }
868
+ otlpSpans.forEach((span, spanIndex) => {
869
+ const normalized = normalizeOtlpSpan(span, warnings, {
870
+ resourceAttributes: resourceAttributes2,
871
+ path: options.artifactPath,
872
+ resourceSpanIndex,
873
+ scopeSpanIndex,
874
+ spanIndex
875
+ });
876
+ if (normalized) {
877
+ spans.push(normalized);
878
+ }
879
+ });
880
+ });
881
+ });
882
+ if (spans.length === 0) {
883
+ addWarning(warnings, "empty_otlp_trace", "OTLP JSON did not contain any readable spans.", {
884
+ rawKind: "otlp_json",
885
+ path: options.artifactPath
886
+ });
887
+ }
888
+ const traceId = firstString(spans.map((span) => span.trace_id));
889
+ const rootSpanId = firstRootSpanId(spans);
890
+ const resourceAttributes = singleDistinctRecord(resourceAttributeSets);
891
+ const createdAt = (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
892
+ const artifactPath = options.artifactPath;
893
+ const envelope = dropUndefined2({
894
+ schema_version: EXECUTION_TRACE_SCHEMA_VERSION,
895
+ artifact_id: `otlp-trace-${traceId ?? rootSpanId ?? "unknown"}`,
896
+ created_at: createdAt,
897
+ eval: dropUndefined2({
898
+ run_id: options.runId,
899
+ test_id: options.testId ?? traceId ?? "otlp-trace",
900
+ suite: options.suite,
901
+ target: options.target ?? "unknown"
902
+ }),
903
+ trace: dropUndefined2({
904
+ format: TRACE_ENVELOPE_FORMAT,
905
+ trace_id: traceId,
906
+ root_span_id: rootSpanId,
907
+ resource: resourceAttributes ? { attributes: resourceAttributes } : void 0,
908
+ scope: firstScope,
909
+ spans
910
+ }),
911
+ source: dropUndefined2({
912
+ kind: "otlp",
913
+ path: artifactPath,
914
+ format: "otlp_json",
915
+ version: "1",
916
+ metadata: dropUndefined2({
917
+ resource_spans_count: resourceSpans.length,
918
+ scope_spans_count: scopeSpanCount
919
+ })
920
+ }),
921
+ capture: {
922
+ content: "metadata",
923
+ redaction_level: "partial"
924
+ },
925
+ conversion_warnings: warnings.length > 0 ? warnings : void 0,
926
+ artifacts: artifactPath ? { raw_trace_path: artifactPath } : void 0
927
+ });
928
+ const traceSession = traceEnvelopeToTraceSessionResponse(envelope, options);
929
+ return {
930
+ status: "ok",
931
+ format: "otlp_json",
932
+ traceSession,
933
+ warnings: traceSession.conversion_warnings
934
+ };
935
+ }
936
+ function normalizeOtlpScope(value, warnings, context) {
937
+ if (value === void 0) {
938
+ return void 0;
939
+ }
940
+ const scope = asRecord2(value);
941
+ if (!scope) {
942
+ addWarning(warnings, "malformed_otlp_scope", "OTLP scope must be an object and was ignored.", {
943
+ ...context,
944
+ rawKind: context.rawKind ?? "otlp_scope"
945
+ });
946
+ return void 0;
947
+ }
948
+ warnUnknownFields(scope, OTLP_SCOPE_FIELDS, warnings, context);
949
+ const normalized = dropUndefined2({
950
+ name: stringValue2(scope.name),
951
+ version: stringValue2(scope.version)
952
+ });
953
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
954
+ }
955
+ function normalizeOtlpSpan(value, warnings, context) {
956
+ const span = asRecord2(value);
957
+ const details = {
958
+ resource_span_index: context.resourceSpanIndex,
959
+ scope_span_index: context.scopeSpanIndex,
960
+ span_index: context.spanIndex
961
+ };
962
+ if (!span) {
963
+ addWarning(warnings, "malformed_otlp_span", "OTLP span must be an object and was skipped.", {
964
+ rawKind: "otlp_span",
965
+ path: context.path,
966
+ details
967
+ });
968
+ return void 0;
969
+ }
970
+ warnUnknownFields(span, OTLP_SPAN_FIELDS, warnings, {
971
+ rawKind: "otlp_span",
972
+ path: context.path,
973
+ details
974
+ });
975
+ const spanId = stringValue2(span.spanId) ?? `missing-span-${context.resourceSpanIndex}-${context.scopeSpanIndex}-${context.spanIndex}`;
976
+ if (!stringValue2(span.spanId)) {
977
+ addWarning(
978
+ warnings,
979
+ "missing_span_id",
980
+ "OTLP span was missing spanId; a stable ID was assigned.",
981
+ {
982
+ rawKind: "otlp_span",
983
+ path: context.path,
984
+ spanId,
985
+ details
986
+ }
987
+ );
988
+ }
989
+ const traceId = stringValue2(span.traceId);
990
+ if (!traceId) {
991
+ addWarning(warnings, "missing_trace_id", "OTLP span was missing traceId.", {
992
+ rawKind: "otlp_span",
993
+ path: context.path,
994
+ spanId,
995
+ details
996
+ });
997
+ }
998
+ const name = stringValue2(span.name) ?? spanId;
999
+ if (!stringValue2(span.name)) {
1000
+ addWarning(warnings, "missing_span_name", "OTLP span was missing name; spanId was used.", {
1001
+ rawKind: "otlp_span",
1002
+ path: context.path,
1003
+ spanId,
1004
+ details
1005
+ });
1006
+ }
1007
+ const parentSpanId = span.parentSpanId === void 0 || span.parentSpanId === null || span.parentSpanId === "" ? null : stringValue2(span.parentSpanId);
1008
+ if (span.parentSpanId !== void 0 && span.parentSpanId !== null && span.parentSpanId !== "" && !parentSpanId) {
1009
+ addWarning(
1010
+ warnings,
1011
+ "malformed_parent_span_id",
1012
+ "OTLP span parentSpanId was not a string and was ignored.",
1013
+ {
1014
+ rawKind: "otlp_span",
1015
+ path: context.path,
1016
+ spanId,
1017
+ details
1018
+ }
1019
+ );
1020
+ }
1021
+ const attributes = parseOtlpAttributes(span.attributes, warnings, {
1022
+ rawKind: "otlp_span_attributes",
1023
+ path: context.path,
1024
+ spanId,
1025
+ details
1026
+ });
1027
+ const events = normalizeOtlpEvents(span.events, warnings, {
1028
+ path: context.path,
1029
+ spanId,
1030
+ details
1031
+ });
1032
+ return dropUndefined2({
1033
+ trace_id: traceId,
1034
+ span_id: spanId,
1035
+ parent_span_id: parentSpanId,
1036
+ name,
1037
+ kind: normalizeOtlpSpanKind(span.kind, warnings, {
1038
+ rawKind: "otlp_span",
1039
+ path: context.path,
1040
+ spanId,
1041
+ details
1042
+ }),
1043
+ start_time_unix_nano: normalizeUnixNano(span.startTimeUnixNano, "startTimeUnixNano", warnings, {
1044
+ rawKind: "otlp_span",
1045
+ path: context.path,
1046
+ spanId,
1047
+ details
1048
+ }),
1049
+ end_time_unix_nano: normalizeUnixNano(span.endTimeUnixNano, "endTimeUnixNano", warnings, {
1050
+ rawKind: "otlp_span",
1051
+ path: context.path,
1052
+ spanId,
1053
+ details
1054
+ }),
1055
+ status: normalizeOtlpStatus(span.status, warnings, {
1056
+ rawKind: "otlp_span_status",
1057
+ path: context.path,
1058
+ spanId,
1059
+ details
1060
+ }),
1061
+ resource_attributes: context.resourceAttributes,
1062
+ attributes,
1063
+ events
1064
+ });
1065
+ }
1066
+ function normalizeOtlpEvents(value, warnings, context) {
1067
+ if (value === void 0) {
1068
+ return void 0;
1069
+ }
1070
+ if (!Array.isArray(value)) {
1071
+ addWarning(warnings, "malformed_otlp_events", "OTLP span events must be an array.", {
1072
+ rawKind: "otlp_span_events",
1073
+ path: context.path,
1074
+ spanId: context.spanId,
1075
+ details: context.details
1076
+ });
1077
+ return void 0;
1078
+ }
1079
+ const events = value.flatMap((event, eventIndex) => {
1080
+ const eventRecord = asRecord2(event);
1081
+ const details = { ...context.details, event_index: eventIndex };
1082
+ if (!eventRecord) {
1083
+ addWarning(
1084
+ warnings,
1085
+ "malformed_otlp_event",
1086
+ "OTLP span event must be an object and was skipped.",
1087
+ {
1088
+ rawKind: "otlp_span_event",
1089
+ path: context.path,
1090
+ spanId: context.spanId,
1091
+ details
1092
+ }
1093
+ );
1094
+ return [];
1095
+ }
1096
+ warnUnknownFields(eventRecord, OTLP_EVENT_FIELDS, warnings, {
1097
+ rawKind: "otlp_span_event",
1098
+ path: context.path,
1099
+ spanId: context.spanId,
1100
+ details
1101
+ });
1102
+ const name = stringValue2(eventRecord.name) ?? `event-${eventIndex}`;
1103
+ if (!stringValue2(eventRecord.name)) {
1104
+ addWarning(
1105
+ warnings,
1106
+ "missing_event_name",
1107
+ "OTLP span event was missing name; a stable name was assigned.",
1108
+ {
1109
+ rawKind: "otlp_span_event",
1110
+ path: context.path,
1111
+ spanId: context.spanId,
1112
+ details
1113
+ }
1114
+ );
1115
+ }
1116
+ return [
1117
+ dropUndefined2({
1118
+ name,
1119
+ time_unix_nano: normalizeUnixNano(eventRecord.timeUnixNano, "timeUnixNano", warnings, {
1120
+ rawKind: "otlp_span_event",
1121
+ path: context.path,
1122
+ spanId: context.spanId,
1123
+ details
1124
+ }),
1125
+ attributes: parseOtlpAttributes(eventRecord.attributes, warnings, {
1126
+ rawKind: "otlp_event_attributes",
1127
+ path: context.path,
1128
+ spanId: context.spanId,
1129
+ details
1130
+ })
1131
+ })
1132
+ ];
1133
+ });
1134
+ return events.length > 0 ? events : void 0;
1135
+ }
1136
+ function parseOtlpAttributes(value, warnings, context) {
1137
+ if (value === void 0) {
1138
+ return void 0;
1139
+ }
1140
+ if (asRecord2(value)) {
1141
+ addWarning(
1142
+ warnings,
1143
+ "nonstandard_otlp_attribute_map",
1144
+ "OTLP attributes were an object map; values were preserved as-is.",
1145
+ context
1146
+ );
1147
+ return value;
1148
+ }
1149
+ if (!Array.isArray(value)) {
1150
+ addWarning(warnings, "malformed_otlp_attributes", "OTLP attributes must be an array.", context);
1151
+ return void 0;
1152
+ }
1153
+ const attributes = {};
1154
+ value.forEach((attribute, attributeIndex) => {
1155
+ const attributeRecord = asRecord2(attribute);
1156
+ const details = { ...context.details ?? {}, attribute_index: attributeIndex };
1157
+ if (!attributeRecord) {
1158
+ addWarning(
1159
+ warnings,
1160
+ "malformed_otlp_attribute",
1161
+ "OTLP attribute must be an object and was skipped.",
1162
+ { ...context, details }
1163
+ );
1164
+ return;
1165
+ }
1166
+ warnUnknownFields(attributeRecord, OTLP_ATTRIBUTE_FIELDS, warnings, {
1167
+ ...context,
1168
+ rawKind: context.rawKind ?? "otlp_attribute",
1169
+ details
1170
+ });
1171
+ const key = stringValue2(attributeRecord.key);
1172
+ if (!key) {
1173
+ addWarning(
1174
+ warnings,
1175
+ "malformed_otlp_attribute",
1176
+ "OTLP attribute was missing key and was skipped.",
1177
+ {
1178
+ ...context,
1179
+ details
1180
+ }
1181
+ );
1182
+ return;
1183
+ }
1184
+ if (Object.hasOwn(attributes, key)) {
1185
+ addWarning(
1186
+ warnings,
1187
+ "duplicate_otlp_attribute",
1188
+ `OTLP attribute "${key}" appeared more than once; the last value was used.`,
1189
+ {
1190
+ ...context,
1191
+ details: { ...details, key }
1192
+ }
1193
+ );
1194
+ }
1195
+ const parsed = parseOtlpAnyValue(attributeRecord.value, warnings, {
1196
+ ...context,
1197
+ rawKind: "otlp_attribute_value",
1198
+ details: { ...details, key }
1199
+ });
1200
+ if (parsed.ok) {
1201
+ attributes[key] = parsed.value;
1202
+ }
1203
+ });
1204
+ return Object.keys(attributes).length > 0 ? attributes : void 0;
1205
+ }
1206
+ function parseOtlpAnyValue(value, warnings, context) {
1207
+ if (isPrimitive(value)) {
1208
+ addWarning(
1209
+ warnings,
1210
+ "nonstandard_otlp_value",
1211
+ "OTLP attribute value was primitive; value was preserved as-is.",
1212
+ context
1213
+ );
1214
+ return { ok: true, value };
1215
+ }
1216
+ const record = asRecord2(value);
1217
+ if (!record) {
1218
+ addWarning(
1219
+ warnings,
1220
+ "malformed_otlp_value",
1221
+ "OTLP attribute value must be an object.",
1222
+ context
1223
+ );
1224
+ return { ok: false };
1225
+ }
1226
+ const presentFields = Object.keys(record).filter((key) => OTLP_ANY_VALUE_FIELDS.has(key));
1227
+ const unknownFields = Object.keys(record).filter((key) => !OTLP_ANY_VALUE_FIELDS.has(key));
1228
+ if (unknownFields.length > 0) {
1229
+ addWarning(
1230
+ warnings,
1231
+ "unknown_otlp_value_field",
1232
+ `Unknown OTLP attribute value field(s) ignored: ${unknownFields.join(", ")}.`,
1233
+ { ...context, details: { ...context.details ?? {}, fields: unknownFields } }
1234
+ );
1235
+ }
1236
+ if (presentFields.length > 1) {
1237
+ addWarning(
1238
+ warnings,
1239
+ "ambiguous_otlp_value",
1240
+ `OTLP attribute value had multiple value fields; ${presentFields[0]} was used.`,
1241
+ { ...context, details: { ...context.details ?? {}, fields: presentFields } }
1242
+ );
1243
+ }
1244
+ const field = presentFields[0];
1245
+ if (!field) {
1246
+ addWarning(
1247
+ warnings,
1248
+ "malformed_otlp_value",
1249
+ "OTLP attribute value did not contain a recognized value field; raw value was preserved.",
1250
+ context
1251
+ );
1252
+ return { ok: true, value };
1253
+ }
1254
+ switch (field) {
1255
+ case "stringValue":
1256
+ case "bytesValue":
1257
+ return { ok: true, value: stringValue2(record[field]) ?? String(record[field] ?? "") };
1258
+ case "boolValue":
1259
+ return { ok: true, value: Boolean(record.boolValue) };
1260
+ case "intValue":
1261
+ return { ok: true, value: integerOrString(record.intValue) };
1262
+ case "doubleValue":
1263
+ return { ok: true, value: numberOrString(record.doubleValue) };
1264
+ case "arrayValue":
1265
+ return parseOtlpArrayValue(record.arrayValue, warnings, context);
1266
+ case "kvlistValue":
1267
+ return parseOtlpKeyValueList(record.kvlistValue, warnings, context);
1268
+ default:
1269
+ return { ok: true, value };
1270
+ }
1271
+ }
1272
+ function parseOtlpArrayValue(value, warnings, context) {
1273
+ const arrayValue = asRecord2(value);
1274
+ if (!arrayValue || !Array.isArray(arrayValue.values)) {
1275
+ addWarning(
1276
+ warnings,
1277
+ "malformed_otlp_array_value",
1278
+ "OTLP arrayValue.values must be an array.",
1279
+ context
1280
+ );
1281
+ return { ok: false };
1282
+ }
1283
+ return {
1284
+ ok: true,
1285
+ value: arrayValue.values.map(
1286
+ (entry, index) => parseOtlpAnyValue(entry, warnings, {
1287
+ ...context,
1288
+ details: { ...context.details ?? {}, value_index: index }
1289
+ }).value
1290
+ )
1291
+ };
1292
+ }
1293
+ function parseOtlpKeyValueList(value, warnings, context) {
1294
+ const kvlistValue = asRecord2(value);
1295
+ if (!kvlistValue || !Array.isArray(kvlistValue.values)) {
1296
+ addWarning(
1297
+ warnings,
1298
+ "malformed_otlp_kvlist_value",
1299
+ "OTLP kvlistValue.values must be an array.",
1300
+ context
1301
+ );
1302
+ return { ok: false };
1303
+ }
1304
+ return {
1305
+ ok: true,
1306
+ value: parseOtlpAttributes(kvlistValue.values, warnings, context) ?? {}
1307
+ };
1308
+ }
1309
+ function normalizeOtlpSpanKind(value, warnings, context) {
1310
+ if (value === void 0) {
1311
+ return void 0;
1312
+ }
1313
+ if (typeof value === "string" && value.length > 0) {
1314
+ return value.replace(/^SPAN_KIND_/, "");
1315
+ }
1316
+ if (typeof value !== "number" || !Number.isInteger(value)) {
1317
+ addWarning(
1318
+ warnings,
1319
+ "malformed_span_kind",
1320
+ "OTLP span kind was not a string or integer.",
1321
+ context
1322
+ );
1323
+ return void 0;
1324
+ }
1325
+ if (value === 0) return "INTERNAL";
1326
+ if (value === 1) return "SERVER";
1327
+ if (value === 2) return "CLIENT";
1328
+ if (value === 3) return "PRODUCER";
1329
+ if (value === 4 || value === 5) return "CONSUMER";
1330
+ addWarning(
1331
+ warnings,
1332
+ "unknown_span_kind",
1333
+ `Unknown OTLP span kind ${value} was preserved as a string.`,
1334
+ context
1335
+ );
1336
+ return String(value);
1337
+ }
1338
+ function normalizeOtlpStatus(value, warnings, context) {
1339
+ if (value === void 0) {
1340
+ return void 0;
1341
+ }
1342
+ const status = asRecord2(value);
1343
+ if (!status) {
1344
+ addWarning(warnings, "malformed_otlp_status", "OTLP span status must be an object.", context);
1345
+ return void 0;
1346
+ }
1347
+ warnUnknownFields(status, OTLP_STATUS_FIELDS, warnings, context);
1348
+ return dropUndefined2({
1349
+ code: normalizeStatusCode(status.code, warnings, context),
1350
+ message: stringValue2(status.message)
1351
+ });
1352
+ }
1353
+ function normalizeStatusCode(value, warnings, context) {
1354
+ if (value === void 0) {
1355
+ return void 0;
1356
+ }
1357
+ if (typeof value === "string" && value.length > 0) {
1358
+ return value.replace(/^STATUS_CODE_/, "");
1359
+ }
1360
+ if (typeof value === "number" && Number.isInteger(value)) {
1361
+ if (value === 0) return "UNSET";
1362
+ if (value === 1) return "OK";
1363
+ if (value === 2) return "ERROR";
1364
+ addWarning(
1365
+ warnings,
1366
+ "unknown_status_code",
1367
+ `Unknown OTLP status code ${value} was preserved as a string.`,
1368
+ context
1369
+ );
1370
+ return String(value);
1371
+ }
1372
+ addWarning(
1373
+ warnings,
1374
+ "malformed_status_code",
1375
+ "OTLP status code was not a string or integer.",
1376
+ context
1377
+ );
1378
+ return void 0;
1379
+ }
1380
+ function normalizeUnixNano(value, field, warnings, context) {
1381
+ if (value === void 0) {
1382
+ return void 0;
1383
+ }
1384
+ if (typeof value === "string" && value.length > 0) {
1385
+ if (!/^\d+$/.test(value)) {
1386
+ addWarning(
1387
+ warnings,
1388
+ "malformed_unix_nano",
1389
+ `OTLP ${field} was not an unsigned integer string.`,
1390
+ context
1391
+ );
1392
+ }
1393
+ return value;
1394
+ }
1395
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
1396
+ return String(Math.trunc(value));
1397
+ }
1398
+ addWarning(warnings, "malformed_unix_nano", `OTLP ${field} was not a string or number.`, context);
1399
+ return void 0;
1400
+ }
1401
+ function firstRootSpanId(spans) {
1402
+ const spanIds = new Set(spans.map((span) => span.span_id));
1403
+ return spans.find(
1404
+ (span) => !span.parent_span_id || typeof span.parent_span_id === "string" && !spanIds.has(span.parent_span_id)
1405
+ )?.span_id ?? spans[0]?.span_id;
1406
+ }
1407
+ function singleDistinctRecord(records) {
1408
+ if (records.length === 0) {
1409
+ return void 0;
1410
+ }
1411
+ const unique = new Map(records.map((record) => [stableRecordKey(record), record]));
1412
+ return unique.size === 1 ? [...unique.values()][0] : void 0;
1413
+ }
1414
+ function stableRecordKey(record) {
1415
+ return JSON.stringify(
1416
+ Object.keys(record).sort().map((key) => [key, record[key]])
1417
+ );
1418
+ }
1419
+ function firstString(values) {
1420
+ return values.find((value) => typeof value === "string" && value.length > 0);
1421
+ }
1422
+ function integerOrString(value) {
1423
+ if (typeof value === "number" && Number.isFinite(value)) {
1424
+ return value;
1425
+ }
1426
+ if (typeof value === "string") {
1427
+ const numberValue = Number(value);
1428
+ return Number.isSafeInteger(numberValue) ? numberValue : value;
1429
+ }
1430
+ return String(value ?? "");
1431
+ }
1432
+ function numberOrString(value) {
1433
+ if (typeof value === "number" && Number.isFinite(value)) {
1434
+ return value;
1435
+ }
1436
+ if (typeof value === "string") {
1437
+ const numberValue = Number(value);
1438
+ return Number.isFinite(numberValue) ? numberValue : value;
1439
+ }
1440
+ return String(value ?? "");
1441
+ }
1442
+ function warnUnknownFields(record, allowed, warnings, context) {
1443
+ const unknownFields = Object.keys(record).filter((key) => !allowed.has(key));
1444
+ for (const field of unknownFields) {
1445
+ addWarning(
1446
+ warnings,
1447
+ "unknown_otlp_field",
1448
+ `Unknown OTLP ${context.rawKind ?? "object"} field "${field}" was ignored.`,
1449
+ { ...context, details: { ...context.details ?? {}, field } }
1450
+ );
1451
+ }
1452
+ }
1453
+ function addWarning(warnings, code, message, context = {}) {
1454
+ warnings.push(
1455
+ dropUndefined2({
1456
+ code,
1457
+ severity: "warning",
1458
+ span_id: context.spanId,
1459
+ source_ref: context.path || context.rawKind ? dropUndefined2({
1460
+ path: context.path,
1461
+ raw_kind: context.rawKind,
1462
+ span_id: context.spanId
1463
+ }) : void 0,
1464
+ message,
1465
+ details: context.details
1466
+ })
1467
+ );
1468
+ }
1469
+ function asRecord2(value) {
1470
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1471
+ }
1472
+ function stringValue2(value) {
1473
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1474
+ }
1475
+ function isPrimitive(value) {
1476
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null;
1477
+ }
1478
+ function dropUndefined2(value) {
1479
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
1480
+ }
54
1481
  function codeGraderInstruction(graderName, description) {
55
1482
  const desc = description ? ` This grader: ${description}.` : "";
56
1483
  return `Run \`agentv eval assert ${graderName} --agent-output <agent_output> --agent-input <original_prompt>\` and check the result.${desc} The command accepts --agent-output (the agent's full response text) and --agent-input (the original user prompt). It returns JSON on stdout: {"score": 0-1, "reasoning": "..."}. A score >= 0.5 means pass (exit 0); below 0.5 means fail (exit 1).`;
@@ -682,12 +2109,27 @@ var RESULTS_REPO_RESULTS_DIR = ".agentv/results";
682
2109
  var RESULTS_REPO_RUNS_DIR = "runs";
683
2110
  var RESULTS_REPO_METADATA_DIR = "metadata";
684
2111
  var RESULTS_REPO_TRACKED_DIRS = [RESULTS_REPO_RUNS_DIR, RESULTS_REPO_METADATA_DIR];
685
- var RESULTS_REPO_COMMIT_EMAIL = "agentv@results-repo";
686
- var RESULTS_REPO_COMMIT_NAME = "AgentV Results";
687
- var DEFAULT_RESULTS_BRANCH = "agentv/results/v1";
2112
+ var FALLBACK_RESULTS_REPO_COMMIT_EMAIL = "agentv@results-repo";
2113
+ var FALLBACK_RESULTS_REPO_COMMIT_NAME = "AgentV Results";
2114
+ var GIT_COMMIT_IDENTITY_ENV_KEYS = [
2115
+ "GIT_AUTHOR_NAME",
2116
+ "GIT_AUTHOR_EMAIL",
2117
+ "GIT_COMMITTER_NAME",
2118
+ "GIT_COMMITTER_EMAIL"
2119
+ ];
2120
+ var GIT_ENV_INHERIT_ALLOWLIST = /* @__PURE__ */ new Set([
2121
+ "GIT_ASKPASS",
2122
+ "GIT_PASSWORD",
2123
+ "GIT_SSH_COMMAND",
2124
+ "GIT_TOKEN",
2125
+ "GIT_USERNAME"
2126
+ ]);
2127
+ var DEFAULT_RESULTS_BRANCH = AGENTV_RESULTS_PRIMARY_REF;
2128
+ var MANAGED_RESULTS_REMOTE = "agentv-results";
688
2129
  var GIT_EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
689
2130
  var RESULTS_REPO_GENESIS_MESSAGE = "chore(results): initialize AgentV results branch";
690
2131
  var RESULTS_REPO_GENESIS_DATE = "@0 +0000";
2132
+ var RESULT_INDEX_FILENAME2 = "index.jsonl";
691
2133
  var activeResultsRepoSyncs = /* @__PURE__ */ new Set();
692
2134
  function sanitizeRepoSlug(repo) {
693
2135
  return repo.trim().replace(/[^A-Za-z0-9._-]+/g, "-");
@@ -706,16 +2148,24 @@ function expandHome(p) {
706
2148
  }
707
2149
  return p;
708
2150
  }
2151
+ function resolveLocalPath(p, baseDir) {
2152
+ const expanded = expandHome(p);
2153
+ return path4.isAbsolute(expanded) ? expanded : path4.resolve(baseDir, expanded);
2154
+ }
709
2155
  function normalizeResultsConfig(config, options) {
2156
+ const baseDir = options?.baseDir ?? process.cwd();
710
2157
  const repoUrl = (config.repo_url ?? config.repo)?.trim();
711
2158
  const repoPath = config.repo_path?.trim();
2159
+ const explicitClonePath = config.path?.trim();
712
2160
  const repo = repoUrl ?? repoPath ?? "";
713
2161
  const branch = config.branch?.trim() || (repoPath ? DEFAULT_RESULTS_BRANCH : void 0);
714
- const remote = config.remote?.trim() || "origin";
2162
+ const useStorageBranchWorktree = Boolean(repoPath || repoUrl && explicitClonePath && branch);
2163
+ const remote = config.remote?.trim() || (repoUrl && useStorageBranchWorktree ? MANAGED_RESULTS_REMOTE : "origin");
715
2164
  const autoPush = config.sync?.auto_push ?? config.auto_push === true;
716
2165
  const requirePush = config.sync?.require_push === true;
717
- const resolvedRepoPath = repoPath ? path4.resolve(options?.baseDir ?? process.cwd(), expandHome(repoPath)) : void 0;
718
- const resolvedPath = config.path ? expandHome(config.path.trim()) : repoUrl ? path4.join(getAgentvDataDir(), "results", sanitizeRepoSlug(repoUrl)) : resolvedRepoPath ?? path4.join(getAgentvDataDir(), "results", sanitizeRepoSlug(repo));
2166
+ const pushConflictPolicy = config.sync?.push_conflict_policy ?? "block";
2167
+ const resolvedRepoPath = repoPath ? resolveLocalPath(repoPath, baseDir) : void 0;
2168
+ const resolvedPath = explicitClonePath ? resolveLocalPath(explicitClonePath, baseDir) : repoUrl ? path4.join(getAgentvDataDir(), "results", sanitizeRepoSlug(repoUrl)) : resolvedRepoPath ?? path4.join(getAgentvDataDir(), "results", sanitizeRepoSlug(repo));
719
2169
  return {
720
2170
  mode: "github",
721
2171
  repo,
@@ -726,7 +2176,9 @@ function normalizeResultsConfig(config, options) {
726
2176
  path: resolvedPath,
727
2177
  auto_push: autoPush,
728
2178
  require_push: requirePush,
729
- branch_prefix: config.branch_prefix?.trim() || "eval-results"
2179
+ push_conflict_policy: pushConflictPolicy,
2180
+ branch_prefix: config.branch_prefix?.trim() || "eval-results",
2181
+ storageBranchWorktree: useStorageBranchWorktree
730
2182
  };
731
2183
  }
732
2184
  function resolveResultsRepoUrl(repo) {
@@ -743,6 +2195,9 @@ function getResultsRepoLocalPaths(repo) {
743
2195
  statusFile: path4.join(rootDir, "status.json")
744
2196
  };
745
2197
  }
2198
+ function usesStorageBranchWorktree(config) {
2199
+ return config.storageBranchWorktree === true || Boolean(config.repo_path);
2200
+ }
746
2201
  function readPersistedStatus(statusFile) {
747
2202
  if (!existsSync(statusFile)) {
748
2203
  return {};
@@ -764,13 +2219,14 @@ async function runCommand(executable, args, options) {
764
2219
  cwd: options?.cwd,
765
2220
  env: options?.env ?? process.env
766
2221
  });
767
- return { stdout, stderr };
2222
+ return { stdout, stderr, exitCode: 0 };
768
2223
  } catch (error) {
769
2224
  if (options?.check === false && error && typeof error === "object") {
770
2225
  const execError = error;
771
2226
  return {
772
2227
  stdout: execError.stdout ?? "",
773
- stderr: execError.stderr ?? ""
2228
+ stderr: execError.stderr ?? "",
2229
+ exitCode: typeof execError.code === "number" ? execError.code : 1
774
2230
  };
775
2231
  }
776
2232
  throw withFriendlyGitHubAuthError(error);
@@ -779,7 +2235,7 @@ async function runCommand(executable, args, options) {
779
2235
  function getGitEnv() {
780
2236
  const env = {};
781
2237
  for (const [key, value] of Object.entries(process.env)) {
782
- if (value !== void 0 && !(key.startsWith("GIT_") && key !== "GIT_SSH_COMMAND")) {
2238
+ if (value !== void 0 && (!key.startsWith("GIT_") || GIT_ENV_INHERIT_ALLOWLIST.has(key))) {
783
2239
  env[key] = value;
784
2240
  }
785
2241
  }
@@ -792,9 +2248,59 @@ async function runGit(args, options) {
792
2248
  async function runGh(args, options) {
793
2249
  return runCommand("gh", args, options);
794
2250
  }
795
- async function ensureResultsRepoCommitIdentity(repoDir) {
796
- await runGit(["config", "user.email", RESULTS_REPO_COMMIT_EMAIL], { cwd: repoDir });
797
- await runGit(["config", "user.name", RESULTS_REPO_COMMIT_NAME], { cwd: repoDir });
2251
+ function gitErrorText(error) {
2252
+ const parts = [];
2253
+ if (error && typeof error === "object") {
2254
+ const record = error;
2255
+ if (typeof record.stdout === "string") parts.push(record.stdout);
2256
+ if (typeof record.stderr === "string") parts.push(record.stderr);
2257
+ if (typeof record.message === "string") parts.push(record.message);
2258
+ } else if (typeof error === "string") {
2259
+ parts.push(error);
2260
+ }
2261
+ return parts.join("\n").toLowerCase();
2262
+ }
2263
+ function isMissingGitIdentityError(error) {
2264
+ const text = gitErrorText(error);
2265
+ return text.includes("author identity unknown") || text.includes("committer identity unknown") || text.includes("please tell me who you are") || text.includes("unable to auto-detect email address") || text.includes("empty ident name");
2266
+ }
2267
+ function fallbackResultsRepoCommitEnv() {
2268
+ return {
2269
+ GIT_AUTHOR_NAME: FALLBACK_RESULTS_REPO_COMMIT_NAME,
2270
+ GIT_AUTHOR_EMAIL: FALLBACK_RESULTS_REPO_COMMIT_EMAIL,
2271
+ GIT_COMMITTER_NAME: FALLBACK_RESULTS_REPO_COMMIT_NAME,
2272
+ GIT_COMMITTER_EMAIL: FALLBACK_RESULTS_REPO_COMMIT_EMAIL
2273
+ };
2274
+ }
2275
+ function configuredGitCommitIdentityEnv() {
2276
+ const env = {};
2277
+ for (const key of GIT_COMMIT_IDENTITY_ENV_KEYS) {
2278
+ const value = process.env[key];
2279
+ if (value !== void 0) {
2280
+ env[key] = value;
2281
+ }
2282
+ }
2283
+ return env;
2284
+ }
2285
+ async function runGitWithFallbackCommitIdentity(args, options) {
2286
+ const env = {
2287
+ ...configuredGitCommitIdentityEnv(),
2288
+ ...options.env
2289
+ };
2290
+ try {
2291
+ return await runGit(args, { ...options, env });
2292
+ } catch (error) {
2293
+ if (!isMissingGitIdentityError(error)) {
2294
+ throw error;
2295
+ }
2296
+ return runGit(args, {
2297
+ ...options,
2298
+ env: {
2299
+ ...env,
2300
+ ...fallbackResultsRepoCommitEnv()
2301
+ }
2302
+ });
2303
+ }
798
2304
  }
799
2305
  async function resolveDefaultBranch(repoDir) {
800
2306
  try {
@@ -816,16 +2322,39 @@ async function resolveDefaultBranch(repoDir) {
816
2322
  return "main";
817
2323
  }
818
2324
  async function fetchResultsRepo(repoDir, remote = "origin", branch) {
819
- await runGit(["fetch", remote, "--prune"], { cwd: repoDir });
820
2325
  if (branch) {
821
2326
  await fetchResultsBranchRef(repoDir, remote, branch);
2327
+ await runGit(["remote", "prune", remote], { cwd: repoDir, check: false });
2328
+ return;
822
2329
  }
2330
+ await runGit(["fetch", remote, "--prune"], { cwd: repoDir });
2331
+ }
2332
+ function isMissingRemoteBranchFetch(stderr) {
2333
+ const lower = stderr.toLowerCase();
2334
+ return lower.includes("couldn't find remote ref") || lower.includes("could not find remote ref");
823
2335
  }
824
2336
  async function fetchResultsBranchRef(repoDir, remote, branch) {
825
- await runGit(["fetch", remote, `+refs/heads/${branch}:refs/remotes/${remote}/${branch}`], {
2337
+ const { exitCode, stderr } = await runGit(
2338
+ ["fetch", remote, `+refs/heads/${branch}:refs/remotes/${remote}/${branch}`],
2339
+ {
2340
+ cwd: repoDir,
2341
+ check: false
2342
+ }
2343
+ );
2344
+ const fetchError = stderr.trim();
2345
+ if (exitCode !== 0 && !isMissingRemoteBranchFetch(fetchError)) {
2346
+ throw new Error(fetchError);
2347
+ }
2348
+ }
2349
+ async function fetchResultsArtifactRef(repoDir, remote, branch) {
2350
+ const refspec = `+refs/heads/${branch}:refs/remotes/${remote}/${branch}`;
2351
+ await runGit(["fetch", "--filter=blob:none", remote, refspec], {
826
2352
  cwd: repoDir,
827
2353
  check: false
828
2354
  });
2355
+ if (!await gitRefExists(repoDir, `refs/remotes/${remote}/${branch}`)) {
2356
+ await fetchResultsBranchRef(repoDir, remote, branch);
2357
+ }
829
2358
  }
830
2359
  function remoteBranchRef(branch, remote = "origin") {
831
2360
  return `${remote}/${branch}`;
@@ -893,16 +2422,12 @@ async function createOrphanResultsBranch(repoDir, branch) {
893
2422
  });
894
2423
  }
895
2424
  async function createResultsGenesisCommit(repoDir) {
896
- await ensureResultsRepoCommitIdentity(repoDir);
897
2425
  const { stdout } = await runGit(
898
2426
  ["commit-tree", GIT_EMPTY_TREE, "-m", RESULTS_REPO_GENESIS_MESSAGE],
899
2427
  {
900
2428
  cwd: repoDir,
901
2429
  env: {
902
- GIT_AUTHOR_NAME: RESULTS_REPO_COMMIT_NAME,
903
- GIT_AUTHOR_EMAIL: RESULTS_REPO_COMMIT_EMAIL,
904
- GIT_COMMITTER_NAME: RESULTS_REPO_COMMIT_NAME,
905
- GIT_COMMITTER_EMAIL: RESULTS_REPO_COMMIT_EMAIL,
2430
+ ...fallbackResultsRepoCommitEnv(),
906
2431
  GIT_AUTHOR_DATE: RESULTS_REPO_GENESIS_DATE,
907
2432
  GIT_COMMITTER_DATE: RESULTS_REPO_GENESIS_DATE
908
2433
  }
@@ -922,6 +2447,24 @@ async function resolveGitTopLevel(repoDir) {
922
2447
  const { stdout } = await runGit(["rev-parse", "--show-toplevel"], { cwd: repoDir });
923
2448
  return stdout.trim() || repoDir;
924
2449
  }
2450
+ async function ensureResultsRepoRemote(repoDir, config) {
2451
+ if (!config.repo_url) {
2452
+ return;
2453
+ }
2454
+ const remoteUrl = resolveResultsRepoUrl(config.repo_url);
2455
+ const { stdout } = await runGit(["remote", "get-url", config.remote], {
2456
+ cwd: repoDir,
2457
+ check: false
2458
+ });
2459
+ const existingUrl = stdout.trim();
2460
+ if (!existingUrl) {
2461
+ await runGit(["remote", "add", config.remote, remoteUrl], { cwd: repoDir });
2462
+ return;
2463
+ }
2464
+ if (existingUrl !== remoteUrl) {
2465
+ await runGit(["remote", "set-url", config.remote, remoteUrl], { cwd: repoDir });
2466
+ }
2467
+ }
925
2468
  function updateStatusFile(config, patch) {
926
2469
  const repo = typeof config.repo === "string" ? config.repo : config.repo_url ?? config.repo_path ?? "";
927
2470
  const cachePaths = getResultsRepoLocalPaths(repo);
@@ -954,6 +2497,7 @@ async function ensureResultsRepoClone(config) {
954
2497
  resolveResultsRepoUrl(normalized.repo_url ?? normalized.repo),
955
2498
  cloneDir
956
2499
  ]);
2500
+ await ensureResultsRepoRemote(cloneDir, normalized);
957
2501
  return cloneDir;
958
2502
  } catch (error) {
959
2503
  updateStatusFile(normalized, { last_error: withFriendlyGitHubAuthError(error).message });
@@ -963,6 +2507,7 @@ async function ensureResultsRepoClone(config) {
963
2507
  if (!existsSync(gitDir)) {
964
2508
  throw new Error(`Results repo clone path is not a git repository: ${cloneDir}`);
965
2509
  }
2510
+ await ensureResultsRepoRemote(cloneDir, normalized);
966
2511
  return cloneDir;
967
2512
  }
968
2513
  function getResultsRepoStatus(config) {
@@ -986,6 +2531,7 @@ function getResultsRepoStatus(config) {
986
2531
  path: normalized.path,
987
2532
  auto_push: normalized.auto_push,
988
2533
  require_push: normalized.require_push,
2534
+ push_conflict_policy: normalized.push_conflict_policy,
989
2535
  branch_prefix: normalized.branch_prefix,
990
2536
  local_dir: normalized.path,
991
2537
  last_synced_at: persisted.last_synced_at,
@@ -1078,6 +2624,50 @@ async function getAheadBehindForRefs(repoDir, leftRef, rightRef) {
1078
2624
  ...Number.isFinite(behind) && { behind }
1079
2625
  };
1080
2626
  }
2627
+ async function readGitText(repoDir, ref, gitPath) {
2628
+ if (!ref) {
2629
+ return void 0;
2630
+ }
2631
+ const { stdout } = await runGit(["show", `${ref}:${gitPath}`], {
2632
+ cwd: repoDir,
2633
+ check: false
2634
+ });
2635
+ return stdout.length > 0 ? stdout : void 0;
2636
+ }
2637
+ async function getStorageBranchWorktreeDirtyPaths(repoDir, config) {
2638
+ const { stdout: porcelain } = await runGit(
2639
+ ["status", "--porcelain=v1", "--untracked-files=all"],
2640
+ {
2641
+ cwd: repoDir,
2642
+ check: false
2643
+ }
2644
+ );
2645
+ const parsed = parseGitPorcelainPaths(porcelain);
2646
+ const safeDirtyPaths = parsed.dirtyPaths.filter(isSafeResultsRepoPath);
2647
+ const conflictedPaths = parsed.conflictedPaths.filter(isSafeResultsRepoPath);
2648
+ if (safeDirtyPaths.length === 0) {
2649
+ return { dirtyPaths: [], conflictedPaths };
2650
+ }
2651
+ const localRef = `refs/heads/${config.branch}`;
2652
+ const remoteRef = remoteBranchRef(config.branch, config.remote);
2653
+ const comparisonRef = await gitRefExists(repoDir, localRef) ? localRef : await gitRefExists(repoDir, remoteRef) ? remoteRef : void 0;
2654
+ const dirtyPaths = [];
2655
+ for (const gitPath of safeDirtyPaths) {
2656
+ const absolutePath = path4.join(repoDir, ...gitPath.split("/"));
2657
+ if (!existsSync(absolutePath)) {
2658
+ if (await readGitText(repoDir, comparisonRef, gitPath) !== void 0) {
2659
+ dirtyPaths.push(gitPath);
2660
+ }
2661
+ continue;
2662
+ }
2663
+ const localContent = readFileSync2(absolutePath, "utf8");
2664
+ const branchContent = await readGitText(repoDir, comparisonRef, gitPath);
2665
+ if (branchContent === void 0 || localContent !== branchContent) {
2666
+ dirtyPaths.push(gitPath);
2667
+ }
2668
+ }
2669
+ return { dirtyPaths: dirtyPaths.sort(), conflictedPaths };
2670
+ }
1081
2671
  async function inspectResultsStorageBranchGit(repoDir, config) {
1082
2672
  if (!config.branch) {
1083
2673
  return {
@@ -1090,12 +2680,20 @@ async function inspectResultsStorageBranchGit(repoDir, config) {
1090
2680
  const upstream = remoteBranchRef(config.branch, config.remote);
1091
2681
  const localExists = await gitRefExists(repoDir, localRef);
1092
2682
  const remoteExists = await gitRefExists(repoDir, upstream);
2683
+ const worktree = await getStorageBranchWorktreeDirtyPaths(repoDir, {
2684
+ ...config,
2685
+ branch: config.branch
2686
+ });
1093
2687
  const { ahead = 0, behind = 0 } = localExists && remoteExists ? await getAheadBehindForRefs(repoDir, localRef, upstream) : {
1094
2688
  ahead: localExists && !remoteExists ? 1 : 0,
1095
2689
  behind: !localExists && remoteExists ? 1 : 0
1096
2690
  };
1097
2691
  let syncStatus = "clean";
1098
- if (ahead > 0 && behind > 0) {
2692
+ if (worktree.conflictedPaths.length > 0) {
2693
+ syncStatus = "conflicted";
2694
+ } else if (worktree.dirtyPaths.length > 0) {
2695
+ syncStatus = "dirty";
2696
+ } else if (ahead > 0 && behind > 0) {
1099
2697
  syncStatus = "diverged";
1100
2698
  } else if (behind > 0) {
1101
2699
  syncStatus = "behind";
@@ -1108,10 +2706,51 @@ async function inspectResultsStorageBranchGit(repoDir, config) {
1108
2706
  ...remoteExists && { upstream },
1109
2707
  ahead,
1110
2708
  behind,
1111
- dirtyPaths: [],
1112
- conflictedPaths: []
2709
+ dirtyPaths: worktree.dirtyPaths,
2710
+ conflictedPaths: worktree.conflictedPaths
1113
2711
  };
1114
2712
  }
2713
+ async function fastForwardStorageBranchRef(repoDir, normalized, upstream) {
2714
+ await assertValidResultsBranchName(repoDir, normalized.branch);
2715
+ await ensureResultsBranchNotCheckedOut(repoDir, normalized);
2716
+ const localRef = `refs/heads/${normalized.branch}`;
2717
+ const localExists = await gitRefExists(repoDir, localRef);
2718
+ const updateArgs = ["update-ref", localRef, upstream];
2719
+ if (localExists) {
2720
+ const { stdout: localSha } = await runGit(["rev-parse", localRef], { cwd: repoDir });
2721
+ updateArgs.push(localSha.trim());
2722
+ }
2723
+ await runGit(updateArgs, { cwd: repoDir });
2724
+ await runGit(["branch", "--set-upstream-to", upstream, normalized.branch], {
2725
+ cwd: repoDir,
2726
+ check: false
2727
+ });
2728
+ }
2729
+ async function getDirtyPathsChangedUpstream(repoDir, normalized, dirtyPaths, upstream) {
2730
+ const safeDirtyPaths = [...new Set(dirtyPaths.filter(isSafeResultsRepoPath))].sort();
2731
+ if (safeDirtyPaths.length === 0) {
2732
+ return [];
2733
+ }
2734
+ const localRef = `refs/heads/${normalized.branch}`;
2735
+ const localExists = await gitRefExists(repoDir, localRef);
2736
+ let changedPaths;
2737
+ if (localExists) {
2738
+ const { stdout } = await runGit(["diff", "--name-only", `${localRef}..${upstream}`], {
2739
+ cwd: repoDir,
2740
+ check: false
2741
+ });
2742
+ changedPaths = stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).filter(isSafeResultsRepoPath).sort();
2743
+ } else {
2744
+ changedPaths = [];
2745
+ for (const dirtyPath of safeDirtyPaths) {
2746
+ if (await readGitText(repoDir, upstream, dirtyPath) !== void 0) {
2747
+ changedPaths.push(dirtyPath);
2748
+ }
2749
+ }
2750
+ }
2751
+ const dirtySet = new Set(safeDirtyPaths);
2752
+ return [...new Set(changedPaths.filter((changedPath) => dirtySet.has(changedPath)))].sort();
2753
+ }
1115
2754
  async function hasInProgressGitConflict(repoDir) {
1116
2755
  const markers = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "REBASE_HEAD"];
1117
2756
  for (const marker of markers) {
@@ -1213,9 +2852,37 @@ function lastErrorForGitInspection(status, inspection) {
1213
2852
  }
1214
2853
  return void 0;
1215
2854
  }
2855
+ var ResultsBranchPushConflictError = class extends Error {
2856
+ constructor(result) {
2857
+ super(result.block_reason ?? "Results branch push conflict");
2858
+ this.result = result;
2859
+ this.name = "ResultsBranchPushConflictError";
2860
+ }
2861
+ };
2862
+ function pushDetailsToWire(details) {
2863
+ if (!details) {
2864
+ return {};
2865
+ }
2866
+ return {
2867
+ push_conflict_policy: details.pushConflictPolicy,
2868
+ target_branch: details.targetBranch,
2869
+ ...details.remoteCommit !== void 0 && { remote_commit: details.remoteCommit },
2870
+ ...details.localCommit !== void 0 && { local_commit: details.localCommit },
2871
+ ...details.backupRef !== void 0 && { backup_ref: details.backupRef },
2872
+ ...details.backupCommit !== void 0 && { backup_commit: details.backupCommit },
2873
+ ...details.previousRemoteCommit !== void 0 && {
2874
+ previous_remote_commit: details.previousRemoteCommit
2875
+ },
2876
+ ...details.forcePushedCommit !== void 0 && {
2877
+ force_pushed_commit: details.forcePushedCommit
2878
+ },
2879
+ ...details.leaseCommit !== void 0 && { lease_commit: details.leaseCommit }
2880
+ };
2881
+ }
1216
2882
  function withBlockedStatus(status, blockReason, flags) {
1217
2883
  return {
1218
2884
  ...status,
2885
+ ...pushDetailsToWire(flags?.pushDetails),
1219
2886
  blocked: true,
1220
2887
  block_reason: blockReason,
1221
2888
  ...flags?.pullPerformed !== void 0 && { pull_performed: flags.pullPerformed },
@@ -1223,9 +2890,23 @@ function withBlockedStatus(status, blockReason, flags) {
1223
2890
  ...flags?.commitCreated !== void 0 && { commit_created: flags.commitCreated }
1224
2891
  };
1225
2892
  }
2893
+ function withPushConflictStatus(status, blockReason, details, flags) {
2894
+ return withBlockedStatus(
2895
+ {
2896
+ ...status,
2897
+ sync_status: "push_conflict"
2898
+ },
2899
+ blockReason,
2900
+ {
2901
+ ...flags,
2902
+ pushDetails: details
2903
+ }
2904
+ );
2905
+ }
1226
2906
  function withActionFlags(status, flags) {
1227
2907
  return {
1228
2908
  ...status,
2909
+ ...pushDetailsToWire(flags.pushDetails),
1229
2910
  blocked: false,
1230
2911
  pull_performed: flags.pullPerformed,
1231
2912
  push_performed: flags.pushPerformed,
@@ -1266,6 +2947,121 @@ function getPushTargetBranch(upstream, baseBranch, remote = "origin") {
1266
2947
  const prefix = `${remote}/`;
1267
2948
  return upstream?.startsWith(prefix) ? upstream.slice(prefix.length) : baseBranch;
1268
2949
  }
2950
+ function timestampForBackupRef(date = /* @__PURE__ */ new Date()) {
2951
+ const pad = (value) => String(value).padStart(2, "0");
2952
+ return `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}T${pad(
2953
+ date.getUTCHours()
2954
+ )}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}Z`;
2955
+ }
2956
+ function slugifyBackupTargetBranch(branch) {
2957
+ return branch.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "results";
2958
+ }
2959
+ function buildResultsBackupRef(targetBranch, remoteCommit) {
2960
+ return `agentv/backups/${timestampForBackupRef()}-${slugifyBackupTargetBranch(
2961
+ targetBranch
2962
+ )}-${remoteCommit.slice(0, 7)}`;
2963
+ }
2964
+ async function getCommitSha(repoDir, ref) {
2965
+ if (!ref) {
2966
+ return void 0;
2967
+ }
2968
+ const { stdout } = await runGit(["rev-parse", "--verify", `${ref}^{commit}`], {
2969
+ cwd: repoDir,
2970
+ check: false
2971
+ });
2972
+ const sha = stdout.trim();
2973
+ return sha.length > 0 ? sha : void 0;
2974
+ }
2975
+ function isNonFastForwardPushError(error) {
2976
+ const text = gitErrorText(error);
2977
+ return text.includes("non-fast-forward") || text.includes("fetch first") || text.includes("tip is behind its remote") || text.includes("note about fast-forwards") || text.includes("stale info");
2978
+ }
2979
+ function formatShortSha(sha) {
2980
+ return sha ? sha.slice(0, 12) : "unknown";
2981
+ }
2982
+ function buildBlockedPushConflictReason(details) {
2983
+ return `Results branch push conflict on ${details.targetBranch}: remote ${formatShortSha(
2984
+ details.remoteCommit
2985
+ )}, local ${formatShortSha(details.localCommit)}. Configure results.sync.push_conflict_policy: backup_and_force_push to back up the remote ref before replacing it.`;
2986
+ }
2987
+ async function resolveResultBranchPushConflict(params) {
2988
+ await fetchResultsRepo(params.repoDir, params.normalized.remote, params.targetBranch);
2989
+ const remoteRef = remoteBranchRef(params.targetBranch, params.normalized.remote);
2990
+ const remoteCommit = await getCommitSha(params.repoDir, remoteRef);
2991
+ const localCommit = await getCommitSha(params.repoDir, params.sourceRef);
2992
+ const baseDetails = {
2993
+ pushConflictPolicy: params.normalized.push_conflict_policy,
2994
+ targetBranch: params.targetBranch,
2995
+ ...remoteCommit !== void 0 && {
2996
+ remoteCommit,
2997
+ previousRemoteCommit: remoteCommit,
2998
+ leaseCommit: remoteCommit
2999
+ },
3000
+ ...localCommit !== void 0 && { localCommit }
3001
+ };
3002
+ if (!remoteCommit) {
3003
+ return {
3004
+ blocked: true,
3005
+ blockReason: `Results branch push conflict on ${params.targetBranch}: remote commit could not be resolved after fetch`,
3006
+ details: baseDetails
3007
+ };
3008
+ }
3009
+ if (params.normalized.push_conflict_policy === "block") {
3010
+ return {
3011
+ blocked: true,
3012
+ blockReason: buildBlockedPushConflictReason(baseDetails),
3013
+ details: baseDetails
3014
+ };
3015
+ }
3016
+ const backupRef = buildResultsBackupRef(params.targetBranch, remoteCommit);
3017
+ const backupDetails = {
3018
+ ...baseDetails,
3019
+ backupRef,
3020
+ backupCommit: remoteCommit
3021
+ };
3022
+ try {
3023
+ await assertValidResultsBranchName(params.repoDir, backupRef);
3024
+ await runGit(
3025
+ ["push", "--porcelain", params.normalized.remote, `${remoteCommit}:refs/heads/${backupRef}`],
3026
+ { cwd: params.repoDir }
3027
+ );
3028
+ } catch (error) {
3029
+ return {
3030
+ blocked: true,
3031
+ blockReason: `Results branch backup creation failed for ${params.targetBranch} at ${formatShortSha(
3032
+ remoteCommit
3033
+ )}: ${getStatusMessage(error)}`,
3034
+ details: backupDetails
3035
+ };
3036
+ }
3037
+ try {
3038
+ await runGit(
3039
+ [
3040
+ "push",
3041
+ "--porcelain",
3042
+ `--force-with-lease=refs/heads/${params.targetBranch}:${remoteCommit}`,
3043
+ params.normalized.remote,
3044
+ `${params.sourceRef}:refs/heads/${params.targetBranch}`
3045
+ ],
3046
+ { cwd: params.repoDir }
3047
+ );
3048
+ } catch (error) {
3049
+ return {
3050
+ blocked: true,
3051
+ blockReason: `Results branch force push lease failed for ${params.targetBranch}; remote changed after backup ${backupRef} was created with lease ${formatShortSha(
3052
+ remoteCommit
3053
+ )}: ${getStatusMessage(error)}`,
3054
+ details: backupDetails
3055
+ };
3056
+ }
3057
+ return {
3058
+ blocked: false,
3059
+ details: {
3060
+ ...backupDetails,
3061
+ ...localCommit !== void 0 && { forcePushedCommit: localCommit }
3062
+ }
3063
+ };
3064
+ }
1269
3065
  async function statusFromInspection(normalized, repoDir) {
1270
3066
  return withGitInspection(
1271
3067
  getResultsRepoStatus(normalized),
@@ -1291,7 +3087,8 @@ async function getResultsRepoSyncStatus(config) {
1291
3087
  };
1292
3088
  }
1293
3089
  try {
1294
- if (normalized.repo_path) {
3090
+ await ensureResultsRepoRemote(normalized.path, normalized);
3091
+ if (usesStorageBranchWorktree(normalized)) {
1295
3092
  await fetchResultsRepo(normalized.path, normalized.remote, normalized.branch).catch(
1296
3093
  () => void 0
1297
3094
  );
@@ -1321,7 +3118,7 @@ async function syncResultsRepo(config) {
1321
3118
  try {
1322
3119
  const repoDir = await ensureResultsRepoClone(normalized);
1323
3120
  await fetchResultsRepo(repoDir, normalized.remote, normalized.branch);
1324
- if (!normalized.repo_path) {
3121
+ if (!usesStorageBranchWorktree(normalized)) {
1325
3122
  await checkoutConfiguredResultsBranch(repoDir, normalized);
1326
3123
  }
1327
3124
  updateStatusFile(normalized, {
@@ -1354,9 +3151,10 @@ async function syncResultsRepoForProject(config) {
1354
3151
  let pullPerformed = false;
1355
3152
  let pushPerformed = false;
1356
3153
  let commitCreated = false;
3154
+ let pushDetails;
1357
3155
  try {
1358
3156
  const repoDir = await ensureResultsRepoClone(normalized);
1359
- if (normalized.repo_path) {
3157
+ if (usesStorageBranchWorktree(normalized)) {
1360
3158
  try {
1361
3159
  await fetchResultsRepo(repoDir, normalized.remote, normalized.branch);
1362
3160
  } catch (error) {
@@ -1364,6 +3162,171 @@ async function syncResultsRepoForProject(config) {
1364
3162
  throw error;
1365
3163
  }
1366
3164
  }
3165
+ let inspection2 = await inspectResultsStorageBranchGit(repoDir, normalized);
3166
+ const fastForwardBehindStorageBranch = async () => {
3167
+ if ((inspection2.behind ?? 0) <= 0 || (inspection2.ahead ?? 0) > 0) {
3168
+ return void 0;
3169
+ }
3170
+ if (!inspection2.upstream) {
3171
+ const status2 = withGitInspection(getResultsRepoStatus(normalized), inspection2);
3172
+ updateStatusFile(normalized, {
3173
+ last_error: "Results repo has no upstream branch to pull from"
3174
+ });
3175
+ return withBlockedStatus(status2, "Results repo has no upstream branch to pull from", {
3176
+ pullPerformed,
3177
+ pushPerformed,
3178
+ commitCreated
3179
+ });
3180
+ }
3181
+ if (!normalized.branch) {
3182
+ return void 0;
3183
+ }
3184
+ try {
3185
+ await fastForwardStorageBranchRef(
3186
+ repoDir,
3187
+ { ...normalized, branch: normalized.branch },
3188
+ inspection2.upstream
3189
+ );
3190
+ pullPerformed = true;
3191
+ inspection2 = await inspectResultsStorageBranchGit(repoDir, normalized);
3192
+ return void 0;
3193
+ } catch (error) {
3194
+ inspection2 = await inspectResultsStorageBranchGit(repoDir, normalized);
3195
+ const status2 = withGitInspection(getResultsRepoStatus(normalized), inspection2);
3196
+ const reason = `Results repo could not be fast-forwarded: ${getStatusMessage(error)}`;
3197
+ updateStatusFile(normalized, { last_error: reason });
3198
+ return withBlockedStatus(status2, reason, {
3199
+ pullPerformed,
3200
+ pushPerformed,
3201
+ commitCreated
3202
+ });
3203
+ }
3204
+ };
3205
+ if (inspection2.syncStatus === "conflicted") {
3206
+ const status2 = withGitInspection(getResultsRepoStatus(normalized), inspection2);
3207
+ updateStatusFile(normalized, {
3208
+ last_error: "Results repo has unresolved git conflicts"
3209
+ });
3210
+ return withBlockedStatus(status2, "Results repo has unresolved git conflicts", {
3211
+ pullPerformed,
3212
+ pushPerformed,
3213
+ commitCreated
3214
+ });
3215
+ }
3216
+ if ((inspection2.ahead ?? 0) > 0 && (inspection2.behind ?? 0) > 0) {
3217
+ const status2 = withGitInspection(getResultsRepoStatus(normalized), inspection2);
3218
+ if (!normalized.branch) {
3219
+ updateStatusFile(normalized, {
3220
+ last_error: "Results repo local and remote histories have diverged"
3221
+ });
3222
+ return withBlockedStatus(
3223
+ status2,
3224
+ "Results repo local and remote histories have diverged",
3225
+ {
3226
+ pullPerformed,
3227
+ pushPerformed,
3228
+ commitCreated
3229
+ }
3230
+ );
3231
+ }
3232
+ const localRef = `refs/heads/${normalized.branch}`;
3233
+ const aheadPaths = await getAheadPaths(repoDir, inspection2.upstream, localRef);
3234
+ if (!inspection2.upstream || !areSafeResultsRepoPaths(aheadPaths)) {
3235
+ const reason = !inspection2.upstream ? "Results repo has no upstream branch to push to" : "Results repo has non-results committed changes";
3236
+ updateStatusFile(normalized, { last_error: reason });
3237
+ return withBlockedStatus(status2, reason, {
3238
+ pullPerformed,
3239
+ pushPerformed,
3240
+ commitCreated
3241
+ });
3242
+ }
3243
+ const outcome = await resolveResultBranchPushConflict({
3244
+ normalized: { ...normalized, branch: normalized.branch },
3245
+ repoDir,
3246
+ targetBranch: normalized.branch,
3247
+ sourceRef: localRef
3248
+ });
3249
+ pushDetails = outcome.details;
3250
+ if (outcome.blocked) {
3251
+ updateStatusFile(normalized, { last_error: outcome.blockReason });
3252
+ return withPushConflictStatus(status2, outcome.blockReason, outcome.details, {
3253
+ pullPerformed,
3254
+ pushPerformed,
3255
+ commitCreated
3256
+ });
3257
+ }
3258
+ pushPerformed = true;
3259
+ await fetchResultsRepo(repoDir, normalized.remote, normalized.branch).catch(
3260
+ () => void 0
3261
+ );
3262
+ inspection2 = await inspectResultsStorageBranchGit(repoDir, normalized);
3263
+ }
3264
+ if (inspection2.syncStatus === "dirty") {
3265
+ if (!normalized.auto_push && !normalized.require_push) {
3266
+ const status2 = withGitInspection(getResultsRepoStatus(normalized), inspection2);
3267
+ updateStatusFile(normalized, {
3268
+ last_error: "Results repo has uncommitted changes and auto_push is disabled"
3269
+ });
3270
+ return withBlockedStatus(
3271
+ status2,
3272
+ "Results repo has uncommitted changes and auto_push is disabled",
3273
+ {
3274
+ pullPerformed,
3275
+ pushPerformed,
3276
+ commitCreated
3277
+ }
3278
+ );
3279
+ }
3280
+ if ((inspection2.behind ?? 0) > 0) {
3281
+ if (inspection2.upstream && normalized.branch) {
3282
+ const changedDirtyPaths = await getDirtyPathsChangedUpstream(
3283
+ repoDir,
3284
+ { ...normalized, branch: normalized.branch },
3285
+ inspection2.dirtyPaths,
3286
+ inspection2.upstream
3287
+ );
3288
+ if (changedDirtyPaths.length > 0) {
3289
+ const conflictInspection = {
3290
+ ...inspection2,
3291
+ syncStatus: "conflicted",
3292
+ conflictedPaths: [
3293
+ .../* @__PURE__ */ new Set([...inspection2.conflictedPaths, ...changedDirtyPaths])
3294
+ ].sort()
3295
+ };
3296
+ const status2 = withGitInspection(
3297
+ getResultsRepoStatus(normalized),
3298
+ conflictInspection
3299
+ );
3300
+ const reason = `Results repo local metadata changes conflict with upstream changes: ${changedDirtyPaths.join(", ")}`;
3301
+ updateStatusFile(normalized, { last_error: reason });
3302
+ return withBlockedStatus(status2, reason, {
3303
+ pullPerformed,
3304
+ pushPerformed,
3305
+ commitCreated
3306
+ });
3307
+ }
3308
+ }
3309
+ const blockedStatus = await fastForwardBehindStorageBranch();
3310
+ if (blockedStatus) {
3311
+ return blockedStatus;
3312
+ }
3313
+ }
3314
+ if (normalized.branch) {
3315
+ commitCreated = await commitStorageBranchWorktreePaths({
3316
+ normalized: { ...normalized, branch: normalized.branch },
3317
+ repoDir,
3318
+ paths: inspection2.dirtyPaths,
3319
+ commitMessage: "chore(results): sync local result metadata"
3320
+ });
3321
+ inspection2 = await inspectResultsStorageBranchGit(repoDir, normalized);
3322
+ }
3323
+ }
3324
+ if ((inspection2.behind ?? 0) > 0 && (inspection2.ahead ?? 0) === 0) {
3325
+ const blockedStatus = await fastForwardBehindStorageBranch();
3326
+ if (blockedStatus) {
3327
+ return blockedStatus;
3328
+ }
3329
+ }
1367
3330
  if (normalized.auto_push || normalized.require_push) {
1368
3331
  if (normalized.branch) {
1369
3332
  const localRef = `refs/heads/${normalized.branch}`;
@@ -1379,21 +3342,55 @@ async function syncResultsRepoForProject(config) {
1379
3342
  { cwd: repoDir }
1380
3343
  );
1381
3344
  pushPerformed = true;
3345
+ await fetchResultsRepo(repoDir, normalized.remote, normalized.branch).catch(
3346
+ () => void 0
3347
+ );
1382
3348
  } catch (error) {
1383
- updateStatusFile(normalized, { last_error: getStatusMessage(error) });
1384
- if (normalized.require_push) {
1385
- throw error;
1386
- }
1387
- const status = await getResultsRepoSyncStatus(normalized);
1388
- return withBlockedStatus(
1389
- status,
1390
- `Results repo push was rejected: ${getStatusMessage(error)}`,
1391
- {
1392
- pullPerformed,
1393
- pushPerformed,
1394
- commitCreated
3349
+ if (isNonFastForwardPushError(error)) {
3350
+ const outcome = await resolveResultBranchPushConflict({
3351
+ normalized: { ...normalized, branch: normalized.branch },
3352
+ repoDir,
3353
+ targetBranch: normalized.branch,
3354
+ sourceRef: localRef
3355
+ });
3356
+ pushDetails = outcome.details;
3357
+ if (!outcome.blocked) {
3358
+ pushPerformed = true;
3359
+ await fetchResultsRepo(repoDir, normalized.remote, normalized.branch).catch(
3360
+ () => void 0
3361
+ );
3362
+ inspection2 = await inspectResultsStorageBranchGit(repoDir, normalized);
3363
+ } else {
3364
+ updateStatusFile(normalized, { last_error: outcome.blockReason });
3365
+ const status2 = withGitInspection(
3366
+ getResultsRepoStatus(normalized),
3367
+ await inspectResultsStorageBranchGit(repoDir, normalized)
3368
+ );
3369
+ return withPushConflictStatus(status2, outcome.blockReason, outcome.details, {
3370
+ pullPerformed,
3371
+ pushPerformed,
3372
+ commitCreated
3373
+ });
1395
3374
  }
1396
- );
3375
+ } else {
3376
+ updateStatusFile(normalized, { last_error: getStatusMessage(error) });
3377
+ if (normalized.require_push) {
3378
+ throw error;
3379
+ }
3380
+ const status2 = withGitInspection(
3381
+ getResultsRepoStatus(normalized),
3382
+ await inspectResultsStorageBranchGit(repoDir, normalized)
3383
+ );
3384
+ return withBlockedStatus(
3385
+ status2,
3386
+ `Results repo push was rejected: ${getStatusMessage(error)}`,
3387
+ {
3388
+ pullPerformed,
3389
+ pushPerformed,
3390
+ commitCreated
3391
+ }
3392
+ );
3393
+ }
1397
3394
  }
1398
3395
  }
1399
3396
  }
@@ -1402,11 +3399,11 @@ async function syncResultsRepoForProject(config) {
1402
3399
  last_synced_at: (/* @__PURE__ */ new Date()).toISOString(),
1403
3400
  last_error: void 0
1404
3401
  });
1405
- return withActionFlags(await getResultsRepoSyncStatus(normalized), {
1406
- pullPerformed,
1407
- pushPerformed,
1408
- commitCreated
1409
- });
3402
+ const status = withGitInspection(
3403
+ getResultsRepoStatus(normalized),
3404
+ await inspectResultsStorageBranchGit(repoDir, normalized)
3405
+ );
3406
+ return withActionFlags(status, { pullPerformed, pushPerformed, commitCreated, pushDetails });
1410
3407
  }
1411
3408
  await fetchResultsRepo(repoDir, normalized.remote, normalized.branch);
1412
3409
  await checkoutConfiguredResultsBranch(repoDir, normalized);
@@ -1469,8 +3466,7 @@ async function syncResultsRepoForProject(config) {
1469
3466
  if (inspection.syncStatus === "dirty") {
1470
3467
  const trackedDirs = await existingTrackedResultsDirs(repoDir);
1471
3468
  await runGit(["add", "--all", "--", ...trackedDirs], { cwd: repoDir });
1472
- await ensureResultsRepoCommitIdentity(repoDir);
1473
- await runGit(
3469
+ await runGitWithFallbackCommitIdentity(
1474
3470
  ["commit", "-m", "chore(results): sync local result metadata", "--", ...trackedDirs],
1475
3471
  {
1476
3472
  cwd: repoDir
@@ -1482,14 +3478,36 @@ async function syncResultsRepoForProject(config) {
1482
3478
  }
1483
3479
  if (inspection.syncStatus === "diverged") {
1484
3480
  const status = withGitInspection(getResultsRepoStatus(normalized), inspection);
1485
- updateStatusFile(normalized, {
1486
- last_error: "Results repo local and remote histories have diverged"
1487
- });
1488
- return withBlockedStatus(status, "Results repo local and remote histories have diverged", {
1489
- pullPerformed,
1490
- pushPerformed,
1491
- commitCreated
3481
+ const aheadPaths = await getAheadPaths(repoDir, inspection.upstream);
3482
+ if (!inspection.upstream || !areSafeResultsRepoPaths(aheadPaths)) {
3483
+ const reason = !inspection.upstream ? "Results repo has no upstream branch to push to" : "Results repo has non-results committed changes";
3484
+ updateStatusFile(normalized, { last_error: reason });
3485
+ return withBlockedStatus(status, reason, {
3486
+ pullPerformed,
3487
+ pushPerformed,
3488
+ commitCreated
3489
+ });
3490
+ }
3491
+ const baseBranch = normalized.branch ?? await resolveDefaultBranch(repoDir);
3492
+ const targetBranch = getPushTargetBranch(inspection.upstream, baseBranch, normalized.remote);
3493
+ const outcome = await resolveResultBranchPushConflict({
3494
+ normalized: { ...normalized, branch: targetBranch },
3495
+ repoDir,
3496
+ targetBranch,
3497
+ sourceRef: "HEAD"
1492
3498
  });
3499
+ pushDetails = outcome.details;
3500
+ if (outcome.blocked) {
3501
+ updateStatusFile(normalized, { last_error: outcome.blockReason });
3502
+ return withPushConflictStatus(status, outcome.blockReason, outcome.details, {
3503
+ pullPerformed,
3504
+ pushPerformed,
3505
+ commitCreated
3506
+ });
3507
+ }
3508
+ pushPerformed = true;
3509
+ await fetchResultsRepo(repoDir, normalized.remote, normalized.branch);
3510
+ inspection = await inspectResultsRepoGit(repoDir, normalized);
1493
3511
  }
1494
3512
  if ((inspection.behind ?? 0) > 0 && (inspection.ahead ?? 0) === 0) {
1495
3513
  if (!inspection.upstream) {
@@ -1547,18 +3565,45 @@ async function syncResultsRepoForProject(config) {
1547
3565
  await fetchResultsRepo(repoDir, normalized.remote, normalized.branch);
1548
3566
  inspection = await inspectResultsRepoGit(repoDir, normalized);
1549
3567
  } catch (error) {
1550
- await fetchResultsRepo(repoDir, normalized.remote, normalized.branch).catch(
1551
- () => void 0
1552
- );
1553
- inspection = await inspectResultsRepoGit(repoDir, normalized);
1554
- const status = withGitInspection(getResultsRepoStatus(normalized), inspection);
1555
- const reason = `Results repo push was rejected: ${getStatusMessage(error)}`;
1556
- updateStatusFile(normalized, { last_error: reason });
1557
- return withBlockedStatus(status, reason, {
1558
- pullPerformed,
1559
- pushPerformed,
1560
- commitCreated
1561
- });
3568
+ if (isNonFastForwardPushError(error)) {
3569
+ const outcome = await resolveResultBranchPushConflict({
3570
+ normalized: { ...normalized, branch: targetBranch },
3571
+ repoDir,
3572
+ targetBranch,
3573
+ sourceRef: "HEAD"
3574
+ });
3575
+ pushDetails = outcome.details;
3576
+ if (!outcome.blocked) {
3577
+ pushPerformed = true;
3578
+ await fetchResultsRepo(repoDir, normalized.remote, normalized.branch);
3579
+ inspection = await inspectResultsRepoGit(repoDir, normalized);
3580
+ } else {
3581
+ await fetchResultsRepo(repoDir, normalized.remote, normalized.branch).catch(
3582
+ () => void 0
3583
+ );
3584
+ inspection = await inspectResultsRepoGit(repoDir, normalized);
3585
+ const status = withGitInspection(getResultsRepoStatus(normalized), inspection);
3586
+ updateStatusFile(normalized, { last_error: outcome.blockReason });
3587
+ return withPushConflictStatus(status, outcome.blockReason, outcome.details, {
3588
+ pullPerformed,
3589
+ pushPerformed,
3590
+ commitCreated
3591
+ });
3592
+ }
3593
+ } else {
3594
+ await fetchResultsRepo(repoDir, normalized.remote, normalized.branch).catch(
3595
+ () => void 0
3596
+ );
3597
+ inspection = await inspectResultsRepoGit(repoDir, normalized);
3598
+ const status = withGitInspection(getResultsRepoStatus(normalized), inspection);
3599
+ const reason = `Results repo push was rejected: ${getStatusMessage(error)}`;
3600
+ updateStatusFile(normalized, { last_error: reason });
3601
+ return withBlockedStatus(status, reason, {
3602
+ pullPerformed,
3603
+ pushPerformed,
3604
+ commitCreated
3605
+ });
3606
+ }
1562
3607
  }
1563
3608
  }
1564
3609
  updateStatusFile(normalized, {
@@ -1568,7 +3613,8 @@ async function syncResultsRepoForProject(config) {
1568
3613
  return withActionFlags(await statusFromInspection(normalized, repoDir), {
1569
3614
  pullPerformed,
1570
3615
  pushPerformed,
1571
- commitCreated
3616
+ commitCreated,
3617
+ pushDetails
1572
3618
  });
1573
3619
  } catch (error) {
1574
3620
  updateStatusFile(normalized, {
@@ -1627,7 +3673,7 @@ async function stageResultsArtifacts(params) {
1627
3673
  }
1628
3674
  function resolveResultsRepoRunsDir(config) {
1629
3675
  const normalized = normalizeResultsConfig(config);
1630
- return path4.join(normalized.path, RESULTS_REPO_RESULTS_DIR, "runs");
3676
+ return path4.join(normalized.path, RESULTS_REPO_RESULTS_DIR);
1631
3677
  }
1632
3678
  async function directorySizeBytes(targetPath) {
1633
3679
  const entry = await stat(targetPath);
@@ -1649,8 +3695,9 @@ async function commitAndPushResultsBranch(params) {
1649
3695
  if (diffStdout.trim().length === 0) {
1650
3696
  return false;
1651
3697
  }
1652
- await ensureResultsRepoCommitIdentity(params.repoDir);
1653
- await runGit(["commit", "-m", params.commitMessage], { cwd: params.repoDir });
3698
+ await runGitWithFallbackCommitIdentity(["commit", "-m", params.commitMessage], {
3699
+ cwd: params.repoDir
3700
+ });
1654
3701
  await runGit(["push", "-u", "origin", params.branchName], { cwd: params.repoDir });
1655
3702
  return true;
1656
3703
  }
@@ -1685,7 +3732,6 @@ async function createDraftResultsPr(params) {
1685
3732
  );
1686
3733
  return stdout.trim();
1687
3734
  }
1688
- var DIRECT_PUSH_MAX_RETRIES = 3;
1689
3735
  async function hasUnpushedCommits(repoDir, upstreamRef, branch) {
1690
3736
  const { stdout } = await runGit(["rev-list", "--count", `${upstreamRef}..refs/heads/${branch}`], {
1691
3737
  cwd: repoDir,
@@ -1693,14 +3739,6 @@ async function hasUnpushedCommits(repoDir, upstreamRef, branch) {
1693
3739
  });
1694
3740
  return Number.parseInt(stdout.trim(), 10) > 0;
1695
3741
  }
1696
- async function countUnpushedCommits(repoDir, upstreamRef, branch) {
1697
- const { stdout } = await runGit(["rev-list", "--count", `${upstreamRef}..refs/heads/${branch}`], {
1698
- cwd: repoDir,
1699
- check: false
1700
- });
1701
- const count = Number.parseInt(stdout.trim(), 10);
1702
- return Number.isFinite(count) ? count : 0;
1703
- }
1704
3742
  async function assertValidResultsBranchName(repoDir, branch) {
1705
3743
  if (branch.length === 0 || branch.includes("..") || branch.startsWith("/") || branch.endsWith("/") || branch.endsWith(".lock") || [...branch].some((char) => {
1706
3744
  const code = char.charCodeAt(0);
@@ -1713,7 +3751,10 @@ async function assertValidResultsBranchName(repoDir, branch) {
1713
3751
  function normalizeDestinationPath(destinationPath) {
1714
3752
  const normalized = destinationPath.split(path4.sep).join("/");
1715
3753
  const segments = normalized.split("/").filter(Boolean);
1716
- if (segments.length === 0 || normalized.startsWith("/") || segments.some((segment) => segment === "..")) {
3754
+ if (segments.length === 0 || normalized.startsWith("/") || segments.some((segment) => segment === "..") || normalized.split("").some((char) => {
3755
+ const code = char.charCodeAt(0);
3756
+ return code <= 31 || code === 127;
3757
+ })) {
1717
3758
  throw new Error(`Invalid results destination path: ${destinationPath}`);
1718
3759
  }
1719
3760
  return segments.join("/");
@@ -1734,6 +3775,175 @@ async function listSourceFiles(sourceDir) {
1734
3775
  entries.sort();
1735
3776
  return entries;
1736
3777
  }
3778
+ function isRecord2(value) {
3779
+ return !!value && typeof value === "object" && !Array.isArray(value);
3780
+ }
3781
+ function artifactSidecarPointers(record) {
3782
+ if (!isRecord2(record) || !isRecord2(record.artifact_pointers)) {
3783
+ return [];
3784
+ }
3785
+ const pointers = [];
3786
+ for (const pointer of Object.values(record.artifact_pointers)) {
3787
+ if (!isRecord2(pointer)) {
3788
+ continue;
3789
+ }
3790
+ if (pointer.ref !== AGENTV_RESULTS_ARTIFACTS_REF || typeof pointer.path !== "string") {
3791
+ continue;
3792
+ }
3793
+ pointers.push({
3794
+ path: pointer.path,
3795
+ ...typeof pointer.sha256 === "string" ? { sha256: pointer.sha256 } : {},
3796
+ ...typeof pointer.object_version === "string" ? { objectVersion: pointer.object_version } : {}
3797
+ });
3798
+ }
3799
+ return pointers;
3800
+ }
3801
+ function artifactSidecarKey(destinationPath, pointerPath) {
3802
+ return path4.posix.join(
3803
+ RESULTS_REPO_RUNS_DIR,
3804
+ normalizeDestinationPath(destinationPath),
3805
+ normalizeDestinationPath(pointerPath)
3806
+ );
3807
+ }
3808
+ function collectArtifactSidecarPointers(sourceDir) {
3809
+ const indexPath = path4.join(sourceDir, RESULT_INDEX_FILENAME2);
3810
+ if (!existsSync(indexPath)) {
3811
+ return [];
3812
+ }
3813
+ const pointers = [];
3814
+ for (const line of readFileSync2(indexPath, "utf8").split(/\r?\n/)) {
3815
+ const trimmed = line.trim();
3816
+ if (!trimmed) {
3817
+ continue;
3818
+ }
3819
+ try {
3820
+ pointers.push(...artifactSidecarPointers(JSON.parse(trimmed)));
3821
+ } catch {
3822
+ }
3823
+ }
3824
+ return pointers;
3825
+ }
3826
+ function resolveArtifactPointerSourcePath(sourceDir, pointerPath) {
3827
+ const normalizedPointerPath = normalizeDestinationPath(pointerPath);
3828
+ const sourceRoot = path4.resolve(sourceDir);
3829
+ const sourcePath = path4.resolve(sourceRoot, ...normalizedPointerPath.split("/"));
3830
+ const relative = path4.relative(sourceRoot, sourcePath);
3831
+ if (relative.startsWith("..") || path4.isAbsolute(relative)) {
3832
+ throw new Error(`Artifact pointer path escapes run directory: ${pointerPath}`);
3833
+ }
3834
+ return sourcePath;
3835
+ }
3836
+ function verifyArtifactPointerChecksum(pointer, content) {
3837
+ const sha256 = createHash("sha256").update(content).digest("hex");
3838
+ if (pointer.sha256 && pointer.sha256 !== sha256) {
3839
+ throw new Error(`Artifact pointer checksum mismatch for ${pointer.path}`);
3840
+ }
3841
+ if (pointer.objectVersion && pointer.objectVersion !== `sha256:${sha256}`) {
3842
+ throw new Error(`Artifact pointer object_version mismatch for ${pointer.path}`);
3843
+ }
3844
+ }
3845
+ async function prepareArtifactSidecar(params) {
3846
+ if (params.pointers.length === 0) {
3847
+ return void 0;
3848
+ }
3849
+ const sidecarRoot = await mkdtemp(path4.join(os.tmpdir(), "agentv-artifact-sidecar-"));
3850
+ const copied = /* @__PURE__ */ new Set();
3851
+ const contentByPath = /* @__PURE__ */ new Map();
3852
+ try {
3853
+ for (const pointer of params.pointers) {
3854
+ const relativePointerPath = normalizeDestinationPath(pointer.path);
3855
+ let content = contentByPath.get(relativePointerPath);
3856
+ if (!content) {
3857
+ const sourcePath = resolveArtifactPointerSourcePath(params.sourceDir, relativePointerPath);
3858
+ content = readFileSync2(sourcePath);
3859
+ contentByPath.set(relativePointerPath, content);
3860
+ }
3861
+ verifyArtifactPointerChecksum(pointer, content);
3862
+ if (copied.has(relativePointerPath)) {
3863
+ continue;
3864
+ }
3865
+ const destinationPath = path4.join(sidecarRoot, ...relativePointerPath.split("/"));
3866
+ mkdirSync(path4.dirname(destinationPath), { recursive: true });
3867
+ writeFileSync(destinationPath, content);
3868
+ copied.add(relativePointerPath);
3869
+ }
3870
+ } catch (error) {
3871
+ await rm(sidecarRoot, { recursive: true, force: true }).catch(() => void 0);
3872
+ throw error;
3873
+ }
3874
+ if (copied.size === 0) {
3875
+ await rm(sidecarRoot, { recursive: true, force: true }).catch(() => void 0);
3876
+ return void 0;
3877
+ }
3878
+ return {
3879
+ sourceDir: sidecarRoot,
3880
+ cleanup: () => rm(sidecarRoot, { recursive: true, force: true })
3881
+ };
3882
+ }
3883
+ function rewritePublishedIndexLine(line, destinationPath) {
3884
+ if (!line.trim()) {
3885
+ return line;
3886
+ }
3887
+ let record;
3888
+ try {
3889
+ record = JSON.parse(line);
3890
+ } catch {
3891
+ return line;
3892
+ }
3893
+ if (!isRecord2(record) || !isRecord2(record.artifact_pointers)) {
3894
+ return line;
3895
+ }
3896
+ let changed = false;
3897
+ for (const pointer of Object.values(record.artifact_pointers)) {
3898
+ if (!isRecord2(pointer)) {
3899
+ continue;
3900
+ }
3901
+ if (pointer.ref !== AGENTV_RESULTS_ARTIFACTS_REF || typeof pointer.path !== "string") {
3902
+ continue;
3903
+ }
3904
+ const key = artifactSidecarKey(destinationPath, pointer.path);
3905
+ if (pointer.key !== key) {
3906
+ pointer.key = key;
3907
+ changed = true;
3908
+ }
3909
+ }
3910
+ return changed ? JSON.stringify(record) : line;
3911
+ }
3912
+ async function preparePublishedResultsSource(params) {
3913
+ if (params.pointers.length === 0) {
3914
+ return void 0;
3915
+ }
3916
+ const publishedRoot = await mkdtemp(path4.join(os.tmpdir(), "agentv-results-published-"));
3917
+ const omittedPaths = new Set(
3918
+ params.pointers.map((pointer) => normalizeDestinationPath(pointer.path))
3919
+ );
3920
+ try {
3921
+ const sourceFiles = await listSourceFiles(params.sourceDir);
3922
+ for (const sourceFile of sourceFiles) {
3923
+ const relativeFile = path4.relative(params.sourceDir, sourceFile).split(path4.sep).join("/");
3924
+ const destinationFile = path4.join(publishedRoot, ...relativeFile.split("/"));
3925
+ if (relativeFile === RESULT_INDEX_FILENAME2) {
3926
+ const original = readFileSync2(sourceFile, "utf8");
3927
+ const rewritten = original.split(/\r?\n/).map((line) => rewritePublishedIndexLine(line, params.destinationPath)).join("\n");
3928
+ mkdirSync(path4.dirname(destinationFile), { recursive: true });
3929
+ writeFileSync(destinationFile, rewritten);
3930
+ continue;
3931
+ }
3932
+ if (omittedPaths.has(relativeFile)) {
3933
+ continue;
3934
+ }
3935
+ mkdirSync(path4.dirname(destinationFile), { recursive: true });
3936
+ await cp(sourceFile, destinationFile, { dereference: false });
3937
+ }
3938
+ } catch (error) {
3939
+ await rm(publishedRoot, { recursive: true, force: true }).catch(() => void 0);
3940
+ throw error;
3941
+ }
3942
+ return {
3943
+ sourceDir: publishedRoot,
3944
+ cleanup: () => rm(publishedRoot, { recursive: true, force: true })
3945
+ };
3946
+ }
1737
3947
  async function getExistingRunTreePaths(repoDir, ref, destinationTreePath) {
1738
3948
  if (!ref) {
1739
3949
  return [];
@@ -1763,13 +3973,103 @@ async function ensureResultsBranchNotCheckedOut(repoDir, normalized) {
1763
3973
  if (currentBranch !== normalized.branch) {
1764
3974
  return;
1765
3975
  }
1766
- if (normalized.repo_path) {
3976
+ if (usesStorageBranchWorktree(normalized)) {
1767
3977
  throw new Error(
1768
3978
  `Refusing to publish results while '${normalized.branch}' is checked out in ${repoDir}`
1769
3979
  );
1770
3980
  }
1771
3981
  await runGit(["checkout", "--detach"], { cwd: repoDir });
1772
3982
  }
3983
+ async function commitStorageBranchWorktreePaths(params) {
3984
+ const { normalized } = params;
3985
+ const paths = [...new Set(params.paths.filter(isSafeResultsRepoPath))].sort();
3986
+ if (paths.length === 0) {
3987
+ return false;
3988
+ }
3989
+ await assertValidResultsBranchName(params.repoDir, normalized.branch);
3990
+ await ensureResultsBranchNotCheckedOut(params.repoDir, normalized);
3991
+ let base = await resolveStorageBranchBase({
3992
+ repoDir: params.repoDir,
3993
+ normalized
3994
+ });
3995
+ if (!base.baseRef) {
3996
+ await createOrphanResultsBranch(params.repoDir, normalized.branch);
3997
+ base = await resolveStorageBranchBase({
3998
+ repoDir: params.repoDir,
3999
+ normalized
4000
+ });
4001
+ }
4002
+ const indexRoot = await mkdtemp(path4.join(os.tmpdir(), "agentv-results-index-"));
4003
+ const indexFile = path4.join(indexRoot, "index");
4004
+ const indexEnv = { GIT_INDEX_FILE: indexFile };
4005
+ try {
4006
+ if (base.baseRef) {
4007
+ await runGit(["read-tree", base.baseRef], { cwd: params.repoDir, env: indexEnv });
4008
+ } else {
4009
+ await runGit(["read-tree", "--empty"], { cwd: params.repoDir, env: indexEnv });
4010
+ }
4011
+ for (const gitPath of paths) {
4012
+ const sourcePath = path4.join(params.repoDir, ...gitPath.split("/"));
4013
+ if (!existsSync(sourcePath)) {
4014
+ await runGit(["update-index", "--force-remove", "--", gitPath], {
4015
+ cwd: params.repoDir,
4016
+ env: indexEnv,
4017
+ check: false
4018
+ });
4019
+ continue;
4020
+ }
4021
+ const fileStat = await lstat(sourcePath);
4022
+ let mode = fileStat.mode & 73 ? "100755" : "100644";
4023
+ if (fileStat.isSymbolicLink()) {
4024
+ mode = "120000";
4025
+ }
4026
+ const { stdout: blob } = await runGit(["hash-object", "-w", "--no-filters", sourcePath], {
4027
+ cwd: params.repoDir
4028
+ });
4029
+ await runGit(["update-index", "--add", "--cacheinfo", `${mode},${blob.trim()},${gitPath}`], {
4030
+ cwd: params.repoDir,
4031
+ env: indexEnv
4032
+ });
4033
+ }
4034
+ const { stdout: newTreeStdout } = await runGit(["write-tree"], {
4035
+ cwd: params.repoDir,
4036
+ env: indexEnv
4037
+ });
4038
+ const newTree = newTreeStdout.trim();
4039
+ if (base.baseTree && newTree === base.baseTree) {
4040
+ return false;
4041
+ }
4042
+ const commitArgs = [
4043
+ "commit-tree",
4044
+ newTree,
4045
+ ...base.baseCommit ? ["-p", base.baseCommit] : [],
4046
+ "-m",
4047
+ params.commitMessage
4048
+ ];
4049
+ const { stdout: commitStdout } = await runGitWithFallbackCommitIdentity(commitArgs, {
4050
+ cwd: params.repoDir
4051
+ });
4052
+ const commitSha = commitStdout.trim();
4053
+ await runGit(
4054
+ [
4055
+ "update-ref",
4056
+ `refs/heads/${normalized.branch}`,
4057
+ commitSha,
4058
+ ...base.localExists ? [base.baseCommit ?? ""] : []
4059
+ ].filter(Boolean),
4060
+ { cwd: params.repoDir }
4061
+ );
4062
+ if (base.remoteExists) {
4063
+ await runGit(["branch", "--set-upstream-to", base.remoteRef, normalized.branch], {
4064
+ cwd: params.repoDir,
4065
+ check: false
4066
+ });
4067
+ }
4068
+ return true;
4069
+ } finally {
4070
+ await rm(indexRoot, { recursive: true, force: true }).catch(() => void 0);
4071
+ }
4072
+ }
1773
4073
  async function commitResultsRunWithTemporaryIndex(params) {
1774
4074
  const { normalized } = params;
1775
4075
  await assertValidResultsBranchName(params.repoDir, normalized.branch);
@@ -1844,7 +4144,6 @@ async function commitResultsRunWithTemporaryIndex(params) {
1844
4144
  upstreamRef: base.remoteExists ? base.remoteRef : void 0
1845
4145
  };
1846
4146
  }
1847
- await ensureResultsRepoCommitIdentity(params.repoDir);
1848
4147
  const commitArgs = [
1849
4148
  "commit-tree",
1850
4149
  newTree,
@@ -1854,7 +4153,9 @@ async function commitResultsRunWithTemporaryIndex(params) {
1854
4153
  "-m",
1855
4154
  `AgentV-Run: ${params.targetRunId}`
1856
4155
  ];
1857
- const { stdout: commitStdout } = await runGit(commitArgs, { cwd: params.repoDir });
4156
+ const { stdout: commitStdout } = await runGitWithFallbackCommitIdentity(commitArgs, {
4157
+ cwd: params.repoDir
4158
+ });
1858
4159
  const commitSha = commitStdout.trim();
1859
4160
  await runGit(
1860
4161
  [
@@ -1880,53 +4181,141 @@ async function commitResultsRunWithTemporaryIndex(params) {
1880
4181
  await rm(indexRoot, { recursive: true, force: true }).catch(() => void 0);
1881
4182
  }
1882
4183
  }
4184
+ function buildDirectPushResult(normalized, changed, outcome) {
4185
+ const details = outcome?.details;
4186
+ return {
4187
+ changed,
4188
+ push_conflict_policy: normalized.push_conflict_policy,
4189
+ ...outcome?.blocked === true && {
4190
+ blocked: true,
4191
+ block_reason: outcome.blockReason,
4192
+ sync_status: "push_conflict"
4193
+ },
4194
+ ...pushDetailsToWire(details)
4195
+ };
4196
+ }
4197
+ function mergeDirectPushResults(normalized, results) {
4198
+ const blocked = results.find((result) => result.blocked);
4199
+ if (blocked) {
4200
+ return blocked;
4201
+ }
4202
+ const detailed = [...results].reverse().find((result) => result.backup_ref !== void 0);
4203
+ return {
4204
+ changed: results.some((result) => result.changed),
4205
+ push_conflict_policy: normalized.push_conflict_policy,
4206
+ ...detailed?.backup_ref !== void 0 && {
4207
+ backup_ref: detailed.backup_ref
4208
+ },
4209
+ ...detailed?.target_branch !== void 0 && { target_branch: detailed.target_branch },
4210
+ ...detailed?.remote_commit !== void 0 && { remote_commit: detailed.remote_commit },
4211
+ ...detailed?.local_commit !== void 0 && { local_commit: detailed.local_commit },
4212
+ ...detailed?.backup_commit !== void 0 && { backup_commit: detailed.backup_commit },
4213
+ ...detailed?.previous_remote_commit !== void 0 && {
4214
+ previous_remote_commit: detailed.previous_remote_commit
4215
+ },
4216
+ ...detailed?.force_pushed_commit !== void 0 && {
4217
+ force_pushed_commit: detailed.force_pushed_commit
4218
+ },
4219
+ ...detailed?.lease_commit !== void 0 && { lease_commit: detailed.lease_commit }
4220
+ };
4221
+ }
1883
4222
  async function pushDirectResultsToStorageBranch(params) {
1884
- for (let attempt = 1; attempt <= DIRECT_PUSH_MAX_RETRIES; attempt++) {
1885
- try {
1886
- await runGit(
1887
- [
1888
- "push",
1889
- "--porcelain",
1890
- params.normalized.remote,
1891
- `refs/heads/${params.storageBranch}:refs/heads/${params.storageBranch}`
1892
- ],
1893
- { cwd: params.repoDir }
1894
- );
1895
- updateStatusFile(params.normalized, {
1896
- last_synced_at: (/* @__PURE__ */ new Date()).toISOString(),
1897
- last_error: void 0
1898
- });
1899
- await fetchResultsRepo(params.repoDir, params.normalized.remote, params.storageBranch).catch(
1900
- () => void 0
1901
- );
1902
- return;
1903
- } catch (error) {
1904
- const message = error instanceof Error ? error.message : String(error);
1905
- if (attempt < DIRECT_PUSH_MAX_RETRIES && message.includes("non-fast-forward")) {
1906
- await fetchResultsRepo(params.repoDir, params.normalized.remote, params.storageBranch);
1907
- const remoteRef = remoteBranchRef(params.storageBranch, params.normalized.remote);
1908
- const localOnlyCount = await gitRefExists(params.repoDir, remoteRef) ? await countUnpushedCommits(params.repoDir, remoteRef, params.storageBranch) : 0;
1909
- if (localOnlyCount > 1) {
1910
- throw new Error(
1911
- `Results branch has ${localOnlyCount} local commits and remote advanced; push manually after reconciling ${params.storageBranch}`
1912
- );
1913
- }
1914
- await commitResultsRunWithTemporaryIndex({
1915
- normalized: params.normalized,
1916
- repoDir: params.repoDir,
1917
- sourceDir: params.sourceDir,
1918
- destinationPath: params.destinationPath,
1919
- commitMessage: params.commitMessage,
1920
- targetRunId: params.targetRunId,
1921
- preferRemoteBase: true
1922
- });
1923
- } else {
1924
- throw error;
1925
- }
4223
+ try {
4224
+ await runGit(
4225
+ [
4226
+ "push",
4227
+ "--porcelain",
4228
+ params.normalized.remote,
4229
+ `refs/heads/${params.storageBranch}:refs/heads/${params.storageBranch}`
4230
+ ],
4231
+ { cwd: params.repoDir }
4232
+ );
4233
+ updateStatusFile(params.normalized, {
4234
+ last_synced_at: (/* @__PURE__ */ new Date()).toISOString(),
4235
+ last_error: void 0
4236
+ });
4237
+ await fetchResultsRepo(params.repoDir, params.normalized.remote, params.storageBranch).catch(
4238
+ () => void 0
4239
+ );
4240
+ return void 0;
4241
+ } catch (error) {
4242
+ if (!isNonFastForwardPushError(error)) {
4243
+ throw error;
4244
+ }
4245
+ const outcome = await resolveResultBranchPushConflict({
4246
+ normalized: params.normalized,
4247
+ repoDir: params.repoDir,
4248
+ targetBranch: params.storageBranch,
4249
+ sourceRef: `refs/heads/${params.storageBranch}`
4250
+ });
4251
+ if (outcome.blocked) {
4252
+ updateStatusFile(params.normalized, { last_error: outcome.blockReason });
4253
+ return outcome;
4254
+ }
4255
+ updateStatusFile(params.normalized, {
4256
+ last_synced_at: (/* @__PURE__ */ new Date()).toISOString(),
4257
+ last_error: void 0
4258
+ });
4259
+ await fetchResultsRepo(params.repoDir, params.normalized.remote, params.storageBranch).catch(
4260
+ () => void 0
4261
+ );
4262
+ return outcome;
4263
+ }
4264
+ }
4265
+ async function commitAndMaybePushRunTree(params) {
4266
+ const result = await commitResultsRunWithTemporaryIndex({
4267
+ normalized: params.normalized,
4268
+ repoDir: params.repoDir,
4269
+ sourceDir: params.sourceDir,
4270
+ destinationPath: params.destinationPath,
4271
+ commitMessage: params.commitMessage,
4272
+ targetRunId: params.targetRunId
4273
+ });
4274
+ if (!params.shouldPush) {
4275
+ updateStatusFile(params.normalized, { last_error: void 0 });
4276
+ return buildDirectPushResult(params.normalized, result.commitCreated);
4277
+ }
4278
+ if (!result.commitCreated) {
4279
+ const localBranchExists2 = await gitRefExists(
4280
+ params.repoDir,
4281
+ `refs/heads/${params.normalized.branch}`
4282
+ );
4283
+ const hasUnpushed = result.upstreamRef ? localBranchExists2 ? await hasUnpushedCommits(params.repoDir, result.upstreamRef, params.normalized.branch) : false : localBranchExists2;
4284
+ if (!hasUnpushed) {
4285
+ return buildDirectPushResult(params.normalized, false);
1926
4286
  }
4287
+ const aheadPaths = result.upstreamRef ? await getAheadPaths(
4288
+ params.repoDir,
4289
+ result.upstreamRef,
4290
+ `refs/heads/${params.normalized.branch}`
4291
+ ) : [];
4292
+ if (result.upstreamRef && !areSafeResultsRepoPaths(aheadPaths)) {
4293
+ const error = new Error("Results repo has non-results committed changes");
4294
+ updateStatusFile(params.normalized, { last_error: error.message });
4295
+ throw error;
4296
+ }
4297
+ const outcome2 = await pushDirectResultsToStorageBranch({
4298
+ normalized: params.normalized,
4299
+ repoDir: params.repoDir,
4300
+ storageBranch: params.normalized.branch
4301
+ });
4302
+ return buildDirectPushResult(params.normalized, !outcome2?.blocked, outcome2);
1927
4303
  }
4304
+ const outcome = await pushDirectResultsToStorageBranch({
4305
+ normalized: params.normalized,
4306
+ repoDir: params.repoDir,
4307
+ storageBranch: params.normalized.branch
4308
+ });
4309
+ return buildDirectPushResult(params.normalized, !outcome?.blocked, outcome);
1928
4310
  }
1929
4311
  async function directPushResults(params) {
4312
+ const result = await directPushResultsWithDetails(params);
4313
+ if (result.blocked) {
4314
+ throw new ResultsBranchPushConflictError(result);
4315
+ }
4316
+ return result.changed;
4317
+ }
4318
+ async function directPushResultsWithDetails(params) {
1930
4319
  const normalized = normalizeResultsConfig(params.config);
1931
4320
  const repoDir = await ensureResultsRepoClone(normalized);
1932
4321
  await fetchResultsRepo(repoDir, normalized.remote, normalized.branch).catch((error) => {
@@ -1940,53 +4329,61 @@ async function directPushResults(params) {
1940
4329
  branch: storageBranch
1941
4330
  };
1942
4331
  const targetRunId = buildGitRunId(params.destinationPath);
1943
- const result = await commitResultsRunWithTemporaryIndex({
1944
- normalized: storageConfig,
1945
- repoDir,
4332
+ const shouldPush = normalized.auto_push || normalized.require_push;
4333
+ const sidecarPointers = collectArtifactSidecarPointers(params.sourceDir);
4334
+ const sidecar = await prepareArtifactSidecar({
1946
4335
  sourceDir: params.sourceDir,
1947
- destinationPath: params.destinationPath,
1948
- commitMessage: params.commitMessage,
1949
- targetRunId
4336
+ pointers: sidecarPointers
1950
4337
  });
1951
- const shouldPush = normalized.auto_push || normalized.require_push;
1952
- if (!shouldPush) {
1953
- updateStatusFile(storageConfig, { last_error: void 0 });
1954
- return result.commitCreated;
1955
- }
1956
- if (!result.commitCreated) {
1957
- const hasUnpushed = result.upstreamRef ? await hasUnpushedCommits(repoDir, result.upstreamRef, storageBranch) : true;
1958
- if (hasUnpushed) {
1959
- const aheadPaths = result.upstreamRef ? await getAheadPaths(repoDir, result.upstreamRef, `refs/heads/${storageBranch}`) : [];
1960
- if (result.upstreamRef && !areSafeResultsRepoPaths(aheadPaths)) {
1961
- const error = new Error("Results repo has non-results committed changes");
1962
- updateStatusFile(storageConfig, { last_error: error.message });
1963
- throw error;
1964
- }
1965
- await pushDirectResultsToStorageBranch({
1966
- normalized: storageConfig,
4338
+ let publishedResultsSource;
4339
+ try {
4340
+ publishedResultsSource = await preparePublishedResultsSource({
4341
+ sourceDir: params.sourceDir,
4342
+ destinationPath: params.destinationPath,
4343
+ pointers: sidecarPointers
4344
+ });
4345
+ const pushResults = [];
4346
+ if (sidecar) {
4347
+ await fetchResultsRepo(repoDir, normalized.remote, AGENTV_RESULTS_ARTIFACTS_REF).catch(
4348
+ (error) => {
4349
+ if (normalized.require_push) {
4350
+ throw error;
4351
+ }
4352
+ }
4353
+ );
4354
+ const sidecarResult = await commitAndMaybePushRunTree({
4355
+ normalized: {
4356
+ ...normalized,
4357
+ branch: AGENTV_RESULTS_ARTIFACTS_REF
4358
+ },
1967
4359
  repoDir,
1968
- storageBranch,
1969
- upstreamRef: result.upstreamRef,
1970
- sourceDir: params.sourceDir,
4360
+ sourceDir: sidecar.sourceDir,
1971
4361
  destinationPath: params.destinationPath,
1972
- commitMessage: params.commitMessage,
1973
- targetRunId
4362
+ commitMessage: `chore(results): publish artifact sidecars for ${targetRunId}`,
4363
+ targetRunId,
4364
+ shouldPush
1974
4365
  });
1975
- return true;
4366
+ pushResults.push(sidecarResult);
4367
+ if (sidecarResult.blocked) {
4368
+ return sidecarResult;
4369
+ }
1976
4370
  }
1977
- return false;
4371
+ pushResults.push(
4372
+ await commitAndMaybePushRunTree({
4373
+ normalized: storageConfig,
4374
+ repoDir,
4375
+ sourceDir: publishedResultsSource?.sourceDir ?? params.sourceDir,
4376
+ destinationPath: params.destinationPath,
4377
+ commitMessage: params.commitMessage,
4378
+ targetRunId,
4379
+ shouldPush
4380
+ })
4381
+ );
4382
+ return mergeDirectPushResults(normalized, pushResults);
4383
+ } finally {
4384
+ await publishedResultsSource?.cleanup().catch(() => void 0);
4385
+ await sidecar?.cleanup().catch(() => void 0);
1978
4386
  }
1979
- await pushDirectResultsToStorageBranch({
1980
- normalized: storageConfig,
1981
- repoDir,
1982
- storageBranch,
1983
- upstreamRef: result.upstreamRef,
1984
- sourceDir: params.sourceDir,
1985
- destinationPath: params.destinationPath,
1986
- commitMessage: params.commitMessage,
1987
- targetRunId
1988
- });
1989
- return true;
1990
4387
  }
1991
4388
  function buildGitRunId(relativeRunPath) {
1992
4389
  const normalized = relativeRunPath.split(path4.sep).join("/");
@@ -2091,6 +4488,39 @@ function parseGitBatchBlobs(output) {
2091
4488
  }
2092
4489
  return blobs;
2093
4490
  }
4491
+ async function readGitResultArtifact(params) {
4492
+ const artifactRef = params.ref ?? AGENTV_RESULTS_ARTIFACTS_REF;
4493
+ const normalizedKey = normalizeDestinationPath(params.key);
4494
+ const remote = params.remote ?? "origin";
4495
+ if (!artifactRef.startsWith("refs/")) {
4496
+ await fetchResultsArtifactRef(params.repoDir, remote, artifactRef).catch(() => void 0);
4497
+ }
4498
+ const candidateRefs = [
4499
+ `refs/remotes/${remote}/${artifactRef}`,
4500
+ `refs/heads/${artifactRef}`,
4501
+ artifactRef
4502
+ ].filter((candidate, index, all) => all.indexOf(candidate) === index);
4503
+ for (const candidateRef of candidateRefs) {
4504
+ const blobs = parseGitBatchBlobs(
4505
+ await runGitBatch(params.repoDir, `${candidateRef}:${normalizedKey}
4506
+ `)
4507
+ );
4508
+ const blob = blobs[0];
4509
+ if (!blob) {
4510
+ continue;
4511
+ }
4512
+ verifyArtifactPointerChecksum(
4513
+ {
4514
+ path: normalizedKey,
4515
+ ...params.sha256 ? { sha256: params.sha256 } : {},
4516
+ ...params.objectVersion ? { objectVersion: params.objectVersion } : {}
4517
+ },
4518
+ blob.content
4519
+ );
4520
+ return Buffer.from(blob.content);
4521
+ }
4522
+ return void 0;
4523
+ }
2094
4524
  function buildWipBranchName(runDir) {
2095
4525
  const hostname = os.hostname().replace(/[^A-Za-z0-9._-]+/g, "-").slice(0, 40);
2096
4526
  const runBasename = path4.basename(runDir).replace(/[^A-Za-z0-9._-]+/g, "-").slice(0, 60);
@@ -2117,8 +4547,6 @@ async function setupWipWorktree(params) {
2117
4547
  await runGit(["worktree", "add", "-B", params.wipBranch, worktreeDir, baseRef], {
2118
4548
  cwd: cloneDir
2119
4549
  });
2120
- await runGit(["config", "user.email", "agentv@wip-checkpoint"], { cwd: worktreeDir });
2121
- await runGit(["config", "user.name", "AgentV WIP Checkpoint"], { cwd: worktreeDir });
2122
4550
  return {
2123
4551
  wipBranch: params.wipBranch,
2124
4552
  worktreeDir,
@@ -2156,7 +4584,7 @@ async function pushWipCheckpoint(params) {
2156
4584
  return false;
2157
4585
  }
2158
4586
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2159
- await runGit(
4587
+ await runGitWithFallbackCommitIdentity(
2160
4588
  ["commit", "--amend", "-m", `wip(results): checkpoint ${params.handle.wipBranch} ${timestamp}`],
2161
4589
  { cwd: params.handle.worktreeDir }
2162
4590
  );
@@ -2282,10 +4710,10 @@ async function materializeGitRun(repoDir, relativeRunPath, ref = "origin/main")
2282
4710
  }
2283
4711
  async function syncProject(entry) {
2284
4712
  if (!entry.repoUrl) {
2285
- throw new Error(`Project '${entry.id}' has no repo_url defined`);
4713
+ throw new Error(`Project '${entry.id}' has no repo.url defined`);
2286
4714
  }
2287
4715
  if (!entry.ref) {
2288
- throw new Error(`Project '${entry.id}' has no ref defined`);
4716
+ throw new Error(`Project '${entry.id}' has no repo.branch defined`);
2289
4717
  }
2290
4718
  const dest = entry.path;
2291
4719
  if (existsSync2(`${dest}/.git`)) {
@@ -3412,6 +5840,10 @@ function createAgentKernel() {
3412
5840
  }
3413
5841
 
3414
5842
  export {
5843
+ TRACE_SESSION_SCHEMA_VERSION,
5844
+ traceEnvelopeToTraceSessionResponse,
5845
+ buildTraceSpanTree,
5846
+ normalizeTraceArtifactToTraceSessionResponse,
3415
5847
  transpileEvalYaml,
3416
5848
  transpileEvalYamlFile,
3417
5849
  getOutputFilenames,
@@ -3437,6 +5869,8 @@ export {
3437
5869
  pushResultsRepoBranch,
3438
5870
  createDraftResultsPr,
3439
5871
  directPushResults,
5872
+ directPushResultsWithDetails,
5873
+ readGitResultArtifact,
3440
5874
  buildWipBranchName,
3441
5875
  setupWipWorktree,
3442
5876
  pushWipCheckpoint,
@@ -3460,4 +5894,4 @@ export {
3460
5894
  TranscriptProvider,
3461
5895
  createAgentKernel
3462
5896
  };
3463
- //# sourceMappingURL=chunk-RBOP5X36.js.map
5897
+ //# sourceMappingURL=chunk-5NDSGGMJ.js.map