@tangle-network/traces 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -4
- package/dist/{chunk-YVCHWHCA.js → chunk-2NINDZCW.js} +5165 -4531
- package/dist/cli.js +74 -1
- package/dist/index.d.ts +169 -2
- package/dist/index.js +546 -9
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -18,14 +18,15 @@ import {
|
|
|
18
18
|
DEFAULT_ASK_CONCURRENCY,
|
|
19
19
|
DEFAULT_HARNESS,
|
|
20
20
|
DEFAULT_SANDBOX_BASE_URL,
|
|
21
|
+
DEFAULT_SWEEP_THRESHOLD,
|
|
21
22
|
EMPTY_SPAN_RUN_SNAPSHOT,
|
|
22
23
|
EXPORT_RULE,
|
|
23
24
|
EmptySessionError,
|
|
24
25
|
FACT_LIST_CAP,
|
|
25
26
|
FACT_TEXT_CAP,
|
|
26
|
-
FactoryAdapter,
|
|
27
27
|
ForgeAdapter,
|
|
28
28
|
GeminiAdapter,
|
|
29
|
+
HarnessSessionsAdapter,
|
|
29
30
|
INGEST_SOURCE_CLI,
|
|
30
31
|
INHERITED_SOURCE_ATTR,
|
|
31
32
|
INHERITED_SPANS_OMITTED_ATTR,
|
|
@@ -39,7 +40,6 @@ import {
|
|
|
39
40
|
POLICY_EVIDENCE_NOTE,
|
|
40
41
|
PREPARED_CONTEXT_BYTE_CEILING,
|
|
41
42
|
PartialUploadError,
|
|
42
|
-
PiAdapter,
|
|
43
43
|
QwenAdapter,
|
|
44
44
|
REACTIONS,
|
|
45
45
|
RESULT_FILE,
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
SYNTHESIZED_SOURCE_ATTR,
|
|
55
55
|
SYNTHESIZED_SPAN_ATTR,
|
|
56
56
|
SessionWorkflowError,
|
|
57
|
+
SkillUsageStore,
|
|
57
58
|
TRACE_QUESTION_PREVIEW_HEAD_CHARS,
|
|
58
59
|
ZERO_ROLL,
|
|
59
60
|
addSpend,
|
|
@@ -75,6 +76,7 @@ import {
|
|
|
75
76
|
buildSessionFactsReport,
|
|
76
77
|
buildSessionIndex,
|
|
77
78
|
buildSessionIndexFromRows,
|
|
79
|
+
buildSkillUsageReport,
|
|
78
80
|
buildSpanRunTree,
|
|
79
81
|
buildTraceFindingPacket,
|
|
80
82
|
capText,
|
|
@@ -101,6 +103,9 @@ import {
|
|
|
101
103
|
createFindingRejectionTally,
|
|
102
104
|
cwdMatchesSelection,
|
|
103
105
|
cwdMatchesSelectionResolved,
|
|
106
|
+
defaultSkillCatalogRoots,
|
|
107
|
+
defaultSkillUsageSources,
|
|
108
|
+
defaultSkillUsageStorePath,
|
|
104
109
|
defaultTraceLiveAnalysts,
|
|
105
110
|
describeSessionRelationship,
|
|
106
111
|
diffRuns,
|
|
@@ -139,6 +144,8 @@ import {
|
|
|
139
144
|
isChatTrajectoryMessage,
|
|
140
145
|
isFileRunContextDir,
|
|
141
146
|
isInheritedSpan,
|
|
147
|
+
isMissingJsonSource,
|
|
148
|
+
isMissingPathError,
|
|
142
149
|
isSynthesizedSpan,
|
|
143
150
|
iterSerializedSpans,
|
|
144
151
|
knownHarnesses,
|
|
@@ -167,6 +174,7 @@ import {
|
|
|
167
174
|
projectSessionBundle,
|
|
168
175
|
readCoordinationLog,
|
|
169
176
|
readFileRunContext,
|
|
177
|
+
readInstalledSkills,
|
|
170
178
|
readJsonFile,
|
|
171
179
|
readJsonl,
|
|
172
180
|
readOtlpInput,
|
|
@@ -179,6 +187,7 @@ import {
|
|
|
179
187
|
readSessionIndexFile,
|
|
180
188
|
readSpanRunSnapshot,
|
|
181
189
|
recordSessionCorruption,
|
|
190
|
+
recordToolOutput,
|
|
182
191
|
redactSpans,
|
|
183
192
|
renderConformance,
|
|
184
193
|
renderInspectionReport,
|
|
@@ -187,6 +196,7 @@ import {
|
|
|
187
196
|
renderRunWatchSnapshot,
|
|
188
197
|
renderSessionFacts,
|
|
189
198
|
renderSessionFactsContext,
|
|
199
|
+
renderSkillUsage,
|
|
190
200
|
renderSpanRunSnapshot,
|
|
191
201
|
renderTraceQuestionInstructions,
|
|
192
202
|
renderTraceQuestionPrompt,
|
|
@@ -220,9 +230,11 @@ import {
|
|
|
220
230
|
sessionHash,
|
|
221
231
|
sessionIdFromAttributes,
|
|
222
232
|
sessionIntegrityAttributes,
|
|
233
|
+
sessionJsonlOptions,
|
|
223
234
|
sessionTextSignatures,
|
|
224
235
|
shellCommands,
|
|
225
236
|
skippedAnalyses,
|
|
237
|
+
sourceOf,
|
|
226
238
|
span,
|
|
227
239
|
spanRunTotals,
|
|
228
240
|
stampEnvironmentAttrs,
|
|
@@ -236,10 +248,12 @@ import {
|
|
|
236
248
|
summarizeSpanStructure,
|
|
237
249
|
takeJsonl,
|
|
238
250
|
textIsSynthetic,
|
|
251
|
+
textSources,
|
|
239
252
|
toOpenInferenceSpan,
|
|
240
253
|
toRuntimeStore,
|
|
241
254
|
toTraceSpanEvents,
|
|
242
255
|
tokens,
|
|
256
|
+
toolIoAttributes,
|
|
243
257
|
totalAgenticFailureMessage,
|
|
244
258
|
totalFindingRejections,
|
|
245
259
|
traceAgenticKinds,
|
|
@@ -270,7 +284,517 @@ import {
|
|
|
270
284
|
writeTraceQuestionsArtifacts,
|
|
271
285
|
zaiChatCaller,
|
|
272
286
|
zeroPricedCost
|
|
273
|
-
} from "./chunk-
|
|
287
|
+
} from "./chunk-2NINDZCW.js";
|
|
288
|
+
|
|
289
|
+
// src/adapters/factory.ts
|
|
290
|
+
import { readdir, stat } from "fs/promises";
|
|
291
|
+
import { homedir } from "os";
|
|
292
|
+
import { basename, join } from "path";
|
|
293
|
+
var SERVICE = "factory";
|
|
294
|
+
function textOf(content) {
|
|
295
|
+
return capText(
|
|
296
|
+
(content ?? []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("")
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
var FactoryAdapter = class {
|
|
300
|
+
harness = "factory";
|
|
301
|
+
aliases = ["factory-droids", "droid"];
|
|
302
|
+
root() {
|
|
303
|
+
return join(homedir(), ".factory", "sessions");
|
|
304
|
+
}
|
|
305
|
+
async locate(opts = {}) {
|
|
306
|
+
const root = this.root();
|
|
307
|
+
let dirs;
|
|
308
|
+
try {
|
|
309
|
+
dirs = await readdir(root);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
if (isMissingPathError(error)) return [];
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
const refs = [];
|
|
315
|
+
for (const dir of dirs) {
|
|
316
|
+
const dp = join(root, dir);
|
|
317
|
+
let files;
|
|
318
|
+
try {
|
|
319
|
+
files = await readdir(dp);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
if (isMissingPathError(error)) continue;
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
const cwd = dir.replace(/-/g, "/");
|
|
325
|
+
if (opts.cwd && !cwd.startsWith(opts.cwd)) continue;
|
|
326
|
+
for (const f of files) {
|
|
327
|
+
if (!f.endsWith(".jsonl")) continue;
|
|
328
|
+
const path = join(dp, f);
|
|
329
|
+
let st;
|
|
330
|
+
try {
|
|
331
|
+
st = await stat(path);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
if (isMissingPathError(error)) continue;
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
if (opts.sinceMs && st.mtimeMs < opts.sinceMs) continue;
|
|
337
|
+
refs.push({ harness: this.harness, sessionId: basename(f, ".jsonl"), path, cwd, mtimeMs: st.mtimeMs });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
341
|
+
}
|
|
342
|
+
async sourcePaths(ref) {
|
|
343
|
+
const sidecar = ref.path.replace(/\.jsonl$/, ".settings.json");
|
|
344
|
+
try {
|
|
345
|
+
await stat(sidecar);
|
|
346
|
+
return [ref.path, sidecar];
|
|
347
|
+
} catch (error) {
|
|
348
|
+
if (isMissingPathError(error)) return [ref.path];
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async parse(ref, options = {}) {
|
|
353
|
+
let settings = {};
|
|
354
|
+
try {
|
|
355
|
+
settings = await readJsonFile(ref.path.replace(/\.jsonl$/, ".settings.json"));
|
|
356
|
+
} catch (error) {
|
|
357
|
+
if (!isMissingJsonSource(error)) throw error;
|
|
358
|
+
}
|
|
359
|
+
const sourceTraceId = ref.sessionId;
|
|
360
|
+
const sourceRootId = `root:${sourceTraceId}`;
|
|
361
|
+
const spans = [];
|
|
362
|
+
const toolByUseId = /* @__PURE__ */ new Map();
|
|
363
|
+
let firstTimestamp;
|
|
364
|
+
let lastTimestamp;
|
|
365
|
+
let sessionLine;
|
|
366
|
+
let sawLine = false;
|
|
367
|
+
let step = 0;
|
|
368
|
+
let lastLlm = sourceRootId;
|
|
369
|
+
for await (const l of readJsonl(ref.path, sessionJsonlOptions(ref, options))) {
|
|
370
|
+
if (!sawLine) {
|
|
371
|
+
firstTimestamp = l.timestamp;
|
|
372
|
+
sawLine = true;
|
|
373
|
+
}
|
|
374
|
+
lastTimestamp = l.timestamp;
|
|
375
|
+
if (!sessionLine && l.type === "session_start") {
|
|
376
|
+
sessionLine = { id: l.id, timestamp: l.timestamp };
|
|
377
|
+
}
|
|
378
|
+
if (l.type !== "message" || !l.message) continue;
|
|
379
|
+
const ts = l.timestamp ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
380
|
+
const role = l.message.role;
|
|
381
|
+
const text = textOf(l.message.content);
|
|
382
|
+
if (role === "user") {
|
|
383
|
+
if (text) {
|
|
384
|
+
spans.push(
|
|
385
|
+
userPromptSpan({
|
|
386
|
+
traceId: sourceTraceId,
|
|
387
|
+
spanId: `user:${l.id ?? step}`,
|
|
388
|
+
parentSpanId: sourceRootId,
|
|
389
|
+
startTime: ts,
|
|
390
|
+
content: text,
|
|
391
|
+
contentSource: textSources(l.message, "content"),
|
|
392
|
+
service: SERVICE,
|
|
393
|
+
agent: SERVICE,
|
|
394
|
+
step
|
|
395
|
+
})
|
|
396
|
+
);
|
|
397
|
+
step += 1;
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
const llmId = `llm:${l.id ?? step}`;
|
|
401
|
+
spans.push(
|
|
402
|
+
span({
|
|
403
|
+
traceId: sourceTraceId,
|
|
404
|
+
spanId: llmId,
|
|
405
|
+
parentSpanId: sourceRootId,
|
|
406
|
+
name: `message.${role ?? "unknown"}`,
|
|
407
|
+
kind: "LLM",
|
|
408
|
+
startTime: ts,
|
|
409
|
+
service: SERVICE,
|
|
410
|
+
agent: SERVICE,
|
|
411
|
+
model: settings.model ?? null,
|
|
412
|
+
step,
|
|
413
|
+
content: text || null,
|
|
414
|
+
contentSource: textSources(l.message, "content")
|
|
415
|
+
})
|
|
416
|
+
);
|
|
417
|
+
lastLlm = llmId;
|
|
418
|
+
step += 1;
|
|
419
|
+
}
|
|
420
|
+
for (const b of l.message.content ?? []) {
|
|
421
|
+
if (b.type === "tool_use" && b.name) {
|
|
422
|
+
const t = span({
|
|
423
|
+
traceId: sourceTraceId,
|
|
424
|
+
spanId: `tool:${b.id ?? `${l.id}:${step}`}`,
|
|
425
|
+
parentSpanId: lastLlm,
|
|
426
|
+
name: `tool.${b.name}`,
|
|
427
|
+
kind: "TOOL",
|
|
428
|
+
startTime: ts,
|
|
429
|
+
service: SERVICE,
|
|
430
|
+
agent: SERVICE,
|
|
431
|
+
tool: b.name,
|
|
432
|
+
step,
|
|
433
|
+
extra: toolIoAttributes({ input: b.input, inputSource: sourceOf(b, "input") })
|
|
434
|
+
});
|
|
435
|
+
spans.push(t);
|
|
436
|
+
if (b.id) toolByUseId.set(b.id, t);
|
|
437
|
+
step += 1;
|
|
438
|
+
} else if (b.type === "tool_result" && b.tool_use_id) {
|
|
439
|
+
const t = toolByUseId.get(b.tool_use_id);
|
|
440
|
+
if (t) {
|
|
441
|
+
t.status = b.is_error === true ? { code: "ERROR", message: "tool reported error" } : { code: "OK" };
|
|
442
|
+
recordToolOutput(t, b.content, sourceOf(b, "content"));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const traceId = sessionLine?.id ?? sourceTraceId;
|
|
448
|
+
const rootId = `root:${traceId}`;
|
|
449
|
+
for (const item of spans) {
|
|
450
|
+
item.trace_id = traceId;
|
|
451
|
+
if (item.parent_span_id === sourceRootId) item.parent_span_id = rootId;
|
|
452
|
+
}
|
|
453
|
+
const root = span({
|
|
454
|
+
traceId,
|
|
455
|
+
spanId: rootId,
|
|
456
|
+
parentSpanId: null,
|
|
457
|
+
name: "session",
|
|
458
|
+
kind: "AGENT",
|
|
459
|
+
startTime: sessionLine?.timestamp ?? firstTimestamp ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
460
|
+
endTime: lastTimestamp,
|
|
461
|
+
service: SERVICE,
|
|
462
|
+
agent: SERVICE,
|
|
463
|
+
model: settings.model ?? null,
|
|
464
|
+
extra: settings.tokenUsage ? {
|
|
465
|
+
"session.input_tokens": settings.tokenUsage.inputTokens ?? 0,
|
|
466
|
+
"session.output_tokens": settings.tokenUsage.outputTokens ?? 0
|
|
467
|
+
} : void 0
|
|
468
|
+
});
|
|
469
|
+
if (sessionLine?.id) root.attributes["tangle.sessionId"] = sessionLine.id;
|
|
470
|
+
return [root, ...spans];
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
// src/adapters/pi.ts
|
|
475
|
+
import { readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
476
|
+
import { homedir as homedir2 } from "os";
|
|
477
|
+
import { basename as basename2, join as join2, resolve } from "path";
|
|
478
|
+
var SERVICE2 = "pi";
|
|
479
|
+
function terminalStatus(message) {
|
|
480
|
+
const stopReason = message?.stopReason ?? "missing";
|
|
481
|
+
if (message?.role === "assistant" && stopReason === "stop") {
|
|
482
|
+
return { status: "OK", stopReason };
|
|
483
|
+
}
|
|
484
|
+
if (message?.role === "assistant" && (stopReason === "error" || message.errorMessage)) {
|
|
485
|
+
return {
|
|
486
|
+
status: "ERROR",
|
|
487
|
+
statusMessage: message.errorMessage?.slice(0, 500) ?? "Pi assistant stopped with an error",
|
|
488
|
+
stopReason
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
status: "UNSET",
|
|
493
|
+
statusMessage: message === void 0 ? "Pi session contained no messages" : `Pi session ended after ${message.role} with stopReason "${stopReason}"`,
|
|
494
|
+
stopReason
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function isToolBlock(b) {
|
|
498
|
+
return typeof b.type === "string" && /tool[_-]?call|tool[_-]?use|^tool$/i.test(b.type);
|
|
499
|
+
}
|
|
500
|
+
function isToolResultBlock(b) {
|
|
501
|
+
return typeof b.type === "string" && /tool[_-]?result|tool[_-]?output/i.test(b.type);
|
|
502
|
+
}
|
|
503
|
+
function textOf2(content) {
|
|
504
|
+
if (!content) return "";
|
|
505
|
+
return capText(
|
|
506
|
+
content.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("")
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
function toolResultOutput(content) {
|
|
510
|
+
if (content?.length === 1 && content[0]?.type === "text" && typeof content[0].text === "string") {
|
|
511
|
+
return content[0].text;
|
|
512
|
+
}
|
|
513
|
+
return content;
|
|
514
|
+
}
|
|
515
|
+
function completeToolSpan(toolSpan, result) {
|
|
516
|
+
toolSpan.end_time = result.timestamp;
|
|
517
|
+
toolSpan.status = result.isError ? { code: "ERROR", message: result.errorMessage } : { code: "OK" };
|
|
518
|
+
recordToolOutput(toolSpan, result.output, result.source);
|
|
519
|
+
}
|
|
520
|
+
var PiAdapter = class {
|
|
521
|
+
harness = "pi";
|
|
522
|
+
sessionsRoot;
|
|
523
|
+
constructor(options = {}) {
|
|
524
|
+
this.sessionsRoot = resolve(options.sessionsRoot ?? join2(homedir2(), ".pi", "agent", "sessions"));
|
|
525
|
+
}
|
|
526
|
+
root() {
|
|
527
|
+
return this.sessionsRoot;
|
|
528
|
+
}
|
|
529
|
+
async locate(opts = {}) {
|
|
530
|
+
const root = this.root();
|
|
531
|
+
let entries;
|
|
532
|
+
try {
|
|
533
|
+
entries = await readdir2(root, { withFileTypes: true });
|
|
534
|
+
} catch (error) {
|
|
535
|
+
if (isMissingPathError(error)) return [];
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
const paths = [];
|
|
539
|
+
for (const entry of entries) {
|
|
540
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
541
|
+
paths.push(join2(root, entry.name));
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (!entry.isDirectory()) continue;
|
|
545
|
+
const dir = join2(root, entry.name);
|
|
546
|
+
let files;
|
|
547
|
+
try {
|
|
548
|
+
files = await readdir2(dir);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
if (isMissingPathError(error)) continue;
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
553
|
+
for (const file of files) {
|
|
554
|
+
if (file.endsWith(".jsonl")) paths.push(join2(dir, file));
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const refs = [];
|
|
558
|
+
for (const path of paths) {
|
|
559
|
+
const f = basename2(path);
|
|
560
|
+
let st;
|
|
561
|
+
try {
|
|
562
|
+
st = await stat2(path);
|
|
563
|
+
} catch (error) {
|
|
564
|
+
if (isMissingPathError(error)) continue;
|
|
565
|
+
throw error;
|
|
566
|
+
}
|
|
567
|
+
if (opts.sinceMs && st.mtimeMs < opts.sinceMs) continue;
|
|
568
|
+
let id = basename2(f, ".jsonl").replace(/^[\dTZ.-]+_/, "");
|
|
569
|
+
let cwd = null;
|
|
570
|
+
const ref = {
|
|
571
|
+
harness: this.harness,
|
|
572
|
+
sessionId: id,
|
|
573
|
+
path,
|
|
574
|
+
cwd,
|
|
575
|
+
mtimeMs: st.mtimeMs
|
|
576
|
+
};
|
|
577
|
+
const [session] = await takeJsonl(path, 1, sessionJsonlOptions(ref));
|
|
578
|
+
if (session?.type === "session") {
|
|
579
|
+
if (session.id) id = session.id;
|
|
580
|
+
if (session.cwd) cwd = session.cwd;
|
|
581
|
+
}
|
|
582
|
+
ref.sessionId = id;
|
|
583
|
+
ref.cwd = cwd;
|
|
584
|
+
if (ref.integrity) {
|
|
585
|
+
ref.integrity.corruptions = ref.integrity.corruptions.map((receipt) => ({
|
|
586
|
+
...receipt,
|
|
587
|
+
sessionId: id
|
|
588
|
+
}));
|
|
589
|
+
}
|
|
590
|
+
if (opts.cwd && (!cwd || !cwd.startsWith(opts.cwd))) continue;
|
|
591
|
+
refs.push(ref);
|
|
592
|
+
}
|
|
593
|
+
return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
594
|
+
}
|
|
595
|
+
async parse(ref, options = {}) {
|
|
596
|
+
const sourceTraceId = ref.sessionId;
|
|
597
|
+
const sourceRootId = `root:${sourceTraceId}`;
|
|
598
|
+
const spans = [];
|
|
599
|
+
const toolByCallId = /* @__PURE__ */ new Map();
|
|
600
|
+
const pendingResults = /* @__PURE__ */ new Map();
|
|
601
|
+
let firstTimestamp;
|
|
602
|
+
let lastTimestamp;
|
|
603
|
+
let sessionLine;
|
|
604
|
+
let terminalMessage;
|
|
605
|
+
let sawLine = false;
|
|
606
|
+
let step = 0;
|
|
607
|
+
for await (const l of readJsonl(ref.path, sessionJsonlOptions(ref, options))) {
|
|
608
|
+
if (!sawLine) {
|
|
609
|
+
firstTimestamp = l.timestamp;
|
|
610
|
+
sawLine = true;
|
|
611
|
+
}
|
|
612
|
+
lastTimestamp = l.timestamp;
|
|
613
|
+
if (!sessionLine && l.type === "session") {
|
|
614
|
+
sessionLine = { id: l.id, timestamp: l.timestamp };
|
|
615
|
+
}
|
|
616
|
+
if (l.type === "compaction") {
|
|
617
|
+
spans.push(span({
|
|
618
|
+
traceId: sourceTraceId,
|
|
619
|
+
spanId: "compacted:" + (l.id ?? step),
|
|
620
|
+
parentSpanId: sourceRootId,
|
|
621
|
+
name: "session.compacted",
|
|
622
|
+
kind: "CHAIN",
|
|
623
|
+
service: SERVICE2,
|
|
624
|
+
agent: SERVICE2,
|
|
625
|
+
startTime: l.timestamp ?? "",
|
|
626
|
+
step: step++,
|
|
627
|
+
content: typeof l.summary === "string" ? capText(l.summary) : void 0,
|
|
628
|
+
contentSource: sourceOf(l, "summary"),
|
|
629
|
+
extra: {
|
|
630
|
+
...typeof l.tokensBefore === "number" && Number.isFinite(l.tokensBefore) && l.tokensBefore >= 0 ? { "traces.compaction.tokens_before": l.tokensBefore } : {},
|
|
631
|
+
...typeof l.firstKeptEntryId === "string" ? { "traces.pi.first_kept_entry_id": l.firstKeptEntryId } : {},
|
|
632
|
+
...l.systemMessage !== void 0 ? { "gen_ai.system_instructions": JSON.stringify(l.systemMessage) } : {}
|
|
633
|
+
}
|
|
634
|
+
}));
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
if (l.type !== "message" || !l.message) continue;
|
|
638
|
+
const ts = l.timestamp ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
639
|
+
const mid = l.id ?? `m${step}`;
|
|
640
|
+
const msg = l.message;
|
|
641
|
+
terminalMessage = {
|
|
642
|
+
role: msg.role ?? "unknown",
|
|
643
|
+
stopReason: msg.stopReason,
|
|
644
|
+
errorMessage: msg.errorMessage
|
|
645
|
+
};
|
|
646
|
+
const llmId = `llm:${mid}`;
|
|
647
|
+
if (msg.role === "toolResult") {
|
|
648
|
+
const callId = msg.toolCallId ?? `${mid}:result`;
|
|
649
|
+
const errorMessage = textOf2(msg.content).slice(0, 500) || "tool result reported error";
|
|
650
|
+
const result = {
|
|
651
|
+
callId,
|
|
652
|
+
toolName: msg.toolName ?? "tool",
|
|
653
|
+
timestamp: ts,
|
|
654
|
+
isError: msg.isError === true,
|
|
655
|
+
output: toolResultOutput(msg.content),
|
|
656
|
+
source: msg.content?.length === 1 && msg.content[0]?.type === "text" && typeof msg.content[0].text === "string" ? sourceOf(msg.content[0], "text") : sourceOf(msg, "content"),
|
|
657
|
+
errorMessage,
|
|
658
|
+
step
|
|
659
|
+
};
|
|
660
|
+
const toolSpan = toolByCallId.get(callId);
|
|
661
|
+
if (toolSpan) completeToolSpan(toolSpan, result);
|
|
662
|
+
else {
|
|
663
|
+
pendingResults.set(callId, result);
|
|
664
|
+
step += 1;
|
|
665
|
+
}
|
|
666
|
+
continue;
|
|
667
|
+
} else if (msg.role === "user") {
|
|
668
|
+
const prompt = textOf2(msg.content);
|
|
669
|
+
if (prompt) {
|
|
670
|
+
spans.push(
|
|
671
|
+
userPromptSpan({
|
|
672
|
+
traceId: sourceTraceId,
|
|
673
|
+
spanId: `${mid}:user`,
|
|
674
|
+
parentSpanId: sourceRootId,
|
|
675
|
+
startTime: ts,
|
|
676
|
+
service: SERVICE2,
|
|
677
|
+
agent: SERVICE2,
|
|
678
|
+
step,
|
|
679
|
+
content: prompt,
|
|
680
|
+
contentSource: textSources(msg, "content")
|
|
681
|
+
})
|
|
682
|
+
);
|
|
683
|
+
step += 1;
|
|
684
|
+
}
|
|
685
|
+
} else {
|
|
686
|
+
const errored = !!msg.errorMessage;
|
|
687
|
+
spans.push(
|
|
688
|
+
span({
|
|
689
|
+
traceId: sourceTraceId,
|
|
690
|
+
spanId: llmId,
|
|
691
|
+
parentSpanId: sourceRootId,
|
|
692
|
+
name: `message.${msg.role ?? "unknown"}`,
|
|
693
|
+
kind: "LLM",
|
|
694
|
+
startTime: ts,
|
|
695
|
+
status: errored ? "ERROR" : "OK",
|
|
696
|
+
statusMessage: errored ? msg.errorMessage.slice(0, 500) : void 0,
|
|
697
|
+
service: SERVICE2,
|
|
698
|
+
agent: SERVICE2,
|
|
699
|
+
model: msg.model ?? null,
|
|
700
|
+
inputTokens: msg.usage?.input ?? null,
|
|
701
|
+
outputTokens: msg.usage?.output ?? null,
|
|
702
|
+
reasoningTokens: msg.usage?.reasoning ?? null,
|
|
703
|
+
cachedInputTokens: msg.usage?.cacheRead ?? null,
|
|
704
|
+
cacheWriteInputTokens: msg.usage?.cacheWrite ?? null,
|
|
705
|
+
step,
|
|
706
|
+
content: textOf2(msg.content) || null,
|
|
707
|
+
contentSource: textSources(msg, "content")
|
|
708
|
+
})
|
|
709
|
+
);
|
|
710
|
+
step += 1;
|
|
711
|
+
}
|
|
712
|
+
for (const b of msg.content ?? []) {
|
|
713
|
+
if (isToolBlock(b)) {
|
|
714
|
+
const name = b.toolName ?? b.name ?? "tool";
|
|
715
|
+
const callId = b.id ?? b.callId ?? b.toolCallId ?? `${mid}:${step}`;
|
|
716
|
+
const toolSpan = span({
|
|
717
|
+
traceId: sourceTraceId,
|
|
718
|
+
spanId: `tool:${callId}`,
|
|
719
|
+
parentSpanId: llmId,
|
|
720
|
+
name: `tool.${name}`,
|
|
721
|
+
kind: "TOOL",
|
|
722
|
+
startTime: ts,
|
|
723
|
+
service: SERVICE2,
|
|
724
|
+
agent: SERVICE2,
|
|
725
|
+
tool: name,
|
|
726
|
+
status: "UNSET",
|
|
727
|
+
step,
|
|
728
|
+
extra: toolIoAttributes({ input: b.input ?? b.args ?? b.arguments, inputSource: sourceOf(b, b.input != null ? "input" : b.args != null ? "args" : "arguments") })
|
|
729
|
+
});
|
|
730
|
+
spans.push(toolSpan);
|
|
731
|
+
toolByCallId.set(callId, toolSpan);
|
|
732
|
+
const pending = pendingResults.get(callId);
|
|
733
|
+
if (pending) {
|
|
734
|
+
completeToolSpan(toolSpan, pending);
|
|
735
|
+
pendingResults.delete(callId);
|
|
736
|
+
}
|
|
737
|
+
step += 1;
|
|
738
|
+
} else if (isToolResultBlock(b)) {
|
|
739
|
+
const callId = b.toolCallId ?? b.callId ?? b.id ?? "";
|
|
740
|
+
const t = toolByCallId.get(callId);
|
|
741
|
+
if (t) {
|
|
742
|
+
const err = b.isError === true || b.is_error === true;
|
|
743
|
+
t.end_time = ts;
|
|
744
|
+
t.status = err ? { code: "ERROR", message: "tool result reported error" } : { code: "OK" };
|
|
745
|
+
recordToolOutput(t, b.output ?? b.result ?? b.content ?? b.text, sourceOf(b, b.output != null ? "output" : b.result != null ? "result" : b.content != null ? "content" : "text"));
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
for (const result of pendingResults.values()) {
|
|
751
|
+
spans.push(
|
|
752
|
+
span({
|
|
753
|
+
traceId: sourceTraceId,
|
|
754
|
+
spanId: `tool:${result.callId}`,
|
|
755
|
+
parentSpanId: sourceRootId,
|
|
756
|
+
name: `tool.${result.toolName}`,
|
|
757
|
+
kind: "TOOL",
|
|
758
|
+
startTime: result.timestamp,
|
|
759
|
+
endTime: result.timestamp,
|
|
760
|
+
status: result.isError ? "ERROR" : "OK",
|
|
761
|
+
statusMessage: result.isError ? result.errorMessage : void 0,
|
|
762
|
+
service: SERVICE2,
|
|
763
|
+
agent: SERVICE2,
|
|
764
|
+
tool: result.toolName,
|
|
765
|
+
step: result.step,
|
|
766
|
+
extra: {
|
|
767
|
+
...toolIoAttributes({ output: result.output, outputSource: result.source }),
|
|
768
|
+
"traces.pi.tool_result_without_call": true
|
|
769
|
+
}
|
|
770
|
+
})
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
const traceId = sessionLine?.id ?? sourceTraceId;
|
|
774
|
+
const rootId = `root:${traceId}`;
|
|
775
|
+
const terminal = terminalStatus(terminalMessage);
|
|
776
|
+
for (const item of spans) {
|
|
777
|
+
item.trace_id = traceId;
|
|
778
|
+
if (item.parent_span_id === sourceRootId) item.parent_span_id = rootId;
|
|
779
|
+
}
|
|
780
|
+
const root = span({
|
|
781
|
+
traceId,
|
|
782
|
+
spanId: rootId,
|
|
783
|
+
parentSpanId: null,
|
|
784
|
+
name: "session",
|
|
785
|
+
kind: "AGENT",
|
|
786
|
+
startTime: sessionLine?.timestamp ?? firstTimestamp ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
787
|
+
endTime: lastTimestamp,
|
|
788
|
+
status: terminal.status,
|
|
789
|
+
statusMessage: terminal.statusMessage,
|
|
790
|
+
service: SERVICE2,
|
|
791
|
+
agent: SERVICE2,
|
|
792
|
+
extra: { "traces.pi.stop_reason": terminal.stopReason }
|
|
793
|
+
});
|
|
794
|
+
if (sessionLine?.id) root.attributes["tangle.sessionId"] = sessionLine.id;
|
|
795
|
+
return [root, ...spans];
|
|
796
|
+
}
|
|
797
|
+
};
|
|
274
798
|
|
|
275
799
|
// src/collect.ts
|
|
276
800
|
async function collectSessions(opts = {}) {
|
|
@@ -306,8 +830,8 @@ import {
|
|
|
306
830
|
import { createHash } from "crypto";
|
|
307
831
|
import { mkdir, mkdtemp, rm, writeFile } from "fs/promises";
|
|
308
832
|
import { tmpdir } from "os";
|
|
309
|
-
import { dirname, isAbsolute, join, relative, resolve } from "path";
|
|
310
|
-
var RETAINED_SESSION_READER_VERSION = "
|
|
833
|
+
import { dirname, isAbsolute, join as join3, relative, resolve as resolve2 } from "path";
|
|
834
|
+
var RETAINED_SESSION_READER_VERSION = "2";
|
|
311
835
|
async function parseRetainedSession(input) {
|
|
312
836
|
input.signal?.throwIfAborted();
|
|
313
837
|
const unselectedFiles = [];
|
|
@@ -332,12 +856,12 @@ async function parseRetainedSession(input) {
|
|
|
332
856
|
return unavailable("Retained capture exceeds the 16 MiB per-file or 64 MiB total text limit.");
|
|
333
857
|
}
|
|
334
858
|
}
|
|
335
|
-
const root = await mkdtemp(
|
|
859
|
+
const root = await mkdtemp(join3(tmpdir(), "traces-retained-"));
|
|
336
860
|
try {
|
|
337
861
|
const files = /* @__PURE__ */ new Map();
|
|
338
862
|
for (const file of input.files) {
|
|
339
863
|
input.signal?.throwIfAborted();
|
|
340
|
-
const path =
|
|
864
|
+
const path = resolve2(root, file.path);
|
|
341
865
|
const rel = relative(root, path);
|
|
342
866
|
if (!file.path || isAbsolute(file.path) || file.path.includes("\\") || file.path.includes("\0") || rel === "" || rel === ".." || rel.startsWith("../") || files.has(path)) {
|
|
343
867
|
throw new Error("Retained source paths must be unique relative files inside the capture.");
|
|
@@ -355,7 +879,7 @@ async function parseRetainedSession(input) {
|
|
|
355
879
|
const ref = { harness: adapter.harness, sessionId: fallback, path, cwd: null, mtimeMs: 0 };
|
|
356
880
|
try {
|
|
357
881
|
const sources = adapter.sourcePaths ? await adapter.sourcePaths(ref, { signal: input.signal }) : [path];
|
|
358
|
-
if (sources.some((source) => !files.has(
|
|
882
|
+
if (sources.some((source) => !files.has(resolve2(source)))) {
|
|
359
883
|
throw new Error("Parser requires files outside the supplied retained capture.");
|
|
360
884
|
}
|
|
361
885
|
const spans = validateOtlpSpans(await adapter.parse(ref, {
|
|
@@ -370,6 +894,10 @@ async function parseRetainedSession(input) {
|
|
|
370
894
|
unselectedFiles.push({ path: file.path, reason: "No unambiguous matching native identity in parser output." });
|
|
371
895
|
continue;
|
|
372
896
|
}
|
|
897
|
+
if (computeSessionFacts(spans, { includeContent: false }).every((facts) => facts.recordSpans === 0)) {
|
|
898
|
+
unselectedFiles.push({ path: file.path, reason: "Native identity was recorded without any session records." });
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
373
901
|
stampSessionIntegrity(ref, spans);
|
|
374
902
|
stampSessionIdentity(spans, input.nativeSessionId);
|
|
375
903
|
for (const span2 of spans) {
|
|
@@ -396,7 +924,7 @@ async function parseRetainedSession(input) {
|
|
|
396
924
|
reason: null,
|
|
397
925
|
sessionId: input.nativeSessionId,
|
|
398
926
|
sourceFiles: chosen.paths.map((path) => {
|
|
399
|
-
const file = files.get(
|
|
927
|
+
const file = files.get(resolve2(path));
|
|
400
928
|
return { path: file.path, sha256: `sha256:${createHash("sha256").update(file.content).digest("hex")}` };
|
|
401
929
|
}).sort((a, b) => a.path.localeCompare(b.path)),
|
|
402
930
|
duplicateFiles: selected.slice(1).map((candidate) => candidate.file.path),
|
|
@@ -428,6 +956,7 @@ export {
|
|
|
428
956
|
DEFAULT_ASK_CONCURRENCY,
|
|
429
957
|
DEFAULT_HARNESS,
|
|
430
958
|
DEFAULT_SANDBOX_BASE_URL,
|
|
959
|
+
DEFAULT_SWEEP_THRESHOLD,
|
|
431
960
|
EMPTY_SPAN_RUN_SNAPSHOT,
|
|
432
961
|
EXPORT_RULE,
|
|
433
962
|
EmptySessionError,
|
|
@@ -436,6 +965,7 @@ export {
|
|
|
436
965
|
FactoryAdapter,
|
|
437
966
|
ForgeAdapter,
|
|
438
967
|
GeminiAdapter,
|
|
968
|
+
HarnessSessionsAdapter,
|
|
439
969
|
INGEST_SOURCE_CLI,
|
|
440
970
|
INHERITED_SOURCE_ATTR,
|
|
441
971
|
INHERITED_SPANS_OMITTED_ATTR,
|
|
@@ -465,6 +995,7 @@ export {
|
|
|
465
995
|
SYNTHESIZED_SOURCE_ATTR,
|
|
466
996
|
SYNTHESIZED_SPAN_ATTR,
|
|
467
997
|
SessionWorkflowError,
|
|
998
|
+
SkillUsageStore,
|
|
468
999
|
TRACE_QUESTION_PREVIEW_HEAD_CHARS,
|
|
469
1000
|
ZERO_ROLL,
|
|
470
1001
|
addSpend,
|
|
@@ -488,6 +1019,7 @@ export {
|
|
|
488
1019
|
buildSessionFactsReport,
|
|
489
1020
|
buildSessionIndex,
|
|
490
1021
|
buildSessionIndexFromRows,
|
|
1022
|
+
buildSkillUsageReport,
|
|
491
1023
|
buildSpanRunTree,
|
|
492
1024
|
buildTraceFindingPacket,
|
|
493
1025
|
capText,
|
|
@@ -516,6 +1048,9 @@ export {
|
|
|
516
1048
|
createHostedClient,
|
|
517
1049
|
cwdMatchesSelection,
|
|
518
1050
|
cwdMatchesSelectionResolved,
|
|
1051
|
+
defaultSkillCatalogRoots,
|
|
1052
|
+
defaultSkillUsageSources,
|
|
1053
|
+
defaultSkillUsageStorePath,
|
|
519
1054
|
defaultTraceLiveAnalysts,
|
|
520
1055
|
describeSessionRelationship,
|
|
521
1056
|
diffRuns,
|
|
@@ -585,6 +1120,7 @@ export {
|
|
|
585
1120
|
projectSessionBundle,
|
|
586
1121
|
readCoordinationLog,
|
|
587
1122
|
readFileRunContext,
|
|
1123
|
+
readInstalledSkills,
|
|
588
1124
|
readJsonFile,
|
|
589
1125
|
readJsonl,
|
|
590
1126
|
readOtlpInput,
|
|
@@ -608,6 +1144,7 @@ export {
|
|
|
608
1144
|
renderRunWatchSnapshot,
|
|
609
1145
|
renderSessionFacts,
|
|
610
1146
|
renderSessionFactsContext,
|
|
1147
|
+
renderSkillUsage,
|
|
611
1148
|
renderSpanRunSnapshot,
|
|
612
1149
|
renderTraceQuestionInstructions,
|
|
613
1150
|
renderTraceQuestionPrompt,
|