agentv 4.42.4 → 4.44.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.
- package/README.md +2 -2
- package/dist/{artifact-writer-7PXSB6LM.js → artifact-writer-72MQQELS.js} +4 -4
- package/dist/{chunk-ISPJY2AX.js → chunk-2N3YSEMI.js} +2454 -415
- package/dist/chunk-2N3YSEMI.js.map +1 -0
- package/dist/{chunk-ZPFM52HV.js → chunk-5DYGN547.js} +2379 -635
- package/dist/chunk-5DYGN547.js.map +1 -0
- package/dist/{chunk-N6U3GXMF.js → chunk-CXHYTYHU.js} +620 -342
- package/dist/chunk-CXHYTYHU.js.map +1 -0
- package/dist/{chunk-6SQDBXT7.js → chunk-KAGZRUO6.js} +2 -2
- package/dist/chunk-KAGZRUO6.js.map +1 -0
- package/dist/{chunk-RBOP5X36.js → chunk-NCNDA3F6.js} +2605 -171
- package/dist/chunk-NCNDA3F6.js.map +1 -0
- package/dist/cli.js +5 -5
- package/dist/dashboard/assets/index-1Hvy8sBy.js +119 -0
- package/dist/dashboard/assets/{index-uXJsSG83.js → index-DitffAxS.js} +1 -1
- package/dist/dashboard/assets/index-J5sPcQO3.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/{dist-VQWTETNY.js → dist-L4YCQOQ6.js} +67 -3
- package/dist/index.js +9 -5
- package/dist/{interactive-TNRJEFA2.js → interactive-DRNRKLEQ.js} +5 -5
- package/dist/skills/agentv-bench/SKILL.md +2 -2
- package/dist/skills/agentv-bench/agents/executor.md +1 -1
- package/dist/skills/agentv-bench/agents/grader.md +1 -1
- package/dist/skills/agentv-bench/references/autoresearch.md +4 -4
- package/dist/skills/agentv-bench/references/eval-yaml-spec.md +2 -1
- package/dist/skills/agentv-bench/references/schemas.md +1 -1
- package/dist/skills/agentv-bench/references/subagent-pipeline.md +4 -4
- package/dist/skills/agentv-eval-writer/SKILL.md +24 -17
- package/dist/skills/agentv-eval-writer/references/config-schema.json +130 -0
- package/dist/skills/agentv-eval-writer/references/eval-schema.json +192 -0
- package/dist/{ts-eval-loader-PYEU2PMP-YLXIQTEQ.js → ts-eval-loader-S7ESROX2-6CB3HB6H.js} +2 -2
- package/package.json +6 -5
- package/dist/chunk-6SQDBXT7.js.map +0 -1
- package/dist/chunk-ISPJY2AX.js.map +0 -1
- package/dist/chunk-N6U3GXMF.js.map +0 -1
- package/dist/chunk-RBOP5X36.js.map +0 -1
- package/dist/chunk-ZPFM52HV.js.map +0 -1
- package/dist/dashboard/assets/index-BwBJwHp4.js +0 -118
- package/dist/dashboard/assets/index-C5v6_zhR.css +0 -1
- /package/dist/{artifact-writer-7PXSB6LM.js.map → artifact-writer-72MQQELS.js.map} +0 -0
- /package/dist/{dist-VQWTETNY.js.map → dist-L4YCQOQ6.js.map} +0 -0
- /package/dist/{interactive-TNRJEFA2.js.map → interactive-DRNRKLEQ.js.map} +0 -0
- /package/dist/{ts-eval-loader-PYEU2PMP-YLXIQTEQ.js.map → ts-eval-loader-S7ESROX2-6CB3HB6H.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-
|
|
24
|
+
} from "./chunk-5DYGN547.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
|
|
686
|
-
var
|
|
687
|
-
var
|
|
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
|
|
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
|
|
718
|
-
const
|
|
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
|
-
|
|
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 && !
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
{
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
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
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
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
|
|
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
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
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
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
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
|
|
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
|
|
1653
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
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
|
|
1944
|
-
|
|
1945
|
-
|
|
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
|
-
|
|
1948
|
-
commitMessage: params.commitMessage,
|
|
1949
|
-
targetRunId
|
|
4336
|
+
pointers: sidecarPointers
|
|
1950
4337
|
});
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
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
|
-
|
|
1969
|
-
upstreamRef: result.upstreamRef,
|
|
1970
|
-
sourceDir: params.sourceDir,
|
|
4360
|
+
sourceDir: sidecar.sourceDir,
|
|
1971
4361
|
destinationPath: params.destinationPath,
|
|
1972
|
-
commitMessage:
|
|
1973
|
-
targetRunId
|
|
4362
|
+
commitMessage: `chore(results): publish artifact sidecars for ${targetRunId}`,
|
|
4363
|
+
targetRunId,
|
|
4364
|
+
shouldPush
|
|
1974
4365
|
});
|
|
1975
|
-
|
|
4366
|
+
pushResults.push(sidecarResult);
|
|
4367
|
+
if (sidecarResult.blocked) {
|
|
4368
|
+
return sidecarResult;
|
|
4369
|
+
}
|
|
1976
4370
|
}
|
|
1977
|
-
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
5897
|
+
//# sourceMappingURL=chunk-NCNDA3F6.js.map
|