@tangle-network/traces 0.9.18 → 0.9.19
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 +1 -0
- package/dist/{chunk-F2OEMHVZ.js → chunk-WCJVRJD5.js} +1149 -452
- package/dist/cli.js +84 -5
- package/dist/index.d.ts +84 -1
- package/dist/index.js +3 -1
- package/package.json +1 -1
|
@@ -165,11 +165,11 @@ function serializeSpans(spans) {
|
|
|
165
165
|
`;
|
|
166
166
|
}
|
|
167
167
|
async function writeOtlpFile(spans, outPath) {
|
|
168
|
-
const { mkdtemp:
|
|
168
|
+
const { mkdtemp: mkdtemp8, writeFile: writeFile9 } = await import("fs/promises");
|
|
169
169
|
const { tmpdir: tmpdir7 } = await import("os");
|
|
170
|
-
const { join:
|
|
171
|
-
const path = outPath ??
|
|
172
|
-
await
|
|
170
|
+
const { join: join25 } = await import("path");
|
|
171
|
+
const path = outPath ?? join25(await mkdtemp8(join25(tmpdir7(), "traces-")), "spans.otlp.jsonl");
|
|
172
|
+
await writeFile9(path, serializeSpans(spans), "utf8");
|
|
173
173
|
return path;
|
|
174
174
|
}
|
|
175
175
|
|
|
@@ -291,6 +291,855 @@ function stampSessionIdentity(spans, fallbackSessionId) {
|
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
// src/chat-trajectory.ts
|
|
295
|
+
import { createHash } from "crypto";
|
|
296
|
+
function isChatTrajectoryMessage(value) {
|
|
297
|
+
return isObject(value) && typeof value.role === "string" && typeof value.content === "string";
|
|
298
|
+
}
|
|
299
|
+
function isChatTrajectory(value) {
|
|
300
|
+
return isObject(value) && Array.isArray(value.messages) && value.messages.length > 0 && value.messages.every(isChatTrajectoryMessage);
|
|
301
|
+
}
|
|
302
|
+
function chatTrajectoryToSpans(input, options = {}) {
|
|
303
|
+
const trajectory = Array.isArray(input) ? { messages: input } : input;
|
|
304
|
+
if (trajectory.messages.length === 0) throw new Error("chat trajectory contains no messages");
|
|
305
|
+
if (!trajectory.messages.every(isChatTrajectoryMessage)) {
|
|
306
|
+
throw new TypeError("chat trajectory messages require string role and content fields");
|
|
307
|
+
}
|
|
308
|
+
const traceId = options.traceId ?? trajectoryId(trajectory);
|
|
309
|
+
const service = options.service ?? trajectory.trajectory_format ?? "chat-trajectory";
|
|
310
|
+
const stepMode = options.stepMode ?? "assistant";
|
|
311
|
+
const times = messageTimes(trajectory.messages);
|
|
312
|
+
const actionCount = trajectory.messages.filter((message) => messageKind(message.role) === "LLM").length;
|
|
313
|
+
const root = span({
|
|
314
|
+
traceId,
|
|
315
|
+
spanId: "root",
|
|
316
|
+
name: "trajectory",
|
|
317
|
+
kind: "AGENT",
|
|
318
|
+
startTime: times[0],
|
|
319
|
+
endTime: times.at(-1),
|
|
320
|
+
service,
|
|
321
|
+
costUsd: finiteNumber(trajectory.info?.model_stats, "instance_cost"),
|
|
322
|
+
extra: compactAttributes({
|
|
323
|
+
"traces.source_format": "chat-trajectory",
|
|
324
|
+
"trajectory.format": trajectory.trajectory_format,
|
|
325
|
+
"trajectory.message_count": trajectory.messages.length,
|
|
326
|
+
"trajectory.action_count": actionCount,
|
|
327
|
+
"trajectory.source_path": options.sourcePath,
|
|
328
|
+
"trajectory.api_calls": finiteNumber(trajectory.info?.model_stats, "api_calls"),
|
|
329
|
+
"trajectory.timestamps_synthetic": trajectory.messages.some(
|
|
330
|
+
(message) => message.timestamp === void 0
|
|
331
|
+
)
|
|
332
|
+
})
|
|
333
|
+
});
|
|
334
|
+
const spans = [root];
|
|
335
|
+
let assistantStep = 0;
|
|
336
|
+
for (const [index, message] of trajectory.messages.entries()) {
|
|
337
|
+
const kind = messageKind(message.role);
|
|
338
|
+
if (kind === "LLM") assistantStep += 1;
|
|
339
|
+
const step = stepMode === "message" ? index + 1 : kind === "LLM" ? assistantStep : void 0;
|
|
340
|
+
const spanId = step === void 0 ? `message-${index + 1}` : `step-${step}`;
|
|
341
|
+
const response = isObject(message.extra?.response) ? message.extra.response : void 0;
|
|
342
|
+
const usage = isObject(response?.usage) ? response.usage : void 0;
|
|
343
|
+
const completionDetails = isObject(usage?.completion_tokens_details) ? usage.completion_tokens_details : void 0;
|
|
344
|
+
const promptDetails = isObject(usage?.prompt_tokens_details) ? usage.prompt_tokens_details : void 0;
|
|
345
|
+
spans.push(
|
|
346
|
+
span({
|
|
347
|
+
traceId,
|
|
348
|
+
spanId,
|
|
349
|
+
parentSpanId: "root",
|
|
350
|
+
name: messageName(kind, message),
|
|
351
|
+
kind,
|
|
352
|
+
startTime: times[index],
|
|
353
|
+
service,
|
|
354
|
+
agent: messageAgent(message.role),
|
|
355
|
+
model: stringValue(response?.model),
|
|
356
|
+
inputTokens: finiteNumber(usage, "prompt_tokens"),
|
|
357
|
+
outputTokens: finiteNumber(usage, "completion_tokens"),
|
|
358
|
+
reasoningTokens: finiteNumber(completionDetails, "reasoning_tokens"),
|
|
359
|
+
cachedInputTokens: finiteNumber(promptDetails, "cached_tokens"),
|
|
360
|
+
step,
|
|
361
|
+
content: capText(message.content),
|
|
362
|
+
extra: compactAttributes({
|
|
363
|
+
"traces.source_format": "chat-trajectory",
|
|
364
|
+
"trajectory.message_index": index + 1,
|
|
365
|
+
"trajectory.action_index": kind === "LLM" ? assistantStep : void 0,
|
|
366
|
+
"trajectory.role": message.role
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
return validateOtlpSpans(spans, "chat trajectory output");
|
|
372
|
+
}
|
|
373
|
+
function trajectoryId(trajectory) {
|
|
374
|
+
const value = trajectory.trajectory_id ?? trajectory.traj_id ?? trajectory.instance_id;
|
|
375
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
376
|
+
if (typeof value === "number" && Number.isSafeInteger(value)) return String(value);
|
|
377
|
+
return `trajectory-${createHash("sha256").update(JSON.stringify(trajectory.messages)).digest("hex").slice(0, 24)}`;
|
|
378
|
+
}
|
|
379
|
+
function messageKind(role) {
|
|
380
|
+
const normalized = role.trim().toLowerCase();
|
|
381
|
+
if (normalized === "assistant" || normalized === "agent") return "LLM";
|
|
382
|
+
if (normalized === "tool" || normalized === "function" || normalized === "observation") {
|
|
383
|
+
return "TOOL";
|
|
384
|
+
}
|
|
385
|
+
return "CHAIN";
|
|
386
|
+
}
|
|
387
|
+
function messageName(kind, message) {
|
|
388
|
+
if (kind === "LLM") return "message.assistant";
|
|
389
|
+
if (kind === "TOOL") return `tool.${message.name?.trim() || "result"}`;
|
|
390
|
+
const role = message.role.trim().toLowerCase();
|
|
391
|
+
if (role === "system") return "system.prompt";
|
|
392
|
+
if (role === "user" || role === "human") return "user.prompt";
|
|
393
|
+
return "message.other";
|
|
394
|
+
}
|
|
395
|
+
function messageAgent(role) {
|
|
396
|
+
const normalized = role.trim().toLowerCase();
|
|
397
|
+
return ["system", "user", "human", "tool", "function", "observation"].includes(normalized) ? void 0 : role.trim();
|
|
398
|
+
}
|
|
399
|
+
function messageTimes(messages) {
|
|
400
|
+
const supplied = messages.filter((message) => message.timestamp !== void 0).length;
|
|
401
|
+
if (supplied === 0) return messages.map((_, index) => new Date(index).toISOString());
|
|
402
|
+
if (supplied !== messages.length) {
|
|
403
|
+
throw new TypeError("chat trajectory timestamps must be present on every message or none");
|
|
404
|
+
}
|
|
405
|
+
const times = messages.map((message, index) => messageTime(message.timestamp, index));
|
|
406
|
+
for (let index = 1; index < times.length; index += 1) {
|
|
407
|
+
if (Date.parse(times[index]) < Date.parse(times[index - 1])) {
|
|
408
|
+
throw new RangeError(
|
|
409
|
+
`chat trajectory message ${index + 1} timestamp precedes message ${index}`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return times;
|
|
414
|
+
}
|
|
415
|
+
function messageTime(value, index) {
|
|
416
|
+
const parsed = parseTime(value);
|
|
417
|
+
if (!parsed) {
|
|
418
|
+
throw new TypeError(`chat trajectory message ${index + 1} has an invalid timestamp`);
|
|
419
|
+
}
|
|
420
|
+
return parsed;
|
|
421
|
+
}
|
|
422
|
+
function parseTime(value) {
|
|
423
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
424
|
+
const millis2 = value < 1e10 ? value * 1e3 : value;
|
|
425
|
+
const date = new Date(millis2);
|
|
426
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : void 0;
|
|
427
|
+
}
|
|
428
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
429
|
+
const millis = Date.parse(value);
|
|
430
|
+
return Number.isFinite(millis) ? new Date(millis).toISOString() : void 0;
|
|
431
|
+
}
|
|
432
|
+
function finiteNumber(value, key) {
|
|
433
|
+
if (!isObject(value)) return void 0;
|
|
434
|
+
const number = value[key];
|
|
435
|
+
return typeof number === "number" && Number.isFinite(number) ? number : void 0;
|
|
436
|
+
}
|
|
437
|
+
function stringValue(value) {
|
|
438
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
439
|
+
}
|
|
440
|
+
function compactAttributes(values) {
|
|
441
|
+
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== void 0));
|
|
442
|
+
}
|
|
443
|
+
function isObject(value) {
|
|
444
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/codetracebench.ts
|
|
448
|
+
import {
|
|
449
|
+
mkdir as mkdir2,
|
|
450
|
+
mkdtemp,
|
|
451
|
+
readFile as readFile3,
|
|
452
|
+
rm as rm2
|
|
453
|
+
} from "fs/promises";
|
|
454
|
+
import { basename as basename2, dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
455
|
+
|
|
456
|
+
// src/codetracebench-files.ts
|
|
457
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
458
|
+
import {
|
|
459
|
+
link,
|
|
460
|
+
lstat,
|
|
461
|
+
mkdir,
|
|
462
|
+
readFile,
|
|
463
|
+
realpath,
|
|
464
|
+
rename,
|
|
465
|
+
rm,
|
|
466
|
+
rmdir,
|
|
467
|
+
stat,
|
|
468
|
+
unlink,
|
|
469
|
+
writeFile
|
|
470
|
+
} from "fs/promises";
|
|
471
|
+
import { hostname } from "os";
|
|
472
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
473
|
+
async function claimImportLock(input) {
|
|
474
|
+
const claimId = randomUUID();
|
|
475
|
+
const path = `${input.outputDirectory}.lock`;
|
|
476
|
+
const temporary = join(
|
|
477
|
+
dirname(path),
|
|
478
|
+
`.${basename(path)}.${claimId}.tmp`
|
|
479
|
+
);
|
|
480
|
+
const metadata = {
|
|
481
|
+
kind: "traces.codetracebench-import-lock",
|
|
482
|
+
claimId,
|
|
483
|
+
pid: process.pid,
|
|
484
|
+
hostname: hostname(),
|
|
485
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
486
|
+
revision: input.revision,
|
|
487
|
+
rowsPath: input.rowsPath,
|
|
488
|
+
trajectoryDirectory: input.trajectoryDirectory,
|
|
489
|
+
outputDirectory: input.outputDirectory
|
|
490
|
+
};
|
|
491
|
+
try {
|
|
492
|
+
await writeFile(temporary, `${JSON.stringify(metadata, null, 2)}
|
|
493
|
+
`, {
|
|
494
|
+
encoding: "utf8",
|
|
495
|
+
flag: "wx",
|
|
496
|
+
mode: 384,
|
|
497
|
+
signal: input.signal
|
|
498
|
+
});
|
|
499
|
+
input.signal?.throwIfAborted();
|
|
500
|
+
} catch (error) {
|
|
501
|
+
await rm(temporary, { force: true });
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
await link(temporary, path);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
await rm(temporary, { force: true });
|
|
508
|
+
if (isErrno(error, "EEXIST")) {
|
|
509
|
+
throw await importLockConflictError(path, input.outputDirectory);
|
|
510
|
+
}
|
|
511
|
+
throw error;
|
|
512
|
+
}
|
|
513
|
+
try {
|
|
514
|
+
await rm(temporary, { force: true });
|
|
515
|
+
} catch (error) {
|
|
516
|
+
await unlink(path);
|
|
517
|
+
throw error;
|
|
518
|
+
}
|
|
519
|
+
return { path, claimId };
|
|
520
|
+
}
|
|
521
|
+
async function importLockConflictError(path, outputDirectory) {
|
|
522
|
+
let owner = "owner metadata is unreadable";
|
|
523
|
+
try {
|
|
524
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
525
|
+
if (isObject2(parsed) && typeof parsed.pid === "number" && typeof parsed.hostname === "string" && typeof parsed.startedAt === "string" && typeof parsed.revision === "string") {
|
|
526
|
+
owner = `PID ${parsed.pid} on ${parsed.hostname}, started ${parsed.startedAt}, revision ${parsed.revision}`;
|
|
527
|
+
}
|
|
528
|
+
} catch {
|
|
529
|
+
}
|
|
530
|
+
return new Error(
|
|
531
|
+
`CodeTraceBench import already in progress for ${outputDirectory}. Lock: ${path}. Owner: ${owner}. Wait for it to finish, or remove the lock only after confirming that process is no longer running.`
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
async function releaseImportLock(lock) {
|
|
535
|
+
let parsed;
|
|
536
|
+
try {
|
|
537
|
+
parsed = JSON.parse(await readFile(lock.path, "utf8"));
|
|
538
|
+
} catch (error) {
|
|
539
|
+
if (isErrno(error, "ENOENT")) return;
|
|
540
|
+
throw new Error(`Could not read CodeTraceBench import lock: ${lock.path}`, {
|
|
541
|
+
cause: error
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
if (!isObject2(parsed) || parsed.claimId !== lock.claimId) {
|
|
545
|
+
throw new Error(
|
|
546
|
+
`CodeTraceBench import lock owner changed; refusing to remove ${lock.path}`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
await unlink(lock.path);
|
|
550
|
+
}
|
|
551
|
+
async function publishStagedFiles(stagingDirectory, outputDirectory, files, signal) {
|
|
552
|
+
signal.throwIfAborted();
|
|
553
|
+
try {
|
|
554
|
+
await mkdir(outputDirectory);
|
|
555
|
+
} catch (error) {
|
|
556
|
+
if (isErrno(error, "EEXIST")) {
|
|
557
|
+
throw new Error(
|
|
558
|
+
`CodeTraceBench output directory appeared while import was running; refusing to replace it: ${outputDirectory}`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
throw error;
|
|
562
|
+
}
|
|
563
|
+
const published = [];
|
|
564
|
+
try {
|
|
565
|
+
for (const file of files) {
|
|
566
|
+
signal.throwIfAborted();
|
|
567
|
+
const destination = join(outputDirectory, file);
|
|
568
|
+
await link(join(stagingDirectory, file), destination);
|
|
569
|
+
published.push(destination);
|
|
570
|
+
}
|
|
571
|
+
} catch (error) {
|
|
572
|
+
for (const path of published.reverse()) {
|
|
573
|
+
await rm(path, { force: true });
|
|
574
|
+
}
|
|
575
|
+
try {
|
|
576
|
+
await rmdir(outputDirectory);
|
|
577
|
+
} catch (cleanupError) {
|
|
578
|
+
if (!isErrno(cleanupError, "ENOENT") && !isErrno(cleanupError, "ENOTEMPTY")) {
|
|
579
|
+
throw new AggregateError(
|
|
580
|
+
[error, cleanupError],
|
|
581
|
+
`CodeTraceBench import failed and could not clean ${outputDirectory}`
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
throw error;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
async function containedDirectory(path) {
|
|
589
|
+
const resolved = resolve(path);
|
|
590
|
+
let canonical;
|
|
591
|
+
try {
|
|
592
|
+
canonical = await realpath(resolved);
|
|
593
|
+
} catch (error) {
|
|
594
|
+
throw new Error(`CodeTraceBench trajectory directory is unavailable: ${resolved}`, {
|
|
595
|
+
cause: error
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
const info = await stat(canonical);
|
|
599
|
+
if (!info.isDirectory()) {
|
|
600
|
+
throw new TypeError(`CodeTraceBench trajectory path is not a directory: ${canonical}`);
|
|
601
|
+
}
|
|
602
|
+
return canonical;
|
|
603
|
+
}
|
|
604
|
+
async function containedFile(root, candidate, label) {
|
|
605
|
+
const path = await optionalContainedFile(root, candidate);
|
|
606
|
+
if (!path) throw new Error(`${label} is missing`);
|
|
607
|
+
return path;
|
|
608
|
+
}
|
|
609
|
+
async function optionalContainedFile(root, candidate) {
|
|
610
|
+
let canonical;
|
|
611
|
+
try {
|
|
612
|
+
canonical = await realpath(candidate);
|
|
613
|
+
} catch (error) {
|
|
614
|
+
if (isErrno(error, "ENOENT")) return void 0;
|
|
615
|
+
throw error;
|
|
616
|
+
}
|
|
617
|
+
const fromRoot = relative(root, canonical);
|
|
618
|
+
if (fromRoot === ".." || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot)) {
|
|
619
|
+
throw new Error(`CodeTraceBench trajectory file escapes its input directory: ${candidate}`);
|
|
620
|
+
}
|
|
621
|
+
const info = await stat(canonical);
|
|
622
|
+
if (!info.isFile()) {
|
|
623
|
+
throw new TypeError(`CodeTraceBench trajectory path is not a file: ${candidate}`);
|
|
624
|
+
}
|
|
625
|
+
return canonical;
|
|
626
|
+
}
|
|
627
|
+
async function assertPathMissing(path) {
|
|
628
|
+
try {
|
|
629
|
+
await lstat(path);
|
|
630
|
+
} catch (error) {
|
|
631
|
+
if (isErrno(error, "ENOENT")) return;
|
|
632
|
+
throw error;
|
|
633
|
+
}
|
|
634
|
+
throw new Error(`CodeTraceBench output directory already exists: ${path}`);
|
|
635
|
+
}
|
|
636
|
+
async function atomicWriteFile(path, content, signal) {
|
|
637
|
+
signal.throwIfAborted();
|
|
638
|
+
const temporary = join(
|
|
639
|
+
dirname(path),
|
|
640
|
+
`.${basename(path)}.${randomUUID()}.tmp`
|
|
641
|
+
);
|
|
642
|
+
try {
|
|
643
|
+
await writeFile(temporary, content, {
|
|
644
|
+
encoding: "utf8",
|
|
645
|
+
flag: "wx",
|
|
646
|
+
signal
|
|
647
|
+
});
|
|
648
|
+
signal.throwIfAborted();
|
|
649
|
+
await rename(temporary, path);
|
|
650
|
+
} finally {
|
|
651
|
+
await rm(temporary, { force: true });
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function sha256(value) {
|
|
655
|
+
return createHash2("sha256").update(value).digest("hex");
|
|
656
|
+
}
|
|
657
|
+
function isObject2(value) {
|
|
658
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
659
|
+
}
|
|
660
|
+
function isErrno(error, code) {
|
|
661
|
+
return isObject2(error) && error.code === code;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/codetracebench-trajectory.ts
|
|
665
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
666
|
+
import { join as join2 } from "path";
|
|
667
|
+
var TRAJECTORY_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,254}$/;
|
|
668
|
+
var LABEL_KEY = /\\?["'](?:incorrect_stages|incorrect_step_ids|unuseful_step_ids|annotation_relpath|incorrect_error_stage_count)\\?["']\s*:/i;
|
|
669
|
+
var LABEL_ARRAY = /\\?["']labels\\?["']\s*:\s*\[[^\]]*\\?["'](?:incorrect|unuseful)\\?["']/i;
|
|
670
|
+
var ANNOTATION_PATH = /(?:agent_failure_analysis|step_annotations(?:_all)?|merged_cleaned_step\d*)[\\/]/i;
|
|
671
|
+
async function importTrajectory(input) {
|
|
672
|
+
const { index, row, trajectoryDirectory, stagingDirectory, signal } = input;
|
|
673
|
+
const caseDirectory = join2(trajectoryDirectory, row.trajId);
|
|
674
|
+
const stepsPath = await containedFile(
|
|
675
|
+
trajectoryDirectory,
|
|
676
|
+
join2(caseDirectory, "steps.json"),
|
|
677
|
+
`${row.trajId}: steps.json`
|
|
678
|
+
);
|
|
679
|
+
signal.throwIfAborted();
|
|
680
|
+
const stepsBytes = await readFile2(stepsPath, { signal });
|
|
681
|
+
const steps = parseSteps(stepsBytes.toString("utf8"), row);
|
|
682
|
+
const task = await readOptionalTask(trajectoryDirectory, caseDirectory, row, signal);
|
|
683
|
+
const messages = trajectoryMessages(row, steps, task.content);
|
|
684
|
+
const spans = chatTrajectoryToSpans(messages, {
|
|
685
|
+
traceId: row.trajId,
|
|
686
|
+
service: "codetracebench",
|
|
687
|
+
sourcePath: `${row.trajId}/steps.json`
|
|
688
|
+
}).map((item) => ({
|
|
689
|
+
...item,
|
|
690
|
+
attributes: {
|
|
691
|
+
...item.attributes,
|
|
692
|
+
"benchmark.name": "CodeTraceBench",
|
|
693
|
+
"trajectory.id": row.trajId,
|
|
694
|
+
...item.attributes["openinference.span.kind"] === "LLM" ? { "agent.name": row.agent } : {}
|
|
695
|
+
}
|
|
696
|
+
}));
|
|
697
|
+
const actionSpans = spans.filter(
|
|
698
|
+
(item) => item.attributes["openinference.span.kind"] === "LLM"
|
|
699
|
+
);
|
|
700
|
+
if (actionSpans.length !== row.stepCount) {
|
|
701
|
+
throw new Error(
|
|
702
|
+
`${row.trajId}: generated ${actionSpans.length} action spans for ${row.stepCount} steps`
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
for (const [stepIndex, item] of actionSpans.entries()) {
|
|
706
|
+
const expectedSpanId = `step-${stepIndex + 1}`;
|
|
707
|
+
if (item.span_id !== expectedSpanId) {
|
|
708
|
+
throw new Error(
|
|
709
|
+
`${row.trajId}: generated action span '${item.span_id}', expected '${expectedSpanId}'`
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
assertNoLabelLeak(spans, row);
|
|
714
|
+
const serialized = serializeSpans(spans);
|
|
715
|
+
assertNoLabelLeakInText(serialized, row, `${row.trajId}: serialized OTLP`);
|
|
716
|
+
const outputFile = `${row.trajId}.otlp.jsonl`;
|
|
717
|
+
await atomicWriteFile(join2(stagingDirectory, outputFile), serialized, signal);
|
|
718
|
+
return {
|
|
719
|
+
index,
|
|
720
|
+
traceId: row.trajId,
|
|
721
|
+
stepCount: row.stepCount,
|
|
722
|
+
spanCount: spans.length,
|
|
723
|
+
taskSource: task.source,
|
|
724
|
+
input: {
|
|
725
|
+
stepsPath: `${row.trajId}/steps.json`,
|
|
726
|
+
stepsSha256: sha256(stepsBytes),
|
|
727
|
+
stepsBytes: stepsBytes.byteLength,
|
|
728
|
+
...task.path ? {
|
|
729
|
+
taskPath: `${row.trajId}/task.md`,
|
|
730
|
+
taskSha256: sha256(task.bytes),
|
|
731
|
+
taskBytes: task.bytes.byteLength
|
|
732
|
+
} : {}
|
|
733
|
+
},
|
|
734
|
+
output: {
|
|
735
|
+
path: outputFile,
|
|
736
|
+
sha256: sha256(serialized),
|
|
737
|
+
bytes: Buffer.byteLength(serialized)
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
function trajectoryMessages(row, steps, task) {
|
|
742
|
+
return [
|
|
743
|
+
{ role: "user", content: task },
|
|
744
|
+
...steps.flatMap((step) => {
|
|
745
|
+
const action = {
|
|
746
|
+
role: "assistant",
|
|
747
|
+
content: [step.thinking, step.action].filter(
|
|
748
|
+
(value) => typeof value === "string" && value.length > 0
|
|
749
|
+
).join("\n"),
|
|
750
|
+
extra: { response: { model: row.model } }
|
|
751
|
+
};
|
|
752
|
+
if (step.observation === null) return [action];
|
|
753
|
+
return [
|
|
754
|
+
action,
|
|
755
|
+
{
|
|
756
|
+
role: "observation",
|
|
757
|
+
content: step.observation,
|
|
758
|
+
...step.tool_type ? { name: step.tool_type } : {}
|
|
759
|
+
}
|
|
760
|
+
];
|
|
761
|
+
})
|
|
762
|
+
];
|
|
763
|
+
}
|
|
764
|
+
function parseRows(text2, path) {
|
|
765
|
+
const trimmed = text2.trim();
|
|
766
|
+
if (!trimmed) throw new Error(`CodeTraceBench rows file is empty: ${path}`);
|
|
767
|
+
try {
|
|
768
|
+
const parsed = JSON.parse(trimmed);
|
|
769
|
+
if (Array.isArray(parsed)) return parsed;
|
|
770
|
+
if (isObject2(parsed)) return [parsed];
|
|
771
|
+
throw new TypeError("JSON must be an object or array");
|
|
772
|
+
} catch (error) {
|
|
773
|
+
if (!/\r?\n/.test(trimmed)) {
|
|
774
|
+
throw new Error(`${path}: invalid JSON: ${errorMessage(error)}`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return trimmed.split(/\r?\n/).flatMap((line, index) => {
|
|
778
|
+
if (!line.trim()) return [];
|
|
779
|
+
try {
|
|
780
|
+
return [JSON.parse(line)];
|
|
781
|
+
} catch (error) {
|
|
782
|
+
throw new Error(`${path}:${index + 1}: invalid JSON: ${errorMessage(error)}`);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
function validateRows(rows, path) {
|
|
787
|
+
if (rows.length === 0) throw new Error(`CodeTraceBench rows file is empty: ${path}`);
|
|
788
|
+
const ids = /* @__PURE__ */ new Set();
|
|
789
|
+
return rows.map((value, index) => {
|
|
790
|
+
if (!isObject2(value)) {
|
|
791
|
+
throw new TypeError(`${path}:${index + 1}: CodeTraceBench row must be an object`);
|
|
792
|
+
}
|
|
793
|
+
const trajId = requiredString(value.traj_id, `${path}:${index + 1} traj_id`);
|
|
794
|
+
if (!TRAJECTORY_ID.test(trajId)) {
|
|
795
|
+
throw new TypeError(
|
|
796
|
+
`${path}:${index + 1}: traj_id must contain only letters, digits, dot, underscore, or hyphen`
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
if (ids.has(trajId)) {
|
|
800
|
+
throw new TypeError(`${path}:${index + 1}: duplicate traj_id '${trajId}'`);
|
|
801
|
+
}
|
|
802
|
+
ids.add(trajId);
|
|
803
|
+
const stepCount = positiveInteger(
|
|
804
|
+
value.step_count,
|
|
805
|
+
`${path}:${index + 1} step_count`
|
|
806
|
+
);
|
|
807
|
+
const annotationRelpath = value.annotation_relpath === void 0 || value.annotation_relpath === null ? void 0 : requiredString(
|
|
808
|
+
value.annotation_relpath,
|
|
809
|
+
`${path}:${index + 1} annotation_relpath`
|
|
810
|
+
);
|
|
811
|
+
return {
|
|
812
|
+
trajId,
|
|
813
|
+
agent: requiredString(value.agent, `${path}:${index + 1} agent`),
|
|
814
|
+
model: requiredString(value.model, `${path}:${index + 1} model`),
|
|
815
|
+
taskName: requiredString(value.task_name, `${path}:${index + 1} task_name`),
|
|
816
|
+
stepCount,
|
|
817
|
+
...annotationRelpath ? { annotationRelpath } : {},
|
|
818
|
+
...value.incorrect_stages === void 0 ? {} : { incorrectStages: value.incorrect_stages }
|
|
819
|
+
};
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
function parseSteps(text2, row) {
|
|
823
|
+
let parsed;
|
|
824
|
+
try {
|
|
825
|
+
parsed = JSON.parse(text2);
|
|
826
|
+
} catch (error) {
|
|
827
|
+
throw new Error(`${row.trajId}/steps.json: invalid JSON: ${errorMessage(error)}`);
|
|
828
|
+
}
|
|
829
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
830
|
+
throw new TypeError(`${row.trajId}/steps.json must contain a non-empty array`);
|
|
831
|
+
}
|
|
832
|
+
if (parsed.length !== row.stepCount) {
|
|
833
|
+
throw new Error(
|
|
834
|
+
`${row.trajId}: steps.json contains ${parsed.length} steps, row declares ${row.stepCount}`
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
return parsed.map((value, index) => {
|
|
838
|
+
const label = `${row.trajId}/steps.json[${index}]`;
|
|
839
|
+
if (!isObject2(value)) throw new TypeError(`${label} must be an object`);
|
|
840
|
+
const stepId = positiveInteger(value.step_id, `${label}.step_id`);
|
|
841
|
+
if (stepId !== index + 1) {
|
|
842
|
+
throw new Error(`${label}.step_id is ${stepId}; expected ${index + 1}`);
|
|
843
|
+
}
|
|
844
|
+
if (!Object.hasOwn(value, "observation")) {
|
|
845
|
+
throw new TypeError(`${label}.observation is required and may be null`);
|
|
846
|
+
}
|
|
847
|
+
const observation = value.observation;
|
|
848
|
+
if (observation !== null && typeof observation !== "string") {
|
|
849
|
+
throw new TypeError(`${label}.observation must be a string or null`);
|
|
850
|
+
}
|
|
851
|
+
return {
|
|
852
|
+
step_id: stepId,
|
|
853
|
+
action: requiredString(value.action, `${label}.action`),
|
|
854
|
+
observation,
|
|
855
|
+
...value.thinking === void 0 ? {} : { thinking: requiredString(value.thinking, `${label}.thinking`) },
|
|
856
|
+
...value.parallel_group === void 0 ? {} : {
|
|
857
|
+
parallel_group: nonNegativeInteger(
|
|
858
|
+
value.parallel_group,
|
|
859
|
+
`${label}.parallel_group`
|
|
860
|
+
)
|
|
861
|
+
},
|
|
862
|
+
...value.tool_type === void 0 ? {} : { tool_type: requiredString(value.tool_type, `${label}.tool_type`) },
|
|
863
|
+
action_ref: fileRef(value.action_ref, `${label}.action_ref`),
|
|
864
|
+
observation_ref: fileRef(value.observation_ref, `${label}.observation_ref`)
|
|
865
|
+
};
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
function fileRef(value, label) {
|
|
869
|
+
if (value === null) return null;
|
|
870
|
+
if (!isObject2(value)) throw new TypeError(`${label} must be an object or null`);
|
|
871
|
+
const lineStart = positiveInteger(value.line_start, `${label}.line_start`);
|
|
872
|
+
const lineEnd = positiveInteger(value.line_end, `${label}.line_end`);
|
|
873
|
+
if (lineEnd < lineStart) {
|
|
874
|
+
throw new RangeError(`${label}.line_end must not precede line_start`);
|
|
875
|
+
}
|
|
876
|
+
return {
|
|
877
|
+
path: requiredString(value.path, `${label}.path`),
|
|
878
|
+
line_start: lineStart,
|
|
879
|
+
line_end: lineEnd,
|
|
880
|
+
content: stringField(value.content, `${label}.content`)
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
async function readOptionalTask(trajectoryDirectory, caseDirectory, row, signal) {
|
|
884
|
+
const candidate = join2(caseDirectory, "task.md");
|
|
885
|
+
const path = await optionalContainedFile(trajectoryDirectory, candidate);
|
|
886
|
+
if (!path) {
|
|
887
|
+
return {
|
|
888
|
+
source: "task_name",
|
|
889
|
+
content: `Task: ${row.taskName}`
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
const bytes = await readFile2(path, { signal });
|
|
893
|
+
const content = bytes.toString("utf8").trim();
|
|
894
|
+
if (!content) throw new Error(`${row.trajId}/task.md is empty`);
|
|
895
|
+
return { source: "task.md", content, path, bytes };
|
|
896
|
+
}
|
|
897
|
+
function assertNoLabelLeak(spans, row) {
|
|
898
|
+
for (const item of spans) {
|
|
899
|
+
const content = item.attributes.content;
|
|
900
|
+
if (typeof content === "string") {
|
|
901
|
+
assertNoLabelLeakInText(content, row, `${row.trajId}/${item.span_id} content`);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
function assertNoLabelLeakInText(text2, row, context) {
|
|
906
|
+
if (LABEL_KEY.test(text2) || LABEL_ARRAY.test(text2) || ANNOTATION_PATH.test(text2)) {
|
|
907
|
+
throw new Error(`${context}: generated trace contains CodeTraceBench annotation data`);
|
|
908
|
+
}
|
|
909
|
+
const annotationPath = row.annotationRelpath?.replaceAll("\\", "/").toLowerCase();
|
|
910
|
+
if (annotationPath && text2.replaceAll("\\", "/").toLowerCase().includes(annotationPath)) {
|
|
911
|
+
throw new Error(`${context}: generated trace contains annotation path`);
|
|
912
|
+
}
|
|
913
|
+
const labels = normalizedLabels(row.incorrectStages);
|
|
914
|
+
if (labels && compact(text2).includes(labels)) {
|
|
915
|
+
throw new Error(`${context}: generated trace contains serialized CodeTraceBench labels`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
function normalizedLabels(value) {
|
|
919
|
+
if (value === void 0 || value === null) return void 0;
|
|
920
|
+
let parsed = value;
|
|
921
|
+
if (typeof value === "string") {
|
|
922
|
+
const trimmed = value.trim();
|
|
923
|
+
if (!trimmed || trimmed === "[]") return void 0;
|
|
924
|
+
try {
|
|
925
|
+
parsed = JSON.parse(trimmed);
|
|
926
|
+
} catch {
|
|
927
|
+
parsed = trimmed;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
const serialized = JSON.stringify(parsed);
|
|
931
|
+
if (!serialized || serialized === "[]" || serialized === '""') return void 0;
|
|
932
|
+
return compact(serialized);
|
|
933
|
+
}
|
|
934
|
+
function compact(value) {
|
|
935
|
+
return value.replace(/\s+/g, "");
|
|
936
|
+
}
|
|
937
|
+
function positiveInteger(value, label) {
|
|
938
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) {
|
|
939
|
+
throw new TypeError(`${label} must be a positive integer`);
|
|
940
|
+
}
|
|
941
|
+
return value;
|
|
942
|
+
}
|
|
943
|
+
function nonNegativeInteger(value, label) {
|
|
944
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
945
|
+
throw new TypeError(`${label} must be a non-negative integer`);
|
|
946
|
+
}
|
|
947
|
+
return value;
|
|
948
|
+
}
|
|
949
|
+
function requiredString(value, label) {
|
|
950
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
951
|
+
throw new TypeError(`${label} must be a non-empty string`);
|
|
952
|
+
}
|
|
953
|
+
return value.trim();
|
|
954
|
+
}
|
|
955
|
+
function stringField(value, label) {
|
|
956
|
+
if (typeof value !== "string") throw new TypeError(`${label} must be a string`);
|
|
957
|
+
return value;
|
|
958
|
+
}
|
|
959
|
+
function errorMessage(error) {
|
|
960
|
+
return error instanceof Error ? error.message : String(error);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// src/codetracebench.ts
|
|
964
|
+
var DEFAULT_CONCURRENCY = 4;
|
|
965
|
+
var MAX_CONCURRENCY = 64;
|
|
966
|
+
var IMMUTABLE_REVISION = /^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$/;
|
|
967
|
+
async function importCodeTraceBench(options) {
|
|
968
|
+
const concurrency = validateConcurrency(options.concurrency ?? DEFAULT_CONCURRENCY);
|
|
969
|
+
const revision = validateRevision(options.revision);
|
|
970
|
+
const rowsPath = resolve2(options.rowsPath);
|
|
971
|
+
const trajectoryPath = resolve2(options.trajectoryDir);
|
|
972
|
+
const outputDirectory = resolve2(options.outDir);
|
|
973
|
+
const outputBase = basename2(outputDirectory);
|
|
974
|
+
if (!outputBase || outputDirectory === dirname2(outputDirectory)) {
|
|
975
|
+
throw new Error("CodeTraceBench output directory must not be a filesystem root");
|
|
976
|
+
}
|
|
977
|
+
options.signal?.throwIfAborted();
|
|
978
|
+
await mkdir2(dirname2(outputDirectory), { recursive: true });
|
|
979
|
+
const lock = await claimImportLock({
|
|
980
|
+
rowsPath,
|
|
981
|
+
trajectoryDirectory: trajectoryPath,
|
|
982
|
+
outputDirectory,
|
|
983
|
+
revision,
|
|
984
|
+
signal: options.signal
|
|
985
|
+
});
|
|
986
|
+
try {
|
|
987
|
+
return await runCodeTraceBenchImport({
|
|
988
|
+
concurrency,
|
|
989
|
+
revision,
|
|
990
|
+
rowsPath,
|
|
991
|
+
trajectoryPath,
|
|
992
|
+
outputDirectory,
|
|
993
|
+
outputBase,
|
|
994
|
+
signal: options.signal
|
|
995
|
+
});
|
|
996
|
+
} finally {
|
|
997
|
+
await releaseImportLock(lock);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
async function runCodeTraceBenchImport(input) {
|
|
1001
|
+
const {
|
|
1002
|
+
concurrency,
|
|
1003
|
+
revision,
|
|
1004
|
+
rowsPath,
|
|
1005
|
+
trajectoryPath,
|
|
1006
|
+
outputDirectory,
|
|
1007
|
+
outputBase,
|
|
1008
|
+
signal: outerSignal
|
|
1009
|
+
} = input;
|
|
1010
|
+
outerSignal?.throwIfAborted();
|
|
1011
|
+
await assertPathMissing(outputDirectory);
|
|
1012
|
+
const trajectoryDirectory = await containedDirectory(trajectoryPath);
|
|
1013
|
+
const rowsBytes = await readFile3(rowsPath, { signal: outerSignal });
|
|
1014
|
+
const rows = parseRows(rowsBytes.toString("utf8"), rowsPath);
|
|
1015
|
+
const sourceRows = validateRows(rows, rowsPath);
|
|
1016
|
+
const stagingDirectory = await mkdtemp(
|
|
1017
|
+
join3(dirname2(outputDirectory), `.${outputBase}.tmp-`)
|
|
1018
|
+
);
|
|
1019
|
+
const stop = new AbortController();
|
|
1020
|
+
const signal = outerSignal ? AbortSignal.any([outerSignal, stop.signal]) : stop.signal;
|
|
1021
|
+
const imported = new Array(sourceRows.length);
|
|
1022
|
+
let nextIndex = 0;
|
|
1023
|
+
try {
|
|
1024
|
+
const workerCount = Math.min(concurrency, sourceRows.length);
|
|
1025
|
+
const workerResults = await Promise.allSettled(
|
|
1026
|
+
Array.from({ length: workerCount }, async () => {
|
|
1027
|
+
while (true) {
|
|
1028
|
+
signal.throwIfAborted();
|
|
1029
|
+
const index = nextIndex;
|
|
1030
|
+
nextIndex += 1;
|
|
1031
|
+
if (index >= sourceRows.length) return;
|
|
1032
|
+
try {
|
|
1033
|
+
imported[index] = await importTrajectory({
|
|
1034
|
+
index,
|
|
1035
|
+
row: sourceRows[index],
|
|
1036
|
+
trajectoryDirectory,
|
|
1037
|
+
stagingDirectory,
|
|
1038
|
+
signal
|
|
1039
|
+
});
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
stop.abort(error);
|
|
1042
|
+
throw error;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
})
|
|
1046
|
+
);
|
|
1047
|
+
const workerFailure = workerResults.find(
|
|
1048
|
+
(result) => result.status === "rejected"
|
|
1049
|
+
);
|
|
1050
|
+
if (workerFailure) throw workerFailure.reason;
|
|
1051
|
+
signal.throwIfAborted();
|
|
1052
|
+
const rowsSha256 = sha256(rowsBytes);
|
|
1053
|
+
const inputSha256 = sha256(
|
|
1054
|
+
JSON.stringify({
|
|
1055
|
+
revision,
|
|
1056
|
+
rowsSha256,
|
|
1057
|
+
traces: imported.map((item) => ({
|
|
1058
|
+
traceId: item.traceId,
|
|
1059
|
+
stepsSha256: item.input.stepsSha256,
|
|
1060
|
+
taskSha256: item.input.taskSha256 ?? null
|
|
1061
|
+
}))
|
|
1062
|
+
})
|
|
1063
|
+
);
|
|
1064
|
+
const outputSha256 = sha256(
|
|
1065
|
+
JSON.stringify(
|
|
1066
|
+
imported.map((item) => ({
|
|
1067
|
+
traceId: item.traceId,
|
|
1068
|
+
sha256: item.output.sha256
|
|
1069
|
+
}))
|
|
1070
|
+
)
|
|
1071
|
+
);
|
|
1072
|
+
const receipt = {
|
|
1073
|
+
kind: "traces.codetracebench-import",
|
|
1074
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1075
|
+
input: {
|
|
1076
|
+
revision,
|
|
1077
|
+
rowsFile: basename2(rowsPath),
|
|
1078
|
+
rowsSha256,
|
|
1079
|
+
rowsBytes: rowsBytes.byteLength,
|
|
1080
|
+
trajectoryDirectory: basename2(trajectoryDirectory),
|
|
1081
|
+
sha256: inputSha256
|
|
1082
|
+
},
|
|
1083
|
+
settings: {
|
|
1084
|
+
concurrency,
|
|
1085
|
+
outputLayout: "<traj_id>.otlp.jsonl"
|
|
1086
|
+
},
|
|
1087
|
+
counts: {
|
|
1088
|
+
rows: sourceRows.length,
|
|
1089
|
+
traces: imported.length,
|
|
1090
|
+
steps: imported.reduce((sum, item) => sum + item.stepCount, 0),
|
|
1091
|
+
spans: imported.reduce((sum, item) => sum + item.spanCount, 0)
|
|
1092
|
+
},
|
|
1093
|
+
safety: {
|
|
1094
|
+
labelLeakScan: "passed",
|
|
1095
|
+
outputDirectoryCreatedExclusively: true,
|
|
1096
|
+
filesPublishedAtomically: true
|
|
1097
|
+
},
|
|
1098
|
+
traces: imported,
|
|
1099
|
+
outputSha256
|
|
1100
|
+
};
|
|
1101
|
+
const receiptFile = "codetracebench-import.json";
|
|
1102
|
+
await atomicWriteFile(
|
|
1103
|
+
join3(stagingDirectory, receiptFile),
|
|
1104
|
+
`${JSON.stringify(receipt, null, 2)}
|
|
1105
|
+
`,
|
|
1106
|
+
signal
|
|
1107
|
+
);
|
|
1108
|
+
signal.throwIfAborted();
|
|
1109
|
+
await publishStagedFiles(
|
|
1110
|
+
stagingDirectory,
|
|
1111
|
+
outputDirectory,
|
|
1112
|
+
[...imported.map((item) => item.output.path), receiptFile],
|
|
1113
|
+
signal
|
|
1114
|
+
);
|
|
1115
|
+
await rm2(stagingDirectory, { recursive: true, force: true });
|
|
1116
|
+
return {
|
|
1117
|
+
directory: outputDirectory,
|
|
1118
|
+
receiptPath: join3(outputDirectory, receiptFile),
|
|
1119
|
+
receipt
|
|
1120
|
+
};
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
await rm2(stagingDirectory, { recursive: true, force: true });
|
|
1123
|
+
throw error;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
function validateConcurrency(value) {
|
|
1127
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > MAX_CONCURRENCY) {
|
|
1128
|
+
throw new RangeError(
|
|
1129
|
+
`CodeTraceBench concurrency must be an integer from 1 to ${MAX_CONCURRENCY}`
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
return value;
|
|
1133
|
+
}
|
|
1134
|
+
function validateRevision(value) {
|
|
1135
|
+
if (typeof value !== "string" || !IMMUTABLE_REVISION.test(value)) {
|
|
1136
|
+
throw new TypeError(
|
|
1137
|
+
"CodeTraceBench revision must be a full 40- or 64-character hexadecimal commit or digest"
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
return value;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
294
1143
|
// src/execution.ts
|
|
295
1144
|
import {
|
|
296
1145
|
summarizeExecution
|
|
@@ -324,7 +1173,7 @@ function summarizeSpanExecution(spans, opts = {}) {
|
|
|
324
1173
|
import { InMemoryTraceStore } from "@tangle-network/agent-eval";
|
|
325
1174
|
|
|
326
1175
|
// src/adapters/tool-io.ts
|
|
327
|
-
import { createHash } from "crypto";
|
|
1176
|
+
import { createHash as createHash3 } from "crypto";
|
|
328
1177
|
var TOOL_IO_VALUE_MAX_BYTES = 16 * 1024;
|
|
329
1178
|
var TOOL_IO_VALUE_KEYS = ["input.value", "output.value"];
|
|
330
1179
|
var TOOL_IO_SIDES = ["input", "output"];
|
|
@@ -380,14 +1229,14 @@ function valueAttributes(side, raw) {
|
|
|
380
1229
|
const serialized = serialize(raw);
|
|
381
1230
|
if (!serialized) return {};
|
|
382
1231
|
const bytes = Buffer.byteLength(serialized.value);
|
|
383
|
-
const
|
|
1232
|
+
const sha2562 = createHash3("sha256").update(serialized.value).digest("hex");
|
|
384
1233
|
const truncated = bytes > TOOL_IO_VALUE_MAX_BYTES;
|
|
385
1234
|
const value = truncated ? truncatedValue(serialized.value, bytes) : serialized.value;
|
|
386
1235
|
return {
|
|
387
1236
|
[`${side}.value`]: value,
|
|
388
1237
|
[`${side}.mime_type`]: serialized.mimeType,
|
|
389
1238
|
[`traces.${side}.bytes`]: bytes,
|
|
390
|
-
[`traces.${side}.sha256`]:
|
|
1239
|
+
[`traces.${side}.sha256`]: sha2562,
|
|
391
1240
|
[`traces.${side}.truncated`]: truncated
|
|
392
1241
|
};
|
|
393
1242
|
}
|
|
@@ -431,7 +1280,7 @@ function normalizeToolIoAttributes(attributes) {
|
|
|
431
1280
|
}
|
|
432
1281
|
attributes[valueKey] = value;
|
|
433
1282
|
attributes[bytesKey] = Buffer.byteLength(value);
|
|
434
|
-
attributes[digestKey] =
|
|
1283
|
+
attributes[digestKey] = createHash3("sha256").update(value).digest("hex");
|
|
435
1284
|
attributes[truncatedKey] = truncated;
|
|
436
1285
|
}
|
|
437
1286
|
}
|
|
@@ -529,7 +1378,7 @@ async function runPipelines(spans, opts = {}) {
|
|
|
529
1378
|
}
|
|
530
1379
|
|
|
531
1380
|
// src/integrity.ts
|
|
532
|
-
import { createHash as
|
|
1381
|
+
import { createHash as createHash4 } from "crypto";
|
|
533
1382
|
var receiptStates = /* @__PURE__ */ new WeakMap();
|
|
534
1383
|
function receiptIdentity(receipt) {
|
|
535
1384
|
return [
|
|
@@ -588,7 +1437,7 @@ function sessionIntegrityAttributes(ref) {
|
|
|
588
1437
|
};
|
|
589
1438
|
}
|
|
590
1439
|
function corruptionDigest(receipts) {
|
|
591
|
-
const hash2 =
|
|
1440
|
+
const hash2 = createHash4("sha256");
|
|
592
1441
|
for (const receipt of receipts) {
|
|
593
1442
|
hash2.update(JSON.stringify(receiptIdentity(receipt)));
|
|
594
1443
|
hash2.update("\n");
|
|
@@ -596,7 +1445,7 @@ function corruptionDigest(receipts) {
|
|
|
596
1445
|
return `sha256:${hash2.digest("hex")}`;
|
|
597
1446
|
}
|
|
598
1447
|
function receiptSpanId(parentSpanId, receipt) {
|
|
599
|
-
const digest =
|
|
1448
|
+
const digest = createHash4("sha256").update(JSON.stringify(receiptIdentity(receipt))).digest("hex");
|
|
600
1449
|
return `${parentSpanId}:corruption:${digest}`;
|
|
601
1450
|
}
|
|
602
1451
|
function stampSessionIntegrity(ref, spans) {
|
|
@@ -641,7 +1490,7 @@ function stampSessionIntegrity(ref, spans) {
|
|
|
641
1490
|
}
|
|
642
1491
|
|
|
643
1492
|
// src/json.ts
|
|
644
|
-
import { readdir, readFile } from "fs/promises";
|
|
1493
|
+
import { readdir, readFile as readFile4 } from "fs/promises";
|
|
645
1494
|
var JsonSourceError = class extends Error {
|
|
646
1495
|
sourcePath;
|
|
647
1496
|
kind;
|
|
@@ -676,7 +1525,7 @@ function isMissingJsonSource(error) {
|
|
|
676
1525
|
async function readJsonFile(path) {
|
|
677
1526
|
let raw;
|
|
678
1527
|
try {
|
|
679
|
-
raw = await
|
|
1528
|
+
raw = await readFile4(path, "utf8");
|
|
680
1529
|
} catch (error) {
|
|
681
1530
|
throw new JsonSourceError(path, "read", error);
|
|
682
1531
|
}
|
|
@@ -695,9 +1544,9 @@ async function listJsonFiles(path) {
|
|
|
695
1544
|
}
|
|
696
1545
|
|
|
697
1546
|
// src/adapters/amp.ts
|
|
698
|
-
import { readdir as readdir2, stat } from "fs/promises";
|
|
1547
|
+
import { readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
699
1548
|
import { homedir } from "os";
|
|
700
|
-
import { basename, join } from "path";
|
|
1549
|
+
import { basename as basename3, join as join4 } from "path";
|
|
701
1550
|
var SERVICE = "amp";
|
|
702
1551
|
function textOf(content) {
|
|
703
1552
|
if (typeof content === "string") return content.slice(0, CONTENT_CAP);
|
|
@@ -708,8 +1557,8 @@ function textOf(content) {
|
|
|
708
1557
|
var AmpAdapter = class {
|
|
709
1558
|
harness = "amp";
|
|
710
1559
|
threadsRoot() {
|
|
711
|
-
const base = process.env.XDG_DATA_HOME ??
|
|
712
|
-
return
|
|
1560
|
+
const base = process.env.XDG_DATA_HOME ?? join4(homedir(), ".local", "share");
|
|
1561
|
+
return join4(base, "amp", "threads");
|
|
713
1562
|
}
|
|
714
1563
|
async locate(opts = {}) {
|
|
715
1564
|
let files;
|
|
@@ -722,17 +1571,17 @@ var AmpAdapter = class {
|
|
|
722
1571
|
const refs = [];
|
|
723
1572
|
for (const f of files) {
|
|
724
1573
|
if (!f.startsWith("T-") || !f.endsWith(".json")) continue;
|
|
725
|
-
const path =
|
|
1574
|
+
const path = join4(this.threadsRoot(), f);
|
|
726
1575
|
let st;
|
|
727
1576
|
try {
|
|
728
|
-
st = await
|
|
1577
|
+
st = await stat2(path);
|
|
729
1578
|
} catch (error) {
|
|
730
1579
|
if (isMissingPathError(error)) continue;
|
|
731
1580
|
throw error;
|
|
732
1581
|
}
|
|
733
1582
|
if (opts.sinceMs && st.mtimeMs < opts.sinceMs) continue;
|
|
734
1583
|
if (opts.cwd) continue;
|
|
735
|
-
refs.push({ harness: this.harness, sessionId:
|
|
1584
|
+
refs.push({ harness: this.harness, sessionId: basename3(f, ".json"), path, cwd: null, mtimeMs: st.mtimeMs });
|
|
736
1585
|
}
|
|
737
1586
|
return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
738
1587
|
}
|
|
@@ -823,7 +1672,7 @@ var AmpAdapter = class {
|
|
|
823
1672
|
|
|
824
1673
|
// src/jsonl.ts
|
|
825
1674
|
import { isUtf8 } from "buffer";
|
|
826
|
-
import { createHash as
|
|
1675
|
+
import { createHash as createHash5 } from "crypto";
|
|
827
1676
|
import { createReadStream } from "fs";
|
|
828
1677
|
var JsonlParseError = class extends SyntaxError {
|
|
829
1678
|
sourcePath;
|
|
@@ -852,7 +1701,7 @@ function corruptionReceipt(rawLine, path, lineNumber, byteOffset) {
|
|
|
852
1701
|
lineNumber,
|
|
853
1702
|
byteOffset,
|
|
854
1703
|
byteLength: rawLine.length,
|
|
855
|
-
sha256:
|
|
1704
|
+
sha256: createHash5("sha256").update(rawLine).digest("hex"),
|
|
856
1705
|
rawBytes: "local_source_only"
|
|
857
1706
|
};
|
|
858
1707
|
}
|
|
@@ -1044,10 +1893,10 @@ function classifyReaction(text2) {
|
|
|
1044
1893
|
var CORRECTIVE_REACTIONS = ["correction", "frustration", "jargon", "structure"];
|
|
1045
1894
|
|
|
1046
1895
|
// src/adapters/claude.ts
|
|
1047
|
-
import { createHash as
|
|
1048
|
-
import { readdir as readdir4, stat as
|
|
1896
|
+
import { createHash as createHash6 } from "crypto";
|
|
1897
|
+
import { readdir as readdir4, stat as stat3 } from "fs/promises";
|
|
1049
1898
|
import { homedir as homedir3 } from "os";
|
|
1050
|
-
import { basename as
|
|
1899
|
+
import { basename as basename6, join as join6, relative as relative4 } from "path";
|
|
1051
1900
|
import {
|
|
1052
1901
|
applyLlmSpanOtlpAttributes as applyLlmSpanOtlpAttributes2,
|
|
1053
1902
|
LLM_CACHED_TOKENS,
|
|
@@ -1057,12 +1906,12 @@ import {
|
|
|
1057
1906
|
} from "@tangle-network/agent-eval/trace-attributes";
|
|
1058
1907
|
|
|
1059
1908
|
// src/adapters/claude-subagent-sources.ts
|
|
1060
|
-
import { readdir as readdir3, realpath } from "fs/promises";
|
|
1909
|
+
import { readdir as readdir3, realpath as realpath2 } from "fs/promises";
|
|
1061
1910
|
import { homedir as homedir2 } from "os";
|
|
1062
|
-
import { basename as
|
|
1911
|
+
import { basename as basename5, dirname as dirname4, isAbsolute as isAbsolute3, join as join5, relative as relative3, resolve as resolve4 } from "path";
|
|
1063
1912
|
|
|
1064
1913
|
// src/adapters/claude-workflow.ts
|
|
1065
|
-
import { basename as
|
|
1914
|
+
import { basename as basename4, dirname as dirname3, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "path";
|
|
1066
1915
|
var ClaudeTaskScopeError = class extends Error {
|
|
1067
1916
|
constructor(message) {
|
|
1068
1917
|
super(message);
|
|
@@ -1073,8 +1922,8 @@ function validWorkflowRunReference(runId, transcriptDir) {
|
|
|
1073
1922
|
if (typeof runId !== "string" || typeof transcriptDir !== "string") return void 0;
|
|
1074
1923
|
const cleanRunId = runId.trim();
|
|
1075
1924
|
const cleanTranscriptDir = transcriptDir.trim();
|
|
1076
|
-
const resolvedTranscriptDir =
|
|
1077
|
-
if (!cleanRunId || cleanRunId === "." || cleanRunId === ".." || cleanRunId.includes("/") || cleanRunId.includes("\\") || !
|
|
1925
|
+
const resolvedTranscriptDir = resolve3(cleanTranscriptDir);
|
|
1926
|
+
if (!cleanRunId || cleanRunId === "." || cleanRunId === ".." || cleanRunId.includes("/") || cleanRunId.includes("\\") || !isAbsolute2(cleanTranscriptDir) || basename4(resolvedTranscriptDir) !== cleanRunId || basename4(dirname3(resolvedTranscriptDir)) !== "workflows" || basename4(dirname3(dirname3(resolvedTranscriptDir))) !== "subagents") {
|
|
1078
1927
|
return void 0;
|
|
1079
1928
|
}
|
|
1080
1929
|
return { runId: cleanRunId, transcriptDir: cleanTranscriptDir };
|
|
@@ -1097,7 +1946,7 @@ function workflowRunFromText(text2) {
|
|
|
1097
1946
|
return validWorkflowRunReference(runIds[0], transcriptDirs[0]);
|
|
1098
1947
|
}
|
|
1099
1948
|
function sameWorkflowRun(left, right) {
|
|
1100
|
-
return left.runId === right.runId &&
|
|
1949
|
+
return left.runId === right.runId && resolve3(left.transcriptDir) === resolve3(right.transcriptDir);
|
|
1101
1950
|
}
|
|
1102
1951
|
function resolveWorkflowRunReference(toolUseId, outputText, structured) {
|
|
1103
1952
|
const text2 = workflowRunFromText(outputText);
|
|
@@ -1151,7 +2000,7 @@ function bindWorkflowRuns(index) {
|
|
|
1151
2000
|
return bindings;
|
|
1152
2001
|
}
|
|
1153
2002
|
function workflowRunIdForSubagent(subDir, file) {
|
|
1154
|
-
const parts =
|
|
2003
|
+
const parts = relative2(subDir, file).split(/[\\/]/);
|
|
1155
2004
|
if (parts[0] !== "workflows") return void 0;
|
|
1156
2005
|
if (parts.length !== 3 || !parts[1] || !/^agent-.*\.jsonl$/.test(parts[2] ?? "")) {
|
|
1157
2006
|
throw new ClaudeTaskScopeError(
|
|
@@ -1167,14 +2016,14 @@ function bindWorkflowSubagent(file, runId, transcriptDir, bindings) {
|
|
|
1167
2016
|
`Cannot link Claude Workflow subagent ${file}: parent result has no run ${runId}`
|
|
1168
2017
|
);
|
|
1169
2018
|
}
|
|
1170
|
-
const expectedDir =
|
|
1171
|
-
if (
|
|
2019
|
+
const expectedDir = resolve3(transcriptDir);
|
|
2020
|
+
if (resolve3(dirname3(file)) !== expectedDir) {
|
|
1172
2021
|
throw new ClaudeTaskScopeError(
|
|
1173
2022
|
`Cannot link Claude Workflow run ${runId}: transcript directory does not match ${expectedDir}`
|
|
1174
2023
|
);
|
|
1175
2024
|
}
|
|
1176
2025
|
const localBindings = runBindings.filter(
|
|
1177
|
-
(binding) =>
|
|
2026
|
+
(binding) => resolve3(binding.transcriptDir) === expectedDir
|
|
1178
2027
|
);
|
|
1179
2028
|
if (localBindings.length === 0) {
|
|
1180
2029
|
throw new ClaudeTaskScopeError(
|
|
@@ -1240,7 +2089,7 @@ async function listSubagentFiles(root, signal) {
|
|
|
1240
2089
|
}
|
|
1241
2090
|
signal?.throwIfAborted();
|
|
1242
2091
|
for (const entry of entries) {
|
|
1243
|
-
const path =
|
|
2092
|
+
const path = join5(dir, entry.name);
|
|
1244
2093
|
if (entry.isDirectory()) {
|
|
1245
2094
|
pending.push(path);
|
|
1246
2095
|
} else if (entry.isFile() && /^agent-.*\.jsonl$/.test(entry.name)) {
|
|
@@ -1251,20 +2100,20 @@ async function listSubagentFiles(root, signal) {
|
|
|
1251
2100
|
return files.sort();
|
|
1252
2101
|
}
|
|
1253
2102
|
function pathIsWithin(root, target) {
|
|
1254
|
-
const path =
|
|
1255
|
-
return path === "" || path !== ".." && !path.startsWith("../") && !path.startsWith("..\\") && !
|
|
2103
|
+
const path = relative3(resolve4(root), resolve4(target));
|
|
2104
|
+
return path === "" || path !== ".." && !path.startsWith("../") && !path.startsWith("..\\") && !isAbsolute3(path);
|
|
1256
2105
|
}
|
|
1257
2106
|
function workflowStorageRoot(ref) {
|
|
1258
|
-
const nativeRoot =
|
|
1259
|
-
return pathIsWithin(nativeRoot, ref.path) ? nativeRoot :
|
|
2107
|
+
const nativeRoot = resolve4(homedir2(), ".claude", "projects");
|
|
2108
|
+
return pathIsWithin(nativeRoot, ref.path) ? nativeRoot : dirname4(resolve4(ref.path));
|
|
1260
2109
|
}
|
|
1261
2110
|
async function workflowTranscriptDirectories(ref, bindings) {
|
|
1262
2111
|
const allowedRoot = workflowStorageRoot(ref);
|
|
1263
|
-
const allowedRealRoot = await
|
|
2112
|
+
const allowedRealRoot = await realpath2(allowedRoot);
|
|
1264
2113
|
const directories = /* @__PURE__ */ new Map();
|
|
1265
2114
|
for (const runBindings of bindings.values()) {
|
|
1266
2115
|
for (const binding of runBindings) {
|
|
1267
|
-
const directory =
|
|
2116
|
+
const directory = resolve4(binding.transcriptDir);
|
|
1268
2117
|
if (!pathIsWithin(allowedRoot, directory)) {
|
|
1269
2118
|
throw new ClaudeTaskScopeError(
|
|
1270
2119
|
`Claude Workflow transcript directory escapes ${allowedRoot}: ${directory}`
|
|
@@ -1272,7 +2121,7 @@ async function workflowTranscriptDirectories(ref, bindings) {
|
|
|
1272
2121
|
}
|
|
1273
2122
|
let realDirectory;
|
|
1274
2123
|
try {
|
|
1275
|
-
realDirectory = await
|
|
2124
|
+
realDirectory = await realpath2(directory);
|
|
1276
2125
|
} catch (error) {
|
|
1277
2126
|
if (isMissingPathError(error)) continue;
|
|
1278
2127
|
throw error;
|
|
@@ -1288,28 +2137,28 @@ async function workflowTranscriptDirectories(ref, bindings) {
|
|
|
1288
2137
|
return [...directories.values()].sort();
|
|
1289
2138
|
}
|
|
1290
2139
|
async function collectClaudeSubagentSources(ref, bindings, signal) {
|
|
1291
|
-
const subDir =
|
|
2140
|
+
const subDir = join5(ref.path.replace(/\.jsonl$/, ""), "subagents");
|
|
1292
2141
|
const filesByPath = /* @__PURE__ */ new Map();
|
|
1293
2142
|
const workflowByPath = /* @__PURE__ */ new Map();
|
|
1294
2143
|
for (const file of await listSubagentFiles(subDir, signal)) {
|
|
1295
|
-
const path =
|
|
2144
|
+
const path = resolve4(file);
|
|
1296
2145
|
filesByPath.set(path, file);
|
|
1297
2146
|
const runId = workflowRunIdForSubagent(subDir, file);
|
|
1298
2147
|
if (runId) {
|
|
1299
2148
|
workflowByPath.set(path, {
|
|
1300
2149
|
runId,
|
|
1301
|
-
transcriptDir:
|
|
2150
|
+
transcriptDir: dirname4(file)
|
|
1302
2151
|
});
|
|
1303
2152
|
}
|
|
1304
2153
|
}
|
|
1305
2154
|
for (const transcriptDir of await workflowTranscriptDirectories(ref, bindings)) {
|
|
1306
2155
|
signal?.throwIfAborted();
|
|
1307
|
-
const runId =
|
|
2156
|
+
const runId = basename5(transcriptDir);
|
|
1308
2157
|
for (const file of await listSubagentFiles(transcriptDir, signal)) {
|
|
1309
|
-
const path =
|
|
2158
|
+
const path = resolve4(file);
|
|
1310
2159
|
filesByPath.set(path, file);
|
|
1311
2160
|
const previous = workflowByPath.get(path);
|
|
1312
|
-
if (previous && (previous.runId !== runId ||
|
|
2161
|
+
if (previous && (previous.runId !== runId || resolve4(previous.transcriptDir) !== resolve4(transcriptDir))) {
|
|
1313
2162
|
throw new ClaudeTaskScopeError(
|
|
1314
2163
|
`Claude Workflow subagent ${file} belongs to multiple transcript directories`
|
|
1315
2164
|
);
|
|
@@ -1458,7 +2307,7 @@ function indexWorkflowProjection(projection, index) {
|
|
|
1458
2307
|
indexWorkflowResults(index, results);
|
|
1459
2308
|
}
|
|
1460
2309
|
function fingerprintClaudeEvent(event) {
|
|
1461
|
-
return
|
|
2310
|
+
return createHash6("sha256").update(JSON.stringify(event)).digest("hex");
|
|
1462
2311
|
}
|
|
1463
2312
|
function startsClaudeTask(projection) {
|
|
1464
2313
|
if (projection.kind !== "user" || projection.prompt === null) return false;
|
|
@@ -1745,7 +2594,7 @@ var ClaudeAdapter = class {
|
|
|
1745
2594
|
harness = "claude-code";
|
|
1746
2595
|
aliases = ["claude", "claudish", "openclaw", "nanoclaw"];
|
|
1747
2596
|
root() {
|
|
1748
|
-
return
|
|
2597
|
+
return join6(homedir3(), ".claude", "projects");
|
|
1749
2598
|
}
|
|
1750
2599
|
async sourcePaths(ref, options = {}) {
|
|
1751
2600
|
const bindings = await readClaudeWorkflowBindings(ref, options);
|
|
@@ -1755,7 +2604,7 @@ var ClaudeAdapter = class {
|
|
|
1755
2604
|
options.signal?.throwIfAborted();
|
|
1756
2605
|
const path = file.replace(/\.jsonl$/, ".meta.json");
|
|
1757
2606
|
try {
|
|
1758
|
-
const source = await
|
|
2607
|
+
const source = await stat3(path);
|
|
1759
2608
|
if (source.isFile()) metadataFiles.push(path);
|
|
1760
2609
|
} catch (error) {
|
|
1761
2610
|
if (!isMissingPathError(error)) throw error;
|
|
@@ -1775,7 +2624,7 @@ var ClaudeAdapter = class {
|
|
|
1775
2624
|
}
|
|
1776
2625
|
const refs = [];
|
|
1777
2626
|
for (const dir of projectDirs) {
|
|
1778
|
-
const dirPath =
|
|
2627
|
+
const dirPath = join6(root, dir);
|
|
1779
2628
|
let files;
|
|
1780
2629
|
try {
|
|
1781
2630
|
files = await readdir4(dirPath);
|
|
@@ -1785,10 +2634,10 @@ var ClaudeAdapter = class {
|
|
|
1785
2634
|
}
|
|
1786
2635
|
for (const f of files) {
|
|
1787
2636
|
if (!f.endsWith(".jsonl")) continue;
|
|
1788
|
-
const path =
|
|
2637
|
+
const path = join6(dirPath, f);
|
|
1789
2638
|
let st;
|
|
1790
2639
|
try {
|
|
1791
|
-
st = await
|
|
2640
|
+
st = await stat3(path);
|
|
1792
2641
|
} catch (error) {
|
|
1793
2642
|
if (isMissingPathError(error)) continue;
|
|
1794
2643
|
throw error;
|
|
@@ -1797,7 +2646,7 @@ var ClaudeAdapter = class {
|
|
|
1797
2646
|
if (opts.sinceMs && st.mtimeMs < opts.sinceMs) continue;
|
|
1798
2647
|
const cwd = `/${dir.replace(/^-/, "").replace(/-/g, "/")}`;
|
|
1799
2648
|
if (opts.cwd && !cwd.startsWith(opts.cwd)) continue;
|
|
1800
|
-
refs.push({ harness: this.harness, sessionId:
|
|
2649
|
+
refs.push({ harness: this.harness, sessionId: basename6(f, ".jsonl"), path, cwd, mtimeMs: st.mtimeMs });
|
|
1801
2650
|
}
|
|
1802
2651
|
}
|
|
1803
2652
|
return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
@@ -1899,7 +2748,7 @@ var ClaudeAdapter = class {
|
|
|
1899
2748
|
}
|
|
1900
2749
|
/** Parse nested `subagents/.../agent-*.jsonl`, parenting each under its Agent call. */
|
|
1901
2750
|
async foldSubagents(ref, traceId, main, workflowBindings, out, options) {
|
|
1902
|
-
const subDir =
|
|
2751
|
+
const subDir = join6(ref.path.replace(/\.jsonl$/, ""), "subagents");
|
|
1903
2752
|
const sources = await collectClaudeSubagentSources(
|
|
1904
2753
|
ref,
|
|
1905
2754
|
workflowBindings,
|
|
@@ -1909,7 +2758,7 @@ var ClaudeAdapter = class {
|
|
|
1909
2758
|
const agents = [];
|
|
1910
2759
|
for (const file of sources.files) {
|
|
1911
2760
|
options.signal?.throwIfAborted();
|
|
1912
|
-
const sourceKey =
|
|
2761
|
+
const sourceKey = relative4(subDir, file).replace(/\.jsonl$/, "").split(/[\\/]/).map((segment) => encodeURIComponent(segment)).join(":");
|
|
1913
2762
|
const metaPath = file.replace(/\.jsonl$/, ".meta.json");
|
|
1914
2763
|
let meta = {};
|
|
1915
2764
|
try {
|
|
@@ -1923,7 +2772,7 @@ var ClaudeAdapter = class {
|
|
|
1923
2772
|
const agent = {
|
|
1924
2773
|
file,
|
|
1925
2774
|
sourceKey,
|
|
1926
|
-
agentId:
|
|
2775
|
+
agentId: basename6(file, ".jsonl").replace(/^agent-/, ""),
|
|
1927
2776
|
meta,
|
|
1928
2777
|
...meta.toolUseId ? { parentToolUseId: meta.toolUseId } : {}
|
|
1929
2778
|
};
|
|
@@ -1936,7 +2785,7 @@ var ClaudeAdapter = class {
|
|
|
1936
2785
|
);
|
|
1937
2786
|
const parsed = await parseClaudeSubagent(ref, traceId, agent, options);
|
|
1938
2787
|
const binding = selectWorkflowBinding(
|
|
1939
|
-
|
|
2788
|
+
basename6(file),
|
|
1940
2789
|
runBindings,
|
|
1941
2790
|
firstSubagentTimestamp(agent, parsed),
|
|
1942
2791
|
meta.toolUseId
|
|
@@ -2304,9 +3153,9 @@ async function resolveCodexParentTask(ref, childSessionId, options = {}) {
|
|
|
2304
3153
|
}
|
|
2305
3154
|
|
|
2306
3155
|
// src/adapters/codex.ts
|
|
2307
|
-
import { readdir as readdir5, stat as
|
|
3156
|
+
import { readdir as readdir5, stat as stat4 } from "fs/promises";
|
|
2308
3157
|
import { homedir as homedir4 } from "os";
|
|
2309
|
-
import { basename as
|
|
3158
|
+
import { basename as basename7, join as join7 } from "path";
|
|
2310
3159
|
var SERVICE3 = "codex";
|
|
2311
3160
|
var SESSION_HEAD_LINES = 40;
|
|
2312
3161
|
function tokenUsageSignature(usage) {
|
|
@@ -2527,7 +3376,7 @@ async function* walkRollouts(root) {
|
|
|
2527
3376
|
throw error;
|
|
2528
3377
|
}
|
|
2529
3378
|
for (const y of years) {
|
|
2530
|
-
const yp =
|
|
3379
|
+
const yp = join7(root, y);
|
|
2531
3380
|
let months;
|
|
2532
3381
|
try {
|
|
2533
3382
|
months = await readdir5(yp);
|
|
@@ -2536,7 +3385,7 @@ async function* walkRollouts(root) {
|
|
|
2536
3385
|
throw error;
|
|
2537
3386
|
}
|
|
2538
3387
|
for (const m of months) {
|
|
2539
|
-
const mp =
|
|
3388
|
+
const mp = join7(yp, m);
|
|
2540
3389
|
let days;
|
|
2541
3390
|
try {
|
|
2542
3391
|
days = await readdir5(mp);
|
|
@@ -2545,7 +3394,7 @@ async function* walkRollouts(root) {
|
|
|
2545
3394
|
throw error;
|
|
2546
3395
|
}
|
|
2547
3396
|
for (const d of days) {
|
|
2548
|
-
const dp =
|
|
3397
|
+
const dp = join7(mp, d);
|
|
2549
3398
|
let files;
|
|
2550
3399
|
try {
|
|
2551
3400
|
files = await readdir5(dp);
|
|
@@ -2554,7 +3403,7 @@ async function* walkRollouts(root) {
|
|
|
2554
3403
|
throw error;
|
|
2555
3404
|
}
|
|
2556
3405
|
for (const f of files) {
|
|
2557
|
-
if (f.startsWith("rollout-") && f.endsWith(".jsonl")) yield
|
|
3406
|
+
if (f.startsWith("rollout-") && f.endsWith(".jsonl")) yield join7(dp, f);
|
|
2558
3407
|
}
|
|
2559
3408
|
}
|
|
2560
3409
|
}
|
|
@@ -2564,19 +3413,19 @@ var CodexAdapter = class {
|
|
|
2564
3413
|
harness = "codex";
|
|
2565
3414
|
aliases = ["codex-acp"];
|
|
2566
3415
|
root() {
|
|
2567
|
-
return process.env.CODEX_HOME ?
|
|
3416
|
+
return process.env.CODEX_HOME ? join7(process.env.CODEX_HOME, "sessions") : join7(homedir4(), ".codex", "sessions");
|
|
2568
3417
|
}
|
|
2569
3418
|
async refFromPath(path, opts) {
|
|
2570
3419
|
let st;
|
|
2571
3420
|
try {
|
|
2572
|
-
st = await
|
|
3421
|
+
st = await stat4(path);
|
|
2573
3422
|
} catch (error) {
|
|
2574
3423
|
if (isMissingPathError(error)) return void 0;
|
|
2575
3424
|
throw error;
|
|
2576
3425
|
}
|
|
2577
3426
|
if (opts.sinceMs && st.mtimeMs < opts.sinceMs) return void 0;
|
|
2578
3427
|
let cwd = null;
|
|
2579
|
-
let id =
|
|
3428
|
+
let id = basename7(path).replace(/^rollout-[\dT-]+-/, "").replace(/\.jsonl$/, "");
|
|
2580
3429
|
const ref = { harness: this.harness, sessionId: id, path, cwd, mtimeMs: st.mtimeMs };
|
|
2581
3430
|
const head = await takeJsonl(path, SESSION_HEAD_LINES, sessionJsonlOptions(ref));
|
|
2582
3431
|
for (const parsed of head) {
|
|
@@ -2604,7 +3453,7 @@ var CodexAdapter = class {
|
|
|
2604
3453
|
async locateBySessionId(sessionId, opts = {}) {
|
|
2605
3454
|
const refs = [];
|
|
2606
3455
|
for await (const path of walkRollouts(this.root())) {
|
|
2607
|
-
if (!
|
|
3456
|
+
if (!basename7(path).includes(sessionId)) continue;
|
|
2608
3457
|
const ref = await this.refFromPath(path, opts);
|
|
2609
3458
|
if (ref?.sessionId === sessionId) refs.push(ref);
|
|
2610
3459
|
}
|
|
@@ -3002,7 +3851,7 @@ var CodexExecStreamError = class extends Error {
|
|
|
3002
3851
|
function objectValue(value) {
|
|
3003
3852
|
return value != null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3004
3853
|
}
|
|
3005
|
-
function
|
|
3854
|
+
function stringValue2(value) {
|
|
3006
3855
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3007
3856
|
}
|
|
3008
3857
|
function isoTime(value) {
|
|
@@ -3043,7 +3892,7 @@ function requireObject2(value, sourcePath, label) {
|
|
|
3043
3892
|
return object;
|
|
3044
3893
|
}
|
|
3045
3894
|
function requireString(value, sourcePath, label) {
|
|
3046
|
-
const string =
|
|
3895
|
+
const string = stringValue2(value);
|
|
3047
3896
|
if (!string) throw new CodexExecStreamError(sourcePath, `${label} must be a non-empty string`);
|
|
3048
3897
|
return string;
|
|
3049
3898
|
}
|
|
@@ -3078,7 +3927,7 @@ function fileChanges(item, sourcePath) {
|
|
|
3078
3927
|
function toolInput(itemType2, item, sourcePath) {
|
|
3079
3928
|
if (itemType2 === "command_execution") {
|
|
3080
3929
|
const command = requireString(item.command, sourcePath, "command_execution.command");
|
|
3081
|
-
const cwd =
|
|
3930
|
+
const cwd = stringValue2(item.cwd);
|
|
3082
3931
|
return { cmd: command, ...cwd ? { cwd } : {} };
|
|
3083
3932
|
}
|
|
3084
3933
|
return { changes: fileChanges(item, sourcePath) };
|
|
@@ -3100,7 +3949,7 @@ function completedStatus(itemType2, item, sourcePath) {
|
|
|
3100
3949
|
};
|
|
3101
3950
|
}
|
|
3102
3951
|
function itemType(item) {
|
|
3103
|
-
return
|
|
3952
|
+
return stringValue2(item.type);
|
|
3104
3953
|
}
|
|
3105
3954
|
function toolItemType(item) {
|
|
3106
3955
|
const type = itemType(item);
|
|
@@ -3153,7 +4002,7 @@ var CodexExecAdapter = class {
|
|
|
3153
4002
|
const id = requireString(item.id, ref.path, `${type}.id`);
|
|
3154
4003
|
const name = toolName(type);
|
|
3155
4004
|
const input = toolInput(type, item, ref.path);
|
|
3156
|
-
const cwd =
|
|
4005
|
+
const cwd = stringValue2(item.cwd);
|
|
3157
4006
|
if (!ref.cwd && cwd) ref.cwd = cwd;
|
|
3158
4007
|
const toolSpan = span({
|
|
3159
4008
|
traceId: threadId,
|
|
@@ -3240,7 +4089,7 @@ var CodexExecAdapter = class {
|
|
|
3240
4089
|
};
|
|
3241
4090
|
for await (const raw of readJsonl(ref.path, sessionJsonlOptions(ref, options))) {
|
|
3242
4091
|
const event = objectValue(raw);
|
|
3243
|
-
const type = event ?
|
|
4092
|
+
const type = event ? stringValue2(event.type) : void 0;
|
|
3244
4093
|
if (!event || !type || !SUPPORTED_EVENT_TYPES.has(type)) {
|
|
3245
4094
|
ignoredEventCount += 1;
|
|
3246
4095
|
continue;
|
|
@@ -3297,7 +4146,7 @@ var CodexExecAdapter = class {
|
|
|
3297
4146
|
if (turn.pendingTools.has(id) || turn.completedItemIds.has(id)) {
|
|
3298
4147
|
fail(`item ${id} started more than once`);
|
|
3299
4148
|
}
|
|
3300
|
-
const status =
|
|
4149
|
+
const status = stringValue2(item.status);
|
|
3301
4150
|
if (status !== void 0 && status !== "in_progress") {
|
|
3302
4151
|
fail(`${toolType}.status on item.started must be "in_progress" when present`);
|
|
3303
4152
|
}
|
|
@@ -3419,15 +4268,15 @@ var CodexExecAdapter = class {
|
|
|
3419
4268
|
};
|
|
3420
4269
|
|
|
3421
4270
|
// src/adapters/copilot.ts
|
|
3422
|
-
import { readdir as readdir6, stat as
|
|
4271
|
+
import { readdir as readdir6, stat as stat5 } from "fs/promises";
|
|
3423
4272
|
import { homedir as homedir5 } from "os";
|
|
3424
|
-
import { join as
|
|
4273
|
+
import { join as join8 } from "path";
|
|
3425
4274
|
var SERVICE5 = "github-copilot";
|
|
3426
4275
|
var CopilotAdapter = class {
|
|
3427
4276
|
harness = "github-copilot";
|
|
3428
4277
|
aliases = ["copilot"];
|
|
3429
4278
|
root() {
|
|
3430
|
-
return
|
|
4279
|
+
return join8(process.env.COPILOT_HOME ?? join8(homedir5(), ".copilot"), "session-state");
|
|
3431
4280
|
}
|
|
3432
4281
|
async locate(opts = {}) {
|
|
3433
4282
|
let dirs;
|
|
@@ -3439,10 +4288,10 @@ var CopilotAdapter = class {
|
|
|
3439
4288
|
}
|
|
3440
4289
|
const refs = [];
|
|
3441
4290
|
for (const id of dirs) {
|
|
3442
|
-
const path =
|
|
4291
|
+
const path = join8(this.root(), id, "events.jsonl");
|
|
3443
4292
|
let st;
|
|
3444
4293
|
try {
|
|
3445
|
-
st = await
|
|
4294
|
+
st = await stat5(path);
|
|
3446
4295
|
} catch (error) {
|
|
3447
4296
|
if (isMissingPathError(error)) continue;
|
|
3448
4297
|
throw error;
|
|
@@ -3543,9 +4392,9 @@ var CopilotAdapter = class {
|
|
|
3543
4392
|
};
|
|
3544
4393
|
|
|
3545
4394
|
// src/adapters/factory.ts
|
|
3546
|
-
import { readdir as readdir7, stat as
|
|
4395
|
+
import { readdir as readdir7, stat as stat6 } from "fs/promises";
|
|
3547
4396
|
import { homedir as homedir6 } from "os";
|
|
3548
|
-
import { basename as
|
|
4397
|
+
import { basename as basename8, join as join9 } from "path";
|
|
3549
4398
|
var SERVICE6 = "factory";
|
|
3550
4399
|
function textOf4(content) {
|
|
3551
4400
|
return capText(
|
|
@@ -3556,7 +4405,7 @@ var FactoryAdapter = class {
|
|
|
3556
4405
|
harness = "factory";
|
|
3557
4406
|
aliases = ["factory-droids", "droid"];
|
|
3558
4407
|
root() {
|
|
3559
|
-
return
|
|
4408
|
+
return join9(homedir6(), ".factory", "sessions");
|
|
3560
4409
|
}
|
|
3561
4410
|
async locate(opts = {}) {
|
|
3562
4411
|
const root = this.root();
|
|
@@ -3569,7 +4418,7 @@ var FactoryAdapter = class {
|
|
|
3569
4418
|
}
|
|
3570
4419
|
const refs = [];
|
|
3571
4420
|
for (const dir of dirs) {
|
|
3572
|
-
const dp =
|
|
4421
|
+
const dp = join9(root, dir);
|
|
3573
4422
|
let files;
|
|
3574
4423
|
try {
|
|
3575
4424
|
files = await readdir7(dp);
|
|
@@ -3581,16 +4430,16 @@ var FactoryAdapter = class {
|
|
|
3581
4430
|
if (opts.cwd && !cwd.startsWith(opts.cwd)) continue;
|
|
3582
4431
|
for (const f of files) {
|
|
3583
4432
|
if (!f.endsWith(".jsonl")) continue;
|
|
3584
|
-
const path =
|
|
4433
|
+
const path = join9(dp, f);
|
|
3585
4434
|
let st;
|
|
3586
4435
|
try {
|
|
3587
|
-
st = await
|
|
4436
|
+
st = await stat6(path);
|
|
3588
4437
|
} catch (error) {
|
|
3589
4438
|
if (isMissingPathError(error)) continue;
|
|
3590
4439
|
throw error;
|
|
3591
4440
|
}
|
|
3592
4441
|
if (opts.sinceMs && st.mtimeMs < opts.sinceMs) continue;
|
|
3593
|
-
refs.push({ harness: this.harness, sessionId:
|
|
4442
|
+
refs.push({ harness: this.harness, sessionId: basename8(f, ".jsonl"), path, cwd, mtimeMs: st.mtimeMs });
|
|
3594
4443
|
}
|
|
3595
4444
|
}
|
|
3596
4445
|
return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
@@ -3715,9 +4564,9 @@ var FactoryAdapter = class {
|
|
|
3715
4564
|
};
|
|
3716
4565
|
|
|
3717
4566
|
// src/adapters/forge.ts
|
|
3718
|
-
import { readdir as readdir8, stat as
|
|
4567
|
+
import { readdir as readdir8, stat as stat7 } from "fs/promises";
|
|
3719
4568
|
import { homedir as homedir7 } from "os";
|
|
3720
|
-
import { basename as
|
|
4569
|
+
import { basename as basename9, join as join10 } from "path";
|
|
3721
4570
|
var SERVICE7 = "forge";
|
|
3722
4571
|
function tokens(t) {
|
|
3723
4572
|
if (!t) return null;
|
|
@@ -3731,7 +4580,7 @@ var ForgeAdapter = class {
|
|
|
3731
4580
|
aliases = ["forgecode"];
|
|
3732
4581
|
/** Directories scanned for `*-dump.json` exports. */
|
|
3733
4582
|
dumpDirs() {
|
|
3734
|
-
return [process.cwd(),
|
|
4583
|
+
return [process.cwd(), join10(homedir7(), ".forge")];
|
|
3735
4584
|
}
|
|
3736
4585
|
async locate(opts = {}) {
|
|
3737
4586
|
const refs = [];
|
|
@@ -3746,16 +4595,16 @@ var ForgeAdapter = class {
|
|
|
3746
4595
|
}
|
|
3747
4596
|
for (const f of files) {
|
|
3748
4597
|
if (!f.endsWith("-dump.json")) continue;
|
|
3749
|
-
const path =
|
|
4598
|
+
const path = join10(dir, f);
|
|
3750
4599
|
let st;
|
|
3751
4600
|
try {
|
|
3752
|
-
st = await
|
|
4601
|
+
st = await stat7(path);
|
|
3753
4602
|
} catch (error) {
|
|
3754
4603
|
if (isMissingPathError(error)) continue;
|
|
3755
4604
|
throw error;
|
|
3756
4605
|
}
|
|
3757
4606
|
if (opts.sinceMs && st.mtimeMs < opts.sinceMs) continue;
|
|
3758
|
-
refs.push({ harness: this.harness, sessionId:
|
|
4607
|
+
refs.push({ harness: this.harness, sessionId: basename9(f, ".json"), path, cwd: dir, mtimeMs: st.mtimeMs });
|
|
3759
4608
|
}
|
|
3760
4609
|
}
|
|
3761
4610
|
return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
@@ -3845,9 +4694,9 @@ var ForgeAdapter = class {
|
|
|
3845
4694
|
};
|
|
3846
4695
|
|
|
3847
4696
|
// src/adapters/gemini.ts
|
|
3848
|
-
import { readdir as readdir9, stat as
|
|
4697
|
+
import { readdir as readdir9, stat as stat8 } from "fs/promises";
|
|
3849
4698
|
import { homedir as homedir8 } from "os";
|
|
3850
|
-
import { join as
|
|
4699
|
+
import { join as join11 } from "path";
|
|
3851
4700
|
function iso(ts, fallback) {
|
|
3852
4701
|
if (typeof ts === "number") return new Date(ts).toISOString();
|
|
3853
4702
|
if (typeof ts === "string" && ts.length > 0) return ts;
|
|
@@ -3872,7 +4721,7 @@ var GeminiFamilyAdapter = class {
|
|
|
3872
4721
|
this.homeDirName = cfg.homeDirName;
|
|
3873
4722
|
}
|
|
3874
4723
|
chatsRoot() {
|
|
3875
|
-
return
|
|
4724
|
+
return join11(homedir8(), this.homeDirName, "tmp");
|
|
3876
4725
|
}
|
|
3877
4726
|
/** `<home>/projects.json` maps `{ <absolute-cwd>: <projectName> }`. The session
|
|
3878
4727
|
* dir name (`tmp/<projectHash>/`) is that projectName for registered projects,
|
|
@@ -3882,7 +4731,7 @@ var GeminiFamilyAdapter = class {
|
|
|
3882
4731
|
const nameToPath = /* @__PURE__ */ new Map();
|
|
3883
4732
|
try {
|
|
3884
4733
|
const projects = (await readJsonFile(
|
|
3885
|
-
|
|
4734
|
+
join11(homedir8(), this.homeDirName, "projects.json")
|
|
3886
4735
|
)).projects ?? {};
|
|
3887
4736
|
for (const [path, name] of Object.entries(projects)) nameToPath.set(name, path);
|
|
3888
4737
|
} catch (error) {
|
|
@@ -3903,7 +4752,7 @@ var GeminiFamilyAdapter = class {
|
|
|
3903
4752
|
const refs = [];
|
|
3904
4753
|
for (const pd of projectDirs) {
|
|
3905
4754
|
const cwd = projectCwds.get(pd) ?? null;
|
|
3906
|
-
const chatsDir =
|
|
4755
|
+
const chatsDir = join11(root, pd, "chats");
|
|
3907
4756
|
let files;
|
|
3908
4757
|
try {
|
|
3909
4758
|
files = await readdir9(chatsDir);
|
|
@@ -3913,10 +4762,10 @@ var GeminiFamilyAdapter = class {
|
|
|
3913
4762
|
}
|
|
3914
4763
|
for (const f of files) {
|
|
3915
4764
|
if (!f.startsWith("session-") || !f.endsWith(".json")) continue;
|
|
3916
|
-
const path =
|
|
4765
|
+
const path = join11(chatsDir, f);
|
|
3917
4766
|
let st;
|
|
3918
4767
|
try {
|
|
3919
|
-
st = await
|
|
4768
|
+
st = await stat8(path);
|
|
3920
4769
|
} catch (error) {
|
|
3921
4770
|
if (isMissingPathError(error)) continue;
|
|
3922
4771
|
throw error;
|
|
@@ -4020,9 +4869,9 @@ var GeminiAdapter = class extends GeminiFamilyAdapter {
|
|
|
4020
4869
|
};
|
|
4021
4870
|
|
|
4022
4871
|
// src/adapters/opencode.ts
|
|
4023
|
-
import { readdir as readdir10, stat as
|
|
4872
|
+
import { readdir as readdir10, stat as stat9 } from "fs/promises";
|
|
4024
4873
|
import { homedir as homedir9 } from "os";
|
|
4025
|
-
import { join as
|
|
4874
|
+
import { join as join12 } from "path";
|
|
4026
4875
|
var SERVICE8 = "opencode";
|
|
4027
4876
|
function isoOf(ms) {
|
|
4028
4877
|
return new Date(ms ?? 0).toISOString();
|
|
@@ -4035,11 +4884,11 @@ function textOf7(parts) {
|
|
|
4035
4884
|
var OpencodeAdapter = class {
|
|
4036
4885
|
harness = "opencode";
|
|
4037
4886
|
storage() {
|
|
4038
|
-
const base = process.env.XDG_DATA_HOME ??
|
|
4039
|
-
return
|
|
4887
|
+
const base = process.env.XDG_DATA_HOME ?? join12(homedir9(), ".local", "share");
|
|
4888
|
+
return join12(base, "opencode", "storage");
|
|
4040
4889
|
}
|
|
4041
4890
|
async locate(opts = {}) {
|
|
4042
|
-
const msgRoot =
|
|
4891
|
+
const msgRoot = join12(this.storage(), "message");
|
|
4043
4892
|
let sessionDirs;
|
|
4044
4893
|
try {
|
|
4045
4894
|
sessionDirs = await readdir10(msgRoot);
|
|
@@ -4049,10 +4898,10 @@ var OpencodeAdapter = class {
|
|
|
4049
4898
|
}
|
|
4050
4899
|
const refs = [];
|
|
4051
4900
|
for (const sid of sessionDirs) {
|
|
4052
|
-
const dir =
|
|
4901
|
+
const dir = join12(msgRoot, sid);
|
|
4053
4902
|
let st;
|
|
4054
4903
|
try {
|
|
4055
|
-
st = await
|
|
4904
|
+
st = await stat9(dir);
|
|
4056
4905
|
} catch (error) {
|
|
4057
4906
|
if (isMissingPathError(error)) continue;
|
|
4058
4907
|
throw error;
|
|
@@ -4067,11 +4916,11 @@ var OpencodeAdapter = class {
|
|
|
4067
4916
|
async parse(ref) {
|
|
4068
4917
|
const traceId = ref.sessionId;
|
|
4069
4918
|
const rootId = `root:${traceId}`;
|
|
4070
|
-
const partRoot =
|
|
4919
|
+
const partRoot = join12(this.storage(), "part");
|
|
4071
4920
|
const msgFiles = await listJsonFiles(ref.path);
|
|
4072
4921
|
const messages = [];
|
|
4073
4922
|
for (const f of msgFiles) {
|
|
4074
|
-
messages.push(await readJsonFile(
|
|
4923
|
+
messages.push(await readJsonFile(join12(ref.path, f)));
|
|
4075
4924
|
}
|
|
4076
4925
|
messages.sort((a, b) => (a.time?.created ?? 0) - (b.time?.created ?? 0));
|
|
4077
4926
|
const spans = [
|
|
@@ -4091,7 +4940,7 @@ var OpencodeAdapter = class {
|
|
|
4091
4940
|
for (const msg of messages) {
|
|
4092
4941
|
const mid = msg.id ?? `m${step}`;
|
|
4093
4942
|
const llmId = `llm:${mid}`;
|
|
4094
|
-
const pdir =
|
|
4943
|
+
const pdir = join12(partRoot, mid);
|
|
4095
4944
|
const parts = [];
|
|
4096
4945
|
let partFiles = [];
|
|
4097
4946
|
try {
|
|
@@ -4100,7 +4949,7 @@ var OpencodeAdapter = class {
|
|
|
4100
4949
|
if (!isMissingJsonSource(error)) throw error;
|
|
4101
4950
|
}
|
|
4102
4951
|
for (const pf of partFiles) {
|
|
4103
|
-
parts.push(await readJsonFile(
|
|
4952
|
+
parts.push(await readJsonFile(join12(pdir, pf)));
|
|
4104
4953
|
}
|
|
4105
4954
|
const turnText = textOf7(parts);
|
|
4106
4955
|
const isUser = msg.role === "user";
|
|
@@ -4176,9 +5025,9 @@ var OpencodeAdapter = class {
|
|
|
4176
5025
|
};
|
|
4177
5026
|
|
|
4178
5027
|
// src/adapters/pi.ts
|
|
4179
|
-
import { readdir as readdir11, stat as
|
|
5028
|
+
import { readdir as readdir11, stat as stat10 } from "fs/promises";
|
|
4180
5029
|
import { homedir as homedir10 } from "os";
|
|
4181
|
-
import { basename as
|
|
5030
|
+
import { basename as basename10, join as join13 } from "path";
|
|
4182
5031
|
var SERVICE9 = "pi";
|
|
4183
5032
|
function terminalStatus(message) {
|
|
4184
5033
|
const stopReason = message?.stopReason ?? "missing";
|
|
@@ -4224,7 +5073,7 @@ function completeToolSpan(toolSpan, result) {
|
|
|
4224
5073
|
var PiAdapter = class {
|
|
4225
5074
|
harness = "pi";
|
|
4226
5075
|
root() {
|
|
4227
|
-
return
|
|
5076
|
+
return join13(homedir10(), ".pi", "agent", "sessions");
|
|
4228
5077
|
}
|
|
4229
5078
|
async locate(opts = {}) {
|
|
4230
5079
|
const root = this.root();
|
|
@@ -4237,7 +5086,7 @@ var PiAdapter = class {
|
|
|
4237
5086
|
}
|
|
4238
5087
|
const refs = [];
|
|
4239
5088
|
for (const dir of dirs) {
|
|
4240
|
-
const dp =
|
|
5089
|
+
const dp = join13(root, dir);
|
|
4241
5090
|
let files;
|
|
4242
5091
|
try {
|
|
4243
5092
|
files = await readdir11(dp);
|
|
@@ -4249,16 +5098,16 @@ var PiAdapter = class {
|
|
|
4249
5098
|
if (opts.cwd && !cwd.startsWith(opts.cwd)) continue;
|
|
4250
5099
|
for (const f of files) {
|
|
4251
5100
|
if (!f.endsWith(".jsonl")) continue;
|
|
4252
|
-
const path =
|
|
5101
|
+
const path = join13(dp, f);
|
|
4253
5102
|
let st;
|
|
4254
5103
|
try {
|
|
4255
|
-
st = await
|
|
5104
|
+
st = await stat10(path);
|
|
4256
5105
|
} catch (error) {
|
|
4257
5106
|
if (isMissingPathError(error)) continue;
|
|
4258
5107
|
throw error;
|
|
4259
5108
|
}
|
|
4260
5109
|
if (opts.sinceMs && st.mtimeMs < opts.sinceMs) continue;
|
|
4261
|
-
const id =
|
|
5110
|
+
const id = basename10(f, ".jsonl").replace(/^[\dTZ.-]+_/, "");
|
|
4262
5111
|
refs.push({ harness: this.harness, sessionId: id, path, cwd, mtimeMs: st.mtimeMs });
|
|
4263
5112
|
}
|
|
4264
5113
|
}
|
|
@@ -4297,14 +5146,14 @@ var PiAdapter = class {
|
|
|
4297
5146
|
const llmId = `llm:${mid}`;
|
|
4298
5147
|
if (msg.role === "toolResult") {
|
|
4299
5148
|
const callId = msg.toolCallId ?? `${mid}:result`;
|
|
4300
|
-
const
|
|
5149
|
+
const errorMessage2 = textOf8(msg.content).slice(0, 500) || "tool result reported error";
|
|
4301
5150
|
const result = {
|
|
4302
5151
|
callId,
|
|
4303
5152
|
toolName: msg.toolName ?? "tool",
|
|
4304
5153
|
timestamp: ts,
|
|
4305
5154
|
isError: msg.isError === true,
|
|
4306
5155
|
output: toolResultOutput(msg.content),
|
|
4307
|
-
errorMessage,
|
|
5156
|
+
errorMessage: errorMessage2,
|
|
4308
5157
|
step
|
|
4309
5158
|
};
|
|
4310
5159
|
const toolSpan = toolByCallId.get(callId);
|
|
@@ -4444,9 +5293,9 @@ var PiAdapter = class {
|
|
|
4444
5293
|
};
|
|
4445
5294
|
|
|
4446
5295
|
// src/adapters/qwen.ts
|
|
4447
|
-
import { readdir as readdir12, stat as
|
|
5296
|
+
import { readdir as readdir12, stat as stat11 } from "fs/promises";
|
|
4448
5297
|
import { homedir as homedir11 } from "os";
|
|
4449
|
-
import { basename as
|
|
5298
|
+
import { basename as basename11, join as join14 } from "path";
|
|
4450
5299
|
var SERVICE10 = "qwen";
|
|
4451
5300
|
function textOf9(parts) {
|
|
4452
5301
|
if (!parts) return "";
|
|
@@ -4456,7 +5305,7 @@ var QwenAdapter = class {
|
|
|
4456
5305
|
harness = "qwen";
|
|
4457
5306
|
aliases = ["qwen-code"];
|
|
4458
5307
|
root() {
|
|
4459
|
-
return
|
|
5308
|
+
return join14(homedir11(), ".qwen", "projects");
|
|
4460
5309
|
}
|
|
4461
5310
|
async locate(opts = {}) {
|
|
4462
5311
|
const root = this.root();
|
|
@@ -4469,7 +5318,7 @@ var QwenAdapter = class {
|
|
|
4469
5318
|
}
|
|
4470
5319
|
const refs = [];
|
|
4471
5320
|
for (const pd of projectDirs) {
|
|
4472
|
-
const chatsDir =
|
|
5321
|
+
const chatsDir = join14(root, pd, "chats");
|
|
4473
5322
|
let files;
|
|
4474
5323
|
try {
|
|
4475
5324
|
files = await readdir12(chatsDir);
|
|
@@ -4480,16 +5329,16 @@ var QwenAdapter = class {
|
|
|
4480
5329
|
if (opts.cwd) continue;
|
|
4481
5330
|
for (const f of files) {
|
|
4482
5331
|
if (!f.endsWith(".jsonl")) continue;
|
|
4483
|
-
const path =
|
|
5332
|
+
const path = join14(chatsDir, f);
|
|
4484
5333
|
let st;
|
|
4485
5334
|
try {
|
|
4486
|
-
st = await
|
|
5335
|
+
st = await stat11(path);
|
|
4487
5336
|
} catch (error) {
|
|
4488
5337
|
if (isMissingPathError(error)) continue;
|
|
4489
5338
|
throw error;
|
|
4490
5339
|
}
|
|
4491
5340
|
if (opts.sinceMs && st.mtimeMs < opts.sinceMs) continue;
|
|
4492
|
-
refs.push({ harness: this.harness, sessionId:
|
|
5341
|
+
refs.push({ harness: this.harness, sessionId: basename11(f, ".jsonl"), path, cwd: null, mtimeMs: st.mtimeMs });
|
|
4493
5342
|
}
|
|
4494
5343
|
}
|
|
4495
5344
|
return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
@@ -4646,8 +5495,8 @@ function selectAdapters(opts) {
|
|
|
4646
5495
|
}
|
|
4647
5496
|
|
|
4648
5497
|
// src/repo.ts
|
|
4649
|
-
import { stat as
|
|
4650
|
-
import { dirname as
|
|
5498
|
+
import { stat as stat12 } from "fs/promises";
|
|
5499
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join15, normalize, parse as parsePath } from "path";
|
|
4651
5500
|
function normalizeRemote(url) {
|
|
4652
5501
|
const raw = url.trim();
|
|
4653
5502
|
if (!raw) return null;
|
|
@@ -4671,7 +5520,7 @@ function normalizeRemote(url) {
|
|
|
4671
5520
|
return null;
|
|
4672
5521
|
}
|
|
4673
5522
|
async function readGit(cwd) {
|
|
4674
|
-
await
|
|
5523
|
+
await stat12(`${cwd}/.git`);
|
|
4675
5524
|
const { execFile } = await import("child_process");
|
|
4676
5525
|
const { promisify } = await import("util");
|
|
4677
5526
|
const run = promisify(execFile);
|
|
@@ -4728,7 +5577,7 @@ function parseWorktreePorcelain(output) {
|
|
|
4728
5577
|
async function equivalentGitCwds(cwd) {
|
|
4729
5578
|
if (!cwd) return [];
|
|
4730
5579
|
const selected = normalize(cwd);
|
|
4731
|
-
if (!
|
|
5580
|
+
if (!isAbsolute4(selected)) return [selected];
|
|
4732
5581
|
const top = await gitOutput(selected, ["rev-parse", "--show-toplevel"]);
|
|
4733
5582
|
if (!top) return [selected];
|
|
4734
5583
|
const worktrees = await gitOutput(top, ["worktree", "list", "--porcelain"]);
|
|
@@ -4744,7 +5593,7 @@ function cwdMatchesSelection(cwd, selections) {
|
|
|
4744
5593
|
}
|
|
4745
5594
|
async function pathStat(path) {
|
|
4746
5595
|
try {
|
|
4747
|
-
return await
|
|
5596
|
+
return await stat12(path);
|
|
4748
5597
|
} catch {
|
|
4749
5598
|
return null;
|
|
4750
5599
|
}
|
|
@@ -4754,13 +5603,13 @@ async function directoryExists(path) {
|
|
|
4754
5603
|
return Boolean(s?.isDirectory());
|
|
4755
5604
|
}
|
|
4756
5605
|
async function nearestExistingDir(path) {
|
|
4757
|
-
if (!path || !
|
|
5606
|
+
if (!path || !isAbsolute4(path)) return null;
|
|
4758
5607
|
let cur = normalize(path);
|
|
4759
5608
|
for (; ; ) {
|
|
4760
5609
|
const s = await pathStat(cur);
|
|
4761
5610
|
if (s?.isDirectory()) return cur;
|
|
4762
|
-
if (s?.isFile()) return
|
|
4763
|
-
const parent =
|
|
5611
|
+
if (s?.isFile()) return dirname5(cur);
|
|
5612
|
+
const parent = dirname5(cur);
|
|
4764
5613
|
if (parent === cur) return null;
|
|
4765
5614
|
cur = parent;
|
|
4766
5615
|
}
|
|
@@ -4768,15 +5617,15 @@ async function nearestExistingDir(path) {
|
|
|
4768
5617
|
async function findGitRoot(path) {
|
|
4769
5618
|
let cur = await nearestExistingDir(path);
|
|
4770
5619
|
while (cur) {
|
|
4771
|
-
if (await pathStat(
|
|
4772
|
-
const parent =
|
|
5620
|
+
if (await pathStat(join15(cur, ".git"))) return cur;
|
|
5621
|
+
const parent = dirname5(cur);
|
|
4773
5622
|
if (parent === cur) return null;
|
|
4774
5623
|
cur = parent;
|
|
4775
5624
|
}
|
|
4776
5625
|
return null;
|
|
4777
5626
|
}
|
|
4778
5627
|
async function repairDashedPath(cwd) {
|
|
4779
|
-
if (!
|
|
5628
|
+
if (!isAbsolute4(cwd) || await directoryExists(cwd)) return null;
|
|
4780
5629
|
const normalized = normalize(cwd);
|
|
4781
5630
|
const parsed = parsePath(normalized);
|
|
4782
5631
|
const parts = normalized.slice(parsed.root.length).split("/").filter(Boolean);
|
|
@@ -4786,7 +5635,7 @@ async function repairDashedPath(cwd) {
|
|
|
4786
5635
|
while (index < parts.length) {
|
|
4787
5636
|
const nextPart = parts[index];
|
|
4788
5637
|
if (!nextPart) break;
|
|
4789
|
-
const exact =
|
|
5638
|
+
const exact = join15(current, nextPart);
|
|
4790
5639
|
if (!await directoryExists(exact)) break;
|
|
4791
5640
|
current = exact;
|
|
4792
5641
|
index += 1;
|
|
@@ -4796,7 +5645,7 @@ async function repairDashedPath(cwd) {
|
|
|
4796
5645
|
let consumed = 0;
|
|
4797
5646
|
for (let n = parts.length - index; n >= 1; n -= 1) {
|
|
4798
5647
|
const segment = parts.slice(index, index + n).join("-");
|
|
4799
|
-
const candidate =
|
|
5648
|
+
const candidate = join15(current, segment);
|
|
4800
5649
|
if (await directoryExists(candidate)) {
|
|
4801
5650
|
match = candidate;
|
|
4802
5651
|
consumed = n;
|
|
@@ -4822,7 +5671,7 @@ var SOURCE_WEIGHT = {
|
|
|
4822
5671
|
function cleanPathCandidate(raw) {
|
|
4823
5672
|
let value = raw.trim().replace(/\\n/g, "").replace(/[),\].}]+$/g, "").replace(/:\d+(?::\d+)?$/g, "");
|
|
4824
5673
|
value = normalize(value);
|
|
4825
|
-
return
|
|
5674
|
+
return isAbsolute4(value) ? value : null;
|
|
4826
5675
|
}
|
|
4827
5676
|
function looksLikeWorkdirKey(key) {
|
|
4828
5677
|
return key === "cwd" || key === "workdir" || key === "workingDirectory" || key === "working_directory";
|
|
@@ -4880,7 +5729,7 @@ function extractAbsolutePaths(spans) {
|
|
|
4880
5729
|
async function resolveRepoAttrs(cwd) {
|
|
4881
5730
|
if (!cwd) return {};
|
|
4882
5731
|
const attrs = { [ATTR.CWD]: cwd };
|
|
4883
|
-
const
|
|
5732
|
+
const basename14 = (() => {
|
|
4884
5733
|
const trimmed = cwd.replace(/\/+$/g, "");
|
|
4885
5734
|
const idx = trimmed.lastIndexOf("/");
|
|
4886
5735
|
const name = idx === -1 ? trimmed : trimmed.slice(idx + 1);
|
|
@@ -4902,7 +5751,7 @@ async function resolveRepoAttrs(cwd) {
|
|
|
4902
5751
|
if (commit) attrs[ATTR.GIT_COMMIT] = commit;
|
|
4903
5752
|
} catch {
|
|
4904
5753
|
}
|
|
4905
|
-
attrs[ATTR.SUBJECT_KEY] =
|
|
5754
|
+
attrs[ATTR.SUBJECT_KEY] = basename14;
|
|
4906
5755
|
return attrs;
|
|
4907
5756
|
}
|
|
4908
5757
|
async function repoCandidateForPath(path) {
|
|
@@ -5007,7 +5856,7 @@ async function stampSpanWorkdirRepoAttrs(spans) {
|
|
|
5007
5856
|
async function awaitWithAbort(operation, signal) {
|
|
5008
5857
|
if (!signal) return operation;
|
|
5009
5858
|
signal.throwIfAborted();
|
|
5010
|
-
return new Promise((
|
|
5859
|
+
return new Promise((resolve7, reject) => {
|
|
5011
5860
|
const stop = () => {
|
|
5012
5861
|
signal.removeEventListener("abort", stop);
|
|
5013
5862
|
reject(signal.reason);
|
|
@@ -5016,7 +5865,7 @@ async function awaitWithAbort(operation, signal) {
|
|
|
5016
5865
|
operation.then(
|
|
5017
5866
|
(value) => {
|
|
5018
5867
|
signal.removeEventListener("abort", stop);
|
|
5019
|
-
|
|
5868
|
+
resolve7(value);
|
|
5020
5869
|
},
|
|
5021
5870
|
(error) => {
|
|
5022
5871
|
signal.removeEventListener("abort", stop);
|
|
@@ -5189,9 +6038,9 @@ function describeSessionRelationship(ref, spans) {
|
|
|
5189
6038
|
}
|
|
5190
6039
|
|
|
5191
6040
|
// src/evidence.ts
|
|
5192
|
-
import { mkdtemp, writeFile } from "fs/promises";
|
|
6041
|
+
import { mkdtemp as mkdtemp2, writeFile as writeFile2 } from "fs/promises";
|
|
5193
6042
|
import { tmpdir } from "os";
|
|
5194
|
-
import { join as
|
|
6043
|
+
import { join as join16 } from "path";
|
|
5195
6044
|
import {
|
|
5196
6045
|
OPENINFERENCE_SPAN_KIND as OPENINFERENCE_SPAN_KIND2,
|
|
5197
6046
|
TOOL_NAME as TOOL_NAME2
|
|
@@ -5317,14 +6166,14 @@ function serializePolicyEvidence(records) {
|
|
|
5317
6166
|
`;
|
|
5318
6167
|
}
|
|
5319
6168
|
async function writePolicyEvidenceFile(records, outPath) {
|
|
5320
|
-
const path = outPath ??
|
|
5321
|
-
await
|
|
6169
|
+
const path = outPath ?? join16(await mkdtemp2(join16(tmpdir(), "traces-evidence-")), "policy-evidence.jsonl");
|
|
6170
|
+
await writeFile2(path, serializePolicyEvidence(records), "utf8");
|
|
5322
6171
|
return path;
|
|
5323
6172
|
}
|
|
5324
6173
|
|
|
5325
6174
|
// src/external.ts
|
|
5326
6175
|
import { spawn } from "child_process";
|
|
5327
|
-
import { readdir as readdir13, readFile as
|
|
6176
|
+
import { readdir as readdir13, readFile as readFile5 } from "fs/promises";
|
|
5328
6177
|
|
|
5329
6178
|
// src/external-analysis-validation.ts
|
|
5330
6179
|
var FINDING_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -5600,13 +6449,13 @@ function terminateWindowsProcessTree(child) {
|
|
|
5600
6449
|
forceKill(child);
|
|
5601
6450
|
return Promise.resolve();
|
|
5602
6451
|
}
|
|
5603
|
-
return new Promise((
|
|
6452
|
+
return new Promise((resolve7) => {
|
|
5604
6453
|
let finished = false;
|
|
5605
6454
|
const finish = () => {
|
|
5606
6455
|
if (finished) return;
|
|
5607
6456
|
finished = true;
|
|
5608
6457
|
forceKill(child);
|
|
5609
|
-
|
|
6458
|
+
resolve7();
|
|
5610
6459
|
};
|
|
5611
6460
|
let killer;
|
|
5612
6461
|
try {
|
|
@@ -5633,7 +6482,7 @@ function signalProcess(pid, signal) {
|
|
|
5633
6482
|
}
|
|
5634
6483
|
}
|
|
5635
6484
|
function readProcessTableWithPs() {
|
|
5636
|
-
return new Promise((
|
|
6485
|
+
return new Promise((resolve7, reject) => {
|
|
5637
6486
|
let child;
|
|
5638
6487
|
try {
|
|
5639
6488
|
child = spawn("ps", ["-A", "-o", "pid=", "-o", "ppid="], {
|
|
@@ -5687,7 +6536,7 @@ function readProcessTableWithPs() {
|
|
|
5687
6536
|
reject(new Error("ps: returned no process records"));
|
|
5688
6537
|
return;
|
|
5689
6538
|
}
|
|
5690
|
-
|
|
6539
|
+
resolve7(rows);
|
|
5691
6540
|
});
|
|
5692
6541
|
});
|
|
5693
6542
|
});
|
|
@@ -5697,10 +6546,10 @@ async function readLinuxProcProcessTable() {
|
|
|
5697
6546
|
const rows = await Promise.all(
|
|
5698
6547
|
entries.filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name)).map(async (entry) => {
|
|
5699
6548
|
try {
|
|
5700
|
-
const
|
|
5701
|
-
const commandEnd =
|
|
6549
|
+
const stat14 = await readFile5(`/proc/${entry.name}/stat`, "utf8");
|
|
6550
|
+
const commandEnd = stat14.lastIndexOf(")");
|
|
5702
6551
|
if (commandEnd < 0) return void 0;
|
|
5703
|
-
const fields =
|
|
6552
|
+
const fields = stat14.slice(commandEnd + 1).trim().split(/\s+/);
|
|
5704
6553
|
const pid = Number(entry.name);
|
|
5705
6554
|
const parentPid = Number(fields[1]);
|
|
5706
6555
|
if (!Number.isSafeInteger(pid) || pid < 1 || !Number.isSafeInteger(parentPid) || parentPid < 0) {
|
|
@@ -5822,7 +6671,7 @@ function runCommand(command, args = [], opts = {}) {
|
|
|
5822
6671
|
throw new RangeError("maxBuffer must be a positive safe integer");
|
|
5823
6672
|
}
|
|
5824
6673
|
if (opts.signal?.aborted) return Promise.reject(abortError(opts.signal));
|
|
5825
|
-
return new Promise((
|
|
6674
|
+
return new Promise((resolve7, reject) => {
|
|
5826
6675
|
let child;
|
|
5827
6676
|
try {
|
|
5828
6677
|
child = spawn(command, [...args], { detached: process.platform !== "win32" });
|
|
@@ -5854,7 +6703,7 @@ function runCommand(command, args = [], opts = {}) {
|
|
|
5854
6703
|
} else if (spawnError) {
|
|
5855
6704
|
reject(spawnError);
|
|
5856
6705
|
} else {
|
|
5857
|
-
|
|
6706
|
+
resolve7({ stdout: out, stderr: err, code: closeCode });
|
|
5858
6707
|
}
|
|
5859
6708
|
};
|
|
5860
6709
|
function requestTermination(error) {
|
|
@@ -6083,22 +6932,22 @@ function commandRedactor(spec) {
|
|
|
6083
6932
|
}
|
|
6084
6933
|
|
|
6085
6934
|
// src/hodoscope.ts
|
|
6086
|
-
import { createHash as
|
|
6087
|
-
import { mkdir, mkdtemp as
|
|
6935
|
+
import { createHash as createHash7 } from "crypto";
|
|
6936
|
+
import { mkdir as mkdir3, mkdtemp as mkdtemp3, readFile as readFile6, readdir as readdir14, writeFile as writeFile3 } from "fs/promises";
|
|
6088
6937
|
import { endianness, tmpdir as tmpdir2 } from "os";
|
|
6089
|
-
import { join as
|
|
6938
|
+
import { join as join17, resolve as resolve5 } from "path";
|
|
6090
6939
|
var HODOSCOPE_VERSION = "0.2.4";
|
|
6091
6940
|
async function writeHodoscopeInput(otlpPath, options = {}) {
|
|
6092
|
-
const explicitDirectory = options.directory ?
|
|
6093
|
-
const directory = explicitDirectory ?? await
|
|
6941
|
+
const explicitDirectory = options.directory ? resolve5(options.directory) : void 0;
|
|
6942
|
+
const directory = explicitDirectory ?? await mkdtemp3(join17(tmpdir2(), "traces-hodoscope-input-"));
|
|
6094
6943
|
if (explicitDirectory) {
|
|
6095
|
-
await
|
|
6944
|
+
await mkdir3(directory, { recursive: true });
|
|
6096
6945
|
if ((await readdir14(directory)).length > 0) {
|
|
6097
6946
|
throw new Error(`Hodoscope explicit directory must be empty: ${directory}`);
|
|
6098
6947
|
}
|
|
6099
6948
|
}
|
|
6100
|
-
const samples =
|
|
6101
|
-
await
|
|
6949
|
+
const samples = join17(directory, "samples");
|
|
6950
|
+
await mkdir3(samples);
|
|
6102
6951
|
const rows = [];
|
|
6103
6952
|
for await (const value of readJsonl(otlpPath)) rows.push(parseOpenInferenceRow(value));
|
|
6104
6953
|
if (rows.length === 0) throw new Error("Hodoscope input contains no spans");
|
|
@@ -6122,8 +6971,8 @@ async function writeHodoscopeInput(otlpPath, options = {}) {
|
|
|
6122
6971
|
throw new Error(`Hodoscope trace '${traceId}' contains no assistant actions`);
|
|
6123
6972
|
}
|
|
6124
6973
|
actionCount += actions;
|
|
6125
|
-
const file = `${
|
|
6126
|
-
await
|
|
6974
|
+
const file = `${createHash7("sha256").update(traceId).digest("hex").slice(0, 24)}.json`;
|
|
6975
|
+
await writeFile3(join17(samples, file), `${JSON.stringify(trajectory, null, 2)}
|
|
6127
6976
|
`, "utf8");
|
|
6128
6977
|
}
|
|
6129
6978
|
return {
|
|
@@ -6145,12 +6994,12 @@ function hodoscopeAnalyzer(options = {}) {
|
|
|
6145
6994
|
return {
|
|
6146
6995
|
name: "hodoscope",
|
|
6147
6996
|
async analyze(otlpPath, runOptions2 = {}) {
|
|
6148
|
-
const outputRoot = options.outputDir ?
|
|
6149
|
-
await
|
|
6150
|
-
const directory = await
|
|
6151
|
-
const input =
|
|
6152
|
-
const analysisFile =
|
|
6153
|
-
const samplesFile =
|
|
6997
|
+
const outputRoot = options.outputDir ? resolve5(options.outputDir) : tmpdir2();
|
|
6998
|
+
await mkdir3(outputRoot, { recursive: true });
|
|
6999
|
+
const directory = await mkdtemp3(join17(outputRoot, "traces-hodoscope-"));
|
|
7000
|
+
const input = join17(directory, "input");
|
|
7001
|
+
const analysisFile = join17(directory, "analysis.hodoscope.json");
|
|
7002
|
+
const samplesFile = join17(directory, "samples.json");
|
|
6154
7003
|
try {
|
|
6155
7004
|
const prepared = await writeHodoscopeInput(otlpPath, {
|
|
6156
7005
|
directory: input,
|
|
@@ -6341,20 +7190,20 @@ function sourceMapEntry(traceId, spanId, turnId) {
|
|
|
6341
7190
|
}
|
|
6342
7191
|
async function readHodoscopeAnalysis(path, expected) {
|
|
6343
7192
|
const value = await readJson(path);
|
|
6344
|
-
if (!
|
|
7193
|
+
if (!isObject3(value) || value.version !== 1 || !Array.isArray(value.summaries)) {
|
|
6345
7194
|
throw new Error("Hodoscope analysis has an unsupported shape");
|
|
6346
7195
|
}
|
|
6347
|
-
const createdAt =
|
|
7196
|
+
const createdAt = requiredString2(value.created_at, "analysis.created_at");
|
|
6348
7197
|
if (!Number.isFinite(Date.parse(createdAt))) {
|
|
6349
7198
|
throw new Error("Hodoscope analysis.created_at must be a valid date-time");
|
|
6350
7199
|
}
|
|
6351
|
-
const source =
|
|
6352
|
-
if (
|
|
7200
|
+
const source = requiredString2(value.source, "analysis.source");
|
|
7201
|
+
if (resolve5(source) !== resolve5(expected.source)) {
|
|
6353
7202
|
throw new Error(
|
|
6354
7203
|
`Hodoscope analysis.source '${source}' does not match requested input '${expected.source}'`
|
|
6355
7204
|
);
|
|
6356
7205
|
}
|
|
6357
|
-
const embeddingModel =
|
|
7206
|
+
const embeddingModel = requiredString2(value.embedding_model, "analysis.embedding_model");
|
|
6358
7207
|
if (embeddingModel !== expected.embeddingModel) {
|
|
6359
7208
|
throw new Error(
|
|
6360
7209
|
`Hodoscope analysis.embedding_model '${embeddingModel}' does not match requested model '${expected.embeddingModel}'`
|
|
@@ -6411,15 +7260,15 @@ async function readHodoscopeAnalysis(path, expected) {
|
|
|
6411
7260
|
}
|
|
6412
7261
|
function parseHodoscopeSummary(value, index, embeddingDimensions) {
|
|
6413
7262
|
const path = `analysis.summaries[${index}]`;
|
|
6414
|
-
if (!
|
|
6415
|
-
const trajectoryId2 =
|
|
7263
|
+
if (!isObject3(value)) throw new Error(`Hodoscope ${path} must be an object`);
|
|
7264
|
+
const trajectoryId2 = requiredString2(value.trajectory_id, `${path}.trajectory_id`);
|
|
6416
7265
|
const turnId = nonNegativeSafeInteger(value.turn_id, `${path}.turn_id`);
|
|
6417
|
-
const summary =
|
|
7266
|
+
const summary = requiredString2(value.summary, `${path}.summary`);
|
|
6418
7267
|
if (summary.trimStart().toLowerCase().startsWith("[error:")) {
|
|
6419
7268
|
throw new Error(`Hodoscope failed to summarize ${formatAction(trajectoryId2, turnId)}`);
|
|
6420
7269
|
}
|
|
6421
|
-
const actionText =
|
|
6422
|
-
const embedding =
|
|
7270
|
+
const actionText = requiredString2(value.action_text, `${path}.action_text`);
|
|
7271
|
+
const embedding = requiredString2(value.embedding, `${path}.embedding`);
|
|
6423
7272
|
validateEmbedding(embedding, embeddingDimensions, `${path}.embedding`);
|
|
6424
7273
|
const taskContext = value.task_context;
|
|
6425
7274
|
if (typeof taskContext !== "string") {
|
|
@@ -6436,16 +7285,16 @@ function parseHodoscopeSummary(value, index, embeddingDimensions) {
|
|
|
6436
7285
|
};
|
|
6437
7286
|
}
|
|
6438
7287
|
function parseHodoscopeSamples(value, expected) {
|
|
6439
|
-
if (!
|
|
7288
|
+
if (!isObject3(value) || !isObject3(value.groups)) {
|
|
6440
7289
|
throw new Error("Hodoscope samples have an unsupported shape");
|
|
6441
7290
|
}
|
|
6442
|
-
const groupBy =
|
|
7291
|
+
const groupBy = requiredString2(value.group_by, "samples.group_by");
|
|
6443
7292
|
if (groupBy !== expected.groupBy) {
|
|
6444
7293
|
throw new Error(
|
|
6445
7294
|
`Hodoscope samples.group_by '${groupBy}' does not match requested '${expected.groupBy}'`
|
|
6446
7295
|
);
|
|
6447
7296
|
}
|
|
6448
|
-
const method =
|
|
7297
|
+
const method = requiredString2(value.method, "samples.method");
|
|
6449
7298
|
if (method !== expected.method) {
|
|
6450
7299
|
throw new Error(
|
|
6451
7300
|
`Hodoscope samples.method '${method}' does not match requested '${expected.method}'`
|
|
@@ -6465,7 +7314,7 @@ function parseHodoscopeSamples(value, expected) {
|
|
|
6465
7314
|
);
|
|
6466
7315
|
const expectedGroupTotals = /* @__PURE__ */ new Map();
|
|
6467
7316
|
for (const [index, summary] of expected.summaries.entries()) {
|
|
6468
|
-
const group =
|
|
7317
|
+
const group = requiredString2(
|
|
6469
7318
|
summary.metadata[groupBy],
|
|
6470
7319
|
`analysis.summaries[${index}].metadata.${groupBy}`
|
|
6471
7320
|
);
|
|
@@ -6477,7 +7326,7 @@ function parseHodoscopeSamples(value, expected) {
|
|
|
6477
7326
|
let reportedSamples = 0;
|
|
6478
7327
|
for (const [group, groupValue] of Object.entries(value.groups)) {
|
|
6479
7328
|
if (!group.trim()) throw new Error("Hodoscope sample group names must be non-empty");
|
|
6480
|
-
if (!
|
|
7329
|
+
if (!isObject3(groupValue) || !Array.isArray(groupValue.samples)) {
|
|
6481
7330
|
throw new Error(`Hodoscope sample group '${group}' has an unsupported shape`);
|
|
6482
7331
|
}
|
|
6483
7332
|
const total = nonNegativeSafeInteger(groupValue.total, `samples.groups.${group}.total`);
|
|
@@ -6498,12 +7347,12 @@ function parseHodoscopeSamples(value, expected) {
|
|
|
6498
7347
|
}
|
|
6499
7348
|
const parsedSamples = groupValue.samples.map((row, index) => {
|
|
6500
7349
|
const path = `samples.groups.${group}.samples[${index}]`;
|
|
6501
|
-
if (!
|
|
7350
|
+
if (!isObject3(row)) throw new Error(`Hodoscope ${path} must be an object`);
|
|
6502
7351
|
const rank = nonNegativeSafeInteger(row.rank, `${path}.rank`);
|
|
6503
7352
|
if (rank !== index) {
|
|
6504
7353
|
throw new Error(`Hodoscope ${path}.rank must be ${index}, received ${rank}`);
|
|
6505
7354
|
}
|
|
6506
|
-
const trajectoryId2 =
|
|
7355
|
+
const trajectoryId2 = requiredString2(row.trajectory_id, `${path}.trajectory_id`);
|
|
6507
7356
|
const turnId = nonNegativeSafeInteger(row.turn_id, `${path}.turn_id`);
|
|
6508
7357
|
const key = actionKey(trajectoryId2, turnId);
|
|
6509
7358
|
if (!expected.sourceByAction.has(key)) {
|
|
@@ -6522,8 +7371,8 @@ function parseHodoscopeSamples(value, expected) {
|
|
|
6522
7371
|
`Hodoscope ${path} has no matching analysis row for ${formatAction(trajectoryId2, turnId)}`
|
|
6523
7372
|
);
|
|
6524
7373
|
}
|
|
6525
|
-
const summary =
|
|
6526
|
-
const actionText =
|
|
7374
|
+
const summary = requiredString2(row.summary, `${path}.summary`);
|
|
7375
|
+
const actionText = requiredString2(row.action_text, `${path}.action_text`);
|
|
6527
7376
|
const metadata = jsonRecord(row.metadata, `${path}.metadata`);
|
|
6528
7377
|
if (summary !== sourceSummary.summary || actionText !== sourceSummary.action_text) {
|
|
6529
7378
|
throw new Error(
|
|
@@ -6577,7 +7426,7 @@ function parseHodoscopeSamples(value, expected) {
|
|
|
6577
7426
|
function candidatesFromSamples(samples, sourceByAction) {
|
|
6578
7427
|
const candidates = [];
|
|
6579
7428
|
for (const [group, value] of Object.entries(samples.groups)) {
|
|
6580
|
-
if (!
|
|
7429
|
+
if (!isObject3(value) || !Array.isArray(value.samples) || !Number.isInteger(value.total)) {
|
|
6581
7430
|
throw new Error(`Hodoscope sample group '${group}' has an unsupported shape`);
|
|
6582
7431
|
}
|
|
6583
7432
|
for (const row of value.samples) {
|
|
@@ -6663,7 +7512,7 @@ function validateEmbedding(encoded, dimensions, path) {
|
|
|
6663
7512
|
}
|
|
6664
7513
|
}
|
|
6665
7514
|
}
|
|
6666
|
-
function
|
|
7515
|
+
function requiredString2(value, path) {
|
|
6667
7516
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
6668
7517
|
throw new Error(`Hodoscope ${path} must be a non-empty string`);
|
|
6669
7518
|
}
|
|
@@ -6676,7 +7525,7 @@ function nonNegativeSafeInteger(value, path) {
|
|
|
6676
7525
|
return value;
|
|
6677
7526
|
}
|
|
6678
7527
|
function jsonRecord(value, path) {
|
|
6679
|
-
if (!
|
|
7528
|
+
if (!isObject3(value)) throw new Error(`Hodoscope ${path} must be a JSON object`);
|
|
6680
7529
|
assertJsonValue(value, path);
|
|
6681
7530
|
return value;
|
|
6682
7531
|
}
|
|
@@ -6691,7 +7540,7 @@ function assertJsonValue(value, path, depth = 0) {
|
|
|
6691
7540
|
}
|
|
6692
7541
|
return;
|
|
6693
7542
|
}
|
|
6694
|
-
if (
|
|
7543
|
+
if (isObject3(value)) {
|
|
6695
7544
|
for (const [key, child] of Object.entries(value)) {
|
|
6696
7545
|
assertJsonValue(child, `${path}.${key}`, depth + 1);
|
|
6697
7546
|
}
|
|
@@ -6705,8 +7554,8 @@ function jsonEquals(left, right) {
|
|
|
6705
7554
|
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
|
6706
7555
|
return left.every((value, index) => jsonEquals(value, right[index]));
|
|
6707
7556
|
}
|
|
6708
|
-
if (
|
|
6709
|
-
if (!
|
|
7557
|
+
if (isObject3(left) || isObject3(right)) {
|
|
7558
|
+
if (!isObject3(left) || !isObject3(right)) return false;
|
|
6710
7559
|
const leftKeys = Object.keys(left).sort();
|
|
6711
7560
|
const rightKeys = Object.keys(right).sort();
|
|
6712
7561
|
return leftKeys.length === rightKeys.length && leftKeys.every(
|
|
@@ -6716,10 +7565,10 @@ function jsonEquals(left, right) {
|
|
|
6716
7565
|
return false;
|
|
6717
7566
|
}
|
|
6718
7567
|
async function readJson(path) {
|
|
6719
|
-
return JSON.parse(await
|
|
7568
|
+
return JSON.parse(await readFile6(path, "utf8"));
|
|
6720
7569
|
}
|
|
6721
7570
|
function parseOpenInferenceRow(value) {
|
|
6722
|
-
if (!
|
|
7571
|
+
if (!isObject3(value) || typeof value.trace_id !== "string" || !value.trace_id || typeof value.span_id !== "string" || !value.span_id || typeof value.name !== "string" || typeof value.start_time !== "string" || !isObject3(value.status) || !isObject3(value.attributes) || value.parent_span_id !== void 0 && value.parent_span_id !== null && typeof value.parent_span_id !== "string") {
|
|
6723
7572
|
throw new TypeError("Hodoscope input must contain complete OpenInference spans");
|
|
6724
7573
|
}
|
|
6725
7574
|
return value;
|
|
@@ -6752,7 +7601,7 @@ function toJson(value) {
|
|
|
6752
7601
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
6753
7602
|
if (typeof value === "number") return Number.isFinite(value) ? value : String(value);
|
|
6754
7603
|
if (Array.isArray(value)) return value.map(toJson);
|
|
6755
|
-
if (
|
|
7604
|
+
if (isObject3(value)) {
|
|
6756
7605
|
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJson(child)]));
|
|
6757
7606
|
}
|
|
6758
7607
|
return String(value ?? "");
|
|
@@ -6760,7 +7609,7 @@ function toJson(value) {
|
|
|
6760
7609
|
function stringifyCompact(value) {
|
|
6761
7610
|
return typeof value === "string" ? value : JSON.stringify(value);
|
|
6762
7611
|
}
|
|
6763
|
-
function
|
|
7612
|
+
function isObject3(value) {
|
|
6764
7613
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6765
7614
|
}
|
|
6766
7615
|
function assertPositiveInteger(name, value) {
|
|
@@ -6769,159 +7618,6 @@ function assertPositiveInteger(name, value) {
|
|
|
6769
7618
|
}
|
|
6770
7619
|
}
|
|
6771
7620
|
|
|
6772
|
-
// src/chat-trajectory.ts
|
|
6773
|
-
import { createHash as createHash6 } from "crypto";
|
|
6774
|
-
function isChatTrajectoryMessage(value) {
|
|
6775
|
-
return isObject2(value) && typeof value.role === "string" && typeof value.content === "string";
|
|
6776
|
-
}
|
|
6777
|
-
function isChatTrajectory(value) {
|
|
6778
|
-
return isObject2(value) && Array.isArray(value.messages) && value.messages.length > 0 && value.messages.every(isChatTrajectoryMessage);
|
|
6779
|
-
}
|
|
6780
|
-
function chatTrajectoryToSpans(input, options = {}) {
|
|
6781
|
-
const trajectory = Array.isArray(input) ? { messages: input } : input;
|
|
6782
|
-
if (trajectory.messages.length === 0) throw new Error("chat trajectory contains no messages");
|
|
6783
|
-
if (!trajectory.messages.every(isChatTrajectoryMessage)) {
|
|
6784
|
-
throw new TypeError("chat trajectory messages require string role and content fields");
|
|
6785
|
-
}
|
|
6786
|
-
const traceId = options.traceId ?? trajectoryId(trajectory);
|
|
6787
|
-
const service = options.service ?? trajectory.trajectory_format ?? "chat-trajectory";
|
|
6788
|
-
const stepMode = options.stepMode ?? "assistant";
|
|
6789
|
-
const times = messageTimes(trajectory.messages);
|
|
6790
|
-
const actionCount = trajectory.messages.filter((message) => messageKind(message.role) === "LLM").length;
|
|
6791
|
-
const root = span({
|
|
6792
|
-
traceId,
|
|
6793
|
-
spanId: "root",
|
|
6794
|
-
name: "trajectory",
|
|
6795
|
-
kind: "AGENT",
|
|
6796
|
-
startTime: times[0],
|
|
6797
|
-
endTime: times.at(-1),
|
|
6798
|
-
service,
|
|
6799
|
-
costUsd: finiteNumber(trajectory.info?.model_stats, "instance_cost"),
|
|
6800
|
-
extra: compactAttributes({
|
|
6801
|
-
"traces.source_format": "chat-trajectory",
|
|
6802
|
-
"trajectory.format": trajectory.trajectory_format,
|
|
6803
|
-
"trajectory.message_count": trajectory.messages.length,
|
|
6804
|
-
"trajectory.action_count": actionCount,
|
|
6805
|
-
"trajectory.source_path": options.sourcePath,
|
|
6806
|
-
"trajectory.api_calls": finiteNumber(trajectory.info?.model_stats, "api_calls"),
|
|
6807
|
-
"trajectory.timestamps_synthetic": trajectory.messages.some(
|
|
6808
|
-
(message) => message.timestamp === void 0
|
|
6809
|
-
)
|
|
6810
|
-
})
|
|
6811
|
-
});
|
|
6812
|
-
const spans = [root];
|
|
6813
|
-
let assistantStep = 0;
|
|
6814
|
-
for (const [index, message] of trajectory.messages.entries()) {
|
|
6815
|
-
const kind = messageKind(message.role);
|
|
6816
|
-
if (kind === "LLM") assistantStep += 1;
|
|
6817
|
-
const step = stepMode === "message" ? index + 1 : kind === "LLM" ? assistantStep : void 0;
|
|
6818
|
-
const spanId = step === void 0 ? `message-${index + 1}` : `step-${step}`;
|
|
6819
|
-
const response = isObject2(message.extra?.response) ? message.extra.response : void 0;
|
|
6820
|
-
const usage = isObject2(response?.usage) ? response.usage : void 0;
|
|
6821
|
-
const completionDetails = isObject2(usage?.completion_tokens_details) ? usage.completion_tokens_details : void 0;
|
|
6822
|
-
const promptDetails = isObject2(usage?.prompt_tokens_details) ? usage.prompt_tokens_details : void 0;
|
|
6823
|
-
spans.push(
|
|
6824
|
-
span({
|
|
6825
|
-
traceId,
|
|
6826
|
-
spanId,
|
|
6827
|
-
parentSpanId: "root",
|
|
6828
|
-
name: messageName(kind, message),
|
|
6829
|
-
kind,
|
|
6830
|
-
startTime: times[index],
|
|
6831
|
-
service,
|
|
6832
|
-
agent: messageAgent(message.role),
|
|
6833
|
-
model: stringValue2(response?.model),
|
|
6834
|
-
inputTokens: finiteNumber(usage, "prompt_tokens"),
|
|
6835
|
-
outputTokens: finiteNumber(usage, "completion_tokens"),
|
|
6836
|
-
reasoningTokens: finiteNumber(completionDetails, "reasoning_tokens"),
|
|
6837
|
-
cachedInputTokens: finiteNumber(promptDetails, "cached_tokens"),
|
|
6838
|
-
step,
|
|
6839
|
-
content: capText(message.content),
|
|
6840
|
-
extra: compactAttributes({
|
|
6841
|
-
"traces.source_format": "chat-trajectory",
|
|
6842
|
-
"trajectory.message_index": index + 1,
|
|
6843
|
-
"trajectory.action_index": kind === "LLM" ? assistantStep : void 0,
|
|
6844
|
-
"trajectory.role": message.role
|
|
6845
|
-
})
|
|
6846
|
-
})
|
|
6847
|
-
);
|
|
6848
|
-
}
|
|
6849
|
-
return validateOtlpSpans(spans, "chat trajectory output");
|
|
6850
|
-
}
|
|
6851
|
-
function trajectoryId(trajectory) {
|
|
6852
|
-
const value = trajectory.trajectory_id ?? trajectory.traj_id ?? trajectory.instance_id;
|
|
6853
|
-
if (typeof value === "string" && value.trim()) return value;
|
|
6854
|
-
if (typeof value === "number" && Number.isSafeInteger(value)) return String(value);
|
|
6855
|
-
return `trajectory-${createHash6("sha256").update(JSON.stringify(trajectory.messages)).digest("hex").slice(0, 24)}`;
|
|
6856
|
-
}
|
|
6857
|
-
function messageKind(role) {
|
|
6858
|
-
const normalized = role.trim().toLowerCase();
|
|
6859
|
-
if (normalized === "assistant" || normalized === "agent") return "LLM";
|
|
6860
|
-
if (normalized === "tool" || normalized === "function" || normalized === "observation") {
|
|
6861
|
-
return "TOOL";
|
|
6862
|
-
}
|
|
6863
|
-
return "CHAIN";
|
|
6864
|
-
}
|
|
6865
|
-
function messageName(kind, message) {
|
|
6866
|
-
if (kind === "LLM") return "message.assistant";
|
|
6867
|
-
if (kind === "TOOL") return `tool.${message.name?.trim() || "result"}`;
|
|
6868
|
-
const role = message.role.trim().toLowerCase();
|
|
6869
|
-
if (role === "system") return "system.prompt";
|
|
6870
|
-
if (role === "user" || role === "human") return "user.prompt";
|
|
6871
|
-
return "message.other";
|
|
6872
|
-
}
|
|
6873
|
-
function messageAgent(role) {
|
|
6874
|
-
const normalized = role.trim().toLowerCase();
|
|
6875
|
-
return ["system", "user", "human", "tool", "function", "observation"].includes(normalized) ? void 0 : role.trim();
|
|
6876
|
-
}
|
|
6877
|
-
function messageTimes(messages) {
|
|
6878
|
-
const supplied = messages.filter((message) => message.timestamp !== void 0).length;
|
|
6879
|
-
if (supplied === 0) return messages.map((_, index) => new Date(index).toISOString());
|
|
6880
|
-
if (supplied !== messages.length) {
|
|
6881
|
-
throw new TypeError("chat trajectory timestamps must be present on every message or none");
|
|
6882
|
-
}
|
|
6883
|
-
const times = messages.map((message, index) => messageTime(message.timestamp, index));
|
|
6884
|
-
for (let index = 1; index < times.length; index += 1) {
|
|
6885
|
-
if (Date.parse(times[index]) < Date.parse(times[index - 1])) {
|
|
6886
|
-
throw new RangeError(
|
|
6887
|
-
`chat trajectory message ${index + 1} timestamp precedes message ${index}`
|
|
6888
|
-
);
|
|
6889
|
-
}
|
|
6890
|
-
}
|
|
6891
|
-
return times;
|
|
6892
|
-
}
|
|
6893
|
-
function messageTime(value, index) {
|
|
6894
|
-
const parsed = parseTime(value);
|
|
6895
|
-
if (!parsed) {
|
|
6896
|
-
throw new TypeError(`chat trajectory message ${index + 1} has an invalid timestamp`);
|
|
6897
|
-
}
|
|
6898
|
-
return parsed;
|
|
6899
|
-
}
|
|
6900
|
-
function parseTime(value) {
|
|
6901
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
6902
|
-
const millis2 = value < 1e10 ? value * 1e3 : value;
|
|
6903
|
-
const date = new Date(millis2);
|
|
6904
|
-
return Number.isFinite(date.getTime()) ? date.toISOString() : void 0;
|
|
6905
|
-
}
|
|
6906
|
-
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
6907
|
-
const millis = Date.parse(value);
|
|
6908
|
-
return Number.isFinite(millis) ? new Date(millis).toISOString() : void 0;
|
|
6909
|
-
}
|
|
6910
|
-
function finiteNumber(value, key) {
|
|
6911
|
-
if (!isObject2(value)) return void 0;
|
|
6912
|
-
const number = value[key];
|
|
6913
|
-
return typeof number === "number" && Number.isFinite(number) ? number : void 0;
|
|
6914
|
-
}
|
|
6915
|
-
function stringValue2(value) {
|
|
6916
|
-
return typeof value === "string" && value.trim() ? value : void 0;
|
|
6917
|
-
}
|
|
6918
|
-
function compactAttributes(values) {
|
|
6919
|
-
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== void 0));
|
|
6920
|
-
}
|
|
6921
|
-
function isObject2(value) {
|
|
6922
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6923
|
-
}
|
|
6924
|
-
|
|
6925
7621
|
// src/redact.ts
|
|
6926
7622
|
import { DEFAULT_REDACTION_RULES, redactValue } from "@tangle-network/agent-eval/traces";
|
|
6927
7623
|
var CODING_REDACTION_RULES = [
|
|
@@ -7002,10 +7698,10 @@ async function applyRedactor(spans, redactor) {
|
|
|
7002
7698
|
}
|
|
7003
7699
|
|
|
7004
7700
|
// src/file-export.ts
|
|
7005
|
-
import { createHash as
|
|
7006
|
-
import { mkdtemp as
|
|
7701
|
+
import { createHash as createHash8 } from "crypto";
|
|
7702
|
+
import { mkdtemp as mkdtemp4, readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
|
|
7007
7703
|
import { tmpdir as tmpdir3 } from "os";
|
|
7008
|
-
import { join as
|
|
7704
|
+
import { join as join18 } from "path";
|
|
7009
7705
|
import {
|
|
7010
7706
|
firstNumberAttr,
|
|
7011
7707
|
LLM_CACHED_TOKEN_ATTR_KEYS,
|
|
@@ -7021,18 +7717,18 @@ import {
|
|
|
7021
7717
|
LLM_REASONING_TOKEN_ATTR_KEYS,
|
|
7022
7718
|
LLM_REASONING_TOKENS
|
|
7023
7719
|
} from "@tangle-network/agent-eval/trace-attributes";
|
|
7024
|
-
function
|
|
7720
|
+
function isObject4(value) {
|
|
7025
7721
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
7026
7722
|
}
|
|
7027
7723
|
function stringValue3(value) {
|
|
7028
7724
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
7029
7725
|
}
|
|
7030
7726
|
function objectValue2(value) {
|
|
7031
|
-
return
|
|
7727
|
+
return isObject4(value) ? value : void 0;
|
|
7032
7728
|
}
|
|
7033
7729
|
function stableJson(value) {
|
|
7034
7730
|
return JSON.stringify(value, (_key, v) => {
|
|
7035
|
-
if (!
|
|
7731
|
+
if (!isObject4(v)) return v;
|
|
7036
7732
|
return Object.keys(v).sort().reduce((acc, key) => {
|
|
7037
7733
|
acc[key] = v[key];
|
|
7038
7734
|
return acc;
|
|
@@ -7040,7 +7736,7 @@ function stableJson(value) {
|
|
|
7040
7736
|
});
|
|
7041
7737
|
}
|
|
7042
7738
|
function hashId(value, chars = 16) {
|
|
7043
|
-
return
|
|
7739
|
+
return createHash8("sha256").update(stableJson(value)).digest("hex").slice(0, chars);
|
|
7044
7740
|
}
|
|
7045
7741
|
function isoTime2(value) {
|
|
7046
7742
|
if (typeof value === "string") {
|
|
@@ -7064,7 +7760,7 @@ function firstTime(obj, keys) {
|
|
|
7064
7760
|
}
|
|
7065
7761
|
function firstTimeDeep(value, keys, depth = 5) {
|
|
7066
7762
|
if (depth < 0) return void 0;
|
|
7067
|
-
if (
|
|
7763
|
+
if (isObject4(value)) {
|
|
7068
7764
|
const direct = firstTime(value, keys);
|
|
7069
7765
|
if (direct) return direct;
|
|
7070
7766
|
for (const child of Object.values(value)) {
|
|
@@ -7091,7 +7787,7 @@ function parseJsonRows(text2) {
|
|
|
7091
7787
|
try {
|
|
7092
7788
|
const parsed = JSON.parse(trimmed);
|
|
7093
7789
|
if (Array.isArray(parsed)) return { rows: parsed };
|
|
7094
|
-
if (
|
|
7790
|
+
if (isObject4(parsed)) {
|
|
7095
7791
|
const events = parsed.events;
|
|
7096
7792
|
if (Array.isArray(events)) return { rows: events, wrapper: parsed };
|
|
7097
7793
|
return { rows: [parsed] };
|
|
@@ -7107,21 +7803,21 @@ function parseJsonRows(text2) {
|
|
|
7107
7803
|
};
|
|
7108
7804
|
}
|
|
7109
7805
|
function isPolicyEvidenceRow(row) {
|
|
7110
|
-
return
|
|
7806
|
+
return isObject4(row) && row.kind === "traces.policy_evidence.session";
|
|
7111
7807
|
}
|
|
7112
7808
|
function isOpenInferenceRow(row) {
|
|
7113
|
-
if (!
|
|
7114
|
-
return typeof row.trace_id === "string" && typeof row.span_id === "string" && (row.parent_span_id === null || typeof row.parent_span_id === "string") && typeof row.name === "string" && typeof row.start_time === "string" && typeof row.end_time === "string" &&
|
|
7809
|
+
if (!isObject4(row)) return false;
|
|
7810
|
+
return typeof row.trace_id === "string" && typeof row.span_id === "string" && (row.parent_span_id === null || typeof row.parent_span_id === "string") && typeof row.name === "string" && typeof row.start_time === "string" && typeof row.end_time === "string" && isObject4(row.status) && isObject4(row.attributes) && isObject4(row.resource) && isObject4(row.scope);
|
|
7115
7811
|
}
|
|
7116
7812
|
function isIntelligenceSpanRow(row) {
|
|
7117
|
-
if (!
|
|
7118
|
-
return typeof row.trace_id === "string" && typeof row.name === "string" &&
|
|
7813
|
+
if (!isObject4(row)) return false;
|
|
7814
|
+
return typeof row.trace_id === "string" && typeof row.name === "string" && isObject4(row.attributes) && (row.start_unix_nano != null || row.start_time != null || row.received_at != null);
|
|
7119
7815
|
}
|
|
7120
7816
|
function eventType(row) {
|
|
7121
7817
|
return stringValue3(row.type) ?? stringValue3(row.event) ?? stringValue3(row.name) ?? stringValue3(row.kind);
|
|
7122
7818
|
}
|
|
7123
7819
|
function looksLikeSandboxEvent(row) {
|
|
7124
|
-
if (!
|
|
7820
|
+
if (!isObject4(row)) return false;
|
|
7125
7821
|
const type = eventType(row)?.toLowerCase();
|
|
7126
7822
|
return type === "start" || type === "raw" || type === "result" || type === "done" || type === "error";
|
|
7127
7823
|
}
|
|
@@ -7153,7 +7849,7 @@ function requireIntelligenceSpanRows(rows) {
|
|
|
7153
7849
|
return rows;
|
|
7154
7850
|
}
|
|
7155
7851
|
function requireObjectRows(rows) {
|
|
7156
|
-
if (!rows.every(
|
|
7852
|
+
if (!rows.every(isObject4)) throw new Error("sandbox-events input must contain only JSON object events");
|
|
7157
7853
|
return rows;
|
|
7158
7854
|
}
|
|
7159
7855
|
function chatTrajectoryRowsToSpans(rows, sourcePath) {
|
|
@@ -7236,7 +7932,7 @@ function policyEvidenceToSpans(records) {
|
|
|
7236
7932
|
}
|
|
7237
7933
|
function findStringKey(value, keys, depth = 4) {
|
|
7238
7934
|
if (depth < 0) return void 0;
|
|
7239
|
-
if (
|
|
7935
|
+
if (isObject4(value)) {
|
|
7240
7936
|
for (const key of keys) {
|
|
7241
7937
|
const found = stringValue3(value[key]);
|
|
7242
7938
|
if (found) return found;
|
|
@@ -7289,9 +7985,9 @@ function eventEndTimestamp(row, fallback) {
|
|
|
7289
7985
|
}
|
|
7290
7986
|
function eventStatus(row, type) {
|
|
7291
7987
|
const lowered = type.toLowerCase();
|
|
7292
|
-
const
|
|
7293
|
-
if (lowered.includes("error") ||
|
|
7294
|
-
const message =
|
|
7988
|
+
const errorMessage2 = stringValue3(row.error) ?? findStringKey(row, ["error", "errorMessage", "error_message"], 3);
|
|
7989
|
+
if (lowered.includes("error") || errorMessage2) {
|
|
7990
|
+
const message = errorMessage2 ?? stringValue3(row.message);
|
|
7295
7991
|
return { status: "ERROR", ...message ? { message: capText(message) } : {} };
|
|
7296
7992
|
}
|
|
7297
7993
|
return { status: "OK" };
|
|
@@ -7524,7 +8220,7 @@ function intelligenceSpansToSpans(rows) {
|
|
|
7524
8220
|
}
|
|
7525
8221
|
function openInferenceToSpans(rows) {
|
|
7526
8222
|
return rows.map((row) => {
|
|
7527
|
-
const attrs =
|
|
8223
|
+
const attrs = isObject4(row.attributes) ? { ...row.attributes } : {};
|
|
7528
8224
|
const resource = objectValue2(row.resource);
|
|
7529
8225
|
const resourceAttrs = objectValue2(resource?.attributes);
|
|
7530
8226
|
if (resourceAttrs) copyDefined(attrs, resourceAttrs);
|
|
@@ -7637,20 +8333,20 @@ async function exportTraceEvidenceFile(inputPath, opts = {}) {
|
|
|
7637
8333
|
redactionsByRule
|
|
7638
8334
|
};
|
|
7639
8335
|
}
|
|
7640
|
-
const text2 = await
|
|
8336
|
+
const text2 = await readFile7(inputPath, "utf8");
|
|
7641
8337
|
return exportTraceEvidenceText(text2, { ...opts, sourcePath: inputPath });
|
|
7642
8338
|
}
|
|
7643
8339
|
async function writeTraceEvidenceExportFile(inputPath, outPath, opts = {}) {
|
|
7644
8340
|
const result = await exportTraceEvidenceFile(inputPath, opts);
|
|
7645
|
-
const path = outPath ??
|
|
7646
|
-
await
|
|
8341
|
+
const path = outPath ?? join18(await mkdtemp4(join18(tmpdir3(), "traces-export-")), "spans.openinference.jsonl");
|
|
8342
|
+
await writeFile4(path, serializeSpans(result.spans), "utf8");
|
|
7647
8343
|
return { ...result, path };
|
|
7648
8344
|
}
|
|
7649
8345
|
|
|
7650
8346
|
// src/inspect.ts
|
|
7651
|
-
import { mkdtemp as
|
|
8347
|
+
import { mkdtemp as mkdtemp5, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
|
|
7652
8348
|
import { tmpdir as tmpdir4 } from "os";
|
|
7653
|
-
import { basename as
|
|
8349
|
+
import { basename as basename12, join as join19 } from "path";
|
|
7654
8350
|
var severityRank = {
|
|
7655
8351
|
high: 0,
|
|
7656
8352
|
medium: 1,
|
|
@@ -7833,7 +8529,7 @@ function addContextFindings(index, findings) {
|
|
|
7833
8529
|
refs: invalidJsonlFiles.slice(0, 5).map((file) => file.path)
|
|
7834
8530
|
});
|
|
7835
8531
|
}
|
|
7836
|
-
const skillRunFiles = files.filter((file) =>
|
|
8532
|
+
const skillRunFiles = files.filter((file) => basename12(file.path) === "skill-runs.jsonl" && (file.jsonl?.rows ?? 0) > 0).map((file) => {
|
|
7837
8533
|
const rows = file.jsonl?.rows ?? 0;
|
|
7838
8534
|
const keys = file.jsonl?.keys ?? {};
|
|
7839
8535
|
const linkKeyCounts = {
|
|
@@ -7934,19 +8630,19 @@ function assertSessionIndex(value) {
|
|
|
7934
8630
|
}
|
|
7935
8631
|
}
|
|
7936
8632
|
async function readSessionIndexFile(path) {
|
|
7937
|
-
const parsed = JSON.parse(await
|
|
8633
|
+
const parsed = JSON.parse(await readFile8(path, "utf8"));
|
|
7938
8634
|
assertSessionIndex(parsed);
|
|
7939
8635
|
return parsed;
|
|
7940
8636
|
}
|
|
7941
8637
|
async function writeInspectionReportFile(report, outPath) {
|
|
7942
|
-
const path = outPath ??
|
|
7943
|
-
await
|
|
8638
|
+
const path = outPath ?? join19(await mkdtemp5(join19(tmpdir4(), "traces-inspect-")), "inspection-report.md");
|
|
8639
|
+
await writeFile5(path, renderInspectionReport(report), "utf8");
|
|
7944
8640
|
return path;
|
|
7945
8641
|
}
|
|
7946
8642
|
|
|
7947
8643
|
// src/adoption.ts
|
|
7948
|
-
import { readFile as
|
|
7949
|
-
import { join as
|
|
8644
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
8645
|
+
import { join as join20 } from "path";
|
|
7950
8646
|
function toolName2(s) {
|
|
7951
8647
|
const n = s.attributes["tool.name"];
|
|
7952
8648
|
if (typeof n === "string") return n;
|
|
@@ -8066,10 +8762,10 @@ async function readLoopRuns(cwds, sessionIds) {
|
|
|
8066
8762
|
seenCwds.add(cwd);
|
|
8067
8763
|
const firstSourceByRun = /* @__PURE__ */ new Map();
|
|
8068
8764
|
for (const artifactDir of [".agent", ".evolve"]) {
|
|
8069
|
-
const path =
|
|
8765
|
+
const path = join20(cwd, artifactDir, "skill-runs.jsonl");
|
|
8070
8766
|
let raw;
|
|
8071
8767
|
try {
|
|
8072
|
-
raw = await
|
|
8768
|
+
raw = await readFile9(path, "utf8");
|
|
8073
8769
|
} catch {
|
|
8074
8770
|
continue;
|
|
8075
8771
|
}
|
|
@@ -8881,9 +9577,9 @@ function renderAdoption(ar) {
|
|
|
8881
9577
|
}
|
|
8882
9578
|
|
|
8883
9579
|
// src/improvement.ts
|
|
8884
|
-
import { access, mkdir as
|
|
9580
|
+
import { access, mkdir as mkdir4, mkdtemp as mkdtemp6, writeFile as writeFile6 } from "fs/promises";
|
|
8885
9581
|
import { tmpdir as tmpdir5 } from "os";
|
|
8886
|
-
import { dirname as
|
|
9582
|
+
import { dirname as dirname6, join as join21, resolve as resolve6 } from "path";
|
|
8887
9583
|
import { pathToFileURL } from "url";
|
|
8888
9584
|
import {
|
|
8889
9585
|
AnalystRegistry,
|
|
@@ -9227,7 +9923,7 @@ function registryFromConfig(config) {
|
|
|
9227
9923
|
async function existingConfigPath(path) {
|
|
9228
9924
|
const candidates = path ? [path] : ["traces.config.mjs", "traces.config.js", "traces.config.cjs"];
|
|
9229
9925
|
for (const candidate of candidates) {
|
|
9230
|
-
const resolved =
|
|
9926
|
+
const resolved = resolve6(candidate);
|
|
9231
9927
|
try {
|
|
9232
9928
|
await access(resolved);
|
|
9233
9929
|
return resolved;
|
|
@@ -9353,29 +10049,29 @@ async function runTraceStoreInvestigation(opts) {
|
|
|
9353
10049
|
};
|
|
9354
10050
|
}
|
|
9355
10051
|
async function writeTraceImprovementArtifacts(result, outDir) {
|
|
9356
|
-
const directory = outDir ?
|
|
9357
|
-
await
|
|
10052
|
+
const directory = outDir ? resolve6(outDir) : await mkdtemp6(join21(tmpdir5(), "traces-improvement-"));
|
|
10053
|
+
await mkdir4(directory, { recursive: true });
|
|
9358
10054
|
const paths = {
|
|
9359
10055
|
directory,
|
|
9360
|
-
result:
|
|
9361
|
-
evidence:
|
|
9362
|
-
report:
|
|
10056
|
+
result: join21(directory, "result.json"),
|
|
10057
|
+
evidence: join21(directory, "evidence.jsonl"),
|
|
10058
|
+
report: join21(directory, "report.md"),
|
|
9363
10059
|
traces: result.otlpPath
|
|
9364
10060
|
};
|
|
9365
10061
|
const { report, ...machineResult } = result;
|
|
9366
10062
|
await Promise.all([
|
|
9367
|
-
|
|
9368
|
-
|
|
9369
|
-
|
|
10063
|
+
writeFile6(paths.result, json(machineResult), "utf8"),
|
|
10064
|
+
writeFile6(paths.evidence, jsonl(buildEvidenceRows(result.findings)), "utf8"),
|
|
10065
|
+
writeFile6(paths.report, report, "utf8")
|
|
9370
10066
|
]);
|
|
9371
10067
|
return paths;
|
|
9372
10068
|
}
|
|
9373
10069
|
async function runTraceImprovement(opts) {
|
|
9374
|
-
const directory = opts.outDir ?
|
|
9375
|
-
await
|
|
10070
|
+
const directory = opts.outDir ? resolve6(opts.outDir) : await mkdtemp6(join21(tmpdir5(), "traces-improvement-"));
|
|
10071
|
+
await mkdir4(directory, { recursive: true });
|
|
9376
10072
|
const investigation = await runTraceInvestigation({
|
|
9377
10073
|
...opts,
|
|
9378
|
-
otlpOutPath: opts.otlpOutPath ??
|
|
10074
|
+
otlpOutPath: opts.otlpOutPath ?? join21(directory, "traces.otlp.jsonl")
|
|
9379
10075
|
});
|
|
9380
10076
|
const result = {
|
|
9381
10077
|
...investigation,
|
|
@@ -9387,32 +10083,32 @@ async function runTraceImprovement(opts) {
|
|
|
9387
10083
|
};
|
|
9388
10084
|
}
|
|
9389
10085
|
async function saveReport(path, report) {
|
|
9390
|
-
await
|
|
9391
|
-
await
|
|
10086
|
+
await mkdir4(dirname6(resolve6(path)), { recursive: true });
|
|
10087
|
+
await writeFile6(path, report, "utf8");
|
|
9392
10088
|
}
|
|
9393
10089
|
|
|
9394
10090
|
// src/live.ts
|
|
9395
|
-
import { createHash as
|
|
10091
|
+
import { createHash as createHash9 } from "crypto";
|
|
9396
10092
|
var VERIFY_RE = /\b(test|tests|vitest|jest|pytest|go test|cargo test|tsc|typecheck|lint|biome|eslint|build|check:invariants|preflight)\b/i;
|
|
9397
10093
|
var CHANGE_RE = /\b(apply_patch|edit|write|create|delete|rename|replace|update|git apply|sed -i|perl -pi|tee\s|cat\s+>)/i;
|
|
9398
10094
|
var CLAIM_DONE_RE = /\b(done|fixed|complete|completed|implemented|merged|verified|all green|tests pass|ready|works now)\b/i;
|
|
9399
|
-
var sleep = (ms, signal) => new Promise((
|
|
10095
|
+
var sleep = (ms, signal) => new Promise((resolve7) => {
|
|
9400
10096
|
if (signal?.aborted) {
|
|
9401
|
-
|
|
10097
|
+
resolve7();
|
|
9402
10098
|
return;
|
|
9403
10099
|
}
|
|
9404
10100
|
const onAbort = () => {
|
|
9405
10101
|
clearTimeout(t);
|
|
9406
|
-
|
|
10102
|
+
resolve7();
|
|
9407
10103
|
};
|
|
9408
10104
|
const t = setTimeout(() => {
|
|
9409
10105
|
signal?.removeEventListener("abort", onAbort);
|
|
9410
|
-
|
|
10106
|
+
resolve7();
|
|
9411
10107
|
}, ms);
|
|
9412
10108
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
9413
10109
|
});
|
|
9414
10110
|
function hash(parts) {
|
|
9415
|
-
return
|
|
10111
|
+
return createHash9("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
|
|
9416
10112
|
}
|
|
9417
10113
|
function asString(value) {
|
|
9418
10114
|
if (typeof value === "string" && value.trim()) return value;
|
|
@@ -10149,11 +10845,11 @@ async function collectSessionWorkflow(adapter, seeds, options = {}) {
|
|
|
10149
10845
|
}
|
|
10150
10846
|
|
|
10151
10847
|
// src/session-selection.ts
|
|
10152
|
-
import { createHash as
|
|
10848
|
+
import { createHash as createHash10 } from "crypto";
|
|
10153
10849
|
import { createReadStream as createReadStream2 } from "fs";
|
|
10154
10850
|
async function fileSha256(path, signal) {
|
|
10155
10851
|
signal?.throwIfAborted();
|
|
10156
|
-
const hash2 =
|
|
10852
|
+
const hash2 = createHash10("sha256");
|
|
10157
10853
|
try {
|
|
10158
10854
|
for await (const chunk of createReadStream2(path, { signal })) {
|
|
10159
10855
|
signal?.throwIfAborted();
|
|
@@ -10181,8 +10877,8 @@ async function sourceSnapshot(adapter, ref, signal) {
|
|
|
10181
10877
|
signal?.throwIfAborted();
|
|
10182
10878
|
files.push({ path, sha256: await fileSha256(path, signal) });
|
|
10183
10879
|
}
|
|
10184
|
-
const
|
|
10185
|
-
return { sha256, files };
|
|
10880
|
+
const sha2562 = files.length === 1 ? files[0].sha256 : createHash10("sha256").update(JSON.stringify(files)).digest("hex");
|
|
10881
|
+
return { sha256: sha2562, files };
|
|
10186
10882
|
}
|
|
10187
10883
|
function summarizeWorkflows(workflows) {
|
|
10188
10884
|
if (workflows.length === 0) return void 0;
|
|
@@ -10276,9 +10972,9 @@ async function collectSessionSelection(groups, options = {}) {
|
|
|
10276
10972
|
}
|
|
10277
10973
|
|
|
10278
10974
|
// src/session-index.ts
|
|
10279
|
-
import { mkdtemp as
|
|
10975
|
+
import { mkdtemp as mkdtemp7, readdir as readdir15, readFile as readFile10, stat as stat13, writeFile as writeFile7 } from "fs/promises";
|
|
10280
10976
|
import { tmpdir as tmpdir6 } from "os";
|
|
10281
|
-
import { dirname as
|
|
10977
|
+
import { dirname as dirname7, join as join22 } from "path";
|
|
10282
10978
|
function indexRow(record2) {
|
|
10283
10979
|
return {
|
|
10284
10980
|
session: record2.session,
|
|
@@ -10333,7 +11029,7 @@ function buildSessionIndex(records, opts = {}) {
|
|
|
10333
11029
|
}
|
|
10334
11030
|
async function pathStat2(path) {
|
|
10335
11031
|
try {
|
|
10336
|
-
return await
|
|
11032
|
+
return await stat13(path);
|
|
10337
11033
|
} catch {
|
|
10338
11034
|
return null;
|
|
10339
11035
|
}
|
|
@@ -10349,13 +11045,13 @@ async function findContextRoot(cwd) {
|
|
|
10349
11045
|
let current = cwd;
|
|
10350
11046
|
const s = await pathStat2(current);
|
|
10351
11047
|
if (!s) return null;
|
|
10352
|
-
if (!s.isDirectory()) current =
|
|
11048
|
+
if (!s.isDirectory()) current = dirname7(current);
|
|
10353
11049
|
for (; ; ) {
|
|
10354
|
-
if (await pathStat2(
|
|
11050
|
+
if (await pathStat2(join22(current, ".git")) || await dirExists(join22(current, ".evolve")) || await fileExists(join22(current, "AGENTS.md")) || await fileExists(join22(current, "CLAUDE.md"))) {
|
|
10355
11051
|
return current;
|
|
10356
11052
|
}
|
|
10357
|
-
const parent =
|
|
10358
|
-
if (parent === current) return s.isDirectory() ? cwd :
|
|
11053
|
+
const parent = dirname7(current);
|
|
11054
|
+
if (parent === current) return s.isDirectory() ? cwd : dirname7(cwd);
|
|
10359
11055
|
current = parent;
|
|
10360
11056
|
}
|
|
10361
11057
|
}
|
|
@@ -10394,7 +11090,7 @@ async function summarizeFile(path, kind) {
|
|
|
10394
11090
|
const base = { kind, path, bytes: Number(s.size), mtimeMs: Number(s.mtimeMs) };
|
|
10395
11091
|
if (!path.endsWith(".md") && !path.endsWith(".jsonl")) return base;
|
|
10396
11092
|
try {
|
|
10397
|
-
const text2 = await
|
|
11093
|
+
const text2 = await readFile10(path, "utf8");
|
|
10398
11094
|
const lines = countLines(text2);
|
|
10399
11095
|
const jsonl2 = path.endsWith(".jsonl") ? summarizeJsonl(text2) : void 0;
|
|
10400
11096
|
return {
|
|
@@ -10419,7 +11115,7 @@ async function walkMarkdown(root, kind, maxFiles = 100) {
|
|
|
10419
11115
|
}
|
|
10420
11116
|
for (const entry of entries) {
|
|
10421
11117
|
if (out.length >= maxFiles) return;
|
|
10422
|
-
const path =
|
|
11118
|
+
const path = join22(dir, entry.name);
|
|
10423
11119
|
if (entry.isDirectory()) {
|
|
10424
11120
|
await walk(path);
|
|
10425
11121
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -10434,20 +11130,20 @@ async function walkMarkdown(root, kind, maxFiles = 100) {
|
|
|
10434
11130
|
async function collectContextRoot(root) {
|
|
10435
11131
|
const files = [];
|
|
10436
11132
|
for (const name of ["AGENTS.md", "CLAUDE.md", "GEMINI.md"]) {
|
|
10437
|
-
const summary = await summarizeFile(
|
|
11133
|
+
const summary = await summarizeFile(join22(root, name), "instruction-doc");
|
|
10438
11134
|
if (summary) files.push(summary);
|
|
10439
11135
|
}
|
|
10440
|
-
const evolve =
|
|
11136
|
+
const evolve = join22(root, ".evolve");
|
|
10441
11137
|
for (const name of ["skill-runs.jsonl", "governor.jsonl", "experiments.jsonl"]) {
|
|
10442
|
-
const summary = await summarizeFile(
|
|
11138
|
+
const summary = await summarizeFile(join22(evolve, name), "evolve-jsonl");
|
|
10443
11139
|
if (summary) files.push(summary);
|
|
10444
11140
|
}
|
|
10445
11141
|
for (const name of ["scorecard.json"]) {
|
|
10446
|
-
const summary = await summarizeFile(
|
|
11142
|
+
const summary = await summarizeFile(join22(evolve, name), "evolve-json");
|
|
10447
11143
|
if (summary) files.push(summary);
|
|
10448
11144
|
}
|
|
10449
|
-
files.push(...await walkMarkdown(
|
|
10450
|
-
files.push(...await walkMarkdown(
|
|
11145
|
+
files.push(...await walkMarkdown(join22(evolve, "reflections"), "reflection"));
|
|
11146
|
+
files.push(...await walkMarkdown(join22(evolve, "handoffs"), "handoff"));
|
|
10451
11147
|
return { root, files: files.sort((a, b) => a.path.localeCompare(b.path)) };
|
|
10452
11148
|
}
|
|
10453
11149
|
async function collectContextIndex(records) {
|
|
@@ -10493,19 +11189,19 @@ function serializeSessionIndex(index) {
|
|
|
10493
11189
|
`;
|
|
10494
11190
|
}
|
|
10495
11191
|
async function writeSessionIndexFile(index, outPath) {
|
|
10496
|
-
const path = outPath ??
|
|
10497
|
-
await
|
|
11192
|
+
const path = outPath ?? join22(await mkdtemp7(join22(tmpdir6(), "traces-index-")), "session-index.json");
|
|
11193
|
+
await writeFile7(path, serializeSessionIndex(index), "utf8");
|
|
10498
11194
|
return path;
|
|
10499
11195
|
}
|
|
10500
11196
|
|
|
10501
11197
|
// src/upload-state.ts
|
|
10502
|
-
import { mkdir as
|
|
11198
|
+
import { mkdir as mkdir5, readFile as readFile11, writeFile as writeFile8 } from "fs/promises";
|
|
10503
11199
|
import { homedir as homedir12 } from "os";
|
|
10504
|
-
import { dirname as
|
|
11200
|
+
import { dirname as dirname8, join as join23 } from "path";
|
|
10505
11201
|
import { contentHash } from "@tangle-network/agent-eval";
|
|
10506
11202
|
function stateFile() {
|
|
10507
|
-
const base = process.env.XDG_STATE_HOME ??
|
|
10508
|
-
return
|
|
11203
|
+
const base = process.env.XDG_STATE_HOME ?? join23(homedir12(), ".local", "state");
|
|
11204
|
+
return join23(base, "tangle-traces", "uploaded.json");
|
|
10509
11205
|
}
|
|
10510
11206
|
function uploadKey(harness, sessionId) {
|
|
10511
11207
|
return `${harness}:${sessionId}`;
|
|
@@ -10518,15 +11214,15 @@ function outboundHash(events, identity) {
|
|
|
10518
11214
|
}
|
|
10519
11215
|
async function loadState() {
|
|
10520
11216
|
try {
|
|
10521
|
-
return JSON.parse(await
|
|
11217
|
+
return JSON.parse(await readFile11(stateFile(), "utf8"));
|
|
10522
11218
|
} catch {
|
|
10523
11219
|
return {};
|
|
10524
11220
|
}
|
|
10525
11221
|
}
|
|
10526
11222
|
async function saveState(state) {
|
|
10527
11223
|
const f = stateFile();
|
|
10528
|
-
await
|
|
10529
|
-
await
|
|
11224
|
+
await mkdir5(dirname8(f), { recursive: true });
|
|
11225
|
+
await writeFile8(f, `${JSON.stringify(state, null, 2)}
|
|
10530
11226
|
`, "utf8");
|
|
10531
11227
|
}
|
|
10532
11228
|
function alreadyUploaded(state, harness, sessionId, hash2) {
|
|
@@ -10534,10 +11230,10 @@ function alreadyUploaded(state, harness, sessionId, hash2) {
|
|
|
10534
11230
|
}
|
|
10535
11231
|
|
|
10536
11232
|
// src/upload.ts
|
|
10537
|
-
import { readFile as
|
|
11233
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
10538
11234
|
import { createRequire } from "module";
|
|
10539
|
-
import { hostname } from "os";
|
|
10540
|
-
import { basename as
|
|
11235
|
+
import { hostname as hostname2 } from "os";
|
|
11236
|
+
import { basename as basename13, join as join24 } from "path";
|
|
10541
11237
|
import { hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
|
|
10542
11238
|
import { REDACTION_VERSION } from "@tangle-network/agent-eval/traces";
|
|
10543
11239
|
var require2 = createRequire(import.meta.url);
|
|
@@ -10561,7 +11257,7 @@ async function planUpload(opts = {}) {
|
|
|
10561
11257
|
async function gitBranch(cwd) {
|
|
10562
11258
|
if (!cwd) return void 0;
|
|
10563
11259
|
try {
|
|
10564
|
-
const head = await
|
|
11260
|
+
const head = await readFile12(join24(cwd, ".git", "HEAD"), "utf8");
|
|
10565
11261
|
const m = head.match(/^ref:\s*refs\/heads\/(.+)$/m);
|
|
10566
11262
|
return m?.[1]?.trim();
|
|
10567
11263
|
} catch {
|
|
@@ -10571,8 +11267,8 @@ async function gitBranch(cwd) {
|
|
|
10571
11267
|
async function sessionMeta(item) {
|
|
10572
11268
|
const meta = {
|
|
10573
11269
|
[ATTR.HARNESS]: item.ref.harness,
|
|
10574
|
-
[ATTR.SESSION_FILE]:
|
|
10575
|
-
[ATTR.HOST]:
|
|
11270
|
+
[ATTR.SESSION_FILE]: basename13(item.ref.path),
|
|
11271
|
+
[ATTR.HOST]: hostname2(),
|
|
10576
11272
|
[ATTR.UPLOADER]: `tangle-traces@${TRACES_VERSION}`,
|
|
10577
11273
|
[ATTR.REDACTION_VERSION]: REDACTION_VERSION,
|
|
10578
11274
|
[ATTR.REDACTION_COUNT]: item.redaction.redactionCount
|
|
@@ -10745,6 +11441,10 @@ export {
|
|
|
10745
11441
|
sessionIdFromAttributes,
|
|
10746
11442
|
indexSessionIdsByTrace,
|
|
10747
11443
|
stampSessionIdentity,
|
|
11444
|
+
isChatTrajectoryMessage,
|
|
11445
|
+
isChatTrajectory,
|
|
11446
|
+
chatTrajectoryToSpans,
|
|
11447
|
+
importCodeTraceBench,
|
|
10748
11448
|
summarizeOtlpExecution,
|
|
10749
11449
|
summarizeSpanExecution,
|
|
10750
11450
|
toRuntimeStore,
|
|
@@ -10804,9 +11504,6 @@ export {
|
|
|
10804
11504
|
commandRedactor,
|
|
10805
11505
|
writeHodoscopeInput,
|
|
10806
11506
|
hodoscopeAnalyzer,
|
|
10807
|
-
isChatTrajectoryMessage,
|
|
10808
|
-
isChatTrajectory,
|
|
10809
|
-
chatTrajectoryToSpans,
|
|
10810
11507
|
CODING_REDACTION_RULES,
|
|
10811
11508
|
TRACES_REDACTION_RULES,
|
|
10812
11509
|
redactSpans,
|