@tangle-network/traces 0.16.4 → 0.17.0
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 +16 -1
- package/dist/{chunk-SMT635QV.js → chunk-4QY4U4XW.js} +495 -130
- package/dist/cli.js +6 -5
- package/dist/index.d.ts +126 -26
- package/dist/index.js +7 -1
- package/docs/trace-analysts.md +6 -2
- package/package.json +2 -2
|
@@ -9,13 +9,13 @@ function sourceFileId(path) {
|
|
|
9
9
|
function pointerPart(value) {
|
|
10
10
|
return value.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
11
11
|
}
|
|
12
|
-
function locateSourceObjects(value, path,
|
|
12
|
+
function locateSourceObjects(value, path, bytes2, offset = 0) {
|
|
13
13
|
if (!value || typeof value !== "object") return;
|
|
14
14
|
const record2 = {
|
|
15
15
|
sourceId: sourceFileId(path),
|
|
16
16
|
recordOffset: offset,
|
|
17
|
-
recordBytes:
|
|
18
|
-
recordSha256: createHash("sha256").update(
|
|
17
|
+
recordBytes: bytes2.length,
|
|
18
|
+
recordSha256: createHash("sha256").update(bytes2).digest("hex")
|
|
19
19
|
};
|
|
20
20
|
const pending = [{ value, pointer: "#" }];
|
|
21
21
|
while (pending.length > 0) {
|
|
@@ -317,35 +317,35 @@ function serialize(value) {
|
|
|
317
317
|
return { value: canonicalJson(value), mimeType: "application/json" };
|
|
318
318
|
}
|
|
319
319
|
function utf8Prefix(value, maxBytes) {
|
|
320
|
-
let
|
|
320
|
+
let bytes2 = 0;
|
|
321
321
|
let end = 0;
|
|
322
322
|
for (const char of value) {
|
|
323
323
|
const charBytes = Buffer.byteLength(char);
|
|
324
|
-
if (
|
|
325
|
-
|
|
324
|
+
if (bytes2 + charBytes > maxBytes) break;
|
|
325
|
+
bytes2 += charBytes;
|
|
326
326
|
end += char.length;
|
|
327
327
|
}
|
|
328
328
|
return Buffer.from(value.slice(0, end), "utf8").toString("utf8");
|
|
329
329
|
}
|
|
330
|
-
function truncatedValue(value,
|
|
330
|
+
function truncatedValue(value, bytes2) {
|
|
331
331
|
const longestMarker = `
|
|
332
|
-
[truncated ${
|
|
332
|
+
[truncated ${bytes2} bytes]`;
|
|
333
333
|
const prefix = utf8Prefix(value, TOOL_IO_VALUE_MAX_BYTES - Buffer.byteLength(longestMarker));
|
|
334
|
-
const omittedBytes =
|
|
334
|
+
const omittedBytes = bytes2 - Buffer.byteLength(prefix);
|
|
335
335
|
return `${prefix}
|
|
336
336
|
[truncated ${omittedBytes} bytes]`;
|
|
337
337
|
}
|
|
338
338
|
function valueAttributes(side, raw) {
|
|
339
339
|
const serialized = serialize(raw);
|
|
340
340
|
if (!serialized) return {};
|
|
341
|
-
const
|
|
341
|
+
const bytes2 = Buffer.byteLength(serialized.value);
|
|
342
342
|
const sha2562 = createHash2("sha256").update(serialized.value).digest("hex");
|
|
343
|
-
const truncated =
|
|
344
|
-
const value = truncated ? truncatedValue(serialized.value,
|
|
343
|
+
const truncated = bytes2 > TOOL_IO_VALUE_MAX_BYTES;
|
|
344
|
+
const value = truncated ? truncatedValue(serialized.value, bytes2) : serialized.value;
|
|
345
345
|
return {
|
|
346
346
|
[`${side}.value`]: value,
|
|
347
347
|
[`${side}.mime_type`]: serialized.mimeType,
|
|
348
|
-
[`traces.${side}.bytes`]:
|
|
348
|
+
[`traces.${side}.bytes`]: bytes2,
|
|
349
349
|
[`traces.${side}.sha256`]: sha2562,
|
|
350
350
|
[`traces.${side}.truncated`]: truncated
|
|
351
351
|
};
|
|
@@ -1011,16 +1011,36 @@ function toOpenInferenceSpan(s) {
|
|
|
1011
1011
|
}
|
|
1012
1012
|
function serializeSpans(spans) {
|
|
1013
1013
|
if (spans.length === 0) return "";
|
|
1014
|
-
|
|
1015
|
-
|
|
1014
|
+
let out = "";
|
|
1015
|
+
for (const line of iterSerializedSpans(spans)) out += line;
|
|
1016
|
+
return out;
|
|
1017
|
+
}
|
|
1018
|
+
function* iterSerializedSpans(spans) {
|
|
1019
|
+
for (const s of validateOtlpSpans(spans, "serialized spans")) {
|
|
1020
|
+
yield `${JSON.stringify(toOpenInferenceSpan(s))}
|
|
1016
1021
|
`;
|
|
1022
|
+
}
|
|
1017
1023
|
}
|
|
1024
|
+
var OTLP_WRITE_CHUNK_CHARS = 4 * 1024 * 1024;
|
|
1018
1025
|
async function writeOtlpFile(spans, outPath) {
|
|
1019
|
-
const { mkdtemp: mkdtemp8,
|
|
1026
|
+
const { mkdtemp: mkdtemp8, open: open2 } = await import("fs/promises");
|
|
1020
1027
|
const { tmpdir: tmpdir8 } = await import("os");
|
|
1021
1028
|
const { join: join35 } = await import("path");
|
|
1022
1029
|
const path = outPath ?? join35(await mkdtemp8(join35(tmpdir8(), "traces-")), "spans.otlp.jsonl");
|
|
1023
|
-
await
|
|
1030
|
+
const handle = await open2(path, "w");
|
|
1031
|
+
try {
|
|
1032
|
+
let chunk = "";
|
|
1033
|
+
for (const line of iterSerializedSpans(spans)) {
|
|
1034
|
+
chunk += line;
|
|
1035
|
+
if (chunk.length >= OTLP_WRITE_CHUNK_CHARS) {
|
|
1036
|
+
await handle.write(chunk);
|
|
1037
|
+
chunk = "";
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
if (chunk.length > 0) await handle.write(chunk);
|
|
1041
|
+
} finally {
|
|
1042
|
+
await handle.close();
|
|
1043
|
+
}
|
|
1024
1044
|
return path;
|
|
1025
1045
|
}
|
|
1026
1046
|
|
|
@@ -1367,18 +1387,18 @@ function isMissingJsonSource(error) {
|
|
|
1367
1387
|
}
|
|
1368
1388
|
async function readJsonFile(path, options = {}) {
|
|
1369
1389
|
let raw;
|
|
1370
|
-
let
|
|
1390
|
+
let bytes2;
|
|
1371
1391
|
try {
|
|
1372
1392
|
if (options.captureSources) {
|
|
1373
|
-
|
|
1374
|
-
raw =
|
|
1393
|
+
bytes2 = await readFile3(path);
|
|
1394
|
+
raw = bytes2.toString("utf8");
|
|
1375
1395
|
} else raw = await readFile3(path, "utf8");
|
|
1376
1396
|
} catch (error) {
|
|
1377
1397
|
throw new JsonSourceError(path, "read", error);
|
|
1378
1398
|
}
|
|
1379
1399
|
try {
|
|
1380
1400
|
const value = JSON.parse(raw);
|
|
1381
|
-
if (
|
|
1401
|
+
if (bytes2) locateSourceObjects(value, path, bytes2);
|
|
1382
1402
|
return value;
|
|
1383
1403
|
} catch (error) {
|
|
1384
1404
|
throw new JsonSourceError(path, "parse", pathSafeParseCause(error));
|
|
@@ -1585,9 +1605,9 @@ function parseLine(rawLine, path, lineNumber, byteOffset, options) {
|
|
|
1585
1605
|
const jsonBytes = rawLine.at(-1) === 13 ? rawLine.subarray(0, -1) : rawLine;
|
|
1586
1606
|
if (jsonBytes.length === 0) return void 0;
|
|
1587
1607
|
if (!isUtf8(jsonBytes)) return handleCorruption(rawLine, path, lineNumber, byteOffset, options);
|
|
1588
|
-
const json2 = jsonBytes.toString("utf8");
|
|
1589
|
-
if (json2.trim().length === 0) return void 0;
|
|
1590
1608
|
try {
|
|
1609
|
+
const json2 = jsonBytes.toString("utf8");
|
|
1610
|
+
if (json2.trim().length === 0) return void 0;
|
|
1591
1611
|
const value = JSON.parse(json2);
|
|
1592
1612
|
if (options.captureSources) locateSourceObjects(value, path, rawLine, byteOffset);
|
|
1593
1613
|
return value;
|
|
@@ -1615,12 +1635,12 @@ async function* readJsonl(path, options = { mode: "strict" }) {
|
|
|
1615
1635
|
try {
|
|
1616
1636
|
for await (const chunk of input) {
|
|
1617
1637
|
options.signal?.throwIfAborted();
|
|
1618
|
-
const
|
|
1638
|
+
const bytes2 = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1619
1639
|
let start = 0;
|
|
1620
|
-
let newline =
|
|
1640
|
+
let newline = bytes2.indexOf(10);
|
|
1621
1641
|
while (newline !== -1) {
|
|
1622
1642
|
options.signal?.throwIfAborted();
|
|
1623
|
-
fragments.push(
|
|
1643
|
+
fragments.push(bytes2.subarray(start, newline));
|
|
1624
1644
|
const rawLine = fragments.length === 1 ? fragments[0] : Buffer.concat(fragments);
|
|
1625
1645
|
fragments = [];
|
|
1626
1646
|
lineNumber += 1;
|
|
@@ -1628,9 +1648,9 @@ async function* readJsonl(path, options = { mode: "strict" }) {
|
|
|
1628
1648
|
if (parsed !== void 0) yield parsed;
|
|
1629
1649
|
byteOffset += rawLine.length + 1;
|
|
1630
1650
|
start = newline + 1;
|
|
1631
|
-
newline =
|
|
1651
|
+
newline = bytes2.indexOf(10, start);
|
|
1632
1652
|
}
|
|
1633
|
-
if (start <
|
|
1653
|
+
if (start < bytes2.length) fragments.push(bytes2.subarray(start));
|
|
1634
1654
|
}
|
|
1635
1655
|
if (fragments.length > 0) {
|
|
1636
1656
|
options.signal?.throwIfAborted();
|
|
@@ -2229,6 +2249,7 @@ function createClaudeStream(startStep) {
|
|
|
2229
2249
|
step: startStep,
|
|
2230
2250
|
sawUserTurn: false,
|
|
2231
2251
|
promptSpans: [],
|
|
2252
|
+
promptSpanByUid: /* @__PURE__ */ new Map(),
|
|
2232
2253
|
sawRecordedOrigin: false,
|
|
2233
2254
|
firstRecordAt: null,
|
|
2234
2255
|
lastRecordAt: null
|
|
@@ -2261,6 +2282,9 @@ function projectToolResult(toolUseId, isError, output, structuredWorkflowRun, ou
|
|
|
2261
2282
|
}
|
|
2262
2283
|
function projectClaudeEvent(event) {
|
|
2263
2284
|
const timestamp = event.timestamp ?? EPOCH;
|
|
2285
|
+
if (event.type === "system" && event.subtype === "compact_boundary") {
|
|
2286
|
+
return { kind: "compaction", timestamp: event.timestamp ?? "", metadata: event.compactMetadata ?? {}, parentToolUseId: event.parent_tool_use_id };
|
|
2287
|
+
}
|
|
2264
2288
|
if (event.type === "assistant" && event.message) {
|
|
2265
2289
|
const tools = [];
|
|
2266
2290
|
for (const block of asBlocks(event.message.content)) {
|
|
@@ -2402,7 +2426,7 @@ function indexWorkflowProjection(projection, index) {
|
|
|
2402
2426
|
function fingerprintClaudeEvent(event) {
|
|
2403
2427
|
return createHash6("sha256").update(JSON.stringify(
|
|
2404
2428
|
event,
|
|
2405
|
-
(key, value) => key === "contentSource" || key.startsWith(SOURCE_ATTRIBUTE_PREFIX) ? void 0 : value
|
|
2429
|
+
(key, value) => key === "contentSource" || key === "originKind" || key.startsWith(SOURCE_ATTRIBUTE_PREFIX) ? void 0 : value
|
|
2406
2430
|
)).digest("hex");
|
|
2407
2431
|
}
|
|
2408
2432
|
function startsClaudeTask(projection) {
|
|
@@ -2416,21 +2440,43 @@ function startsClaudeTask(projection) {
|
|
|
2416
2440
|
function distinctClaudeEvent(event, seen, sourcePath, fallbackUid) {
|
|
2417
2441
|
const projection = projectClaudeEvent(event);
|
|
2418
2442
|
if (event.uuid) {
|
|
2443
|
+
const uid = event.uuid;
|
|
2419
2444
|
const fingerprint = fingerprintClaudeEvent(projection);
|
|
2420
|
-
const
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2445
|
+
const originKind = (projection.kind === "user" || projection.kind === "queued-prompt" ? projection.originKind : null) ?? null;
|
|
2446
|
+
const previous = seen.get(uid);
|
|
2447
|
+
if (previous) {
|
|
2448
|
+
if (previous.fingerprint !== fingerprint) {
|
|
2449
|
+
throw new ClaudeEventConflictError(sourcePath, uid);
|
|
2450
|
+
}
|
|
2451
|
+
if (previous.originKind === null && originKind !== null) return { originEnrichment: originKind, uid };
|
|
2452
|
+
if (previous.originKind !== null && originKind !== null && previous.originKind !== originKind) {
|
|
2453
|
+
throw new ClaudeEventConflictError(sourcePath, uid);
|
|
2424
2454
|
}
|
|
2425
2455
|
return void 0;
|
|
2426
2456
|
}
|
|
2427
|
-
seen.set(
|
|
2457
|
+
seen.set(uid, { fingerprint, originKind });
|
|
2428
2458
|
}
|
|
2429
2459
|
return { projection, uid: event.uuid ?? fallbackUid };
|
|
2430
2460
|
}
|
|
2461
|
+
function applyOriginEnrichment(uid, originKind, state) {
|
|
2462
|
+
const prompt = state.promptSpanByUid.get(uid);
|
|
2463
|
+
if (!prompt) return;
|
|
2464
|
+
if (prompt.attributes[ORIGIN_RECORDED_ATTR] === true) return;
|
|
2465
|
+
state.sawRecordedOrigin = true;
|
|
2466
|
+
prompt.attributes[ORIGIN_RECORDED_ATTR] = true;
|
|
2467
|
+
prompt.attributes[ORIGIN_KIND_ATTR] = originKind;
|
|
2468
|
+
prompt.attributes[ACTOR_ATTR] = claudeActorFromOrigin(
|
|
2469
|
+
originKind,
|
|
2470
|
+
() => prompt.attributes[ACTOR_ATTR]
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2431
2473
|
function consumeDistinctClaudeEvent(event, ctx, state, seen, sourcePath) {
|
|
2432
2474
|
const accepted = distinctClaudeEvent(event, seen, sourcePath, `step${state.step}`);
|
|
2433
2475
|
if (!accepted) return false;
|
|
2476
|
+
if ("originEnrichment" in accepted) {
|
|
2477
|
+
applyOriginEnrichment(accepted.uid, accepted.originEnrichment, state);
|
|
2478
|
+
return false;
|
|
2479
|
+
}
|
|
2434
2480
|
consumeClaudeEvent(accepted.projection, accepted.uid, ctx, state);
|
|
2435
2481
|
return true;
|
|
2436
2482
|
}
|
|
@@ -2440,6 +2486,34 @@ function inStreamParent(parentToolUseId, state) {
|
|
|
2440
2486
|
return parentTool ? { span: parentTool, confidence: "explicit" } : void 0;
|
|
2441
2487
|
}
|
|
2442
2488
|
function consumeClaudeEvent(event, uid, ctx, state) {
|
|
2489
|
+
if (event.kind === "compaction") {
|
|
2490
|
+
const extra = {};
|
|
2491
|
+
const numericFields = {
|
|
2492
|
+
preTokens: "traces.compaction.tokens_before",
|
|
2493
|
+
postTokens: "traces.compaction.tokens_after",
|
|
2494
|
+
durationMs: "traces.compaction.latency_ms",
|
|
2495
|
+
cumulativeDroppedTokens: "traces.compaction.cumulative_dropped_tokens"
|
|
2496
|
+
};
|
|
2497
|
+
for (const [field, key] of Object.entries(numericFields)) {
|
|
2498
|
+
const value = event.metadata[field];
|
|
2499
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) extra[key] = value;
|
|
2500
|
+
}
|
|
2501
|
+
if (typeof event.metadata.trigger === "string") extra["traces.compaction.trigger"] = event.metadata.trigger;
|
|
2502
|
+
const parent = inStreamParent(event.parentToolUseId, state);
|
|
2503
|
+
state.spans.push(span({
|
|
2504
|
+
traceId: ctx.traceId,
|
|
2505
|
+
spanId: ctx.idPrefix + uid + ":compacted",
|
|
2506
|
+
parentSpanId: parent ? parent.span.span_id : ctx.rootParent,
|
|
2507
|
+
name: "session.compacted",
|
|
2508
|
+
kind: "CHAIN",
|
|
2509
|
+
startTime: event.timestamp,
|
|
2510
|
+
service: SERVICE2,
|
|
2511
|
+
agent: ctx.agent,
|
|
2512
|
+
step: state.step++,
|
|
2513
|
+
extra
|
|
2514
|
+
}));
|
|
2515
|
+
return;
|
|
2516
|
+
}
|
|
2443
2517
|
if (event.kind === "assistant") {
|
|
2444
2518
|
const messageId = event.messageId ?? uid;
|
|
2445
2519
|
let llmSpan = state.llmSpanByMessageId.get(messageId);
|
|
@@ -2553,6 +2627,7 @@ function consumeClaudeEvent(event, uid, ctx, state) {
|
|
|
2553
2627
|
if (userParent) prompt.attributes[ATTR3.parentConfidence] = userParent.confidence;
|
|
2554
2628
|
state.spans.push(prompt);
|
|
2555
2629
|
state.promptSpans.push(prompt);
|
|
2630
|
+
state.promptSpanByUid.set(uid, prompt);
|
|
2556
2631
|
state.step += 1;
|
|
2557
2632
|
}
|
|
2558
2633
|
for (const result of event.results) {
|
|
@@ -2582,6 +2657,7 @@ function consumeClaudeEvent(event, uid, ctx, state) {
|
|
|
2582
2657
|
}
|
|
2583
2658
|
state.spans.push(prompt);
|
|
2584
2659
|
state.promptSpans.push(prompt);
|
|
2660
|
+
state.promptSpanByUid.set(uid, prompt);
|
|
2585
2661
|
state.step += 1;
|
|
2586
2662
|
} else if (event.kind === "file-change") {
|
|
2587
2663
|
state.spans.push(span({
|
|
@@ -2743,7 +2819,7 @@ async function readClaudeWorkflowBindings(ref, options) {
|
|
|
2743
2819
|
options.signal?.throwIfAborted();
|
|
2744
2820
|
const accepted = distinctClaudeEvent(event, seen, ref.path, `source${row}`);
|
|
2745
2821
|
row += 1;
|
|
2746
|
-
if (accepted) indexWorkflowProjection(accepted.projection, index);
|
|
2822
|
+
if (accepted && "projection" in accepted) indexWorkflowProjection(accepted.projection, index);
|
|
2747
2823
|
}
|
|
2748
2824
|
return bindWorkflowRuns(index);
|
|
2749
2825
|
}
|
|
@@ -2895,7 +2971,10 @@ var ClaudeAdapter = class {
|
|
|
2895
2971
|
runResult = { failed: event.is_error, subtype: event.subtype ?? "unknown" };
|
|
2896
2972
|
}
|
|
2897
2973
|
const accepted = distinctClaudeEvent(event, seen, ref.path, `step${state.step}`);
|
|
2898
|
-
if (!accepted)
|
|
2974
|
+
if (!accepted || "originEnrichment" in accepted) {
|
|
2975
|
+
if (accepted) applyOriginEnrichment(accepted.uid, accepted.originEnrichment, state);
|
|
2976
|
+
continue;
|
|
2977
|
+
}
|
|
2899
2978
|
indexWorkflowProjection(accepted.projection, workflowIndex);
|
|
2900
2979
|
const startsTask = startsClaudeTask(accepted.projection);
|
|
2901
2980
|
if (startsTask && taskScope === "latest") {
|
|
@@ -4301,12 +4380,12 @@ var CodexAdapter = class {
|
|
|
4301
4380
|
spans.push(turnSpan);
|
|
4302
4381
|
step += 1;
|
|
4303
4382
|
};
|
|
4304
|
-
const recordInheritedContext = (l, ts) => {
|
|
4383
|
+
const recordInheritedContext = (l, ts, inScope = false) => {
|
|
4305
4384
|
if (l.type === "compacted") {
|
|
4306
4385
|
const payload = l.payload;
|
|
4307
4386
|
if (!payload) return;
|
|
4308
4387
|
const summary = capText(typeof payload.message === "string" ? payload.message : "");
|
|
4309
|
-
if (
|
|
4388
|
+
if (claimInheritedSpan()) {
|
|
4310
4389
|
spans.push(span({
|
|
4311
4390
|
traceId,
|
|
4312
4391
|
spanId: `inherited:${step}:compacted`,
|
|
@@ -4321,7 +4400,7 @@ var CodexAdapter = class {
|
|
|
4321
4400
|
contentSource: textSources(payload, "message"),
|
|
4322
4401
|
extra: {
|
|
4323
4402
|
[INHERITED_SPAN_ATTR]: true,
|
|
4324
|
-
[INHERITED_SOURCE_ATTR]: "compacted",
|
|
4403
|
+
[INHERITED_SOURCE_ATTR]: inScope ? "compacted" : "pre-task-prefix",
|
|
4325
4404
|
...typeof payload.window_number === "number" ? { "traces.codex.compaction_window_number": payload.window_number } : {},
|
|
4326
4405
|
...payload.window_id ? { "traces.codex.compaction_window_id": payload.window_id } : {},
|
|
4327
4406
|
...payload.previous_window_id ? { "traces.codex.compaction_previous_window_id": payload.previous_window_id } : {}
|
|
@@ -4495,7 +4574,7 @@ var CodexAdapter = class {
|
|
|
4495
4574
|
step += 1;
|
|
4496
4575
|
}
|
|
4497
4576
|
} else if (l.type === "compacted") {
|
|
4498
|
-
recordInheritedContext(l, ts);
|
|
4577
|
+
recordInheritedContext(l, ts, true);
|
|
4499
4578
|
} else if (l.type === "response_item" && (l.payload?.type === "function_call" || l.payload?.type === "custom_tool_call")) {
|
|
4500
4579
|
const outerName = l.payload.name ?? "tool";
|
|
4501
4580
|
const callId = l.payload.call_id ?? `${step}`;
|
|
@@ -6158,6 +6237,27 @@ var PiAdapter = class {
|
|
|
6158
6237
|
if (!sessionLine && l.type === "session") {
|
|
6159
6238
|
sessionLine = { id: l.id, timestamp: l.timestamp };
|
|
6160
6239
|
}
|
|
6240
|
+
if (l.type === "compaction") {
|
|
6241
|
+
spans.push(span({
|
|
6242
|
+
traceId: sourceTraceId,
|
|
6243
|
+
spanId: "compacted:" + (l.id ?? step),
|
|
6244
|
+
parentSpanId: sourceRootId,
|
|
6245
|
+
name: "session.compacted",
|
|
6246
|
+
kind: "CHAIN",
|
|
6247
|
+
service: SERVICE9,
|
|
6248
|
+
agent: SERVICE9,
|
|
6249
|
+
startTime: l.timestamp ?? "",
|
|
6250
|
+
step: step++,
|
|
6251
|
+
content: typeof l.summary === "string" ? capText(l.summary) : void 0,
|
|
6252
|
+
contentSource: sourceOf(l, "summary"),
|
|
6253
|
+
extra: {
|
|
6254
|
+
...typeof l.tokensBefore === "number" && Number.isFinite(l.tokensBefore) && l.tokensBefore >= 0 ? { "traces.compaction.tokens_before": l.tokensBefore } : {},
|
|
6255
|
+
...typeof l.firstKeptEntryId === "string" ? { "traces.pi.first_kept_entry_id": l.firstKeptEntryId } : {},
|
|
6256
|
+
...l.systemMessage !== void 0 ? { "gen_ai.system_instructions": JSON.stringify(l.systemMessage) } : {}
|
|
6257
|
+
}
|
|
6258
|
+
}));
|
|
6259
|
+
continue;
|
|
6260
|
+
}
|
|
6161
6261
|
if (l.type !== "message" || !l.message) continue;
|
|
6162
6262
|
const ts = l.timestamp ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
6163
6263
|
const mid = l.id ?? `m${step}`;
|
|
@@ -7395,7 +7495,7 @@ async function readLoopRuns(cwds, sessionIds) {
|
|
|
7395
7495
|
}
|
|
7396
7496
|
return { linkedCounts, unlinkedCounts, filesRead };
|
|
7397
7497
|
}
|
|
7398
|
-
|
|
7498
|
+
function computeAdoption(spans) {
|
|
7399
7499
|
const { sessionByTrace, conflicts } = indexSessionIdsByTrace(spans);
|
|
7400
7500
|
const identifiedSessions = new Set(sessionByTrace.values());
|
|
7401
7501
|
const unassignedTraceIds = new Set(
|
|
@@ -7452,11 +7552,9 @@ async function analyzeAdoption(spans, opts = {}) {
|
|
|
7452
7552
|
subagentSpawns[type] = (subagentSpawns[type] ?? 0) + 1;
|
|
7453
7553
|
}
|
|
7454
7554
|
}
|
|
7455
|
-
const {
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
filesRead
|
|
7459
|
-
} = await readLoopRuns(opts.cwds ?? [], identifiedSessions);
|
|
7555
|
+
const loopDispatchedRuns = {};
|
|
7556
|
+
const unlinkedLoopDispatchedRuns = {};
|
|
7557
|
+
const filesRead = 0;
|
|
7460
7558
|
const totalSkillInvocations = Object.values(skillInvocations).reduce((a, b) => a + b, 0);
|
|
7461
7559
|
const totalSkillDocumentReads = Object.values(skillDocumentReads).reduce((a, b) => a + b, 0);
|
|
7462
7560
|
const totalSubagentSpawns = Object.values(subagentSpawns).reduce((a, b) => a + b, 0);
|
|
@@ -7500,6 +7598,19 @@ async function analyzeAdoption(spans, opts = {}) {
|
|
|
7500
7598
|
totalUnlinkedLoopDispatchedRuns
|
|
7501
7599
|
};
|
|
7502
7600
|
}
|
|
7601
|
+
async function analyzeAdoption(spans, opts = {}) {
|
|
7602
|
+
const report = computeAdoption(spans);
|
|
7603
|
+
const { sessionByTrace } = indexSessionIdsByTrace(spans);
|
|
7604
|
+
const history = await readLoopRuns(opts.cwds ?? [], new Set(sessionByTrace.values()));
|
|
7605
|
+
return {
|
|
7606
|
+
...report,
|
|
7607
|
+
loopDispatchedRuns: history.linkedCounts,
|
|
7608
|
+
totalLoopDispatchedRuns: Object.values(history.linkedCounts).reduce((sum, count2) => sum + count2, 0),
|
|
7609
|
+
unlinkedLoopDispatchedRuns: history.unlinkedCounts,
|
|
7610
|
+
totalUnlinkedLoopDispatchedRuns: Object.values(history.unlinkedCounts).reduce((sum, count2) => sum + count2, 0),
|
|
7611
|
+
skillRunFilesRead: history.filesRead
|
|
7612
|
+
};
|
|
7613
|
+
}
|
|
7503
7614
|
|
|
7504
7615
|
// src/agentic-routing.ts
|
|
7505
7616
|
import {
|
|
@@ -8458,13 +8569,13 @@ function mergeEvent(record2, recordIndex, words) {
|
|
|
8458
8569
|
const target = positional[0] ?? null;
|
|
8459
8570
|
const numberInCommand = target === null ? null : /^#?(\d+)$/.exec(target)?.[1] ?? numbersInLine(target)[0] ?? null;
|
|
8460
8571
|
const numberInOutput = numberInCommand === null ? MERGED_NUMBER.exec(record2.output)?.[1] ?? null : null;
|
|
8461
|
-
const
|
|
8462
|
-
if (!reachedGh(record2, "merge",
|
|
8572
|
+
const number2 = numberInCommand ?? numberInOutput;
|
|
8573
|
+
if (!reachedGh(record2, "merge", number2 !== null)) return null;
|
|
8463
8574
|
const evidence3 = numberInCommand !== null ? "the pull request the command names" : numberInOutput !== null ? `the merge line this command printed, in span ${record2.spanId}` : target !== null ? "the branch the command names; no output that joins to it stated a number" : "none: the command named neither a pull request nor a branch";
|
|
8464
8575
|
return {
|
|
8465
8576
|
action: "merge",
|
|
8466
|
-
number,
|
|
8467
|
-
headBranch:
|
|
8577
|
+
number: number2,
|
|
8578
|
+
headBranch: number2 === null ? target : null,
|
|
8468
8579
|
command: words.join(" "),
|
|
8469
8580
|
evidence: evidence3,
|
|
8470
8581
|
spanIds: [record2.spanId],
|
|
@@ -8503,7 +8614,7 @@ function numberFromLaterOutput(records, after, headBranch) {
|
|
|
8503
8614
|
${record2.output}`.split("\n")) {
|
|
8504
8615
|
const line = raw.replace(ANSI, "");
|
|
8505
8616
|
if (!line.includes(headBranch)) continue;
|
|
8506
|
-
for (const
|
|
8617
|
+
for (const number2 of numbersInLine(line)) found.add(number2);
|
|
8507
8618
|
}
|
|
8508
8619
|
if (found.size === 1) return { number: [...found][0], spanId: record2.spanId };
|
|
8509
8620
|
}
|
|
@@ -8592,9 +8703,144 @@ function readPullRequests(spans) {
|
|
|
8592
8703
|
};
|
|
8593
8704
|
}
|
|
8594
8705
|
|
|
8706
|
+
// src/session-facts.ts
|
|
8707
|
+
import { createHash as createHash7 } from "crypto";
|
|
8708
|
+
|
|
8709
|
+
// src/session-measurements.ts
|
|
8710
|
+
var number = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
8711
|
+
function measure(spans, read, reason) {
|
|
8712
|
+
let total = 0;
|
|
8713
|
+
let measured2 = 0;
|
|
8714
|
+
for (const span2 of spans) {
|
|
8715
|
+
const value = read(span2);
|
|
8716
|
+
if (value !== null) {
|
|
8717
|
+
total += value;
|
|
8718
|
+
measured2 += 1;
|
|
8719
|
+
}
|
|
8720
|
+
}
|
|
8721
|
+
return {
|
|
8722
|
+
total: measured2 > 0 || spans.length === 0 ? total : null,
|
|
8723
|
+
measured: measured2,
|
|
8724
|
+
eligible: spans.length,
|
|
8725
|
+
unavailable: measured2 === spans.length ? null : reason
|
|
8726
|
+
};
|
|
8727
|
+
}
|
|
8728
|
+
function bytes(span2, side) {
|
|
8729
|
+
const recorded = number(span2.attributes[`traces.${side}.bytes`]);
|
|
8730
|
+
if (recorded !== null) return recorded;
|
|
8731
|
+
const text2 = span2.attributes[`${side}.value`];
|
|
8732
|
+
if (typeof text2 !== "string" || span2.attributes[`traces.${side}.truncated`] === true) return null;
|
|
8733
|
+
return Buffer.byteLength(text2);
|
|
8734
|
+
}
|
|
8735
|
+
function toolMeasurements(tools) {
|
|
8736
|
+
const groups = /* @__PURE__ */ new Map();
|
|
8737
|
+
for (const tool of tools) {
|
|
8738
|
+
const name = typeof tool.attributes["tool.name"] === "string" ? tool.attributes["tool.name"] : tool.name;
|
|
8739
|
+
const group = groups.get(name);
|
|
8740
|
+
if (group) group.push(tool);
|
|
8741
|
+
else groups.set(name, [tool]);
|
|
8742
|
+
}
|
|
8743
|
+
return [...groups].sort(([a], [b]) => a.localeCompare(b)).map(([name, spans]) => ({
|
|
8744
|
+
name,
|
|
8745
|
+
calls: spans.length,
|
|
8746
|
+
outcomes: {
|
|
8747
|
+
ok: spans.filter((span2) => span2.status.code === "OK").length,
|
|
8748
|
+
error: spans.filter((span2) => span2.status.code === "ERROR").length,
|
|
8749
|
+
unset: spans.filter((span2) => span2.status.code === "UNSET").length
|
|
8750
|
+
},
|
|
8751
|
+
inputBytes: measure(spans, (span2) => bytes(span2, "input"), "Input byte count missing or truncated without a size receipt."),
|
|
8752
|
+
outputBytes: measure(spans, (span2) => bytes(span2, "output"), "Output byte count missing or truncated without a size receipt."),
|
|
8753
|
+
callToResultMs: measure(spans, (span2) => {
|
|
8754
|
+
if (span2.status.code === "UNSET") return null;
|
|
8755
|
+
const elapsed2 = Date.parse(span2.end_time) - Date.parse(span2.start_time);
|
|
8756
|
+
return Number.isFinite(elapsed2) && elapsed2 > 0 ? elapsed2 : null;
|
|
8757
|
+
}, "A completed span with distinct recorded endpoints is required; equal endpoints may be an adapter default."),
|
|
8758
|
+
spanIds: spans.map((span2) => span2.span_id)
|
|
8759
|
+
}));
|
|
8760
|
+
}
|
|
8761
|
+
function compactions(spans) {
|
|
8762
|
+
const events = spans.filter((span2) => span2.name === "session.compacted" && span2.attributes[INHERITED_SOURCE_ATTR] !== "pre-task-prefix").sort((a, b) => Date.parse(a.start_time) - Date.parse(b.start_time));
|
|
8763
|
+
if (events.length === 0) return {
|
|
8764
|
+
value: null,
|
|
8765
|
+
spanIds: [],
|
|
8766
|
+
unavailable: "No compaction boundary was captured. This does not establish that no compaction occurred."
|
|
8767
|
+
};
|
|
8768
|
+
return {
|
|
8769
|
+
value: events.map((span2, i) => {
|
|
8770
|
+
const previous = i > 0 ? events[i - 1] : void 0;
|
|
8771
|
+
const interval = previous ? Date.parse(span2.start_time) - Date.parse(previous.start_time) : NaN;
|
|
8772
|
+
return {
|
|
8773
|
+
spanId: span2.span_id,
|
|
8774
|
+
at: span2.start_time,
|
|
8775
|
+
sincePreviousMs: Number.isFinite(interval) && interval >= 0 ? interval : null,
|
|
8776
|
+
latencyMs: number(span2.attributes["traces.compaction.latency_ms"]),
|
|
8777
|
+
tokensBefore: number(span2.attributes["traces.compaction.tokens_before"]),
|
|
8778
|
+
tokensAfter: number(span2.attributes["traces.compaction.tokens_after"])
|
|
8779
|
+
};
|
|
8780
|
+
}),
|
|
8781
|
+
spanIds: events.map((span2) => span2.span_id),
|
|
8782
|
+
unavailable: null,
|
|
8783
|
+
partial: "Observed boundaries only. Latency and context sizes are null unless the source explicitly records them."
|
|
8784
|
+
};
|
|
8785
|
+
}
|
|
8786
|
+
function contextFootprints(spans) {
|
|
8787
|
+
const out = [];
|
|
8788
|
+
for (const span2 of spans) {
|
|
8789
|
+
const fields = ["gen_ai.tool.definitions", "gen_ai.system_instructions"];
|
|
8790
|
+
if (span2.name === "message.system" || span2.name === "message.developer" || span2.name === "user.prompt" && span2.attributes["tangle.actor"] === "injected") fields.push("content");
|
|
8791
|
+
for (const field of fields) {
|
|
8792
|
+
const value = span2.attributes[field];
|
|
8793
|
+
if (value === void 0 || value === null) continue;
|
|
8794
|
+
const text2 = typeof value === "string" ? value : canonicalJson(value);
|
|
8795
|
+
let entries = Array.isArray(value) ? value.length : null;
|
|
8796
|
+
if (typeof value === "string") {
|
|
8797
|
+
try {
|
|
8798
|
+
const parsed = JSON.parse(value);
|
|
8799
|
+
if (Array.isArray(parsed)) entries = parsed.length;
|
|
8800
|
+
} catch {
|
|
8801
|
+
}
|
|
8802
|
+
}
|
|
8803
|
+
out.push({ spanId: span2.span_id, field, bytes: Buffer.byteLength(text2), lines: text2.split("\n").length, entries, scope: "captured-attribute" });
|
|
8804
|
+
}
|
|
8805
|
+
}
|
|
8806
|
+
return out;
|
|
8807
|
+
}
|
|
8808
|
+
function computeSessionMeasurements(spans, tools, sessionId, harness) {
|
|
8809
|
+
const spawns = tools.filter((span2) => span2.attributes["traces.agent.spawn"] === true || span2.attributes["traces.codex.agent_operation"] === "spawn_agent").sort((a, b) => Date.parse(a.start_time) - Date.parse(b.start_time));
|
|
8810
|
+
return {
|
|
8811
|
+
observedSpawns: spawns.length,
|
|
8812
|
+
spawnIntervalsMs: spawns.map((span2, i) => {
|
|
8813
|
+
const previous = spawns[i - 1];
|
|
8814
|
+
const interval = previous ? Date.parse(span2.start_time) - Date.parse(previous.start_time) : NaN;
|
|
8815
|
+
return { spanId: span2.span_id, intervalMs: Number.isFinite(interval) && interval >= 0 ? interval : null };
|
|
8816
|
+
}),
|
|
8817
|
+
execution: spans.length > 0 ? summarizeSpanExecution(spans) : null,
|
|
8818
|
+
adoption: computeAdoption(spans),
|
|
8819
|
+
relationship: describeSessionRelationship({
|
|
8820
|
+
sessionId: sessionId ?? spans[0]?.trace_id ?? "",
|
|
8821
|
+
harness: harness ?? "",
|
|
8822
|
+
path: "",
|
|
8823
|
+
cwd: null,
|
|
8824
|
+
mtimeMs: 0
|
|
8825
|
+
}, spans),
|
|
8826
|
+
tools: toolMeasurements(tools),
|
|
8827
|
+
compactions: compactions(spans),
|
|
8828
|
+
contextFootprints: contextFootprints(spans),
|
|
8829
|
+
coverage: {
|
|
8830
|
+
profile: "Native spans do not establish the authored or effective AgentProfile revision; join the Runtime profile receipt by recorded identity.",
|
|
8831
|
+
materializedResources: "Captured attributes measure only the visible representation. Missing tools, skills, MCP definitions, and resources are unknown.",
|
|
8832
|
+
contextTokens: "No tokenizer estimates are substituted for the actual request. Per-component context token counts require producer telemetry.",
|
|
8833
|
+
toolTokens: "Tool byte counts are not model input tokens; token attribution to tool content is not captured.",
|
|
8834
|
+
compaction: "Only recorded boundaries are counted. Zero-duration adapter defaults are not measured compaction latency.",
|
|
8835
|
+
semanticAssessments: "Success at the task, useful skill application, waste, reward hacking, safety, and dishonesty require separately attributed evaluation."
|
|
8836
|
+
}
|
|
8837
|
+
};
|
|
8838
|
+
}
|
|
8839
|
+
|
|
8595
8840
|
// src/session-facts.ts
|
|
8596
8841
|
import { OPENINFERENCE_SPAN_KIND as OPENINFERENCE_SPAN_KIND4, TOOL_NAME as TOOL_NAME5 } from "@tangle-network/agent-eval/trace-attributes";
|
|
8597
8842
|
import { ingestSpans, rankFirstFailure } from "@tangle-network/agent-eval/diagnosis";
|
|
8843
|
+
var SESSION_FACTS_MEASUREMENT_VERSION = "3";
|
|
8598
8844
|
var SESSION_TOKEN_TOTAL_ATTR = "traces.session.total_tokens";
|
|
8599
8845
|
var SPAN_SYNTHESIZED_ATTR = "traces.codex.span_synthesized";
|
|
8600
8846
|
var CORRUPTION_COUNT_ATTR = "traces.session.corruption_count";
|
|
@@ -8869,15 +9115,17 @@ function humanTurnsOf(spans) {
|
|
|
8869
9115
|
excluded: [...excluded].sort(([left], [right]) => left.localeCompare(right)).map(([reason, spanIds]) => ({ reason, turns: spanIds.length, spanIds }))
|
|
8870
9116
|
};
|
|
8871
9117
|
}
|
|
8872
|
-
function computeSessionFacts(spans) {
|
|
9118
|
+
function computeSessionFacts(spans, options = {}) {
|
|
8873
9119
|
const { sessionByTrace } = indexSessionIdsByTrace(spans);
|
|
8874
9120
|
const byTrace = /* @__PURE__ */ new Map();
|
|
8875
9121
|
for (const span2 of spans) {
|
|
8876
|
-
|
|
9122
|
+
const group = byTrace.get(span2.trace_id);
|
|
9123
|
+
if (group) group.push(span2);
|
|
9124
|
+
else byTrace.set(span2.trace_id, [span2]);
|
|
8877
9125
|
}
|
|
8878
|
-
return [...byTrace].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([traceId, traceSpans]) => sessionFactsForTrace(traceId, sessionByTrace.get(traceId) ?? null, traceSpans));
|
|
9126
|
+
return [...byTrace].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([traceId, traceSpans]) => sessionFactsForTrace(traceId, sessionByTrace.get(traceId) ?? null, traceSpans, options));
|
|
8879
9127
|
}
|
|
8880
|
-
function sessionFactsForTrace(traceId, sessionId, unordered) {
|
|
9128
|
+
function sessionFactsForTrace(traceId, sessionId, unordered, options) {
|
|
8881
9129
|
const spans = [...unordered].sort(byTraceOrder);
|
|
8882
9130
|
const harness = spans.map((span2) => stringAttr2(span2, "service.name")).find((name) => name !== null) ?? null;
|
|
8883
9131
|
const ownSpans = spans.filter((span2) => !isSubagentSpan(span2.attributes));
|
|
@@ -8916,7 +9164,7 @@ function sessionFactsForTrace(traceId, sessionId, unordered) {
|
|
|
8916
9164
|
(span2) => span2.parent_span_id !== null && span2.attributes[CORRUPTION_RECEIPT_VERSION_ATTR] === void 0
|
|
8917
9165
|
).length;
|
|
8918
9166
|
const corruptionSpan = spans.find((span2) => typeof span2.attributes[CORRUPTION_COUNT_ATTR] === "number");
|
|
8919
|
-
|
|
9167
|
+
const facts = {
|
|
8920
9168
|
schemaVersion: 1,
|
|
8921
9169
|
kind: "traces.session_facts",
|
|
8922
9170
|
traceId,
|
|
@@ -8933,6 +9181,7 @@ function sessionFactsForTrace(traceId, sessionId, unordered) {
|
|
|
8933
9181
|
spanIds: [],
|
|
8934
9182
|
unavailable: `no span carries ${CORRUPTION_COUNT_ATTR}; this trace was not read through the session integrity path, so whether any record was skipped is unknown`
|
|
8935
9183
|
},
|
|
9184
|
+
measurements: computeSessionMeasurements(ownSpans, invoked, sessionId, harness),
|
|
8936
9185
|
toolCalls: {
|
|
8937
9186
|
value: invoked.length,
|
|
8938
9187
|
spanIds: invoked.map((span2) => span2.span_id),
|
|
@@ -8985,15 +9234,50 @@ function sessionFactsForTrace(traceId, sessionId, unordered) {
|
|
|
8985
9234
|
unavailable: `no span carries ${SESSION_TOKEN_TOTAL_ATTR}; the per-turn llm.turn deltas in this trace are a different quantity and summing them would not be the harness total`
|
|
8986
9235
|
}
|
|
8987
9236
|
};
|
|
9237
|
+
if (options.includeContent !== false) return facts;
|
|
9238
|
+
const withheld = () => ({
|
|
9239
|
+
value: null,
|
|
9240
|
+
spanIds: [],
|
|
9241
|
+
unavailable: "Content was not requested."
|
|
9242
|
+
});
|
|
9243
|
+
return {
|
|
9244
|
+
...facts,
|
|
9245
|
+
humanTurns: withheld(),
|
|
9246
|
+
finalMessages: withheld(),
|
|
9247
|
+
changedFiles: withheld(),
|
|
9248
|
+
pullRequests: withheld(),
|
|
9249
|
+
subagents: {
|
|
9250
|
+
...facts.subagents,
|
|
9251
|
+
value: facts.subagents.value?.map((entry) => ({
|
|
9252
|
+
...entry,
|
|
9253
|
+
taskName: null,
|
|
9254
|
+
taskNameUnavailable: "Assignment text was not requested."
|
|
9255
|
+
})) ?? null
|
|
9256
|
+
},
|
|
9257
|
+
firstFailure: facts.firstFailure.status === "found" ? { ...facts.firstFailure, message: null, classification: facts.firstFailure.classification ? { ...facts.firstFailure.classification, reason: "Content was not requested." } : null } : facts.firstFailure
|
|
9258
|
+
};
|
|
8988
9259
|
}
|
|
8989
9260
|
function buildSessionFactsReport(spans, options = {}) {
|
|
9261
|
+
const digest = createHash7("sha256");
|
|
9262
|
+
const ordered2 = [...spans].sort((a, b) => a.trace_id.localeCompare(b.trace_id) || a.span_id.localeCompare(b.span_id));
|
|
9263
|
+
let evidenceThrough = null;
|
|
9264
|
+
for (const span2 of ordered2) {
|
|
9265
|
+
digest.update(canonicalJson(span2)).update("\n");
|
|
9266
|
+
for (const time of [span2.start_time, span2.end_time]) {
|
|
9267
|
+
if (Number.isFinite(Date.parse(time)) && (evidenceThrough === null || Date.parse(time) > Date.parse(evidenceThrough))) evidenceThrough = time;
|
|
9268
|
+
}
|
|
9269
|
+
}
|
|
8990
9270
|
return {
|
|
8991
9271
|
schemaVersion: 1,
|
|
8992
9272
|
kind: "traces.session_facts_report",
|
|
9273
|
+
measurementVersion: SESSION_FACTS_MEASUREMENT_VERSION,
|
|
9274
|
+
sourceDigest: "sha256:" + digest.digest("hex"),
|
|
9275
|
+
evidenceThrough,
|
|
9276
|
+
contentIncluded: options.includeContent !== false,
|
|
8993
9277
|
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8994
9278
|
harness: options.harness ?? null,
|
|
8995
9279
|
spanCount: spans.length,
|
|
8996
|
-
sessions: computeSessionFacts(spans)
|
|
9280
|
+
sessions: computeSessionFacts(spans, options)
|
|
8997
9281
|
};
|
|
8998
9282
|
}
|
|
8999
9283
|
function factLine(label2, fact, rendered) {
|
|
@@ -9042,6 +9326,8 @@ function renderSessionFacts(report) {
|
|
|
9042
9326
|
lines.push(factLine("changed files", facts.changedFiles, String(facts.changedFiles.value?.length ?? 0)));
|
|
9043
9327
|
lines.push(factLine("first record", facts.firstRecordAt, String(facts.firstRecordAt.value)));
|
|
9044
9328
|
lines.push(factLine("last record", facts.lastRecordAt, String(facts.lastRecordAt.value)));
|
|
9329
|
+
lines.push(factLine("compactions", facts.measurements.compactions, String(facts.measurements.compactions.value?.length ?? 0)));
|
|
9330
|
+
lines.push(" skills: " + facts.measurements.adoption.totalSkillInvocations + " recorded invocation(s), " + facts.measurements.adoption.totalSkillDocumentReads + " document read(s); telemetry " + facts.measurements.adoption.skillTelemetryStatus);
|
|
9045
9331
|
lines.push(factLine("token total", facts.tokenTotal, String(facts.tokenTotal.value)));
|
|
9046
9332
|
lines.push(` first failure: ${renderFirstFailure(facts.firstFailure)}`);
|
|
9047
9333
|
if ((facts.unreadRecords.value ?? 0) > 0) {
|
|
@@ -9087,6 +9373,9 @@ ${JSON.stringify({ sessions, ...dropped2.length ? { omitted_fields: dropped2 } :
|
|
|
9087
9373
|
["tool_call_span_ids", (facts) => {
|
|
9088
9374
|
facts.tool_calls.span_ids = [];
|
|
9089
9375
|
}],
|
|
9376
|
+
["measurements", (facts) => {
|
|
9377
|
+
delete facts.measurements;
|
|
9378
|
+
}],
|
|
9090
9379
|
["tool_calls_by_name", (facts) => {
|
|
9091
9380
|
delete facts.tool_calls_by_name;
|
|
9092
9381
|
}],
|
|
@@ -9211,7 +9500,8 @@ function compactFacts(facts) {
|
|
|
9211
9500
|
first_record_at: factJson(facts.firstRecordAt),
|
|
9212
9501
|
last_record_at: factJson(facts.lastRecordAt),
|
|
9213
9502
|
token_total: factJson(facts.tokenTotal),
|
|
9214
|
-
first_failure: compactFirstFailure(facts.firstFailure)
|
|
9503
|
+
first_failure: compactFirstFailure(facts.firstFailure),
|
|
9504
|
+
measurements: facts.measurements
|
|
9215
9505
|
};
|
|
9216
9506
|
}
|
|
9217
9507
|
function compactFirstFailure(failure2) {
|
|
@@ -9240,7 +9530,7 @@ function sessionFactsContext(spans, options = {}) {
|
|
|
9240
9530
|
if (report.sessions.length === 0) return void 0;
|
|
9241
9531
|
return renderSessionFactsContext(report, options);
|
|
9242
9532
|
}
|
|
9243
|
-
var SESSION_FACTS_VERSION_SUFFIX = "session-facts.
|
|
9533
|
+
var SESSION_FACTS_VERSION_SUFFIX = "session-facts." + SESSION_FACTS_MEASUREMENT_VERSION;
|
|
9244
9534
|
function withSessionFactsContext(definitions, spans, options = {}) {
|
|
9245
9535
|
const sheet = sessionFactsContext(spans, options);
|
|
9246
9536
|
if (sheet === void 0) return [...definitions];
|
|
@@ -9267,13 +9557,13 @@ import { OtlpFileTraceStore } from "@tangle-network/agent-eval/traces";
|
|
|
9267
9557
|
|
|
9268
9558
|
// src/bundle-source.ts
|
|
9269
9559
|
import { isUtf8 as isUtf82 } from "buffer";
|
|
9270
|
-
import { createHash as
|
|
9560
|
+
import { createHash as createHash10 } from "crypto";
|
|
9271
9561
|
import { constants } from "fs";
|
|
9272
9562
|
import { lstat as lstat2, open, realpath as realpath3 } from "fs/promises";
|
|
9273
9563
|
import { dirname as dirname8, isAbsolute as isAbsolute4, join as join20, relative as relative5, resolve as resolve7, sep as sep2 } from "path";
|
|
9274
9564
|
|
|
9275
9565
|
// src/bundle.ts
|
|
9276
|
-
import { createHash as
|
|
9566
|
+
import { createHash as createHash9 } from "crypto";
|
|
9277
9567
|
import { copyFile, cp, lstat, mkdir as mkdir2, readdir as readdir16, readFile as readFile7, stat as stat14, writeFile as writeFile5 } from "fs/promises";
|
|
9278
9568
|
import { dirname as dirname7, join as join19, relative as relative4, resolve as resolve6, sep } from "path";
|
|
9279
9569
|
|
|
@@ -10502,7 +10792,7 @@ function measured(value, render) {
|
|
|
10502
10792
|
}
|
|
10503
10793
|
function distributionRow(label2, distribution, unit = "") {
|
|
10504
10794
|
if (distribution.n === 0) return `| ${label2} | 0 | not captured | not captured | not captured | not captured | not captured |`;
|
|
10505
|
-
const value = (
|
|
10795
|
+
const value = (number2) => measured(number2, (captured) => `${decimal(captured)}${unit}`);
|
|
10506
10796
|
return `| ${label2} | ${count(distribution.n)} | ${value(distribution.min)} | ${value(distribution.mean)} | ${value(distribution.p50)} | ${value(distribution.p95)} | ${value(distribution.max)} |`;
|
|
10507
10797
|
}
|
|
10508
10798
|
function tokenRows(usage) {
|
|
@@ -11853,7 +12143,7 @@ async function writeSessionIndexFile(index, outPath) {
|
|
|
11853
12143
|
}
|
|
11854
12144
|
|
|
11855
12145
|
// src/session-selection.ts
|
|
11856
|
-
import { createHash as
|
|
12146
|
+
import { createHash as createHash8 } from "crypto";
|
|
11857
12147
|
import { createReadStream as createReadStream2 } from "fs";
|
|
11858
12148
|
|
|
11859
12149
|
// src/session-workflow.ts
|
|
@@ -12179,7 +12469,7 @@ async function collectSessionWorkflow(adapter, seeds, options = {}) {
|
|
|
12179
12469
|
// src/session-selection.ts
|
|
12180
12470
|
async function fileSha256(path, signal) {
|
|
12181
12471
|
signal?.throwIfAborted();
|
|
12182
|
-
const hash2 =
|
|
12472
|
+
const hash2 = createHash8("sha256");
|
|
12183
12473
|
try {
|
|
12184
12474
|
for await (const chunk of createReadStream2(path, { signal })) {
|
|
12185
12475
|
signal?.throwIfAborted();
|
|
@@ -12207,7 +12497,7 @@ async function sourceSnapshot(adapter, ref, signal) {
|
|
|
12207
12497
|
signal?.throwIfAborted();
|
|
12208
12498
|
files.push({ path, sha256: await fileSha256(path, signal) });
|
|
12209
12499
|
}
|
|
12210
|
-
const sha2562 = files.length === 1 ? files[0].sha256 :
|
|
12500
|
+
const sha2562 = files.length === 1 ? files[0].sha256 : createHash8("sha256").update(JSON.stringify(files)).digest("hex");
|
|
12211
12501
|
return { sha256: sha2562, files };
|
|
12212
12502
|
}
|
|
12213
12503
|
function summarizeWorkflows(workflows) {
|
|
@@ -12333,8 +12623,8 @@ var KNOWN_LIMITS = [
|
|
|
12333
12623
|
"progress.md is copied whole; no session-dated section extraction is performed",
|
|
12334
12624
|
"open pull-request state is not captured: the bundler makes no network calls"
|
|
12335
12625
|
];
|
|
12336
|
-
function sha256Hex(
|
|
12337
|
-
return
|
|
12626
|
+
function sha256Hex(bytes2) {
|
|
12627
|
+
return createHash9("sha256").update(bytes2).digest("hex");
|
|
12338
12628
|
}
|
|
12339
12629
|
function bundlePath(...parts) {
|
|
12340
12630
|
return parts.join("/");
|
|
@@ -12741,13 +13031,13 @@ async function verifySessionBundle(bundleDir) {
|
|
|
12741
13031
|
let source;
|
|
12742
13032
|
if (manifest.projection) {
|
|
12743
13033
|
const sourceManifest = join19(manifest.projection.sourceDirectory, "manifest.json");
|
|
12744
|
-
const
|
|
13034
|
+
const bytes2 = await readFile7(sourceManifest).catch((error) => {
|
|
12745
13035
|
if (isMissingPathError(error)) return null;
|
|
12746
13036
|
throw error;
|
|
12747
13037
|
});
|
|
12748
13038
|
source = {
|
|
12749
13039
|
directory: manifest.projection.sourceDirectory,
|
|
12750
|
-
status:
|
|
13040
|
+
status: bytes2 === null ? "unavailable" : sha256Hex(bytes2) === manifest.projection.sourceManifestSha256 ? "match" : "mismatch"
|
|
12751
13041
|
};
|
|
12752
13042
|
}
|
|
12753
13043
|
return {
|
|
@@ -12791,8 +13081,8 @@ async function openBundleFile(root, file) {
|
|
|
12791
13081
|
return handle;
|
|
12792
13082
|
}
|
|
12793
13083
|
async function fileDigest(handle, signal) {
|
|
12794
|
-
const hash2 =
|
|
12795
|
-
for await (const
|
|
13084
|
+
const hash2 = createHash10("sha256");
|
|
13085
|
+
for await (const bytes2 of handle.createReadStream({ start: 0, autoClose: false, signal })) hash2.update(bytes2);
|
|
12796
13086
|
return hash2.digest("hex");
|
|
12797
13087
|
}
|
|
12798
13088
|
async function validateFile(root, file, signal) {
|
|
@@ -12892,7 +13182,7 @@ async function createBundleSourceReader(directory, spans, options = {}) {
|
|
|
12892
13182
|
handle = await openBundleFile(root, file);
|
|
12893
13183
|
const before = await handle.stat();
|
|
12894
13184
|
if (await fileDigest(handle, signal) !== file.sha256) return unavailable("retained source digest mismatch");
|
|
12895
|
-
const hash2 =
|
|
13185
|
+
const hash2 = createHash10("sha256");
|
|
12896
13186
|
const record2 = Buffer.alloc(ref.recordBytes);
|
|
12897
13187
|
let position = 0;
|
|
12898
13188
|
for await (const raw of handle.createReadStream({
|
|
@@ -12901,10 +13191,10 @@ async function createBundleSourceReader(directory, spans, options = {}) {
|
|
|
12901
13191
|
autoClose: false,
|
|
12902
13192
|
signal
|
|
12903
13193
|
})) {
|
|
12904
|
-
const
|
|
12905
|
-
hash2.update(
|
|
12906
|
-
|
|
12907
|
-
position +=
|
|
13194
|
+
const bytes3 = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
|
|
13195
|
+
hash2.update(bytes3);
|
|
13196
|
+
bytes3.copy(record2, position);
|
|
13197
|
+
position += bytes3.length;
|
|
12908
13198
|
}
|
|
12909
13199
|
const after = await handle.stat();
|
|
12910
13200
|
if (position !== ref.recordBytes || hash2.digest("hex") !== ref.recordSha256 || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs) {
|
|
@@ -12927,22 +13217,22 @@ async function createBundleSourceReader(directory, spans, options = {}) {
|
|
|
12927
13217
|
if (input.offset > field.length) return unavailable("source offset is outside the selected field");
|
|
12928
13218
|
const window = field.subarray(input.offset, Math.min(field.length, input.offset + input.limit));
|
|
12929
13219
|
if (window.length > 0 && (window[0] & 192) === 128) return unavailable("offset splits a UTF-8 character");
|
|
12930
|
-
let
|
|
13220
|
+
let bytes2 = window;
|
|
12931
13221
|
for (let omitted = 0; omitted <= 3; omitted += 1) {
|
|
12932
|
-
|
|
12933
|
-
if (isUtf82(
|
|
13222
|
+
bytes2 = window.subarray(0, Math.max(0, window.length - omitted));
|
|
13223
|
+
if (isUtf82(bytes2)) break;
|
|
12934
13224
|
}
|
|
12935
|
-
if (!isUtf82(
|
|
13225
|
+
if (!isUtf82(bytes2) || bytes2.length === 0 && input.offset < field.length) {
|
|
12936
13226
|
return unavailable("window cannot contain a complete UTF-8 character");
|
|
12937
13227
|
}
|
|
12938
|
-
const next = input.offset +
|
|
13228
|
+
const next = input.offset + bytes2.length;
|
|
12939
13229
|
return {
|
|
12940
13230
|
status: "available",
|
|
12941
13231
|
trace_id: input.trace_id,
|
|
12942
13232
|
span_id: input.span_id,
|
|
12943
13233
|
attribute: input.attribute,
|
|
12944
13234
|
source_index: sourceIndex,
|
|
12945
|
-
text:
|
|
13235
|
+
text: bytes2.toString("utf8"),
|
|
12946
13236
|
offset: input.offset,
|
|
12947
13237
|
total_bytes: field.length,
|
|
12948
13238
|
next_offset: next < field.length ? next : null,
|
|
@@ -13075,7 +13365,7 @@ async function analyzeSpans(spans, opts = {}) {
|
|
|
13075
13365
|
}
|
|
13076
13366
|
|
|
13077
13367
|
// src/chat-trajectory.ts
|
|
13078
|
-
import { createHash as
|
|
13368
|
+
import { createHash as createHash11 } from "crypto";
|
|
13079
13369
|
function isChatTrajectoryMessage(value) {
|
|
13080
13370
|
return isObject2(value) && typeof value.role === "string" && typeof value.content === "string";
|
|
13081
13371
|
}
|
|
@@ -13160,7 +13450,7 @@ function trajectoryId(trajectory) {
|
|
|
13160
13450
|
const value = trajectory.trajectory_id ?? trajectory.traj_id ?? trajectory.instance_id;
|
|
13161
13451
|
if (typeof value === "string" && value.trim()) return value;
|
|
13162
13452
|
if (typeof value === "number" && Number.isSafeInteger(value)) return String(value);
|
|
13163
|
-
return `trajectory-${
|
|
13453
|
+
return `trajectory-${createHash11("sha256").update(JSON.stringify(trajectory.messages)).digest("hex").slice(0, 24)}`;
|
|
13164
13454
|
}
|
|
13165
13455
|
function messageKind(role) {
|
|
13166
13456
|
const normalized = role.trim().toLowerCase();
|
|
@@ -13217,8 +13507,8 @@ function parseTime2(value) {
|
|
|
13217
13507
|
}
|
|
13218
13508
|
function finiteNumber(value, key) {
|
|
13219
13509
|
if (!isObject2(value)) return void 0;
|
|
13220
|
-
const
|
|
13221
|
-
return typeof
|
|
13510
|
+
const number2 = value[key];
|
|
13511
|
+
return typeof number2 === "number" && Number.isFinite(number2) ? number2 : void 0;
|
|
13222
13512
|
}
|
|
13223
13513
|
function stringValue2(value) {
|
|
13224
13514
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
@@ -13240,7 +13530,7 @@ import {
|
|
|
13240
13530
|
import { basename as basename13, dirname as dirname10, join as join23, resolve as resolve9 } from "path";
|
|
13241
13531
|
|
|
13242
13532
|
// src/codetracebench-files.ts
|
|
13243
|
-
import { createHash as
|
|
13533
|
+
import { createHash as createHash12, randomUUID } from "crypto";
|
|
13244
13534
|
import {
|
|
13245
13535
|
link,
|
|
13246
13536
|
lstat as lstat3,
|
|
@@ -13438,7 +13728,7 @@ async function atomicWriteFile(path, content, signal) {
|
|
|
13438
13728
|
}
|
|
13439
13729
|
}
|
|
13440
13730
|
function sha256(value) {
|
|
13441
|
-
return
|
|
13731
|
+
return createHash12("sha256").update(value).digest("hex");
|
|
13442
13732
|
}
|
|
13443
13733
|
function isObject3(value) {
|
|
13444
13734
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -13675,10 +13965,10 @@ async function readOptionalTask(trajectoryDirectory, caseDirectory, row, signal)
|
|
|
13675
13965
|
content: `Task: ${row.taskName}`
|
|
13676
13966
|
};
|
|
13677
13967
|
}
|
|
13678
|
-
const
|
|
13679
|
-
const content =
|
|
13968
|
+
const bytes2 = await readFile9(path, { signal });
|
|
13969
|
+
const content = bytes2.toString("utf8").trim();
|
|
13680
13970
|
if (!content) throw new Error(`${row.trajId}/task.md is empty`);
|
|
13681
|
-
return { source: "task.md", content, path, bytes };
|
|
13971
|
+
return { source: "task.md", content, path, bytes: bytes2 };
|
|
13682
13972
|
}
|
|
13683
13973
|
function assertNoLabelLeak(spans, row) {
|
|
13684
13974
|
for (const item of spans) {
|
|
@@ -14328,7 +14618,7 @@ async function cmdVerifyFindings(argv) {
|
|
|
14328
14618
|
}
|
|
14329
14619
|
|
|
14330
14620
|
// src/hodoscope.ts
|
|
14331
|
-
import { createHash as
|
|
14621
|
+
import { createHash as createHash13 } from "crypto";
|
|
14332
14622
|
import { mkdir as mkdir5, mkdtemp as mkdtemp5, readFile as readFile11, readdir as readdir17, writeFile as writeFile7 } from "fs/promises";
|
|
14333
14623
|
import { endianness, tmpdir as tmpdir5 } from "os";
|
|
14334
14624
|
import { join as join24, resolve as resolve10 } from "path";
|
|
@@ -14367,7 +14657,7 @@ async function writeHodoscopeInput(otlpPath, options = {}) {
|
|
|
14367
14657
|
throw new Error(`Hodoscope trace '${traceId}' contains no assistant actions`);
|
|
14368
14658
|
}
|
|
14369
14659
|
actionCount += actions;
|
|
14370
|
-
const file = `${
|
|
14660
|
+
const file = `${createHash13("sha256").update(traceId).digest("hex").slice(0, 24)}.json`;
|
|
14371
14661
|
await writeFile7(join24(samples, file), `${JSON.stringify(trajectory, null, 2)}
|
|
14372
14662
|
`, "utf8");
|
|
14373
14663
|
}
|
|
@@ -14879,7 +15169,7 @@ function validateEmbedding(encoded, dimensions, path) {
|
|
|
14879
15169
|
`Hodoscope ${path} encodes ${encoded.length} characters; expected ${expectedLength} for ${dimensions} float32 values`
|
|
14880
15170
|
);
|
|
14881
15171
|
}
|
|
14882
|
-
const
|
|
15172
|
+
const bytes2 = new Uint8Array(dimensions * 4);
|
|
14883
15173
|
for (let group = 0; group < dimensions; group += 1) {
|
|
14884
15174
|
let value = 0;
|
|
14885
15175
|
const encodedOffset = group * 5;
|
|
@@ -14895,12 +15185,12 @@ function validateEmbedding(encoded, dimensions, path) {
|
|
|
14895
15185
|
throw new Error(`Hodoscope ${path} contains an overflowing base85 block at index ${group}`);
|
|
14896
15186
|
}
|
|
14897
15187
|
const byteOffset = group * 4;
|
|
14898
|
-
|
|
14899
|
-
|
|
14900
|
-
|
|
14901
|
-
|
|
15188
|
+
bytes2[byteOffset] = Math.floor(value / 16777216) & 255;
|
|
15189
|
+
bytes2[byteOffset + 1] = Math.floor(value / 65536) & 255;
|
|
15190
|
+
bytes2[byteOffset + 2] = Math.floor(value / 256) & 255;
|
|
15191
|
+
bytes2[byteOffset + 3] = value & 255;
|
|
14902
15192
|
}
|
|
14903
|
-
const view = new DataView(
|
|
15193
|
+
const view = new DataView(bytes2.buffer, bytes2.byteOffset, bytes2.byteLength);
|
|
14904
15194
|
const littleEndian = endianness() === "LE";
|
|
14905
15195
|
for (let index = 0; index < dimensions; index += 1) {
|
|
14906
15196
|
if (!Number.isFinite(view.getFloat32(index * 4, littleEndian))) {
|
|
@@ -15015,7 +15305,7 @@ function assertPositiveInteger(name, value) {
|
|
|
15015
15305
|
}
|
|
15016
15306
|
|
|
15017
15307
|
// src/analyst-engine-prime.ts
|
|
15018
|
-
import { createHash as
|
|
15308
|
+
import { createHash as createHash14 } from "crypto";
|
|
15019
15309
|
import { request as httpRequest } from "http";
|
|
15020
15310
|
import { request as httpsRequest } from "https";
|
|
15021
15311
|
var DEFAULT_BRIDGE_URL = "http://localhost:4181";
|
|
@@ -15028,6 +15318,17 @@ var MAX_FINDINGS = 10;
|
|
|
15028
15318
|
var MAX_SPAN_IDS_PER_FINDING = 8;
|
|
15029
15319
|
var SEVERITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "low", "info"]);
|
|
15030
15320
|
var ANALYST_ID = "prime";
|
|
15321
|
+
var TRAJECTORY_FILE_NAME = "trajectory.otlp.jsonl";
|
|
15322
|
+
async function fileDelivery(otlpPath, perAttributeCharCap, signal) {
|
|
15323
|
+
signal?.throwIfAborted();
|
|
15324
|
+
const { copyFile: copyFile2, mkdtemp: mkdtemp8, stat: stat19 } = await import("fs/promises");
|
|
15325
|
+
const { tmpdir: tmpdir8 } = await import("os");
|
|
15326
|
+
const { join: join35 } = await import("path");
|
|
15327
|
+
const cwd = await mkdtemp8(join35(tmpdir8(), "traces-prime-"));
|
|
15328
|
+
await copyFile2(otlpPath, join35(cwd, TRAJECTORY_FILE_NAME));
|
|
15329
|
+
const artifactBytes = (await stat19(otlpPath)).size;
|
|
15330
|
+
return { mode: "file-cwd", perAttributeCharCap, renderedChars: artifactBytes, cwd, fileName: TRAJECTORY_FILE_NAME };
|
|
15331
|
+
}
|
|
15031
15332
|
function positiveIntegerEnv(name) {
|
|
15032
15333
|
const raw = process.env[name];
|
|
15033
15334
|
if (raw === void 0 || raw === "") return void 0;
|
|
@@ -15078,7 +15379,7 @@ function capRowAttributes(row, cap) {
|
|
|
15078
15379
|
}
|
|
15079
15380
|
return { ...row, attributes: capped };
|
|
15080
15381
|
}
|
|
15081
|
-
async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, signal) {
|
|
15382
|
+
async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, deliveryMode, signal) {
|
|
15082
15383
|
const rows = [];
|
|
15083
15384
|
const spanTraces = /* @__PURE__ */ new Map();
|
|
15084
15385
|
let index = 0;
|
|
@@ -15102,9 +15403,18 @@ async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, si
|
|
|
15102
15403
|
}
|
|
15103
15404
|
if (rows.length === 0) throw new Error(`${otlpPath} contains no spans`);
|
|
15104
15405
|
const traceCount = new Set(rows.map((row) => row.trace_id)).size;
|
|
15406
|
+
if (deliveryMode === "file") {
|
|
15407
|
+
return {
|
|
15408
|
+
rows,
|
|
15409
|
+
rendered: null,
|
|
15410
|
+
delivery: await fileDelivery(otlpPath, perAttributeCharCap, signal),
|
|
15411
|
+
traceCount,
|
|
15412
|
+
spanTraces
|
|
15413
|
+
};
|
|
15414
|
+
}
|
|
15105
15415
|
let projected = rows;
|
|
15106
15416
|
let rendered = JSON.stringify(projected);
|
|
15107
|
-
|
|
15417
|
+
let delivery = {
|
|
15108
15418
|
mode: "inline-json",
|
|
15109
15419
|
perAttributeCharCap: null,
|
|
15110
15420
|
renderedChars: rendered.length
|
|
@@ -15112,18 +15422,30 @@ async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, si
|
|
|
15112
15422
|
if (rendered.length > maxInlineChars) {
|
|
15113
15423
|
projected = rows.map((row) => capRowAttributes(row, perAttributeCharCap));
|
|
15114
15424
|
rendered = JSON.stringify(projected);
|
|
15115
|
-
delivery
|
|
15116
|
-
|
|
15425
|
+
delivery = {
|
|
15426
|
+
mode: "inline-json",
|
|
15427
|
+
perAttributeCharCap,
|
|
15428
|
+
renderedChars: rendered.length
|
|
15429
|
+
};
|
|
15117
15430
|
}
|
|
15118
15431
|
if (rendered.length > maxInlineChars) {
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15432
|
+
if (deliveryMode === "inline") {
|
|
15433
|
+
throw new Error(
|
|
15434
|
+
`trajectory renders to ${rendered.length} chars even at per-attribute cap ${perAttributeCharCap} (budget ${maxInlineChars}); inline delivery impossible`
|
|
15435
|
+
);
|
|
15436
|
+
}
|
|
15437
|
+
return {
|
|
15438
|
+
rows,
|
|
15439
|
+
rendered: null,
|
|
15440
|
+
delivery: await fileDelivery(otlpPath, perAttributeCharCap, signal),
|
|
15441
|
+
traceCount,
|
|
15442
|
+
spanTraces
|
|
15443
|
+
};
|
|
15122
15444
|
}
|
|
15123
15445
|
return { rows: projected, rendered, delivery, traceCount, spanTraces };
|
|
15124
15446
|
}
|
|
15125
15447
|
var OUTPUT_CONTRACT_LINES = [
|
|
15126
|
-
"OUTPUT CONTRACT (
|
|
15448
|
+
"OUTPUT CONTRACT (inline delivery \u2014 the whole trajectory is in this prompt):",
|
|
15127
15449
|
"You are a one-shot analyst. Every fact you need is in the TRAJECTORY JSON below.",
|
|
15128
15450
|
"Do not run shell commands, do not read or write files, do not use any tools.",
|
|
15129
15451
|
"Reply with EXACTLY one fenced ```json code block and no other fenced block. The JSON object has exactly two fields:",
|
|
@@ -15139,7 +15461,35 @@ var OUTPUT_CONTRACT_LINES = [
|
|
|
15139
15461
|
`Report at most ${MAX_FINDINGS} findings; a finding cites 1..${MAX_SPAN_IDS_PER_FINDING} span_ids, each copied VERBATIM from a span in the trajectory below.`,
|
|
15140
15462
|
'"findings" is [] only for a clean trajectory.'
|
|
15141
15463
|
];
|
|
15464
|
+
var FILE_DELIVERY_CONTRACT_LINES = [
|
|
15465
|
+
"OUTPUT CONTRACT (file delivery \u2014 the trajectory is a FILE in your working directory):",
|
|
15466
|
+
`You are a recursive language model with a REPL. The trajectory is ${TRAJECTORY_FILE_NAME} in your`,
|
|
15467
|
+
"working directory: OpenInference JSONL, one span object per line. Load it yourself and inspect it with code",
|
|
15468
|
+
'(e.g. rows = [json.loads(line) for line in open("trajectory.otlp.jsonl")]), paging rather than printing it whole;',
|
|
15469
|
+
"fan out with rlm() for sub-questions when that pays for itself. Stay inside your working directory.",
|
|
15470
|
+
"Reply with EXACTLY one fenced ```json code block and no other fenced block. The JSON object has exactly two fields:",
|
|
15471
|
+
' "answer": string \u2014 ONE short sentence (max 300 chars) summarizing your verdict.',
|
|
15472
|
+
' "findings": array (possibly empty) of finding rows, each exactly:',
|
|
15473
|
+
' {"span_ids": [string, ...],',
|
|
15474
|
+
' "severity": "critical"|"high"|"medium"|"low"|"info",',
|
|
15475
|
+
' "area": string (short kebab-case topic, max 40 chars),',
|
|
15476
|
+
' "claim": string (ONE short sentence, max 200 chars),',
|
|
15477
|
+
' "action": string (optional, ONE short imperative sentence, max 200 chars),',
|
|
15478
|
+
' "confidence": number 0..1}',
|
|
15479
|
+
"Do NOT include a rationale field. Keep every string SHORT \u2014 long strings get corrupted in transport and void your work.",
|
|
15480
|
+
`Report at most ${MAX_FINDINGS} findings; a finding cites 1..${MAX_SPAN_IDS_PER_FINDING} span_ids, each copied VERBATIM from a span in ${TRAJECTORY_FILE_NAME}.`,
|
|
15481
|
+
'"findings" is [] only for a clean trajectory.'
|
|
15482
|
+
];
|
|
15142
15483
|
function buildPrompt(question, projection) {
|
|
15484
|
+
if (projection.delivery.mode === "file-cwd") {
|
|
15485
|
+
return [
|
|
15486
|
+
`QUESTION: ${question}`,
|
|
15487
|
+
"",
|
|
15488
|
+
...FILE_DELIVERY_CONTRACT_LINES,
|
|
15489
|
+
"",
|
|
15490
|
+
`TRAJECTORY (${projection.traceCount} trace(s); ${projection.rows.length} spans): the file ${projection.delivery.fileName} in your working directory (${projection.delivery.renderedChars} bytes). It is NOT inlined in this prompt.`
|
|
15491
|
+
].join("\n");
|
|
15492
|
+
}
|
|
15143
15493
|
return [
|
|
15144
15494
|
`QUESTION: ${question}`,
|
|
15145
15495
|
"",
|
|
@@ -15239,7 +15589,7 @@ function toAnalystFinding(row, index, spanTraces, model, producedAt) {
|
|
|
15239
15589
|
const traces = spanTraces.get(spanId);
|
|
15240
15590
|
return spanEvidenceUri([...traces][0], spanId);
|
|
15241
15591
|
});
|
|
15242
|
-
const findingId = `prime-${
|
|
15592
|
+
const findingId = `prime-${createHash14("sha256").update(`${index}
|
|
15243
15593
|
${row.claim}
|
|
15244
15594
|
${uris.join("\n")}`).digest("hex").slice(0, 12)}`;
|
|
15245
15595
|
return {
|
|
@@ -15286,7 +15636,7 @@ var httpJsonTransport = ({ url, body, signal }) => {
|
|
|
15286
15636
|
};
|
|
15287
15637
|
var PrimeBridgeError = class extends Error {
|
|
15288
15638
|
};
|
|
15289
|
-
async function callBridge(transport, url, model, content, timeoutMs, callerSignal, turn) {
|
|
15639
|
+
async function callBridge(transport, url, model, content, timeoutMs, callerSignal, turn, cwd) {
|
|
15290
15640
|
const controller = new AbortController();
|
|
15291
15641
|
const timer = setTimeout(
|
|
15292
15642
|
() => controller.abort(new Error(`${turn}bridge call exceeded ${timeoutMs}ms`)),
|
|
@@ -15299,7 +15649,7 @@ async function callBridge(transport, url, model, content, timeoutMs, callerSigna
|
|
|
15299
15649
|
try {
|
|
15300
15650
|
response = await transport({
|
|
15301
15651
|
url,
|
|
15302
|
-
body: { model, messages: [{ role: "user", content }] },
|
|
15652
|
+
body: { model, messages: [{ role: "user", content }], ...cwd ? { cwd } : {} },
|
|
15303
15653
|
signal: controller.signal
|
|
15304
15654
|
});
|
|
15305
15655
|
} catch (error) {
|
|
@@ -15346,6 +15696,10 @@ function primeAnalyzer(opts = {}) {
|
|
|
15346
15696
|
throw new RangeError("perAttributeCharCap must be a positive safe integer");
|
|
15347
15697
|
}
|
|
15348
15698
|
const repairEnabled = opts.repair ?? true;
|
|
15699
|
+
const deliveryMode = opts.delivery ?? "auto";
|
|
15700
|
+
if (deliveryMode !== "auto" && deliveryMode !== "file" && deliveryMode !== "inline") {
|
|
15701
|
+
throw new RangeError(`delivery must be 'auto', 'file', or 'inline', got '${String(deliveryMode)}'`);
|
|
15702
|
+
}
|
|
15349
15703
|
const transport = opts.transport ?? httpJsonTransport;
|
|
15350
15704
|
const url = `${bridgeUrl}/v1/chat/completions`;
|
|
15351
15705
|
return {
|
|
@@ -15353,12 +15707,19 @@ function primeAnalyzer(opts = {}) {
|
|
|
15353
15707
|
async analyze(otlpPath, analyzeOpts = {}) {
|
|
15354
15708
|
let projection;
|
|
15355
15709
|
try {
|
|
15356
|
-
projection = await projectArtifact(
|
|
15710
|
+
projection = await projectArtifact(
|
|
15711
|
+
otlpPath,
|
|
15712
|
+
maxInlineChars,
|
|
15713
|
+
perAttributeCharCap,
|
|
15714
|
+
deliveryMode,
|
|
15715
|
+
analyzeOpts.signal
|
|
15716
|
+
);
|
|
15357
15717
|
} catch (error) {
|
|
15358
15718
|
return failure("", error instanceof Error ? error.message : String(error));
|
|
15359
15719
|
}
|
|
15360
15720
|
const question = analyzeOpts.prompt ?? opts.defaultPrompt ?? DEFAULT_QUESTION;
|
|
15361
|
-
const deliveryLine = `delivery: ${projection.delivery.mode} (${projection.delivery.renderedChars} chars, per-attribute cap ${projection.delivery.perAttributeCharCap ?? "none"})`;
|
|
15721
|
+
const deliveryLine = projection.delivery.mode === "file-cwd" ? `delivery: file-cwd (${projection.rows.length} spans; ${projection.delivery.fileName} in cwd ${projection.delivery.cwd}, ${projection.delivery.renderedChars} bytes; per-attribute cap not applied)` : `delivery: ${projection.delivery.mode} (${projection.delivery.renderedChars} chars, per-attribute cap ${projection.delivery.perAttributeCharCap ?? "none"})`;
|
|
15722
|
+
const deliveryCwd = projection.delivery.mode === "file-cwd" ? projection.delivery.cwd : void 0;
|
|
15362
15723
|
let reply;
|
|
15363
15724
|
try {
|
|
15364
15725
|
reply = await callBridge(
|
|
@@ -15368,7 +15729,8 @@ function primeAnalyzer(opts = {}) {
|
|
|
15368
15729
|
buildPrompt(question, projection),
|
|
15369
15730
|
timeoutMs,
|
|
15370
15731
|
analyzeOpts.signal,
|
|
15371
|
-
""
|
|
15732
|
+
"",
|
|
15733
|
+
deliveryCwd
|
|
15372
15734
|
);
|
|
15373
15735
|
} catch (error) {
|
|
15374
15736
|
if (!(error instanceof PrimeBridgeError)) throw error;
|
|
@@ -15525,7 +15887,7 @@ async function applyRedactor(spans, redactor) {
|
|
|
15525
15887
|
}
|
|
15526
15888
|
|
|
15527
15889
|
// src/file-export.ts
|
|
15528
|
-
import { createHash as
|
|
15890
|
+
import { createHash as createHash15 } from "crypto";
|
|
15529
15891
|
import { mkdtemp as mkdtemp6, readFile as readFile12, writeFile as writeFile8 } from "fs/promises";
|
|
15530
15892
|
import { tmpdir as tmpdir6 } from "os";
|
|
15531
15893
|
import { join as join25 } from "path";
|
|
@@ -15563,7 +15925,7 @@ function stableJson(value) {
|
|
|
15563
15925
|
});
|
|
15564
15926
|
}
|
|
15565
15927
|
function hashId(value, chars = 16) {
|
|
15566
|
-
return
|
|
15928
|
+
return createHash15("sha256").update(stableJson(value)).digest("hex").slice(0, chars);
|
|
15567
15929
|
}
|
|
15568
15930
|
function isoTime2(value) {
|
|
15569
15931
|
if (typeof value === "string") {
|
|
@@ -16502,7 +16864,7 @@ async function writeInspectionReportFile(report, outPath) {
|
|
|
16502
16864
|
}
|
|
16503
16865
|
|
|
16504
16866
|
// src/live.ts
|
|
16505
|
-
import { createHash as
|
|
16867
|
+
import { createHash as createHash16 } from "crypto";
|
|
16506
16868
|
var VERIFY_RE = /\b(test|tests|vitest|jest|pytest|go test|cargo test|tsc|typecheck|lint|biome|eslint|build|check:invariants|preflight)\b/i;
|
|
16507
16869
|
var CHANGE_RE = /\b(apply_patch|edit|write|create|delete|rename|replace|update|git apply|sed -i|perl -pi|tee\s|cat\s+>)/i;
|
|
16508
16870
|
var CLAIM_DONE_RE = /\b(done|fixed|complete|completed|implemented|merged|verified|all green|tests pass|ready|works now)\b/i;
|
|
@@ -16522,7 +16884,7 @@ var sleep = (ms2, signal) => new Promise((resolve13) => {
|
|
|
16522
16884
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
16523
16885
|
});
|
|
16524
16886
|
function hash(parts) {
|
|
16525
|
-
return
|
|
16887
|
+
return createHash16("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
|
|
16526
16888
|
}
|
|
16527
16889
|
function asString(value) {
|
|
16528
16890
|
if (typeof value === "string" && value.trim()) return value;
|
|
@@ -18748,7 +19110,7 @@ async function watchSessions(opts) {
|
|
|
18748
19110
|
}
|
|
18749
19111
|
|
|
18750
19112
|
// src/bundle-view.ts
|
|
18751
|
-
import { createHash as
|
|
19113
|
+
import { createHash as createHash17 } from "crypto";
|
|
18752
19114
|
import { mkdir as mkdir7, readdir as readdir20, readFile as readFile16, writeFile as writeFile11 } from "fs/promises";
|
|
18753
19115
|
import { dirname as dirname11, join as join31, resolve as resolve12 } from "path";
|
|
18754
19116
|
var EVIDENCE_ONLY_ALLOW_LIST = [
|
|
@@ -18941,8 +19303,8 @@ async function projectSessionBundle(opts) {
|
|
|
18941
19303
|
const candidates = [];
|
|
18942
19304
|
for (const path of await listSessionBundleFiles(sourceDir)) {
|
|
18943
19305
|
if (path === "manifest.json") continue;
|
|
18944
|
-
const
|
|
18945
|
-
const sha2562 =
|
|
19306
|
+
const bytes2 = await readFile16(join31(sourceDir, path));
|
|
19307
|
+
const sha2562 = createHash17("sha256").update(bytes2).digest("hex");
|
|
18946
19308
|
const recorded = sourceHashes.get(path);
|
|
18947
19309
|
if (!recorded) {
|
|
18948
19310
|
throw new Error(
|
|
@@ -18956,10 +19318,10 @@ async function projectSessionBundle(opts) {
|
|
|
18956
19318
|
}
|
|
18957
19319
|
const verdict = classifyEvidenceOnlyPath(path);
|
|
18958
19320
|
if (!verdict.carried) {
|
|
18959
|
-
excluded.push({ path, bytes:
|
|
19321
|
+
excluded.push({ path, bytes: bytes2.length, sha256: sha2562, rule: verdict.rule, reason: verdict.reason });
|
|
18960
19322
|
continue;
|
|
18961
19323
|
}
|
|
18962
|
-
candidates.push({ path, bytes, sha256: sha2562, carries: verdict.carries, owned: verdict.owned });
|
|
19324
|
+
candidates.push({ path, bytes: bytes2, sha256: sha2562, carries: verdict.carries, owned: verdict.owned });
|
|
18963
19325
|
}
|
|
18964
19326
|
const report = await findSessionTextLeaks({
|
|
18965
19327
|
viewDir: sourceDir,
|
|
@@ -19018,7 +19380,7 @@ async function projectSessionBundle(opts) {
|
|
|
19018
19380
|
knownLimits: [...source.knownLimits, ...EVIDENCE_ONLY_LIMITS],
|
|
19019
19381
|
projection: {
|
|
19020
19382
|
sourceDirectory: sourceDir,
|
|
19021
|
-
sourceManifestSha256:
|
|
19383
|
+
sourceManifestSha256: createHash17("sha256").update(sourceManifestBytes).digest("hex"),
|
|
19022
19384
|
leakCheck
|
|
19023
19385
|
}
|
|
19024
19386
|
};
|
|
@@ -19442,14 +19804,14 @@ async function verifySafe(target, options = {}) {
|
|
|
19442
19804
|
const files = await listFiles(target, unreadable);
|
|
19443
19805
|
for (const file of files) {
|
|
19444
19806
|
const label2 = relative7(target, file) || basename17(file);
|
|
19445
|
-
let
|
|
19807
|
+
let bytes2;
|
|
19446
19808
|
try {
|
|
19447
|
-
|
|
19809
|
+
bytes2 = await readFile19(file);
|
|
19448
19810
|
} catch (error) {
|
|
19449
19811
|
unreadable.push(`${label2}: ${error instanceof Error ? error.message : String(error)}`);
|
|
19450
19812
|
continue;
|
|
19451
19813
|
}
|
|
19452
|
-
const text2 = decodeUtf8(
|
|
19814
|
+
const text2 = decodeUtf8(bytes2);
|
|
19453
19815
|
if (text2 === void 0) {
|
|
19454
19816
|
unreadable.push(`${label2}: not UTF-8 text`);
|
|
19455
19817
|
continue;
|
|
@@ -19498,10 +19860,10 @@ async function listFiles(target, unreadable) {
|
|
|
19498
19860
|
await walk(target);
|
|
19499
19861
|
return out;
|
|
19500
19862
|
}
|
|
19501
|
-
function decodeUtf8(
|
|
19502
|
-
if (
|
|
19863
|
+
function decodeUtf8(bytes2) {
|
|
19864
|
+
if (bytes2.includes(0)) return void 0;
|
|
19503
19865
|
try {
|
|
19504
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(
|
|
19866
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
19505
19867
|
} catch {
|
|
19506
19868
|
return void 0;
|
|
19507
19869
|
}
|
|
@@ -19572,6 +19934,7 @@ export {
|
|
|
19572
19934
|
EXPORT_RULE,
|
|
19573
19935
|
toOpenInferenceSpan,
|
|
19574
19936
|
serializeSpans,
|
|
19937
|
+
iterSerializedSpans,
|
|
19575
19938
|
writeOtlpFile,
|
|
19576
19939
|
summarizeOtlpExecution,
|
|
19577
19940
|
summarizeSpanExecution,
|
|
@@ -19633,6 +19996,7 @@ export {
|
|
|
19633
19996
|
serializePolicyEvidence,
|
|
19634
19997
|
writePolicyEvidenceFile,
|
|
19635
19998
|
countSkillRunsJsonl,
|
|
19999
|
+
computeAdoption,
|
|
19636
20000
|
analyzeAdoption,
|
|
19637
20001
|
planTraceAgenticRoute,
|
|
19638
20002
|
traceAgenticKinds,
|
|
@@ -19644,6 +20008,7 @@ export {
|
|
|
19644
20008
|
shellCommands,
|
|
19645
20009
|
commandTextFromInput,
|
|
19646
20010
|
readPullRequests,
|
|
20011
|
+
SESSION_FACTS_MEASUREMENT_VERSION,
|
|
19647
20012
|
SESSION_TOKEN_TOTAL_ATTR,
|
|
19648
20013
|
SPAN_SYNTHESIZED_ATTR,
|
|
19649
20014
|
FACT_TEXT_CAP,
|