@tangle-network/traces 0.16.4 → 0.17.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 +16 -1
- package/dist/{chunk-SMT635QV.js → chunk-SGQOMHZP.js} +519 -136
- package/dist/cli.js +6 -5
- package/dist/index.d.ts +126 -26
- package/dist/index.js +7 -1
- package/docs/trace-analysts.md +47 -2
- package/package.json +1 -1
|
@@ -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,46 @@ 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) {
|
|
2452
|
+
seen.set(uid, { fingerprint, originKind });
|
|
2453
|
+
return { originEnrichment: originKind, uid };
|
|
2454
|
+
}
|
|
2455
|
+
if (previous.originKind !== null && originKind !== null && previous.originKind !== originKind) {
|
|
2456
|
+
throw new ClaudeEventConflictError(sourcePath, uid);
|
|
2424
2457
|
}
|
|
2425
2458
|
return void 0;
|
|
2426
2459
|
}
|
|
2427
|
-
seen.set(
|
|
2460
|
+
seen.set(uid, { fingerprint, originKind });
|
|
2428
2461
|
}
|
|
2429
2462
|
return { projection, uid: event.uuid ?? fallbackUid };
|
|
2430
2463
|
}
|
|
2464
|
+
function applyOriginEnrichment(uid, originKind, state) {
|
|
2465
|
+
const prompt = state.promptSpanByUid.get(uid);
|
|
2466
|
+
if (!prompt) return;
|
|
2467
|
+
if (prompt.attributes[ORIGIN_RECORDED_ATTR] === true) return;
|
|
2468
|
+
state.sawRecordedOrigin = true;
|
|
2469
|
+
prompt.attributes[ORIGIN_RECORDED_ATTR] = true;
|
|
2470
|
+
prompt.attributes[ORIGIN_KIND_ATTR] = originKind;
|
|
2471
|
+
prompt.attributes[ACTOR_ATTR] = claudeActorFromOrigin(
|
|
2472
|
+
originKind,
|
|
2473
|
+
() => prompt.attributes[ACTOR_ATTR]
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2431
2476
|
function consumeDistinctClaudeEvent(event, ctx, state, seen, sourcePath) {
|
|
2432
2477
|
const accepted = distinctClaudeEvent(event, seen, sourcePath, `step${state.step}`);
|
|
2433
2478
|
if (!accepted) return false;
|
|
2479
|
+
if ("originEnrichment" in accepted) {
|
|
2480
|
+
applyOriginEnrichment(accepted.uid, accepted.originEnrichment, state);
|
|
2481
|
+
return false;
|
|
2482
|
+
}
|
|
2434
2483
|
consumeClaudeEvent(accepted.projection, accepted.uid, ctx, state);
|
|
2435
2484
|
return true;
|
|
2436
2485
|
}
|
|
@@ -2440,6 +2489,34 @@ function inStreamParent(parentToolUseId, state) {
|
|
|
2440
2489
|
return parentTool ? { span: parentTool, confidence: "explicit" } : void 0;
|
|
2441
2490
|
}
|
|
2442
2491
|
function consumeClaudeEvent(event, uid, ctx, state) {
|
|
2492
|
+
if (event.kind === "compaction") {
|
|
2493
|
+
const extra = {};
|
|
2494
|
+
const numericFields = {
|
|
2495
|
+
preTokens: "traces.compaction.tokens_before",
|
|
2496
|
+
postTokens: "traces.compaction.tokens_after",
|
|
2497
|
+
durationMs: "traces.compaction.latency_ms",
|
|
2498
|
+
cumulativeDroppedTokens: "traces.compaction.cumulative_dropped_tokens"
|
|
2499
|
+
};
|
|
2500
|
+
for (const [field, key] of Object.entries(numericFields)) {
|
|
2501
|
+
const value = event.metadata[field];
|
|
2502
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) extra[key] = value;
|
|
2503
|
+
}
|
|
2504
|
+
if (typeof event.metadata.trigger === "string") extra["traces.compaction.trigger"] = event.metadata.trigger;
|
|
2505
|
+
const parent = inStreamParent(event.parentToolUseId, state);
|
|
2506
|
+
state.spans.push(span({
|
|
2507
|
+
traceId: ctx.traceId,
|
|
2508
|
+
spanId: ctx.idPrefix + uid + ":compacted",
|
|
2509
|
+
parentSpanId: parent ? parent.span.span_id : ctx.rootParent,
|
|
2510
|
+
name: "session.compacted",
|
|
2511
|
+
kind: "CHAIN",
|
|
2512
|
+
startTime: event.timestamp,
|
|
2513
|
+
service: SERVICE2,
|
|
2514
|
+
agent: ctx.agent,
|
|
2515
|
+
step: state.step++,
|
|
2516
|
+
extra
|
|
2517
|
+
}));
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2443
2520
|
if (event.kind === "assistant") {
|
|
2444
2521
|
const messageId = event.messageId ?? uid;
|
|
2445
2522
|
let llmSpan = state.llmSpanByMessageId.get(messageId);
|
|
@@ -2553,6 +2630,7 @@ function consumeClaudeEvent(event, uid, ctx, state) {
|
|
|
2553
2630
|
if (userParent) prompt.attributes[ATTR3.parentConfidence] = userParent.confidence;
|
|
2554
2631
|
state.spans.push(prompt);
|
|
2555
2632
|
state.promptSpans.push(prompt);
|
|
2633
|
+
state.promptSpanByUid.set(uid, prompt);
|
|
2556
2634
|
state.step += 1;
|
|
2557
2635
|
}
|
|
2558
2636
|
for (const result of event.results) {
|
|
@@ -2582,6 +2660,7 @@ function consumeClaudeEvent(event, uid, ctx, state) {
|
|
|
2582
2660
|
}
|
|
2583
2661
|
state.spans.push(prompt);
|
|
2584
2662
|
state.promptSpans.push(prompt);
|
|
2663
|
+
state.promptSpanByUid.set(uid, prompt);
|
|
2585
2664
|
state.step += 1;
|
|
2586
2665
|
} else if (event.kind === "file-change") {
|
|
2587
2666
|
state.spans.push(span({
|
|
@@ -2743,7 +2822,7 @@ async function readClaudeWorkflowBindings(ref, options) {
|
|
|
2743
2822
|
options.signal?.throwIfAborted();
|
|
2744
2823
|
const accepted = distinctClaudeEvent(event, seen, ref.path, `source${row}`);
|
|
2745
2824
|
row += 1;
|
|
2746
|
-
if (accepted) indexWorkflowProjection(accepted.projection, index);
|
|
2825
|
+
if (accepted && "projection" in accepted) indexWorkflowProjection(accepted.projection, index);
|
|
2747
2826
|
}
|
|
2748
2827
|
return bindWorkflowRuns(index);
|
|
2749
2828
|
}
|
|
@@ -2895,7 +2974,10 @@ var ClaudeAdapter = class {
|
|
|
2895
2974
|
runResult = { failed: event.is_error, subtype: event.subtype ?? "unknown" };
|
|
2896
2975
|
}
|
|
2897
2976
|
const accepted = distinctClaudeEvent(event, seen, ref.path, `step${state.step}`);
|
|
2898
|
-
if (!accepted)
|
|
2977
|
+
if (!accepted || "originEnrichment" in accepted) {
|
|
2978
|
+
if (accepted) applyOriginEnrichment(accepted.uid, accepted.originEnrichment, state);
|
|
2979
|
+
continue;
|
|
2980
|
+
}
|
|
2899
2981
|
indexWorkflowProjection(accepted.projection, workflowIndex);
|
|
2900
2982
|
const startsTask = startsClaudeTask(accepted.projection);
|
|
2901
2983
|
if (startsTask && taskScope === "latest") {
|
|
@@ -4301,12 +4383,12 @@ var CodexAdapter = class {
|
|
|
4301
4383
|
spans.push(turnSpan);
|
|
4302
4384
|
step += 1;
|
|
4303
4385
|
};
|
|
4304
|
-
const recordInheritedContext = (l, ts) => {
|
|
4386
|
+
const recordInheritedContext = (l, ts, inScope = false) => {
|
|
4305
4387
|
if (l.type === "compacted") {
|
|
4306
4388
|
const payload = l.payload;
|
|
4307
4389
|
if (!payload) return;
|
|
4308
4390
|
const summary = capText(typeof payload.message === "string" ? payload.message : "");
|
|
4309
|
-
if (
|
|
4391
|
+
if (claimInheritedSpan()) {
|
|
4310
4392
|
spans.push(span({
|
|
4311
4393
|
traceId,
|
|
4312
4394
|
spanId: `inherited:${step}:compacted`,
|
|
@@ -4321,7 +4403,7 @@ var CodexAdapter = class {
|
|
|
4321
4403
|
contentSource: textSources(payload, "message"),
|
|
4322
4404
|
extra: {
|
|
4323
4405
|
[INHERITED_SPAN_ATTR]: true,
|
|
4324
|
-
[INHERITED_SOURCE_ATTR]: "compacted",
|
|
4406
|
+
[INHERITED_SOURCE_ATTR]: inScope ? "compacted" : "pre-task-prefix",
|
|
4325
4407
|
...typeof payload.window_number === "number" ? { "traces.codex.compaction_window_number": payload.window_number } : {},
|
|
4326
4408
|
...payload.window_id ? { "traces.codex.compaction_window_id": payload.window_id } : {},
|
|
4327
4409
|
...payload.previous_window_id ? { "traces.codex.compaction_previous_window_id": payload.previous_window_id } : {}
|
|
@@ -4495,7 +4577,7 @@ var CodexAdapter = class {
|
|
|
4495
4577
|
step += 1;
|
|
4496
4578
|
}
|
|
4497
4579
|
} else if (l.type === "compacted") {
|
|
4498
|
-
recordInheritedContext(l, ts);
|
|
4580
|
+
recordInheritedContext(l, ts, true);
|
|
4499
4581
|
} else if (l.type === "response_item" && (l.payload?.type === "function_call" || l.payload?.type === "custom_tool_call")) {
|
|
4500
4582
|
const outerName = l.payload.name ?? "tool";
|
|
4501
4583
|
const callId = l.payload.call_id ?? `${step}`;
|
|
@@ -6158,6 +6240,27 @@ var PiAdapter = class {
|
|
|
6158
6240
|
if (!sessionLine && l.type === "session") {
|
|
6159
6241
|
sessionLine = { id: l.id, timestamp: l.timestamp };
|
|
6160
6242
|
}
|
|
6243
|
+
if (l.type === "compaction") {
|
|
6244
|
+
spans.push(span({
|
|
6245
|
+
traceId: sourceTraceId,
|
|
6246
|
+
spanId: "compacted:" + (l.id ?? step),
|
|
6247
|
+
parentSpanId: sourceRootId,
|
|
6248
|
+
name: "session.compacted",
|
|
6249
|
+
kind: "CHAIN",
|
|
6250
|
+
service: SERVICE9,
|
|
6251
|
+
agent: SERVICE9,
|
|
6252
|
+
startTime: l.timestamp ?? "",
|
|
6253
|
+
step: step++,
|
|
6254
|
+
content: typeof l.summary === "string" ? capText(l.summary) : void 0,
|
|
6255
|
+
contentSource: sourceOf(l, "summary"),
|
|
6256
|
+
extra: {
|
|
6257
|
+
...typeof l.tokensBefore === "number" && Number.isFinite(l.tokensBefore) && l.tokensBefore >= 0 ? { "traces.compaction.tokens_before": l.tokensBefore } : {},
|
|
6258
|
+
...typeof l.firstKeptEntryId === "string" ? { "traces.pi.first_kept_entry_id": l.firstKeptEntryId } : {},
|
|
6259
|
+
...l.systemMessage !== void 0 ? { "gen_ai.system_instructions": JSON.stringify(l.systemMessage) } : {}
|
|
6260
|
+
}
|
|
6261
|
+
}));
|
|
6262
|
+
continue;
|
|
6263
|
+
}
|
|
6161
6264
|
if (l.type !== "message" || !l.message) continue;
|
|
6162
6265
|
const ts = l.timestamp ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
6163
6266
|
const mid = l.id ?? `m${step}`;
|
|
@@ -7395,7 +7498,7 @@ async function readLoopRuns(cwds, sessionIds) {
|
|
|
7395
7498
|
}
|
|
7396
7499
|
return { linkedCounts, unlinkedCounts, filesRead };
|
|
7397
7500
|
}
|
|
7398
|
-
|
|
7501
|
+
function computeAdoption(spans) {
|
|
7399
7502
|
const { sessionByTrace, conflicts } = indexSessionIdsByTrace(spans);
|
|
7400
7503
|
const identifiedSessions = new Set(sessionByTrace.values());
|
|
7401
7504
|
const unassignedTraceIds = new Set(
|
|
@@ -7452,11 +7555,9 @@ async function analyzeAdoption(spans, opts = {}) {
|
|
|
7452
7555
|
subagentSpawns[type] = (subagentSpawns[type] ?? 0) + 1;
|
|
7453
7556
|
}
|
|
7454
7557
|
}
|
|
7455
|
-
const {
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
filesRead
|
|
7459
|
-
} = await readLoopRuns(opts.cwds ?? [], identifiedSessions);
|
|
7558
|
+
const loopDispatchedRuns = {};
|
|
7559
|
+
const unlinkedLoopDispatchedRuns = {};
|
|
7560
|
+
const filesRead = 0;
|
|
7460
7561
|
const totalSkillInvocations = Object.values(skillInvocations).reduce((a, b) => a + b, 0);
|
|
7461
7562
|
const totalSkillDocumentReads = Object.values(skillDocumentReads).reduce((a, b) => a + b, 0);
|
|
7462
7563
|
const totalSubagentSpawns = Object.values(subagentSpawns).reduce((a, b) => a + b, 0);
|
|
@@ -7500,6 +7601,19 @@ async function analyzeAdoption(spans, opts = {}) {
|
|
|
7500
7601
|
totalUnlinkedLoopDispatchedRuns
|
|
7501
7602
|
};
|
|
7502
7603
|
}
|
|
7604
|
+
async function analyzeAdoption(spans, opts = {}) {
|
|
7605
|
+
const report = computeAdoption(spans);
|
|
7606
|
+
const { sessionByTrace } = indexSessionIdsByTrace(spans);
|
|
7607
|
+
const history = await readLoopRuns(opts.cwds ?? [], new Set(sessionByTrace.values()));
|
|
7608
|
+
return {
|
|
7609
|
+
...report,
|
|
7610
|
+
loopDispatchedRuns: history.linkedCounts,
|
|
7611
|
+
totalLoopDispatchedRuns: Object.values(history.linkedCounts).reduce((sum, count2) => sum + count2, 0),
|
|
7612
|
+
unlinkedLoopDispatchedRuns: history.unlinkedCounts,
|
|
7613
|
+
totalUnlinkedLoopDispatchedRuns: Object.values(history.unlinkedCounts).reduce((sum, count2) => sum + count2, 0),
|
|
7614
|
+
skillRunFilesRead: history.filesRead
|
|
7615
|
+
};
|
|
7616
|
+
}
|
|
7503
7617
|
|
|
7504
7618
|
// src/agentic-routing.ts
|
|
7505
7619
|
import {
|
|
@@ -8458,13 +8572,13 @@ function mergeEvent(record2, recordIndex, words) {
|
|
|
8458
8572
|
const target = positional[0] ?? null;
|
|
8459
8573
|
const numberInCommand = target === null ? null : /^#?(\d+)$/.exec(target)?.[1] ?? numbersInLine(target)[0] ?? null;
|
|
8460
8574
|
const numberInOutput = numberInCommand === null ? MERGED_NUMBER.exec(record2.output)?.[1] ?? null : null;
|
|
8461
|
-
const
|
|
8462
|
-
if (!reachedGh(record2, "merge",
|
|
8575
|
+
const number2 = numberInCommand ?? numberInOutput;
|
|
8576
|
+
if (!reachedGh(record2, "merge", number2 !== null)) return null;
|
|
8463
8577
|
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
8578
|
return {
|
|
8465
8579
|
action: "merge",
|
|
8466
|
-
number,
|
|
8467
|
-
headBranch:
|
|
8580
|
+
number: number2,
|
|
8581
|
+
headBranch: number2 === null ? target : null,
|
|
8468
8582
|
command: words.join(" "),
|
|
8469
8583
|
evidence: evidence3,
|
|
8470
8584
|
spanIds: [record2.spanId],
|
|
@@ -8503,7 +8617,7 @@ function numberFromLaterOutput(records, after, headBranch) {
|
|
|
8503
8617
|
${record2.output}`.split("\n")) {
|
|
8504
8618
|
const line = raw.replace(ANSI, "");
|
|
8505
8619
|
if (!line.includes(headBranch)) continue;
|
|
8506
|
-
for (const
|
|
8620
|
+
for (const number2 of numbersInLine(line)) found.add(number2);
|
|
8507
8621
|
}
|
|
8508
8622
|
if (found.size === 1) return { number: [...found][0], spanId: record2.spanId };
|
|
8509
8623
|
}
|
|
@@ -8592,9 +8706,144 @@ function readPullRequests(spans) {
|
|
|
8592
8706
|
};
|
|
8593
8707
|
}
|
|
8594
8708
|
|
|
8709
|
+
// src/session-facts.ts
|
|
8710
|
+
import { createHash as createHash7 } from "crypto";
|
|
8711
|
+
|
|
8712
|
+
// src/session-measurements.ts
|
|
8713
|
+
var number = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
8714
|
+
function measure(spans, read, reason) {
|
|
8715
|
+
let total = 0;
|
|
8716
|
+
let measured2 = 0;
|
|
8717
|
+
for (const span2 of spans) {
|
|
8718
|
+
const value = read(span2);
|
|
8719
|
+
if (value !== null) {
|
|
8720
|
+
total += value;
|
|
8721
|
+
measured2 += 1;
|
|
8722
|
+
}
|
|
8723
|
+
}
|
|
8724
|
+
return {
|
|
8725
|
+
total: measured2 > 0 || spans.length === 0 ? total : null,
|
|
8726
|
+
measured: measured2,
|
|
8727
|
+
eligible: spans.length,
|
|
8728
|
+
unavailable: measured2 === spans.length ? null : reason
|
|
8729
|
+
};
|
|
8730
|
+
}
|
|
8731
|
+
function bytes(span2, side) {
|
|
8732
|
+
const recorded = number(span2.attributes[`traces.${side}.bytes`]);
|
|
8733
|
+
if (recorded !== null) return recorded;
|
|
8734
|
+
const text2 = span2.attributes[`${side}.value`];
|
|
8735
|
+
if (typeof text2 !== "string" || span2.attributes[`traces.${side}.truncated`] === true) return null;
|
|
8736
|
+
return Buffer.byteLength(text2);
|
|
8737
|
+
}
|
|
8738
|
+
function toolMeasurements(tools) {
|
|
8739
|
+
const groups = /* @__PURE__ */ new Map();
|
|
8740
|
+
for (const tool of tools) {
|
|
8741
|
+
const name = typeof tool.attributes["tool.name"] === "string" ? tool.attributes["tool.name"] : tool.name;
|
|
8742
|
+
const group = groups.get(name);
|
|
8743
|
+
if (group) group.push(tool);
|
|
8744
|
+
else groups.set(name, [tool]);
|
|
8745
|
+
}
|
|
8746
|
+
return [...groups].sort(([a], [b]) => a.localeCompare(b)).map(([name, spans]) => ({
|
|
8747
|
+
name,
|
|
8748
|
+
calls: spans.length,
|
|
8749
|
+
outcomes: {
|
|
8750
|
+
ok: spans.filter((span2) => span2.status.code === "OK").length,
|
|
8751
|
+
error: spans.filter((span2) => span2.status.code === "ERROR").length,
|
|
8752
|
+
unset: spans.filter((span2) => span2.status.code === "UNSET").length
|
|
8753
|
+
},
|
|
8754
|
+
inputBytes: measure(spans, (span2) => bytes(span2, "input"), "Input byte count missing or truncated without a size receipt."),
|
|
8755
|
+
outputBytes: measure(spans, (span2) => bytes(span2, "output"), "Output byte count missing or truncated without a size receipt."),
|
|
8756
|
+
callToResultMs: measure(spans, (span2) => {
|
|
8757
|
+
if (span2.status.code === "UNSET") return null;
|
|
8758
|
+
const elapsed2 = Date.parse(span2.end_time) - Date.parse(span2.start_time);
|
|
8759
|
+
return Number.isFinite(elapsed2) && elapsed2 > 0 ? elapsed2 : null;
|
|
8760
|
+
}, "A completed span with distinct recorded endpoints is required; equal endpoints may be an adapter default."),
|
|
8761
|
+
spanIds: spans.map((span2) => span2.span_id)
|
|
8762
|
+
}));
|
|
8763
|
+
}
|
|
8764
|
+
function compactions(spans) {
|
|
8765
|
+
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));
|
|
8766
|
+
if (events.length === 0) return {
|
|
8767
|
+
value: null,
|
|
8768
|
+
spanIds: [],
|
|
8769
|
+
unavailable: "No compaction boundary was captured. This does not establish that no compaction occurred."
|
|
8770
|
+
};
|
|
8771
|
+
return {
|
|
8772
|
+
value: events.map((span2, i) => {
|
|
8773
|
+
const previous = i > 0 ? events[i - 1] : void 0;
|
|
8774
|
+
const interval = previous ? Date.parse(span2.start_time) - Date.parse(previous.start_time) : NaN;
|
|
8775
|
+
return {
|
|
8776
|
+
spanId: span2.span_id,
|
|
8777
|
+
at: span2.start_time,
|
|
8778
|
+
sincePreviousMs: Number.isFinite(interval) && interval >= 0 ? interval : null,
|
|
8779
|
+
latencyMs: number(span2.attributes["traces.compaction.latency_ms"]),
|
|
8780
|
+
tokensBefore: number(span2.attributes["traces.compaction.tokens_before"]),
|
|
8781
|
+
tokensAfter: number(span2.attributes["traces.compaction.tokens_after"])
|
|
8782
|
+
};
|
|
8783
|
+
}),
|
|
8784
|
+
spanIds: events.map((span2) => span2.span_id),
|
|
8785
|
+
unavailable: null,
|
|
8786
|
+
partial: "Observed boundaries only. Latency and context sizes are null unless the source explicitly records them."
|
|
8787
|
+
};
|
|
8788
|
+
}
|
|
8789
|
+
function contextFootprints(spans) {
|
|
8790
|
+
const out = [];
|
|
8791
|
+
for (const span2 of spans) {
|
|
8792
|
+
const fields = ["gen_ai.tool.definitions", "gen_ai.system_instructions"];
|
|
8793
|
+
if (span2.name === "message.system" || span2.name === "message.developer" || span2.name === "user.prompt" && span2.attributes["tangle.actor"] === "injected") fields.push("content");
|
|
8794
|
+
for (const field of fields) {
|
|
8795
|
+
const value = span2.attributes[field];
|
|
8796
|
+
if (value === void 0 || value === null) continue;
|
|
8797
|
+
const text2 = typeof value === "string" ? value : canonicalJson(value);
|
|
8798
|
+
let entries = Array.isArray(value) ? value.length : null;
|
|
8799
|
+
if (typeof value === "string") {
|
|
8800
|
+
try {
|
|
8801
|
+
const parsed = JSON.parse(value);
|
|
8802
|
+
if (Array.isArray(parsed)) entries = parsed.length;
|
|
8803
|
+
} catch {
|
|
8804
|
+
}
|
|
8805
|
+
}
|
|
8806
|
+
out.push({ spanId: span2.span_id, field, bytes: Buffer.byteLength(text2), lines: text2.split("\n").length, entries, scope: "captured-attribute" });
|
|
8807
|
+
}
|
|
8808
|
+
}
|
|
8809
|
+
return out;
|
|
8810
|
+
}
|
|
8811
|
+
function computeSessionMeasurements(spans, tools, sessionId, harness) {
|
|
8812
|
+
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));
|
|
8813
|
+
return {
|
|
8814
|
+
observedSpawns: spawns.length,
|
|
8815
|
+
spawnIntervalsMs: spawns.map((span2, i) => {
|
|
8816
|
+
const previous = spawns[i - 1];
|
|
8817
|
+
const interval = previous ? Date.parse(span2.start_time) - Date.parse(previous.start_time) : NaN;
|
|
8818
|
+
return { spanId: span2.span_id, intervalMs: Number.isFinite(interval) && interval >= 0 ? interval : null };
|
|
8819
|
+
}),
|
|
8820
|
+
execution: spans.length > 0 ? summarizeSpanExecution(spans) : null,
|
|
8821
|
+
adoption: computeAdoption(spans),
|
|
8822
|
+
relationship: describeSessionRelationship({
|
|
8823
|
+
sessionId: sessionId ?? spans[0]?.trace_id ?? "",
|
|
8824
|
+
harness: harness ?? "",
|
|
8825
|
+
path: "",
|
|
8826
|
+
cwd: null,
|
|
8827
|
+
mtimeMs: 0
|
|
8828
|
+
}, spans),
|
|
8829
|
+
tools: toolMeasurements(tools),
|
|
8830
|
+
compactions: compactions(spans),
|
|
8831
|
+
contextFootprints: contextFootprints(spans),
|
|
8832
|
+
coverage: {
|
|
8833
|
+
profile: "Native spans do not establish the authored or effective AgentProfile revision; join the Runtime profile receipt by recorded identity.",
|
|
8834
|
+
materializedResources: "Captured attributes measure only the visible representation. Missing tools, skills, MCP definitions, and resources are unknown.",
|
|
8835
|
+
contextTokens: "No tokenizer estimates are substituted for the actual request. Per-component context token counts require producer telemetry.",
|
|
8836
|
+
toolTokens: "Tool byte counts are not model input tokens; token attribution to tool content is not captured.",
|
|
8837
|
+
compaction: "Only recorded boundaries are counted. Zero-duration adapter defaults are not measured compaction latency.",
|
|
8838
|
+
semanticAssessments: "Success at the task, useful skill application, waste, reward hacking, safety, and dishonesty require separately attributed evaluation."
|
|
8839
|
+
}
|
|
8840
|
+
};
|
|
8841
|
+
}
|
|
8842
|
+
|
|
8595
8843
|
// src/session-facts.ts
|
|
8596
8844
|
import { OPENINFERENCE_SPAN_KIND as OPENINFERENCE_SPAN_KIND4, TOOL_NAME as TOOL_NAME5 } from "@tangle-network/agent-eval/trace-attributes";
|
|
8597
8845
|
import { ingestSpans, rankFirstFailure } from "@tangle-network/agent-eval/diagnosis";
|
|
8846
|
+
var SESSION_FACTS_MEASUREMENT_VERSION = "3";
|
|
8598
8847
|
var SESSION_TOKEN_TOTAL_ATTR = "traces.session.total_tokens";
|
|
8599
8848
|
var SPAN_SYNTHESIZED_ATTR = "traces.codex.span_synthesized";
|
|
8600
8849
|
var CORRUPTION_COUNT_ATTR = "traces.session.corruption_count";
|
|
@@ -8869,15 +9118,17 @@ function humanTurnsOf(spans) {
|
|
|
8869
9118
|
excluded: [...excluded].sort(([left], [right]) => left.localeCompare(right)).map(([reason, spanIds]) => ({ reason, turns: spanIds.length, spanIds }))
|
|
8870
9119
|
};
|
|
8871
9120
|
}
|
|
8872
|
-
function computeSessionFacts(spans) {
|
|
9121
|
+
function computeSessionFacts(spans, options = {}) {
|
|
8873
9122
|
const { sessionByTrace } = indexSessionIdsByTrace(spans);
|
|
8874
9123
|
const byTrace = /* @__PURE__ */ new Map();
|
|
8875
9124
|
for (const span2 of spans) {
|
|
8876
|
-
|
|
9125
|
+
const group = byTrace.get(span2.trace_id);
|
|
9126
|
+
if (group) group.push(span2);
|
|
9127
|
+
else byTrace.set(span2.trace_id, [span2]);
|
|
8877
9128
|
}
|
|
8878
|
-
return [...byTrace].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([traceId, traceSpans]) => sessionFactsForTrace(traceId, sessionByTrace.get(traceId) ?? null, traceSpans));
|
|
9129
|
+
return [...byTrace].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([traceId, traceSpans]) => sessionFactsForTrace(traceId, sessionByTrace.get(traceId) ?? null, traceSpans, options));
|
|
8879
9130
|
}
|
|
8880
|
-
function sessionFactsForTrace(traceId, sessionId, unordered) {
|
|
9131
|
+
function sessionFactsForTrace(traceId, sessionId, unordered, options) {
|
|
8881
9132
|
const spans = [...unordered].sort(byTraceOrder);
|
|
8882
9133
|
const harness = spans.map((span2) => stringAttr2(span2, "service.name")).find((name) => name !== null) ?? null;
|
|
8883
9134
|
const ownSpans = spans.filter((span2) => !isSubagentSpan(span2.attributes));
|
|
@@ -8916,7 +9167,7 @@ function sessionFactsForTrace(traceId, sessionId, unordered) {
|
|
|
8916
9167
|
(span2) => span2.parent_span_id !== null && span2.attributes[CORRUPTION_RECEIPT_VERSION_ATTR] === void 0
|
|
8917
9168
|
).length;
|
|
8918
9169
|
const corruptionSpan = spans.find((span2) => typeof span2.attributes[CORRUPTION_COUNT_ATTR] === "number");
|
|
8919
|
-
|
|
9170
|
+
const facts = {
|
|
8920
9171
|
schemaVersion: 1,
|
|
8921
9172
|
kind: "traces.session_facts",
|
|
8922
9173
|
traceId,
|
|
@@ -8933,6 +9184,7 @@ function sessionFactsForTrace(traceId, sessionId, unordered) {
|
|
|
8933
9184
|
spanIds: [],
|
|
8934
9185
|
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
9186
|
},
|
|
9187
|
+
measurements: computeSessionMeasurements(ownSpans, invoked, sessionId, harness),
|
|
8936
9188
|
toolCalls: {
|
|
8937
9189
|
value: invoked.length,
|
|
8938
9190
|
spanIds: invoked.map((span2) => span2.span_id),
|
|
@@ -8985,15 +9237,50 @@ function sessionFactsForTrace(traceId, sessionId, unordered) {
|
|
|
8985
9237
|
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
9238
|
}
|
|
8987
9239
|
};
|
|
9240
|
+
if (options.includeContent !== false) return facts;
|
|
9241
|
+
const withheld = () => ({
|
|
9242
|
+
value: null,
|
|
9243
|
+
spanIds: [],
|
|
9244
|
+
unavailable: "Content was not requested."
|
|
9245
|
+
});
|
|
9246
|
+
return {
|
|
9247
|
+
...facts,
|
|
9248
|
+
humanTurns: withheld(),
|
|
9249
|
+
finalMessages: withheld(),
|
|
9250
|
+
changedFiles: withheld(),
|
|
9251
|
+
pullRequests: withheld(),
|
|
9252
|
+
subagents: {
|
|
9253
|
+
...facts.subagents,
|
|
9254
|
+
value: facts.subagents.value?.map((entry) => ({
|
|
9255
|
+
...entry,
|
|
9256
|
+
taskName: null,
|
|
9257
|
+
taskNameUnavailable: "Assignment text was not requested."
|
|
9258
|
+
})) ?? null
|
|
9259
|
+
},
|
|
9260
|
+
firstFailure: facts.firstFailure.status === "found" ? { ...facts.firstFailure, message: null, classification: facts.firstFailure.classification ? { ...facts.firstFailure.classification, reason: "Content was not requested." } : null } : facts.firstFailure
|
|
9261
|
+
};
|
|
8988
9262
|
}
|
|
8989
9263
|
function buildSessionFactsReport(spans, options = {}) {
|
|
9264
|
+
const digest = createHash7("sha256");
|
|
9265
|
+
const ordered2 = [...spans].sort((a, b) => a.trace_id.localeCompare(b.trace_id) || a.span_id.localeCompare(b.span_id));
|
|
9266
|
+
let evidenceThrough = null;
|
|
9267
|
+
for (const span2 of ordered2) {
|
|
9268
|
+
digest.update(canonicalJson(span2)).update("\n");
|
|
9269
|
+
for (const time of [span2.start_time, span2.end_time]) {
|
|
9270
|
+
if (Number.isFinite(Date.parse(time)) && (evidenceThrough === null || Date.parse(time) > Date.parse(evidenceThrough))) evidenceThrough = time;
|
|
9271
|
+
}
|
|
9272
|
+
}
|
|
8990
9273
|
return {
|
|
8991
9274
|
schemaVersion: 1,
|
|
8992
9275
|
kind: "traces.session_facts_report",
|
|
9276
|
+
measurementVersion: SESSION_FACTS_MEASUREMENT_VERSION,
|
|
9277
|
+
sourceDigest: "sha256:" + digest.digest("hex"),
|
|
9278
|
+
evidenceThrough,
|
|
9279
|
+
contentIncluded: options.includeContent !== false,
|
|
8993
9280
|
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8994
9281
|
harness: options.harness ?? null,
|
|
8995
9282
|
spanCount: spans.length,
|
|
8996
|
-
sessions: computeSessionFacts(spans)
|
|
9283
|
+
sessions: computeSessionFacts(spans, options)
|
|
8997
9284
|
};
|
|
8998
9285
|
}
|
|
8999
9286
|
function factLine(label2, fact, rendered) {
|
|
@@ -9042,6 +9329,8 @@ function renderSessionFacts(report) {
|
|
|
9042
9329
|
lines.push(factLine("changed files", facts.changedFiles, String(facts.changedFiles.value?.length ?? 0)));
|
|
9043
9330
|
lines.push(factLine("first record", facts.firstRecordAt, String(facts.firstRecordAt.value)));
|
|
9044
9331
|
lines.push(factLine("last record", facts.lastRecordAt, String(facts.lastRecordAt.value)));
|
|
9332
|
+
lines.push(factLine("compactions", facts.measurements.compactions, String(facts.measurements.compactions.value?.length ?? 0)));
|
|
9333
|
+
lines.push(" skills: " + facts.measurements.adoption.totalSkillInvocations + " recorded invocation(s), " + facts.measurements.adoption.totalSkillDocumentReads + " document read(s); telemetry " + facts.measurements.adoption.skillTelemetryStatus);
|
|
9045
9334
|
lines.push(factLine("token total", facts.tokenTotal, String(facts.tokenTotal.value)));
|
|
9046
9335
|
lines.push(` first failure: ${renderFirstFailure(facts.firstFailure)}`);
|
|
9047
9336
|
if ((facts.unreadRecords.value ?? 0) > 0) {
|
|
@@ -9087,6 +9376,9 @@ ${JSON.stringify({ sessions, ...dropped2.length ? { omitted_fields: dropped2 } :
|
|
|
9087
9376
|
["tool_call_span_ids", (facts) => {
|
|
9088
9377
|
facts.tool_calls.span_ids = [];
|
|
9089
9378
|
}],
|
|
9379
|
+
["measurements", (facts) => {
|
|
9380
|
+
delete facts.measurements;
|
|
9381
|
+
}],
|
|
9090
9382
|
["tool_calls_by_name", (facts) => {
|
|
9091
9383
|
delete facts.tool_calls_by_name;
|
|
9092
9384
|
}],
|
|
@@ -9211,7 +9503,8 @@ function compactFacts(facts) {
|
|
|
9211
9503
|
first_record_at: factJson(facts.firstRecordAt),
|
|
9212
9504
|
last_record_at: factJson(facts.lastRecordAt),
|
|
9213
9505
|
token_total: factJson(facts.tokenTotal),
|
|
9214
|
-
first_failure: compactFirstFailure(facts.firstFailure)
|
|
9506
|
+
first_failure: compactFirstFailure(facts.firstFailure),
|
|
9507
|
+
measurements: facts.measurements
|
|
9215
9508
|
};
|
|
9216
9509
|
}
|
|
9217
9510
|
function compactFirstFailure(failure2) {
|
|
@@ -9240,7 +9533,7 @@ function sessionFactsContext(spans, options = {}) {
|
|
|
9240
9533
|
if (report.sessions.length === 0) return void 0;
|
|
9241
9534
|
return renderSessionFactsContext(report, options);
|
|
9242
9535
|
}
|
|
9243
|
-
var SESSION_FACTS_VERSION_SUFFIX = "session-facts.
|
|
9536
|
+
var SESSION_FACTS_VERSION_SUFFIX = "session-facts." + SESSION_FACTS_MEASUREMENT_VERSION;
|
|
9244
9537
|
function withSessionFactsContext(definitions, spans, options = {}) {
|
|
9245
9538
|
const sheet = sessionFactsContext(spans, options);
|
|
9246
9539
|
if (sheet === void 0) return [...definitions];
|
|
@@ -9267,13 +9560,13 @@ import { OtlpFileTraceStore } from "@tangle-network/agent-eval/traces";
|
|
|
9267
9560
|
|
|
9268
9561
|
// src/bundle-source.ts
|
|
9269
9562
|
import { isUtf8 as isUtf82 } from "buffer";
|
|
9270
|
-
import { createHash as
|
|
9563
|
+
import { createHash as createHash10 } from "crypto";
|
|
9271
9564
|
import { constants } from "fs";
|
|
9272
9565
|
import { lstat as lstat2, open, realpath as realpath3 } from "fs/promises";
|
|
9273
9566
|
import { dirname as dirname8, isAbsolute as isAbsolute4, join as join20, relative as relative5, resolve as resolve7, sep as sep2 } from "path";
|
|
9274
9567
|
|
|
9275
9568
|
// src/bundle.ts
|
|
9276
|
-
import { createHash as
|
|
9569
|
+
import { createHash as createHash9 } from "crypto";
|
|
9277
9570
|
import { copyFile, cp, lstat, mkdir as mkdir2, readdir as readdir16, readFile as readFile7, stat as stat14, writeFile as writeFile5 } from "fs/promises";
|
|
9278
9571
|
import { dirname as dirname7, join as join19, relative as relative4, resolve as resolve6, sep } from "path";
|
|
9279
9572
|
|
|
@@ -10502,7 +10795,7 @@ function measured(value, render) {
|
|
|
10502
10795
|
}
|
|
10503
10796
|
function distributionRow(label2, distribution, unit = "") {
|
|
10504
10797
|
if (distribution.n === 0) return `| ${label2} | 0 | not captured | not captured | not captured | not captured | not captured |`;
|
|
10505
|
-
const value = (
|
|
10798
|
+
const value = (number2) => measured(number2, (captured) => `${decimal(captured)}${unit}`);
|
|
10506
10799
|
return `| ${label2} | ${count(distribution.n)} | ${value(distribution.min)} | ${value(distribution.mean)} | ${value(distribution.p50)} | ${value(distribution.p95)} | ${value(distribution.max)} |`;
|
|
10507
10800
|
}
|
|
10508
10801
|
function tokenRows(usage) {
|
|
@@ -11853,7 +12146,7 @@ async function writeSessionIndexFile(index, outPath) {
|
|
|
11853
12146
|
}
|
|
11854
12147
|
|
|
11855
12148
|
// src/session-selection.ts
|
|
11856
|
-
import { createHash as
|
|
12149
|
+
import { createHash as createHash8 } from "crypto";
|
|
11857
12150
|
import { createReadStream as createReadStream2 } from "fs";
|
|
11858
12151
|
|
|
11859
12152
|
// src/session-workflow.ts
|
|
@@ -12179,7 +12472,7 @@ async function collectSessionWorkflow(adapter, seeds, options = {}) {
|
|
|
12179
12472
|
// src/session-selection.ts
|
|
12180
12473
|
async function fileSha256(path, signal) {
|
|
12181
12474
|
signal?.throwIfAborted();
|
|
12182
|
-
const hash2 =
|
|
12475
|
+
const hash2 = createHash8("sha256");
|
|
12183
12476
|
try {
|
|
12184
12477
|
for await (const chunk of createReadStream2(path, { signal })) {
|
|
12185
12478
|
signal?.throwIfAborted();
|
|
@@ -12207,7 +12500,7 @@ async function sourceSnapshot(adapter, ref, signal) {
|
|
|
12207
12500
|
signal?.throwIfAborted();
|
|
12208
12501
|
files.push({ path, sha256: await fileSha256(path, signal) });
|
|
12209
12502
|
}
|
|
12210
|
-
const sha2562 = files.length === 1 ? files[0].sha256 :
|
|
12503
|
+
const sha2562 = files.length === 1 ? files[0].sha256 : createHash8("sha256").update(JSON.stringify(files)).digest("hex");
|
|
12211
12504
|
return { sha256: sha2562, files };
|
|
12212
12505
|
}
|
|
12213
12506
|
function summarizeWorkflows(workflows) {
|
|
@@ -12333,8 +12626,8 @@ var KNOWN_LIMITS = [
|
|
|
12333
12626
|
"progress.md is copied whole; no session-dated section extraction is performed",
|
|
12334
12627
|
"open pull-request state is not captured: the bundler makes no network calls"
|
|
12335
12628
|
];
|
|
12336
|
-
function sha256Hex(
|
|
12337
|
-
return
|
|
12629
|
+
function sha256Hex(bytes2) {
|
|
12630
|
+
return createHash9("sha256").update(bytes2).digest("hex");
|
|
12338
12631
|
}
|
|
12339
12632
|
function bundlePath(...parts) {
|
|
12340
12633
|
return parts.join("/");
|
|
@@ -12741,13 +13034,13 @@ async function verifySessionBundle(bundleDir) {
|
|
|
12741
13034
|
let source;
|
|
12742
13035
|
if (manifest.projection) {
|
|
12743
13036
|
const sourceManifest = join19(manifest.projection.sourceDirectory, "manifest.json");
|
|
12744
|
-
const
|
|
13037
|
+
const bytes2 = await readFile7(sourceManifest).catch((error) => {
|
|
12745
13038
|
if (isMissingPathError(error)) return null;
|
|
12746
13039
|
throw error;
|
|
12747
13040
|
});
|
|
12748
13041
|
source = {
|
|
12749
13042
|
directory: manifest.projection.sourceDirectory,
|
|
12750
|
-
status:
|
|
13043
|
+
status: bytes2 === null ? "unavailable" : sha256Hex(bytes2) === manifest.projection.sourceManifestSha256 ? "match" : "mismatch"
|
|
12751
13044
|
};
|
|
12752
13045
|
}
|
|
12753
13046
|
return {
|
|
@@ -12791,8 +13084,8 @@ async function openBundleFile(root, file) {
|
|
|
12791
13084
|
return handle;
|
|
12792
13085
|
}
|
|
12793
13086
|
async function fileDigest(handle, signal) {
|
|
12794
|
-
const hash2 =
|
|
12795
|
-
for await (const
|
|
13087
|
+
const hash2 = createHash10("sha256");
|
|
13088
|
+
for await (const bytes2 of handle.createReadStream({ start: 0, autoClose: false, signal })) hash2.update(bytes2);
|
|
12796
13089
|
return hash2.digest("hex");
|
|
12797
13090
|
}
|
|
12798
13091
|
async function validateFile(root, file, signal) {
|
|
@@ -12892,7 +13185,7 @@ async function createBundleSourceReader(directory, spans, options = {}) {
|
|
|
12892
13185
|
handle = await openBundleFile(root, file);
|
|
12893
13186
|
const before = await handle.stat();
|
|
12894
13187
|
if (await fileDigest(handle, signal) !== file.sha256) return unavailable("retained source digest mismatch");
|
|
12895
|
-
const hash2 =
|
|
13188
|
+
const hash2 = createHash10("sha256");
|
|
12896
13189
|
const record2 = Buffer.alloc(ref.recordBytes);
|
|
12897
13190
|
let position = 0;
|
|
12898
13191
|
for await (const raw of handle.createReadStream({
|
|
@@ -12901,10 +13194,10 @@ async function createBundleSourceReader(directory, spans, options = {}) {
|
|
|
12901
13194
|
autoClose: false,
|
|
12902
13195
|
signal
|
|
12903
13196
|
})) {
|
|
12904
|
-
const
|
|
12905
|
-
hash2.update(
|
|
12906
|
-
|
|
12907
|
-
position +=
|
|
13197
|
+
const bytes3 = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
|
|
13198
|
+
hash2.update(bytes3);
|
|
13199
|
+
bytes3.copy(record2, position);
|
|
13200
|
+
position += bytes3.length;
|
|
12908
13201
|
}
|
|
12909
13202
|
const after = await handle.stat();
|
|
12910
13203
|
if (position !== ref.recordBytes || hash2.digest("hex") !== ref.recordSha256 || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs) {
|
|
@@ -12927,22 +13220,22 @@ async function createBundleSourceReader(directory, spans, options = {}) {
|
|
|
12927
13220
|
if (input.offset > field.length) return unavailable("source offset is outside the selected field");
|
|
12928
13221
|
const window = field.subarray(input.offset, Math.min(field.length, input.offset + input.limit));
|
|
12929
13222
|
if (window.length > 0 && (window[0] & 192) === 128) return unavailable("offset splits a UTF-8 character");
|
|
12930
|
-
let
|
|
13223
|
+
let bytes2 = window;
|
|
12931
13224
|
for (let omitted = 0; omitted <= 3; omitted += 1) {
|
|
12932
|
-
|
|
12933
|
-
if (isUtf82(
|
|
13225
|
+
bytes2 = window.subarray(0, Math.max(0, window.length - omitted));
|
|
13226
|
+
if (isUtf82(bytes2)) break;
|
|
12934
13227
|
}
|
|
12935
|
-
if (!isUtf82(
|
|
13228
|
+
if (!isUtf82(bytes2) || bytes2.length === 0 && input.offset < field.length) {
|
|
12936
13229
|
return unavailable("window cannot contain a complete UTF-8 character");
|
|
12937
13230
|
}
|
|
12938
|
-
const next = input.offset +
|
|
13231
|
+
const next = input.offset + bytes2.length;
|
|
12939
13232
|
return {
|
|
12940
13233
|
status: "available",
|
|
12941
13234
|
trace_id: input.trace_id,
|
|
12942
13235
|
span_id: input.span_id,
|
|
12943
13236
|
attribute: input.attribute,
|
|
12944
13237
|
source_index: sourceIndex,
|
|
12945
|
-
text:
|
|
13238
|
+
text: bytes2.toString("utf8"),
|
|
12946
13239
|
offset: input.offset,
|
|
12947
13240
|
total_bytes: field.length,
|
|
12948
13241
|
next_offset: next < field.length ? next : null,
|
|
@@ -13075,7 +13368,7 @@ async function analyzeSpans(spans, opts = {}) {
|
|
|
13075
13368
|
}
|
|
13076
13369
|
|
|
13077
13370
|
// src/chat-trajectory.ts
|
|
13078
|
-
import { createHash as
|
|
13371
|
+
import { createHash as createHash11 } from "crypto";
|
|
13079
13372
|
function isChatTrajectoryMessage(value) {
|
|
13080
13373
|
return isObject2(value) && typeof value.role === "string" && typeof value.content === "string";
|
|
13081
13374
|
}
|
|
@@ -13160,7 +13453,7 @@ function trajectoryId(trajectory) {
|
|
|
13160
13453
|
const value = trajectory.trajectory_id ?? trajectory.traj_id ?? trajectory.instance_id;
|
|
13161
13454
|
if (typeof value === "string" && value.trim()) return value;
|
|
13162
13455
|
if (typeof value === "number" && Number.isSafeInteger(value)) return String(value);
|
|
13163
|
-
return `trajectory-${
|
|
13456
|
+
return `trajectory-${createHash11("sha256").update(JSON.stringify(trajectory.messages)).digest("hex").slice(0, 24)}`;
|
|
13164
13457
|
}
|
|
13165
13458
|
function messageKind(role) {
|
|
13166
13459
|
const normalized = role.trim().toLowerCase();
|
|
@@ -13217,8 +13510,8 @@ function parseTime2(value) {
|
|
|
13217
13510
|
}
|
|
13218
13511
|
function finiteNumber(value, key) {
|
|
13219
13512
|
if (!isObject2(value)) return void 0;
|
|
13220
|
-
const
|
|
13221
|
-
return typeof
|
|
13513
|
+
const number2 = value[key];
|
|
13514
|
+
return typeof number2 === "number" && Number.isFinite(number2) ? number2 : void 0;
|
|
13222
13515
|
}
|
|
13223
13516
|
function stringValue2(value) {
|
|
13224
13517
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
@@ -13240,7 +13533,7 @@ import {
|
|
|
13240
13533
|
import { basename as basename13, dirname as dirname10, join as join23, resolve as resolve9 } from "path";
|
|
13241
13534
|
|
|
13242
13535
|
// src/codetracebench-files.ts
|
|
13243
|
-
import { createHash as
|
|
13536
|
+
import { createHash as createHash12, randomUUID } from "crypto";
|
|
13244
13537
|
import {
|
|
13245
13538
|
link,
|
|
13246
13539
|
lstat as lstat3,
|
|
@@ -13438,7 +13731,7 @@ async function atomicWriteFile(path, content, signal) {
|
|
|
13438
13731
|
}
|
|
13439
13732
|
}
|
|
13440
13733
|
function sha256(value) {
|
|
13441
|
-
return
|
|
13734
|
+
return createHash12("sha256").update(value).digest("hex");
|
|
13442
13735
|
}
|
|
13443
13736
|
function isObject3(value) {
|
|
13444
13737
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -13675,10 +13968,10 @@ async function readOptionalTask(trajectoryDirectory, caseDirectory, row, signal)
|
|
|
13675
13968
|
content: `Task: ${row.taskName}`
|
|
13676
13969
|
};
|
|
13677
13970
|
}
|
|
13678
|
-
const
|
|
13679
|
-
const content =
|
|
13971
|
+
const bytes2 = await readFile9(path, { signal });
|
|
13972
|
+
const content = bytes2.toString("utf8").trim();
|
|
13680
13973
|
if (!content) throw new Error(`${row.trajId}/task.md is empty`);
|
|
13681
|
-
return { source: "task.md", content, path, bytes };
|
|
13974
|
+
return { source: "task.md", content, path, bytes: bytes2 };
|
|
13682
13975
|
}
|
|
13683
13976
|
function assertNoLabelLeak(spans, row) {
|
|
13684
13977
|
for (const item of spans) {
|
|
@@ -14328,7 +14621,7 @@ async function cmdVerifyFindings(argv) {
|
|
|
14328
14621
|
}
|
|
14329
14622
|
|
|
14330
14623
|
// src/hodoscope.ts
|
|
14331
|
-
import { createHash as
|
|
14624
|
+
import { createHash as createHash13 } from "crypto";
|
|
14332
14625
|
import { mkdir as mkdir5, mkdtemp as mkdtemp5, readFile as readFile11, readdir as readdir17, writeFile as writeFile7 } from "fs/promises";
|
|
14333
14626
|
import { endianness, tmpdir as tmpdir5 } from "os";
|
|
14334
14627
|
import { join as join24, resolve as resolve10 } from "path";
|
|
@@ -14367,7 +14660,7 @@ async function writeHodoscopeInput(otlpPath, options = {}) {
|
|
|
14367
14660
|
throw new Error(`Hodoscope trace '${traceId}' contains no assistant actions`);
|
|
14368
14661
|
}
|
|
14369
14662
|
actionCount += actions;
|
|
14370
|
-
const file = `${
|
|
14663
|
+
const file = `${createHash13("sha256").update(traceId).digest("hex").slice(0, 24)}.json`;
|
|
14371
14664
|
await writeFile7(join24(samples, file), `${JSON.stringify(trajectory, null, 2)}
|
|
14372
14665
|
`, "utf8");
|
|
14373
14666
|
}
|
|
@@ -14879,7 +15172,7 @@ function validateEmbedding(encoded, dimensions, path) {
|
|
|
14879
15172
|
`Hodoscope ${path} encodes ${encoded.length} characters; expected ${expectedLength} for ${dimensions} float32 values`
|
|
14880
15173
|
);
|
|
14881
15174
|
}
|
|
14882
|
-
const
|
|
15175
|
+
const bytes2 = new Uint8Array(dimensions * 4);
|
|
14883
15176
|
for (let group = 0; group < dimensions; group += 1) {
|
|
14884
15177
|
let value = 0;
|
|
14885
15178
|
const encodedOffset = group * 5;
|
|
@@ -14895,12 +15188,12 @@ function validateEmbedding(encoded, dimensions, path) {
|
|
|
14895
15188
|
throw new Error(`Hodoscope ${path} contains an overflowing base85 block at index ${group}`);
|
|
14896
15189
|
}
|
|
14897
15190
|
const byteOffset = group * 4;
|
|
14898
|
-
|
|
14899
|
-
|
|
14900
|
-
|
|
14901
|
-
|
|
15191
|
+
bytes2[byteOffset] = Math.floor(value / 16777216) & 255;
|
|
15192
|
+
bytes2[byteOffset + 1] = Math.floor(value / 65536) & 255;
|
|
15193
|
+
bytes2[byteOffset + 2] = Math.floor(value / 256) & 255;
|
|
15194
|
+
bytes2[byteOffset + 3] = value & 255;
|
|
14902
15195
|
}
|
|
14903
|
-
const view = new DataView(
|
|
15196
|
+
const view = new DataView(bytes2.buffer, bytes2.byteOffset, bytes2.byteLength);
|
|
14904
15197
|
const littleEndian = endianness() === "LE";
|
|
14905
15198
|
for (let index = 0; index < dimensions; index += 1) {
|
|
14906
15199
|
if (!Number.isFinite(view.getFloat32(index * 4, littleEndian))) {
|
|
@@ -15015,7 +15308,7 @@ function assertPositiveInteger(name, value) {
|
|
|
15015
15308
|
}
|
|
15016
15309
|
|
|
15017
15310
|
// src/analyst-engine-prime.ts
|
|
15018
|
-
import { createHash as
|
|
15311
|
+
import { createHash as createHash14 } from "crypto";
|
|
15019
15312
|
import { request as httpRequest } from "http";
|
|
15020
15313
|
import { request as httpsRequest } from "https";
|
|
15021
15314
|
var DEFAULT_BRIDGE_URL = "http://localhost:4181";
|
|
@@ -15028,6 +15321,17 @@ var MAX_FINDINGS = 10;
|
|
|
15028
15321
|
var MAX_SPAN_IDS_PER_FINDING = 8;
|
|
15029
15322
|
var SEVERITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "low", "info"]);
|
|
15030
15323
|
var ANALYST_ID = "prime";
|
|
15324
|
+
var TRAJECTORY_FILE_NAME = "trajectory.otlp.jsonl";
|
|
15325
|
+
async function fileDelivery(otlpPath, perAttributeCharCap, signal) {
|
|
15326
|
+
signal?.throwIfAborted();
|
|
15327
|
+
const { copyFile: copyFile2, mkdtemp: mkdtemp8, stat: stat19 } = await import("fs/promises");
|
|
15328
|
+
const { tmpdir: tmpdir8 } = await import("os");
|
|
15329
|
+
const { join: join35 } = await import("path");
|
|
15330
|
+
const cwd = await mkdtemp8(join35(tmpdir8(), "traces-prime-"));
|
|
15331
|
+
await copyFile2(otlpPath, join35(cwd, TRAJECTORY_FILE_NAME));
|
|
15332
|
+
const artifactBytes = (await stat19(otlpPath)).size;
|
|
15333
|
+
return { mode: "file-cwd", perAttributeCharCap, renderedChars: artifactBytes, cwd, fileName: TRAJECTORY_FILE_NAME };
|
|
15334
|
+
}
|
|
15031
15335
|
function positiveIntegerEnv(name) {
|
|
15032
15336
|
const raw = process.env[name];
|
|
15033
15337
|
if (raw === void 0 || raw === "") return void 0;
|
|
@@ -15078,7 +15382,7 @@ function capRowAttributes(row, cap) {
|
|
|
15078
15382
|
}
|
|
15079
15383
|
return { ...row, attributes: capped };
|
|
15080
15384
|
}
|
|
15081
|
-
async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, signal) {
|
|
15385
|
+
async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, deliveryMode, signal) {
|
|
15082
15386
|
const rows = [];
|
|
15083
15387
|
const spanTraces = /* @__PURE__ */ new Map();
|
|
15084
15388
|
let index = 0;
|
|
@@ -15102,9 +15406,18 @@ async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, si
|
|
|
15102
15406
|
}
|
|
15103
15407
|
if (rows.length === 0) throw new Error(`${otlpPath} contains no spans`);
|
|
15104
15408
|
const traceCount = new Set(rows.map((row) => row.trace_id)).size;
|
|
15409
|
+
if (deliveryMode === "file") {
|
|
15410
|
+
return {
|
|
15411
|
+
rows,
|
|
15412
|
+
rendered: null,
|
|
15413
|
+
delivery: await fileDelivery(otlpPath, perAttributeCharCap, signal),
|
|
15414
|
+
traceCount,
|
|
15415
|
+
spanTraces
|
|
15416
|
+
};
|
|
15417
|
+
}
|
|
15105
15418
|
let projected = rows;
|
|
15106
15419
|
let rendered = JSON.stringify(projected);
|
|
15107
|
-
|
|
15420
|
+
let delivery = {
|
|
15108
15421
|
mode: "inline-json",
|
|
15109
15422
|
perAttributeCharCap: null,
|
|
15110
15423
|
renderedChars: rendered.length
|
|
@@ -15112,18 +15425,30 @@ async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, si
|
|
|
15112
15425
|
if (rendered.length > maxInlineChars) {
|
|
15113
15426
|
projected = rows.map((row) => capRowAttributes(row, perAttributeCharCap));
|
|
15114
15427
|
rendered = JSON.stringify(projected);
|
|
15115
|
-
delivery
|
|
15116
|
-
|
|
15428
|
+
delivery = {
|
|
15429
|
+
mode: "inline-json",
|
|
15430
|
+
perAttributeCharCap,
|
|
15431
|
+
renderedChars: rendered.length
|
|
15432
|
+
};
|
|
15117
15433
|
}
|
|
15118
15434
|
if (rendered.length > maxInlineChars) {
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15435
|
+
if (deliveryMode === "inline") {
|
|
15436
|
+
throw new Error(
|
|
15437
|
+
`trajectory renders to ${rendered.length} chars even at per-attribute cap ${perAttributeCharCap} (budget ${maxInlineChars}); inline delivery impossible`
|
|
15438
|
+
);
|
|
15439
|
+
}
|
|
15440
|
+
return {
|
|
15441
|
+
rows,
|
|
15442
|
+
rendered: null,
|
|
15443
|
+
delivery: await fileDelivery(otlpPath, perAttributeCharCap, signal),
|
|
15444
|
+
traceCount,
|
|
15445
|
+
spanTraces
|
|
15446
|
+
};
|
|
15122
15447
|
}
|
|
15123
15448
|
return { rows: projected, rendered, delivery, traceCount, spanTraces };
|
|
15124
15449
|
}
|
|
15125
15450
|
var OUTPUT_CONTRACT_LINES = [
|
|
15126
|
-
"OUTPUT CONTRACT (
|
|
15451
|
+
"OUTPUT CONTRACT (inline delivery \u2014 the whole trajectory is in this prompt):",
|
|
15127
15452
|
"You are a one-shot analyst. Every fact you need is in the TRAJECTORY JSON below.",
|
|
15128
15453
|
"Do not run shell commands, do not read or write files, do not use any tools.",
|
|
15129
15454
|
"Reply with EXACTLY one fenced ```json code block and no other fenced block. The JSON object has exactly two fields:",
|
|
@@ -15139,7 +15464,38 @@ var OUTPUT_CONTRACT_LINES = [
|
|
|
15139
15464
|
`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
15465
|
'"findings" is [] only for a clean trajectory.'
|
|
15141
15466
|
];
|
|
15467
|
+
var FILE_DELIVERY_CONTRACT_LINES = [
|
|
15468
|
+
"OUTPUT CONTRACT (file delivery \u2014 the trajectory is a FILE in your working directory):",
|
|
15469
|
+
`You are a recursive language model with a REPL. The trajectory is ${TRAJECTORY_FILE_NAME} in your`,
|
|
15470
|
+
"working directory: OpenInference JSONL, one span object per line. Load it yourself and inspect it with code",
|
|
15471
|
+
'(e.g. rows = [json.loads(line) for line in open("trajectory.otlp.jsonl")]), paging rather than printing it whole;',
|
|
15472
|
+
"fan out with rlm() for sub-questions when that pays for itself. Stay inside your working directory.",
|
|
15473
|
+
"Reply with EXACTLY one fenced ```json code block and no other fenced block. The JSON object has exactly two fields:",
|
|
15474
|
+
' "answer": string \u2014 ONE short sentence (max 300 chars) summarizing your verdict.',
|
|
15475
|
+
' "findings": array (possibly empty) of finding rows, each exactly:',
|
|
15476
|
+
' {"span_ids": [string, ...],',
|
|
15477
|
+
' "severity": "critical"|"high"|"medium"|"low"|"info",',
|
|
15478
|
+
' "area": string (short kebab-case topic, max 40 chars),',
|
|
15479
|
+
' "claim": string (ONE short sentence, max 200 chars),',
|
|
15480
|
+
' "action": string (optional, ONE short imperative sentence, max 200 chars),',
|
|
15481
|
+
' "confidence": number 0..1}',
|
|
15482
|
+
"Do NOT include a rationale field. Keep every string SHORT \u2014 long strings get corrupted in transport and void your work.",
|
|
15483
|
+
`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}.`,
|
|
15484
|
+
'"findings" is [] only for a clean trajectory.',
|
|
15485
|
+
"FINAL MESSAGE RULE: after your REPL work is done, send ONE last message that contains NOTHING BUT the",
|
|
15486
|
+
"fenced ```json block above \u2014 no preamble, no summary, no code identifiers around it. A reply whose last",
|
|
15487
|
+
"message is prose or a partial block is discarded unread."
|
|
15488
|
+
];
|
|
15142
15489
|
function buildPrompt(question, projection) {
|
|
15490
|
+
if (projection.delivery.mode === "file-cwd") {
|
|
15491
|
+
return [
|
|
15492
|
+
`QUESTION: ${question}`,
|
|
15493
|
+
"",
|
|
15494
|
+
...FILE_DELIVERY_CONTRACT_LINES,
|
|
15495
|
+
"",
|
|
15496
|
+
`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.`
|
|
15497
|
+
].join("\n");
|
|
15498
|
+
}
|
|
15143
15499
|
return [
|
|
15144
15500
|
`QUESTION: ${question}`,
|
|
15145
15501
|
"",
|
|
@@ -15149,16 +15505,23 @@ function buildPrompt(question, projection) {
|
|
|
15149
15505
|
projection.rendered
|
|
15150
15506
|
].join("\n");
|
|
15151
15507
|
}
|
|
15152
|
-
function
|
|
15508
|
+
function replyWasTruncated(previousReply, finishReason) {
|
|
15509
|
+
if (finishReason === "length") return true;
|
|
15510
|
+
if (finishReason !== null) return false;
|
|
15511
|
+
return extractJsonObject(previousReply) === null;
|
|
15512
|
+
}
|
|
15513
|
+
function buildRepairPrompt(parseDefect, previousReply, finishReason) {
|
|
15514
|
+
const truncated = replyWasTruncated(previousReply, finishReason);
|
|
15153
15515
|
return [
|
|
15154
15516
|
"Your previous reply to a trace-analysis task was structurally malformed and could not be parsed",
|
|
15155
|
-
`(${parseDefect}). Below is your previous reply verbatim
|
|
15156
|
-
"fenced ```json block, no other text, no tools. The JSON object has exactly two fields:",
|
|
15517
|
+
`(${parseDefect}${truncated ? "; it was cut off by the output limit" : ""}). Below is your previous reply verbatim.`,
|
|
15518
|
+
"Re-emit ONLY the corrected JSON \u2014 one fenced ```json block, no other text, no tools. The JSON object has exactly two fields:",
|
|
15157
15519
|
' "answer": string (ONE short sentence, max 300 chars)',
|
|
15158
15520
|
' "findings": array (possibly empty) of {"span_ids": [string, ...],',
|
|
15159
15521
|
' "severity": "critical"|"high"|"medium"|"low"|"info", "area": string (max 40 chars),',
|
|
15160
15522
|
' "claim": string (max 200 chars), "action": string (optional, max 200 chars), "confidence": number 0..1}',
|
|
15161
|
-
"No rationale field. Keep every string SHORT.
|
|
15523
|
+
"No rationale field. Keep every string SHORT.",
|
|
15524
|
+
truncated ? "Your previous reply was cut off by the output limit, so re-emit at most 3 findings \u2014 your strongest \u2014 with terse strings (claim and action under 120 chars each); do not reproduce the whole cut-off list." : "Preserve the span ids and verdicts of your previous reply exactly; shorten prose freely.",
|
|
15162
15525
|
"",
|
|
15163
15526
|
"PREVIOUS REPLY:",
|
|
15164
15527
|
previousReply
|
|
@@ -15239,7 +15602,7 @@ function toAnalystFinding(row, index, spanTraces, model, producedAt) {
|
|
|
15239
15602
|
const traces = spanTraces.get(spanId);
|
|
15240
15603
|
return spanEvidenceUri([...traces][0], spanId);
|
|
15241
15604
|
});
|
|
15242
|
-
const findingId = `prime-${
|
|
15605
|
+
const findingId = `prime-${createHash14("sha256").update(`${index}
|
|
15243
15606
|
${row.claim}
|
|
15244
15607
|
${uris.join("\n")}`).digest("hex").slice(0, 12)}`;
|
|
15245
15608
|
return {
|
|
@@ -15286,7 +15649,7 @@ var httpJsonTransport = ({ url, body, signal }) => {
|
|
|
15286
15649
|
};
|
|
15287
15650
|
var PrimeBridgeError = class extends Error {
|
|
15288
15651
|
};
|
|
15289
|
-
async function callBridge(transport, url, model, content, timeoutMs, callerSignal, turn) {
|
|
15652
|
+
async function callBridge(transport, url, model, content, timeoutMs, callerSignal, turn, cwd) {
|
|
15290
15653
|
const controller = new AbortController();
|
|
15291
15654
|
const timer = setTimeout(
|
|
15292
15655
|
() => controller.abort(new Error(`${turn}bridge call exceeded ${timeoutMs}ms`)),
|
|
@@ -15299,7 +15662,7 @@ async function callBridge(transport, url, model, content, timeoutMs, callerSigna
|
|
|
15299
15662
|
try {
|
|
15300
15663
|
response = await transport({
|
|
15301
15664
|
url,
|
|
15302
|
-
body: { model, messages: [{ role: "user", content }] },
|
|
15665
|
+
body: { model, messages: [{ role: "user", content }], ...cwd ? { cwd } : {} },
|
|
15303
15666
|
signal: controller.signal
|
|
15304
15667
|
});
|
|
15305
15668
|
} catch (error) {
|
|
@@ -15325,7 +15688,12 @@ async function callBridge(transport, url, model, content, timeoutMs, callerSigna
|
|
|
15325
15688
|
if (typeof replyContent !== "string" || replyContent.length === 0) {
|
|
15326
15689
|
throw new PrimeBridgeError(`${turn}bridge reply carries no message content`);
|
|
15327
15690
|
}
|
|
15328
|
-
|
|
15691
|
+
const finishReason = record2.choices?.[0]?.finish_reason;
|
|
15692
|
+
return {
|
|
15693
|
+
content: replyContent,
|
|
15694
|
+
usage: normalizeUsage(record2.usage),
|
|
15695
|
+
finishReason: typeof finishReason === "string" && finishReason.length > 0 ? finishReason : null
|
|
15696
|
+
};
|
|
15329
15697
|
}
|
|
15330
15698
|
function failure(output, error) {
|
|
15331
15699
|
return { analyzer: ANALYST_ID, kind: "report", ok: false, output, error };
|
|
@@ -15346,6 +15714,10 @@ function primeAnalyzer(opts = {}) {
|
|
|
15346
15714
|
throw new RangeError("perAttributeCharCap must be a positive safe integer");
|
|
15347
15715
|
}
|
|
15348
15716
|
const repairEnabled = opts.repair ?? true;
|
|
15717
|
+
const deliveryMode = opts.delivery ?? "auto";
|
|
15718
|
+
if (deliveryMode !== "auto" && deliveryMode !== "file" && deliveryMode !== "inline") {
|
|
15719
|
+
throw new RangeError(`delivery must be 'auto', 'file', or 'inline', got '${String(deliveryMode)}'`);
|
|
15720
|
+
}
|
|
15349
15721
|
const transport = opts.transport ?? httpJsonTransport;
|
|
15350
15722
|
const url = `${bridgeUrl}/v1/chat/completions`;
|
|
15351
15723
|
return {
|
|
@@ -15353,12 +15725,19 @@ function primeAnalyzer(opts = {}) {
|
|
|
15353
15725
|
async analyze(otlpPath, analyzeOpts = {}) {
|
|
15354
15726
|
let projection;
|
|
15355
15727
|
try {
|
|
15356
|
-
projection = await projectArtifact(
|
|
15728
|
+
projection = await projectArtifact(
|
|
15729
|
+
otlpPath,
|
|
15730
|
+
maxInlineChars,
|
|
15731
|
+
perAttributeCharCap,
|
|
15732
|
+
deliveryMode,
|
|
15733
|
+
analyzeOpts.signal
|
|
15734
|
+
);
|
|
15357
15735
|
} catch (error) {
|
|
15358
15736
|
return failure("", error instanceof Error ? error.message : String(error));
|
|
15359
15737
|
}
|
|
15360
15738
|
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"})`;
|
|
15739
|
+
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"})`;
|
|
15740
|
+
const deliveryCwd = projection.delivery.mode === "file-cwd" ? projection.delivery.cwd : void 0;
|
|
15362
15741
|
let reply;
|
|
15363
15742
|
try {
|
|
15364
15743
|
reply = await callBridge(
|
|
@@ -15368,7 +15747,8 @@ function primeAnalyzer(opts = {}) {
|
|
|
15368
15747
|
buildPrompt(question, projection),
|
|
15369
15748
|
timeoutMs,
|
|
15370
15749
|
analyzeOpts.signal,
|
|
15371
|
-
""
|
|
15750
|
+
"",
|
|
15751
|
+
deliveryCwd
|
|
15372
15752
|
);
|
|
15373
15753
|
} catch (error) {
|
|
15374
15754
|
if (!(error instanceof PrimeBridgeError)) throw error;
|
|
@@ -15386,7 +15766,7 @@ function primeAnalyzer(opts = {}) {
|
|
|
15386
15766
|
transport,
|
|
15387
15767
|
url,
|
|
15388
15768
|
model,
|
|
15389
|
-
buildRepairPrompt(parseDefect, reply.content),
|
|
15769
|
+
buildRepairPrompt(parseDefect, reply.content, reply.finishReason),
|
|
15390
15770
|
timeoutMs,
|
|
15391
15771
|
analyzeOpts.signal,
|
|
15392
15772
|
"repair-turn "
|
|
@@ -15525,7 +15905,7 @@ async function applyRedactor(spans, redactor) {
|
|
|
15525
15905
|
}
|
|
15526
15906
|
|
|
15527
15907
|
// src/file-export.ts
|
|
15528
|
-
import { createHash as
|
|
15908
|
+
import { createHash as createHash15 } from "crypto";
|
|
15529
15909
|
import { mkdtemp as mkdtemp6, readFile as readFile12, writeFile as writeFile8 } from "fs/promises";
|
|
15530
15910
|
import { tmpdir as tmpdir6 } from "os";
|
|
15531
15911
|
import { join as join25 } from "path";
|
|
@@ -15563,7 +15943,7 @@ function stableJson(value) {
|
|
|
15563
15943
|
});
|
|
15564
15944
|
}
|
|
15565
15945
|
function hashId(value, chars = 16) {
|
|
15566
|
-
return
|
|
15946
|
+
return createHash15("sha256").update(stableJson(value)).digest("hex").slice(0, chars);
|
|
15567
15947
|
}
|
|
15568
15948
|
function isoTime2(value) {
|
|
15569
15949
|
if (typeof value === "string") {
|
|
@@ -16502,7 +16882,7 @@ async function writeInspectionReportFile(report, outPath) {
|
|
|
16502
16882
|
}
|
|
16503
16883
|
|
|
16504
16884
|
// src/live.ts
|
|
16505
|
-
import { createHash as
|
|
16885
|
+
import { createHash as createHash16 } from "crypto";
|
|
16506
16886
|
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
16887
|
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
16888
|
var CLAIM_DONE_RE = /\b(done|fixed|complete|completed|implemented|merged|verified|all green|tests pass|ready|works now)\b/i;
|
|
@@ -16522,7 +16902,7 @@ var sleep = (ms2, signal) => new Promise((resolve13) => {
|
|
|
16522
16902
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
16523
16903
|
});
|
|
16524
16904
|
function hash(parts) {
|
|
16525
|
-
return
|
|
16905
|
+
return createHash16("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
|
|
16526
16906
|
}
|
|
16527
16907
|
function asString(value) {
|
|
16528
16908
|
if (typeof value === "string" && value.trim()) return value;
|
|
@@ -18748,7 +19128,7 @@ async function watchSessions(opts) {
|
|
|
18748
19128
|
}
|
|
18749
19129
|
|
|
18750
19130
|
// src/bundle-view.ts
|
|
18751
|
-
import { createHash as
|
|
19131
|
+
import { createHash as createHash17 } from "crypto";
|
|
18752
19132
|
import { mkdir as mkdir7, readdir as readdir20, readFile as readFile16, writeFile as writeFile11 } from "fs/promises";
|
|
18753
19133
|
import { dirname as dirname11, join as join31, resolve as resolve12 } from "path";
|
|
18754
19134
|
var EVIDENCE_ONLY_ALLOW_LIST = [
|
|
@@ -18941,8 +19321,8 @@ async function projectSessionBundle(opts) {
|
|
|
18941
19321
|
const candidates = [];
|
|
18942
19322
|
for (const path of await listSessionBundleFiles(sourceDir)) {
|
|
18943
19323
|
if (path === "manifest.json") continue;
|
|
18944
|
-
const
|
|
18945
|
-
const sha2562 =
|
|
19324
|
+
const bytes2 = await readFile16(join31(sourceDir, path));
|
|
19325
|
+
const sha2562 = createHash17("sha256").update(bytes2).digest("hex");
|
|
18946
19326
|
const recorded = sourceHashes.get(path);
|
|
18947
19327
|
if (!recorded) {
|
|
18948
19328
|
throw new Error(
|
|
@@ -18956,10 +19336,10 @@ async function projectSessionBundle(opts) {
|
|
|
18956
19336
|
}
|
|
18957
19337
|
const verdict = classifyEvidenceOnlyPath(path);
|
|
18958
19338
|
if (!verdict.carried) {
|
|
18959
|
-
excluded.push({ path, bytes:
|
|
19339
|
+
excluded.push({ path, bytes: bytes2.length, sha256: sha2562, rule: verdict.rule, reason: verdict.reason });
|
|
18960
19340
|
continue;
|
|
18961
19341
|
}
|
|
18962
|
-
candidates.push({ path, bytes, sha256: sha2562, carries: verdict.carries, owned: verdict.owned });
|
|
19342
|
+
candidates.push({ path, bytes: bytes2, sha256: sha2562, carries: verdict.carries, owned: verdict.owned });
|
|
18963
19343
|
}
|
|
18964
19344
|
const report = await findSessionTextLeaks({
|
|
18965
19345
|
viewDir: sourceDir,
|
|
@@ -19018,7 +19398,7 @@ async function projectSessionBundle(opts) {
|
|
|
19018
19398
|
knownLimits: [...source.knownLimits, ...EVIDENCE_ONLY_LIMITS],
|
|
19019
19399
|
projection: {
|
|
19020
19400
|
sourceDirectory: sourceDir,
|
|
19021
|
-
sourceManifestSha256:
|
|
19401
|
+
sourceManifestSha256: createHash17("sha256").update(sourceManifestBytes).digest("hex"),
|
|
19022
19402
|
leakCheck
|
|
19023
19403
|
}
|
|
19024
19404
|
};
|
|
@@ -19442,14 +19822,14 @@ async function verifySafe(target, options = {}) {
|
|
|
19442
19822
|
const files = await listFiles(target, unreadable);
|
|
19443
19823
|
for (const file of files) {
|
|
19444
19824
|
const label2 = relative7(target, file) || basename17(file);
|
|
19445
|
-
let
|
|
19825
|
+
let bytes2;
|
|
19446
19826
|
try {
|
|
19447
|
-
|
|
19827
|
+
bytes2 = await readFile19(file);
|
|
19448
19828
|
} catch (error) {
|
|
19449
19829
|
unreadable.push(`${label2}: ${error instanceof Error ? error.message : String(error)}`);
|
|
19450
19830
|
continue;
|
|
19451
19831
|
}
|
|
19452
|
-
const text2 = decodeUtf8(
|
|
19832
|
+
const text2 = decodeUtf8(bytes2);
|
|
19453
19833
|
if (text2 === void 0) {
|
|
19454
19834
|
unreadable.push(`${label2}: not UTF-8 text`);
|
|
19455
19835
|
continue;
|
|
@@ -19498,10 +19878,10 @@ async function listFiles(target, unreadable) {
|
|
|
19498
19878
|
await walk(target);
|
|
19499
19879
|
return out;
|
|
19500
19880
|
}
|
|
19501
|
-
function decodeUtf8(
|
|
19502
|
-
if (
|
|
19881
|
+
function decodeUtf8(bytes2) {
|
|
19882
|
+
if (bytes2.includes(0)) return void 0;
|
|
19503
19883
|
try {
|
|
19504
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(
|
|
19884
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
19505
19885
|
} catch {
|
|
19506
19886
|
return void 0;
|
|
19507
19887
|
}
|
|
@@ -19572,6 +19952,7 @@ export {
|
|
|
19572
19952
|
EXPORT_RULE,
|
|
19573
19953
|
toOpenInferenceSpan,
|
|
19574
19954
|
serializeSpans,
|
|
19955
|
+
iterSerializedSpans,
|
|
19575
19956
|
writeOtlpFile,
|
|
19576
19957
|
summarizeOtlpExecution,
|
|
19577
19958
|
summarizeSpanExecution,
|
|
@@ -19633,6 +20014,7 @@ export {
|
|
|
19633
20014
|
serializePolicyEvidence,
|
|
19634
20015
|
writePolicyEvidenceFile,
|
|
19635
20016
|
countSkillRunsJsonl,
|
|
20017
|
+
computeAdoption,
|
|
19636
20018
|
analyzeAdoption,
|
|
19637
20019
|
planTraceAgenticRoute,
|
|
19638
20020
|
traceAgenticKinds,
|
|
@@ -19644,6 +20026,7 @@ export {
|
|
|
19644
20026
|
shellCommands,
|
|
19645
20027
|
commandTextFromInput,
|
|
19646
20028
|
readPullRequests,
|
|
20029
|
+
SESSION_FACTS_MEASUREMENT_VERSION,
|
|
19647
20030
|
SESSION_TOKEN_TOTAL_ATTR,
|
|
19648
20031
|
SPAN_SYNTHESIZED_ATTR,
|
|
19649
20032
|
FACT_TEXT_CAP,
|