@tangle-network/traces 0.19.1 → 0.21.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 +40 -4
- package/dist/{chunk-YVCHWHCA.js → chunk-KEB25UHZ.js} +5179 -4533
- package/dist/cli.js +86 -1
- package/dist/index.d.ts +176 -1
- package/dist/index.js +541 -8
- package/docs/trace-analysts.md +1 -0
- package/package.json +5 -4
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-KEB25UHZ.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,7 +830,7 @@ 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";
|
|
833
|
+
import { dirname, isAbsolute, join as join3, relative, resolve as resolve2 } from "path";
|
|
310
834
|
var RETAINED_SESSION_READER_VERSION = "2";
|
|
311
835
|
async function parseRetainedSession(input) {
|
|
312
836
|
input.signal?.throwIfAborted();
|
|
@@ -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, {
|
|
@@ -400,7 +924,7 @@ async function parseRetainedSession(input) {
|
|
|
400
924
|
reason: null,
|
|
401
925
|
sessionId: input.nativeSessionId,
|
|
402
926
|
sourceFiles: chosen.paths.map((path) => {
|
|
403
|
-
const file = files.get(
|
|
927
|
+
const file = files.get(resolve2(path));
|
|
404
928
|
return { path: file.path, sha256: `sha256:${createHash("sha256").update(file.content).digest("hex")}` };
|
|
405
929
|
}).sort((a, b) => a.path.localeCompare(b.path)),
|
|
406
930
|
duplicateFiles: selected.slice(1).map((candidate) => candidate.file.path),
|
|
@@ -432,6 +956,7 @@ export {
|
|
|
432
956
|
DEFAULT_ASK_CONCURRENCY,
|
|
433
957
|
DEFAULT_HARNESS,
|
|
434
958
|
DEFAULT_SANDBOX_BASE_URL,
|
|
959
|
+
DEFAULT_SWEEP_THRESHOLD,
|
|
435
960
|
EMPTY_SPAN_RUN_SNAPSHOT,
|
|
436
961
|
EXPORT_RULE,
|
|
437
962
|
EmptySessionError,
|
|
@@ -440,6 +965,7 @@ export {
|
|
|
440
965
|
FactoryAdapter,
|
|
441
966
|
ForgeAdapter,
|
|
442
967
|
GeminiAdapter,
|
|
968
|
+
HarnessSessionsAdapter,
|
|
443
969
|
INGEST_SOURCE_CLI,
|
|
444
970
|
INHERITED_SOURCE_ATTR,
|
|
445
971
|
INHERITED_SPANS_OMITTED_ATTR,
|
|
@@ -469,6 +995,7 @@ export {
|
|
|
469
995
|
SYNTHESIZED_SOURCE_ATTR,
|
|
470
996
|
SYNTHESIZED_SPAN_ATTR,
|
|
471
997
|
SessionWorkflowError,
|
|
998
|
+
SkillUsageStore,
|
|
472
999
|
TRACE_QUESTION_PREVIEW_HEAD_CHARS,
|
|
473
1000
|
ZERO_ROLL,
|
|
474
1001
|
addSpend,
|
|
@@ -492,6 +1019,7 @@ export {
|
|
|
492
1019
|
buildSessionFactsReport,
|
|
493
1020
|
buildSessionIndex,
|
|
494
1021
|
buildSessionIndexFromRows,
|
|
1022
|
+
buildSkillUsageReport,
|
|
495
1023
|
buildSpanRunTree,
|
|
496
1024
|
buildTraceFindingPacket,
|
|
497
1025
|
capText,
|
|
@@ -520,6 +1048,9 @@ export {
|
|
|
520
1048
|
createHostedClient,
|
|
521
1049
|
cwdMatchesSelection,
|
|
522
1050
|
cwdMatchesSelectionResolved,
|
|
1051
|
+
defaultSkillCatalogRoots,
|
|
1052
|
+
defaultSkillUsageSources,
|
|
1053
|
+
defaultSkillUsageStorePath,
|
|
523
1054
|
defaultTraceLiveAnalysts,
|
|
524
1055
|
describeSessionRelationship,
|
|
525
1056
|
diffRuns,
|
|
@@ -589,6 +1120,7 @@ export {
|
|
|
589
1120
|
projectSessionBundle,
|
|
590
1121
|
readCoordinationLog,
|
|
591
1122
|
readFileRunContext,
|
|
1123
|
+
readInstalledSkills,
|
|
592
1124
|
readJsonFile,
|
|
593
1125
|
readJsonl,
|
|
594
1126
|
readOtlpInput,
|
|
@@ -612,6 +1144,7 @@ export {
|
|
|
612
1144
|
renderRunWatchSnapshot,
|
|
613
1145
|
renderSessionFacts,
|
|
614
1146
|
renderSessionFactsContext,
|
|
1147
|
+
renderSkillUsage,
|
|
615
1148
|
renderSpanRunSnapshot,
|
|
616
1149
|
renderTraceQuestionInstructions,
|
|
617
1150
|
renderTraceQuestionPrompt,
|