@tangle-network/traces 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/{chunk-5SBXL62V.js → chunk-RUG26PIS.js} +716 -574
- package/dist/cli.js +20 -64
- package/dist/index.d.ts +15 -15
- package/dist/index.js +1 -1
- package/package.json +3 -3
|
@@ -1,399 +1,3 @@
|
|
|
1
|
-
// src/attributes.ts
|
|
2
|
-
var ATTR = {
|
|
3
|
-
/** = the trace/session id. Wire contract (dedup linkage). */
|
|
4
|
-
SESSION_ID: "tangle.sessionId",
|
|
5
|
-
/** "cli" marks a CLI upload so the platform can drop a re-upload of a session
|
|
6
|
-
* that also streamed live. Wire contract. */
|
|
7
|
-
INGEST_SOURCE: "tangle.ingest_source",
|
|
8
|
-
HARNESS: "tangle.harness",
|
|
9
|
-
CWD: "tangle.cwd",
|
|
10
|
-
/** THE per-session grouping key the spine derives subjects from
|
|
11
|
-
* (`deriveSubjectKey` reads `tangle.subject.key` first). Resolves to the git
|
|
12
|
-
* remote (host/owner/repo) when readable, else the cwd path basename. */
|
|
13
|
-
SUBJECT_KEY: "tangle.subject.key",
|
|
14
|
-
/** Normalized git remote (e.g. `github.com/tangle-network/agent-dev-container`). */
|
|
15
|
-
GIT_REPOSITORY: "git.repository",
|
|
16
|
-
/** Current branch at conversion time. */
|
|
17
|
-
GIT_BRANCH_NAME: "git.branch",
|
|
18
|
-
/** HEAD short sha at conversion time. */
|
|
19
|
-
GIT_COMMIT: "git.commit",
|
|
20
|
-
/** How traces resolved the session cwd: recorded cwd, repaired cwd, or span path. */
|
|
21
|
-
REPO_RESOLUTION_SOURCE: "traces.repo_resolution_source",
|
|
22
|
-
GIT_BRANCH: "tangle.git_branch",
|
|
23
|
-
HOST: "tangle.host",
|
|
24
|
-
/** Basename of the session file. (Renamed from the ambiguous `tangle.source`,
|
|
25
|
-
* which collided with `tangle.ingest_source`.) */
|
|
26
|
-
SESSION_FILE: "tangle.session_file",
|
|
27
|
-
UPLOADED_AT: "tangle.uploaded_at",
|
|
28
|
-
UPLOADER: "tangle.uploader",
|
|
29
|
-
REDACTION_VERSION: "redaction.version",
|
|
30
|
-
REDACTION_COUNT: "redaction.count",
|
|
31
|
-
/** Upload contract: bounded root summary plus location-only receipt child spans.
|
|
32
|
-
* Receipt hashes are deliberate integrity evidence; raw malformed bytes never upload. */
|
|
33
|
-
SESSION_INTEGRITY: "traces.session.integrity",
|
|
34
|
-
CORRUPTION_COUNT: "traces.session.corruption_count",
|
|
35
|
-
CORRUPTION_DIGEST: "traces.session.corruption_digest",
|
|
36
|
-
CORRUPTION_RECEIPT_VERSION: "traces.session.corruption.receipt_version",
|
|
37
|
-
CORRUPTION_RECEIPT_KIND: "traces.session.corruption.kind",
|
|
38
|
-
CORRUPTION_SOURCE_PATH: "traces.session.corruption.source_path",
|
|
39
|
-
CORRUPTION_LINE_NUMBER: "traces.session.corruption.line_number",
|
|
40
|
-
CORRUPTION_BYTE_OFFSET: "traces.session.corruption.byte_offset",
|
|
41
|
-
CORRUPTION_BYTE_LENGTH: "traces.session.corruption.byte_length",
|
|
42
|
-
CORRUPTION_SHA256: "traces.session.corruption.sha256",
|
|
43
|
-
RAW_SOURCE_RETENTION: "traces.session.raw_source_retention"
|
|
44
|
-
};
|
|
45
|
-
var INGEST_SOURCE_CLI = "cli";
|
|
46
|
-
var DEFAULT_HARNESS = "claude-code";
|
|
47
|
-
var SESSION_ID_ATTRIBUTE_KEYS = [ATTR.SESSION_ID, "session.id", "traces.session.id"];
|
|
48
|
-
function sessionIdFromAttributes(attributes) {
|
|
49
|
-
return SESSION_ID_ATTRIBUTE_KEYS.map((key) => attributes[key]).find(
|
|
50
|
-
(value) => typeof value === "string" && value.length > 0
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
function indexSessionIdsByTrace(spans) {
|
|
54
|
-
const candidatesByTrace = /* @__PURE__ */ new Map();
|
|
55
|
-
for (const span2 of spans) {
|
|
56
|
-
const candidates = candidatesByTrace.get(span2.trace_id) ?? /* @__PURE__ */ new Set();
|
|
57
|
-
for (const key of SESSION_ID_ATTRIBUTE_KEYS) {
|
|
58
|
-
const value = span2.attributes[key];
|
|
59
|
-
if (typeof value === "string" && value.length > 0) candidates.add(value);
|
|
60
|
-
}
|
|
61
|
-
if (candidates.size > 0) candidatesByTrace.set(span2.trace_id, candidates);
|
|
62
|
-
}
|
|
63
|
-
const sessionByTrace = /* @__PURE__ */ new Map();
|
|
64
|
-
const conflicts = [];
|
|
65
|
-
for (const [traceId, candidates] of candidatesByTrace) {
|
|
66
|
-
const sessionIds2 = [...candidates].sort();
|
|
67
|
-
if (sessionIds2.length === 1) {
|
|
68
|
-
sessionByTrace.set(traceId, sessionIds2[0]);
|
|
69
|
-
} else {
|
|
70
|
-
conflicts.push({ traceId, sessionIds: sessionIds2 });
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
conflicts.sort((a, b) => a.traceId.localeCompare(b.traceId));
|
|
74
|
-
return { sessionByTrace, conflicts };
|
|
75
|
-
}
|
|
76
|
-
function stampSessionIdentity(spans, fallbackSessionId) {
|
|
77
|
-
const { sessionByTrace, conflicts } = indexSessionIdsByTrace(spans);
|
|
78
|
-
const conflictingTraceIds = new Set(conflicts.map((conflict) => conflict.traceId));
|
|
79
|
-
const traceIds = new Set(spans.map((span2) => span2.trace_id));
|
|
80
|
-
const singleTraceId = traceIds.size === 1 ? traceIds.values().next().value : void 0;
|
|
81
|
-
for (const span2 of spans) {
|
|
82
|
-
if (conflictingTraceIds.has(span2.trace_id)) continue;
|
|
83
|
-
const sessionId = sessionByTrace.get(span2.trace_id) ?? singleTraceId ?? fallbackSessionId;
|
|
84
|
-
if (!sessionIdFromAttributes(span2.attributes)) {
|
|
85
|
-
span2.attributes[ATTR.SESSION_ID] = sessionId;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// src/adoption.ts
|
|
91
|
-
import { readFile } from "fs/promises";
|
|
92
|
-
import { join } from "path";
|
|
93
|
-
|
|
94
|
-
// src/adapters/tool-io.ts
|
|
95
|
-
import { createHash } from "crypto";
|
|
96
|
-
var TOOL_IO_VALUE_MAX_BYTES = 16 * 1024;
|
|
97
|
-
var TOOL_IO_VALUE_KEYS = ["input.value", "output.value"];
|
|
98
|
-
var TOOL_IO_SIDES = ["input", "output"];
|
|
99
|
-
var LEGACY_TRUNCATION_MARKER = /\n\[truncated (\d+) bytes; sha256=[a-f0-9]{64}\]$/;
|
|
100
|
-
var COUNTED_TRUNCATION_MARKER = /\n\[truncated \d+ bytes\]$/;
|
|
101
|
-
var TRUNCATION_MARKER = /\n\[truncated\]$/;
|
|
102
|
-
function canonicalJson(value) {
|
|
103
|
-
const sort = (item) => {
|
|
104
|
-
if (Array.isArray(item)) return item.map(sort);
|
|
105
|
-
if (!item || typeof item !== "object") return item;
|
|
106
|
-
return Object.fromEntries(
|
|
107
|
-
Object.entries(item).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => [key, sort(entry)])
|
|
108
|
-
);
|
|
109
|
-
};
|
|
110
|
-
const json2 = JSON.stringify(sort(value));
|
|
111
|
-
if (json2 === void 0) throw new TypeError("tool I/O value is not JSON-serializable");
|
|
112
|
-
return json2;
|
|
113
|
-
}
|
|
114
|
-
function serialize(value) {
|
|
115
|
-
if (value === void 0) return void 0;
|
|
116
|
-
if (typeof value === "string") {
|
|
117
|
-
const trimmed = value.trim();
|
|
118
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
119
|
-
try {
|
|
120
|
-
return { value: canonicalJson(JSON.parse(trimmed)), mimeType: "application/json" };
|
|
121
|
-
} catch {
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return { value, mimeType: "text/plain" };
|
|
125
|
-
}
|
|
126
|
-
return { value: canonicalJson(value), mimeType: "application/json" };
|
|
127
|
-
}
|
|
128
|
-
function utf8Prefix(value, maxBytes) {
|
|
129
|
-
let bytes = 0;
|
|
130
|
-
let end = 0;
|
|
131
|
-
for (const char of value) {
|
|
132
|
-
const charBytes = Buffer.byteLength(char);
|
|
133
|
-
if (bytes + charBytes > maxBytes) break;
|
|
134
|
-
bytes += charBytes;
|
|
135
|
-
end += char.length;
|
|
136
|
-
}
|
|
137
|
-
return Buffer.from(value.slice(0, end), "utf8").toString("utf8");
|
|
138
|
-
}
|
|
139
|
-
function truncatedValue(value, bytes) {
|
|
140
|
-
const longestMarker = `
|
|
141
|
-
[truncated ${bytes} bytes]`;
|
|
142
|
-
const prefix = utf8Prefix(value, TOOL_IO_VALUE_MAX_BYTES - Buffer.byteLength(longestMarker));
|
|
143
|
-
const omittedBytes = bytes - Buffer.byteLength(prefix);
|
|
144
|
-
return `${prefix}
|
|
145
|
-
[truncated ${omittedBytes} bytes]`;
|
|
146
|
-
}
|
|
147
|
-
function valueAttributes(side, raw) {
|
|
148
|
-
const serialized = serialize(raw);
|
|
149
|
-
if (!serialized) return {};
|
|
150
|
-
const bytes = Buffer.byteLength(serialized.value);
|
|
151
|
-
const sha256 = createHash("sha256").update(serialized.value).digest("hex");
|
|
152
|
-
const truncated = bytes > TOOL_IO_VALUE_MAX_BYTES;
|
|
153
|
-
const value = truncated ? truncatedValue(serialized.value, bytes) : serialized.value;
|
|
154
|
-
return {
|
|
155
|
-
[`${side}.value`]: value,
|
|
156
|
-
[`${side}.mime_type`]: serialized.mimeType,
|
|
157
|
-
[`traces.${side}.bytes`]: bytes,
|
|
158
|
-
[`traces.${side}.sha256`]: sha256,
|
|
159
|
-
[`traces.${side}.truncated`]: truncated
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
function toolIoAttributes(io) {
|
|
163
|
-
return {
|
|
164
|
-
...valueAttributes("input", io.input),
|
|
165
|
-
...valueAttributes("output", io.output),
|
|
166
|
-
...io.argsCaptured === void 0 ? {} : { "tool.args_captured": io.argsCaptured }
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
function toolArgumentsFromAttributes(attributes) {
|
|
170
|
-
const declared = attributes["tool.args_captured"];
|
|
171
|
-
const explicit = typeof declared === "boolean" ? declared : declared === "true" ? true : declared === "false" ? false : void 0;
|
|
172
|
-
const argsCaptured = explicit ?? attributes["input.value"] !== void 0;
|
|
173
|
-
return {
|
|
174
|
-
args: argsCaptured ? attributes["input.value"] : void 0,
|
|
175
|
-
argsCaptured
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
function normalizeToolIoAttributes(attributes) {
|
|
179
|
-
for (const side of TOOL_IO_SIDES) {
|
|
180
|
-
const valueKey = `${side}.value`;
|
|
181
|
-
const mimeKey = `${side}.mime_type`;
|
|
182
|
-
const bytesKey = `traces.${side}.bytes`;
|
|
183
|
-
const digestKey = `traces.${side}.sha256`;
|
|
184
|
-
const truncatedKey = `traces.${side}.truncated`;
|
|
185
|
-
const rawValue = attributes[valueKey];
|
|
186
|
-
if (typeof rawValue !== "string") {
|
|
187
|
-
delete attributes[mimeKey];
|
|
188
|
-
delete attributes[bytesKey];
|
|
189
|
-
delete attributes[digestKey];
|
|
190
|
-
delete attributes[truncatedKey];
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
let value = rawValue.replace(LEGACY_TRUNCATION_MARKER, "\n[truncated]").replace(COUNTED_TRUNCATION_MARKER, "\n[truncated]");
|
|
194
|
-
let truncated = TRUNCATION_MARKER.test(value);
|
|
195
|
-
const unboundedBytes = Buffer.byteLength(value);
|
|
196
|
-
if (unboundedBytes > TOOL_IO_VALUE_MAX_BYTES) {
|
|
197
|
-
value = truncatedValue(value, unboundedBytes);
|
|
198
|
-
truncated = true;
|
|
199
|
-
}
|
|
200
|
-
attributes[valueKey] = value;
|
|
201
|
-
attributes[bytesKey] = Buffer.byteLength(value);
|
|
202
|
-
attributes[digestKey] = createHash("sha256").update(value).digest("hex");
|
|
203
|
-
attributes[truncatedKey] = truncated;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
function recordToolOutput(toolSpan, output) {
|
|
207
|
-
if (!toolSpan) return;
|
|
208
|
-
Object.assign(toolSpan.attributes, valueAttributes("output", output));
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// src/adoption.ts
|
|
212
|
-
function toolName(s) {
|
|
213
|
-
const n = s.attributes["tool.name"];
|
|
214
|
-
if (typeof n === "string") return n;
|
|
215
|
-
if (s.name.startsWith("tool.")) return s.name.slice("tool.".length);
|
|
216
|
-
return null;
|
|
217
|
-
}
|
|
218
|
-
function parseInput(s) {
|
|
219
|
-
const captured = toolArgumentsFromAttributes(s.attributes);
|
|
220
|
-
if (!captured.argsCaptured) return {};
|
|
221
|
-
if (captured.args && typeof captured.args === "object") {
|
|
222
|
-
return captured.args;
|
|
223
|
-
}
|
|
224
|
-
if (typeof captured.args !== "string") return {};
|
|
225
|
-
try {
|
|
226
|
-
const v = JSON.parse(captured.args);
|
|
227
|
-
return v && typeof v === "object" ? v : {};
|
|
228
|
-
} catch {
|
|
229
|
-
return {};
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
function skillNameOf(input) {
|
|
233
|
-
const v = input.skill ?? input.command;
|
|
234
|
-
return typeof v === "string" && v.length > 0 ? v : "?";
|
|
235
|
-
}
|
|
236
|
-
function subagentTypeOf(input) {
|
|
237
|
-
const v = input.subagent_type ?? input.agent_type ?? input.type ?? input.name;
|
|
238
|
-
return typeof v === "string" && v.length > 0 ? v : "?";
|
|
239
|
-
}
|
|
240
|
-
function isSpawnAgentTool(name) {
|
|
241
|
-
return name === "spawn_agent" || name.endsWith("__spawn_agent");
|
|
242
|
-
}
|
|
243
|
-
function skillTelemetryCapability(service) {
|
|
244
|
-
if (service === "claude-code") return "supported";
|
|
245
|
-
if (service === "codex") return "unsupported";
|
|
246
|
-
return "unknown";
|
|
247
|
-
}
|
|
248
|
-
var MATERIALIZED_SKILL_RE = /<skills_instructions>|(?:^|\n)#{1,3}\s+(?:available\s+)?skills\b|base directory for this skill:/i;
|
|
249
|
-
function hasMaterializedSkillEvidence(s) {
|
|
250
|
-
if (s.name !== "message.developer" && s.attributes["tangle.actor"] !== "injected") return false;
|
|
251
|
-
const content = s.attributes.content;
|
|
252
|
-
return typeof content === "string" && MATERIALIZED_SKILL_RE.test(content);
|
|
253
|
-
}
|
|
254
|
-
function hasSkillFileReference(s) {
|
|
255
|
-
if (s.attributes["openinference.span.kind"] !== "TOOL") return false;
|
|
256
|
-
const input = s.attributes["input.value"];
|
|
257
|
-
return typeof input === "string" && input.includes("SKILL.md");
|
|
258
|
-
}
|
|
259
|
-
function telemetryStatus(measured, unsupported, unknown) {
|
|
260
|
-
if (measured > 0 && unsupported === 0 && unknown === 0) return "measured";
|
|
261
|
-
if (measured > 0) return "partial";
|
|
262
|
-
if (unsupported > 0 && unknown === 0) return "unsupported";
|
|
263
|
-
return "unknown";
|
|
264
|
-
}
|
|
265
|
-
function countSkillRunsJsonl(raw) {
|
|
266
|
-
const out = {};
|
|
267
|
-
for (const line of raw.split("\n")) {
|
|
268
|
-
const t = line.trim();
|
|
269
|
-
if (!t) continue;
|
|
270
|
-
let o;
|
|
271
|
-
try {
|
|
272
|
-
o = JSON.parse(t);
|
|
273
|
-
} catch {
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
if (!o || typeof o !== "object") continue;
|
|
277
|
-
const rec = o;
|
|
278
|
-
const names = [];
|
|
279
|
-
if (Array.isArray(rec.skills)) {
|
|
280
|
-
for (const s of rec.skills) if (typeof s === "string" && s) names.push(s);
|
|
281
|
-
}
|
|
282
|
-
if (typeof rec.skill === "string" && rec.skill) names.push(rec.skill);
|
|
283
|
-
if (names.length === 0) names.push("?");
|
|
284
|
-
for (const n of names) out[n] = (out[n] ?? 0) + 1;
|
|
285
|
-
}
|
|
286
|
-
return out;
|
|
287
|
-
}
|
|
288
|
-
async function readLoopRuns(cwds) {
|
|
289
|
-
const counts = {};
|
|
290
|
-
let filesRead = 0;
|
|
291
|
-
const seen = /* @__PURE__ */ new Set();
|
|
292
|
-
for (const cwd of cwds) {
|
|
293
|
-
if (!cwd || seen.has(cwd)) continue;
|
|
294
|
-
seen.add(cwd);
|
|
295
|
-
const path = join(cwd, ".evolve", "skill-runs.jsonl");
|
|
296
|
-
let raw;
|
|
297
|
-
try {
|
|
298
|
-
raw = await readFile(path, "utf8");
|
|
299
|
-
} catch {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
filesRead += 1;
|
|
303
|
-
for (const [k, v] of Object.entries(countSkillRunsJsonl(raw))) counts[k] = (counts[k] ?? 0) + v;
|
|
304
|
-
}
|
|
305
|
-
return { counts, filesRead };
|
|
306
|
-
}
|
|
307
|
-
async function analyzeAdoption(spans, opts = {}) {
|
|
308
|
-
const { sessionByTrace, conflicts } = indexSessionIdsByTrace(spans);
|
|
309
|
-
const identifiedSessions = new Set(sessionByTrace.values());
|
|
310
|
-
const unassignedTraceIds = new Set(
|
|
311
|
-
spans.map((span2) => span2.trace_id).filter((traceId) => !sessionByTrace.has(traceId))
|
|
312
|
-
);
|
|
313
|
-
const executionGroup = (span2) => sessionByTrace.get(span2.trace_id) ?? `trace:${span2.trace_id}`;
|
|
314
|
-
const sessions = /* @__PURE__ */ new Set();
|
|
315
|
-
const sessionsWithSkill = /* @__PURE__ */ new Set();
|
|
316
|
-
const sessionsWithSubagent = /* @__PURE__ */ new Set();
|
|
317
|
-
const sessionsWithMaterializedSkills = /* @__PURE__ */ new Set();
|
|
318
|
-
const sessionsWithSkillFileReference = /* @__PURE__ */ new Set();
|
|
319
|
-
const skillInvocations = {};
|
|
320
|
-
const subagentSpawns = {};
|
|
321
|
-
const canonicalSubagentSessions = /* @__PURE__ */ new Set();
|
|
322
|
-
const fallbackSubagents = /* @__PURE__ */ new Map();
|
|
323
|
-
const sessionCapabilities = /* @__PURE__ */ new Map();
|
|
324
|
-
for (const s of spans) {
|
|
325
|
-
const group = executionGroup(s);
|
|
326
|
-
sessions.add(group);
|
|
327
|
-
const observedCapability = skillTelemetryCapability(s.attributes["service.name"]);
|
|
328
|
-
const currentCapability = sessionCapabilities.get(group) ?? "unknown";
|
|
329
|
-
if (observedCapability === "supported" || currentCapability === "unknown") {
|
|
330
|
-
sessionCapabilities.set(group, observedCapability);
|
|
331
|
-
}
|
|
332
|
-
if (hasMaterializedSkillEvidence(s)) sessionsWithMaterializedSkills.add(group);
|
|
333
|
-
if (hasSkillFileReference(s)) sessionsWithSkillFileReference.add(group);
|
|
334
|
-
const tn = toolName(s);
|
|
335
|
-
if (tn === "Skill") {
|
|
336
|
-
sessionCapabilities.set(group, "supported");
|
|
337
|
-
const name = skillNameOf(parseInput(s));
|
|
338
|
-
skillInvocations[name] = (skillInvocations[name] ?? 0) + 1;
|
|
339
|
-
sessionsWithSkill.add(group);
|
|
340
|
-
} else if (tn === "Task" || tn === "Agent") {
|
|
341
|
-
const type = subagentTypeOf(parseInput(s));
|
|
342
|
-
subagentSpawns[type] = (subagentSpawns[type] ?? 0) + 1;
|
|
343
|
-
sessionsWithSubagent.add(group);
|
|
344
|
-
canonicalSubagentSessions.add(group);
|
|
345
|
-
} else if (tn && isSpawnAgentTool(tn)) {
|
|
346
|
-
const types = fallbackSubagents.get(group) ?? [];
|
|
347
|
-
types.push(subagentTypeOf(parseInput(s)));
|
|
348
|
-
fallbackSubagents.set(group, types);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
for (const [traceId, types] of fallbackSubagents) {
|
|
352
|
-
if (canonicalSubagentSessions.has(traceId)) continue;
|
|
353
|
-
sessionsWithSubagent.add(traceId);
|
|
354
|
-
for (const type of types) {
|
|
355
|
-
subagentSpawns[type] = (subagentSpawns[type] ?? 0) + 1;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
const { counts: loopDispatchedRuns, filesRead } = await readLoopRuns(opts.cwds ?? []);
|
|
359
|
-
const totalSkillInvocations = Object.values(skillInvocations).reduce((a, b) => a + b, 0);
|
|
360
|
-
const totalSubagentSpawns = Object.values(subagentSpawns).reduce((a, b) => a + b, 0);
|
|
361
|
-
const totalLoopDispatchedRuns = Object.values(loopDispatchedRuns).reduce((a, b) => a + b, 0);
|
|
362
|
-
let skillTelemetrySessions = 0;
|
|
363
|
-
let skillTelemetryUnsupportedSessions = 0;
|
|
364
|
-
let skillTelemetryUnknownSessions = 0;
|
|
365
|
-
for (const traceId of sessions) {
|
|
366
|
-
const capability = sessionCapabilities.get(traceId) ?? "unknown";
|
|
367
|
-
if (capability === "supported") skillTelemetrySessions += 1;
|
|
368
|
-
else if (capability === "unsupported") skillTelemetryUnsupportedSessions += 1;
|
|
369
|
-
else skillTelemetryUnknownSessions += 1;
|
|
370
|
-
}
|
|
371
|
-
return {
|
|
372
|
-
executionGroupCount: sessions.size,
|
|
373
|
-
identifiedSessionCount: identifiedSessions.size,
|
|
374
|
-
unassignedTraceCount: unassignedTraceIds.size,
|
|
375
|
-
sessionIdentityConflicts: conflicts,
|
|
376
|
-
sessionsWithSkill: sessionsWithSkill.size,
|
|
377
|
-
skillPenetration: skillTelemetrySessions === 0 ? null : sessionsWithSkill.size / skillTelemetrySessions,
|
|
378
|
-
skillTelemetryStatus: telemetryStatus(
|
|
379
|
-
skillTelemetrySessions,
|
|
380
|
-
skillTelemetryUnsupportedSessions,
|
|
381
|
-
skillTelemetryUnknownSessions
|
|
382
|
-
),
|
|
383
|
-
skillTelemetrySessions,
|
|
384
|
-
sessionsWithMaterializedSkills: sessionsWithMaterializedSkills.size,
|
|
385
|
-
sessionsWithSkillFileReference: sessionsWithSkillFileReference.size,
|
|
386
|
-
skillInvocations,
|
|
387
|
-
totalSkillInvocations,
|
|
388
|
-
subagentSpawns,
|
|
389
|
-
totalSubagentSpawns,
|
|
390
|
-
sessionsWithSubagent: sessionsWithSubagent.size,
|
|
391
|
-
loopDispatchedRuns,
|
|
392
|
-
totalLoopDispatchedRuns,
|
|
393
|
-
skillRunFilesRead: filesRead
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
|
|
397
1
|
// src/otlp.ts
|
|
398
2
|
import {
|
|
399
3
|
applyLlmSpanOtlpAttributes,
|
|
@@ -454,18 +58,136 @@ function toOpenInferenceSpan(s) {
|
|
|
454
58
|
attributes: a
|
|
455
59
|
};
|
|
456
60
|
}
|
|
457
|
-
function serializeSpans(spans) {
|
|
458
|
-
if (spans.length === 0) return "";
|
|
459
|
-
return `${spans.map((s) => JSON.stringify(toOpenInferenceSpan(s))).join("\n")}
|
|
460
|
-
`;
|
|
61
|
+
function serializeSpans(spans) {
|
|
62
|
+
if (spans.length === 0) return "";
|
|
63
|
+
return `${spans.map((s) => JSON.stringify(toOpenInferenceSpan(s))).join("\n")}
|
|
64
|
+
`;
|
|
65
|
+
}
|
|
66
|
+
async function writeOtlpFile(spans, outPath) {
|
|
67
|
+
const { mkdtemp: mkdtemp6, writeFile: writeFile7 } = await import("fs/promises");
|
|
68
|
+
const { tmpdir: tmpdir6 } = await import("os");
|
|
69
|
+
const { join: join20 } = await import("path");
|
|
70
|
+
const path = outPath ?? join20(await mkdtemp6(join20(tmpdir6(), "traces-")), "spans.otlp.jsonl");
|
|
71
|
+
await writeFile7(path, serializeSpans(spans), "utf8");
|
|
72
|
+
return path;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/adapters/conversation.ts
|
|
76
|
+
var CONTENT_CAP = 8e3;
|
|
77
|
+
function capText(raw) {
|
|
78
|
+
const t = raw.trim();
|
|
79
|
+
if (t.length <= CONTENT_CAP) return t;
|
|
80
|
+
return `${t.slice(0, CONTENT_CAP)}\u2026 [+${t.length - CONTENT_CAP} chars]`;
|
|
81
|
+
}
|
|
82
|
+
var ACTOR_ATTR = "tangle.actor";
|
|
83
|
+
function userPromptSpan(o) {
|
|
84
|
+
return span({
|
|
85
|
+
traceId: o.traceId,
|
|
86
|
+
spanId: o.spanId,
|
|
87
|
+
parentSpanId: o.parentSpanId,
|
|
88
|
+
name: "user.prompt",
|
|
89
|
+
kind: "CHAIN",
|
|
90
|
+
startTime: o.startTime,
|
|
91
|
+
service: o.service ?? null,
|
|
92
|
+
agent: o.agent ?? null,
|
|
93
|
+
step: o.step,
|
|
94
|
+
content: o.content,
|
|
95
|
+
extra: { [ACTOR_ATTR]: o.actor ?? "human" }
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/arrays.ts
|
|
100
|
+
function appendAll(target, values) {
|
|
101
|
+
for (const value of values) target.push(value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/attributes.ts
|
|
105
|
+
var ATTR = {
|
|
106
|
+
/** = the trace/session id. Wire contract (dedup linkage). */
|
|
107
|
+
SESSION_ID: "tangle.sessionId",
|
|
108
|
+
/** "cli" marks a CLI upload so the platform can drop a re-upload of a session
|
|
109
|
+
* that also streamed live. Wire contract. */
|
|
110
|
+
INGEST_SOURCE: "tangle.ingest_source",
|
|
111
|
+
HARNESS: "tangle.harness",
|
|
112
|
+
CWD: "tangle.cwd",
|
|
113
|
+
/** THE per-session grouping key the spine derives subjects from
|
|
114
|
+
* (`deriveSubjectKey` reads `tangle.subject.key` first). Resolves to the git
|
|
115
|
+
* remote (host/owner/repo) when readable, else the cwd path basename. */
|
|
116
|
+
SUBJECT_KEY: "tangle.subject.key",
|
|
117
|
+
/** Normalized git remote (e.g. `github.com/tangle-network/agent-dev-container`). */
|
|
118
|
+
GIT_REPOSITORY: "git.repository",
|
|
119
|
+
/** Current branch at conversion time. */
|
|
120
|
+
GIT_BRANCH_NAME: "git.branch",
|
|
121
|
+
/** HEAD short sha at conversion time. */
|
|
122
|
+
GIT_COMMIT: "git.commit",
|
|
123
|
+
/** How traces resolved the session cwd: recorded cwd, repaired cwd, or span path. */
|
|
124
|
+
REPO_RESOLUTION_SOURCE: "traces.repo_resolution_source",
|
|
125
|
+
GIT_BRANCH: "tangle.git_branch",
|
|
126
|
+
HOST: "tangle.host",
|
|
127
|
+
/** Basename of the session file. (Renamed from the ambiguous `tangle.source`,
|
|
128
|
+
* which collided with `tangle.ingest_source`.) */
|
|
129
|
+
SESSION_FILE: "tangle.session_file",
|
|
130
|
+
UPLOADED_AT: "tangle.uploaded_at",
|
|
131
|
+
UPLOADER: "tangle.uploader",
|
|
132
|
+
REDACTION_VERSION: "redaction.version",
|
|
133
|
+
REDACTION_COUNT: "redaction.count",
|
|
134
|
+
/** Upload contract: bounded root summary plus location-only receipt child spans.
|
|
135
|
+
* Receipt hashes are deliberate integrity evidence; raw malformed bytes never upload. */
|
|
136
|
+
SESSION_INTEGRITY: "traces.session.integrity",
|
|
137
|
+
CORRUPTION_COUNT: "traces.session.corruption_count",
|
|
138
|
+
CORRUPTION_DIGEST: "traces.session.corruption_digest",
|
|
139
|
+
CORRUPTION_RECEIPT_VERSION: "traces.session.corruption.receipt_version",
|
|
140
|
+
CORRUPTION_RECEIPT_KIND: "traces.session.corruption.kind",
|
|
141
|
+
CORRUPTION_SOURCE_PATH: "traces.session.corruption.source_path",
|
|
142
|
+
CORRUPTION_LINE_NUMBER: "traces.session.corruption.line_number",
|
|
143
|
+
CORRUPTION_BYTE_OFFSET: "traces.session.corruption.byte_offset",
|
|
144
|
+
CORRUPTION_BYTE_LENGTH: "traces.session.corruption.byte_length",
|
|
145
|
+
CORRUPTION_SHA256: "traces.session.corruption.sha256",
|
|
146
|
+
RAW_SOURCE_RETENTION: "traces.session.raw_source_retention"
|
|
147
|
+
};
|
|
148
|
+
var INGEST_SOURCE_CLI = "cli";
|
|
149
|
+
var DEFAULT_HARNESS = "claude-code";
|
|
150
|
+
var SESSION_ID_ATTRIBUTE_KEYS = [ATTR.SESSION_ID, "session.id", "traces.session.id"];
|
|
151
|
+
function sessionIdFromAttributes(attributes) {
|
|
152
|
+
return SESSION_ID_ATTRIBUTE_KEYS.map((key) => attributes[key]).find(
|
|
153
|
+
(value) => typeof value === "string" && value.length > 0
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
function indexSessionIdsByTrace(spans) {
|
|
157
|
+
const candidatesByTrace = /* @__PURE__ */ new Map();
|
|
158
|
+
for (const span2 of spans) {
|
|
159
|
+
const candidates = candidatesByTrace.get(span2.trace_id) ?? /* @__PURE__ */ new Set();
|
|
160
|
+
for (const key of SESSION_ID_ATTRIBUTE_KEYS) {
|
|
161
|
+
const value = span2.attributes[key];
|
|
162
|
+
if (typeof value === "string" && value.length > 0) candidates.add(value);
|
|
163
|
+
}
|
|
164
|
+
if (candidates.size > 0) candidatesByTrace.set(span2.trace_id, candidates);
|
|
165
|
+
}
|
|
166
|
+
const sessionByTrace = /* @__PURE__ */ new Map();
|
|
167
|
+
const conflicts = [];
|
|
168
|
+
for (const [traceId, candidates] of candidatesByTrace) {
|
|
169
|
+
const sessionIds2 = [...candidates].sort();
|
|
170
|
+
if (sessionIds2.length === 1) {
|
|
171
|
+
sessionByTrace.set(traceId, sessionIds2[0]);
|
|
172
|
+
} else {
|
|
173
|
+
conflicts.push({ traceId, sessionIds: sessionIds2 });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
conflicts.sort((a, b) => a.traceId.localeCompare(b.traceId));
|
|
177
|
+
return { sessionByTrace, conflicts };
|
|
461
178
|
}
|
|
462
|
-
|
|
463
|
-
const {
|
|
464
|
-
const
|
|
465
|
-
const
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
179
|
+
function stampSessionIdentity(spans, fallbackSessionId) {
|
|
180
|
+
const { sessionByTrace, conflicts } = indexSessionIdsByTrace(spans);
|
|
181
|
+
const conflictingTraceIds = new Set(conflicts.map((conflict) => conflict.traceId));
|
|
182
|
+
const traceIds = new Set(spans.map((span2) => span2.trace_id));
|
|
183
|
+
const singleTraceId = traceIds.size === 1 ? traceIds.values().next().value : void 0;
|
|
184
|
+
for (const span2 of spans) {
|
|
185
|
+
if (conflictingTraceIds.has(span2.trace_id)) continue;
|
|
186
|
+
const sessionId = sessionByTrace.get(span2.trace_id) ?? singleTraceId ?? fallbackSessionId;
|
|
187
|
+
if (!sessionIdFromAttributes(span2.attributes)) {
|
|
188
|
+
span2.attributes[ATTR.SESSION_ID] = sessionId;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
469
191
|
}
|
|
470
192
|
|
|
471
193
|
// src/execution.ts
|
|
@@ -497,68 +219,6 @@ function summarizeSpanExecution(spans, opts = {}) {
|
|
|
497
219
|
});
|
|
498
220
|
}
|
|
499
221
|
|
|
500
|
-
// src/analyze.ts
|
|
501
|
-
import { buildDefaultAnalystRegistry } from "@tangle-network/agent-eval/analyst";
|
|
502
|
-
import { OtlpFileTraceStore } from "@tangle-network/agent-eval/traces";
|
|
503
|
-
var GENERATED_TRACE_FILE_CEILING = 512 * 1024 * 1024;
|
|
504
|
-
async function analyzeSpans(spans, opts = {}) {
|
|
505
|
-
if (spans.length === 0) throw new Error("analyzeSpans: no spans to analyze");
|
|
506
|
-
const otlpPath = await writeOtlpFile(spans, opts.otlpOutPath);
|
|
507
|
-
const runId = opts.runId ?? `traces-${Date.now()}`;
|
|
508
|
-
const execution = summarizeSpanExecution(spans, {
|
|
509
|
-
experimentId: runId
|
|
510
|
-
});
|
|
511
|
-
const detStore = new OtlpFileTraceStore({
|
|
512
|
-
path: otlpPath,
|
|
513
|
-
maxFileBytes: GENERATED_TRACE_FILE_CEILING,
|
|
514
|
-
perCallByteCeiling: GENERATED_TRACE_FILE_CEILING
|
|
515
|
-
});
|
|
516
|
-
await detStore.ensureIndexed();
|
|
517
|
-
const detRegistry = opts.registry ?? buildDefaultAnalystRegistry({ registry: { log: opts.log } });
|
|
518
|
-
const result = await detRegistry.run(runId, { traceStore: detStore });
|
|
519
|
-
if (opts.ai) {
|
|
520
|
-
const agStore = new OtlpFileTraceStore({ path: otlpPath, maxFileBytes: GENERATED_TRACE_FILE_CEILING });
|
|
521
|
-
await agStore.ensureIndexed();
|
|
522
|
-
const agRegistry = buildDefaultAnalystRegistry({
|
|
523
|
-
ai: opts.ai,
|
|
524
|
-
model: opts.model,
|
|
525
|
-
includeBehavioral: false,
|
|
526
|
-
registry: { log: opts.log }
|
|
527
|
-
});
|
|
528
|
-
const agResult = await agRegistry.run(runId, { traceStore: agStore }, {
|
|
529
|
-
budget: opts.budgetUsd != null ? { totalUsd: opts.budgetUsd } : void 0
|
|
530
|
-
});
|
|
531
|
-
result.findings.push(...agResult.findings);
|
|
532
|
-
result.per_analyst.push(...agResult.per_analyst);
|
|
533
|
-
result.total_cost_usd += agResult.total_cost_usd;
|
|
534
|
-
}
|
|
535
|
-
return { otlpPath, execution, result };
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// src/adapters/conversation.ts
|
|
539
|
-
var CONTENT_CAP = 8e3;
|
|
540
|
-
function capText(raw) {
|
|
541
|
-
const t = raw.trim();
|
|
542
|
-
if (t.length <= CONTENT_CAP) return t;
|
|
543
|
-
return `${t.slice(0, CONTENT_CAP)}\u2026 [+${t.length - CONTENT_CAP} chars]`;
|
|
544
|
-
}
|
|
545
|
-
var ACTOR_ATTR = "tangle.actor";
|
|
546
|
-
function userPromptSpan(o) {
|
|
547
|
-
return span({
|
|
548
|
-
traceId: o.traceId,
|
|
549
|
-
spanId: o.spanId,
|
|
550
|
-
parentSpanId: o.parentSpanId,
|
|
551
|
-
name: "user.prompt",
|
|
552
|
-
kind: "CHAIN",
|
|
553
|
-
startTime: o.startTime,
|
|
554
|
-
service: o.service ?? null,
|
|
555
|
-
agent: o.agent ?? null,
|
|
556
|
-
step: o.step,
|
|
557
|
-
content: o.content,
|
|
558
|
-
extra: { [ACTOR_ATTR]: o.actor ?? "human" }
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
|
|
562
222
|
// src/time.ts
|
|
563
223
|
function parseIsoToEpochMs(ts) {
|
|
564
224
|
if (!ts) return 0;
|
|
@@ -580,6 +240,125 @@ function parseSince(value) {
|
|
|
580
240
|
|
|
581
241
|
// src/runtime-store.ts
|
|
582
242
|
import { InMemoryTraceStore } from "@tangle-network/agent-eval";
|
|
243
|
+
|
|
244
|
+
// src/adapters/tool-io.ts
|
|
245
|
+
import { createHash } from "crypto";
|
|
246
|
+
var TOOL_IO_VALUE_MAX_BYTES = 16 * 1024;
|
|
247
|
+
var TOOL_IO_VALUE_KEYS = ["input.value", "output.value"];
|
|
248
|
+
var TOOL_IO_SIDES = ["input", "output"];
|
|
249
|
+
var LEGACY_TRUNCATION_MARKER = /\n\[truncated (\d+) bytes; sha256=[a-f0-9]{64}\]$/;
|
|
250
|
+
var COUNTED_TRUNCATION_MARKER = /\n\[truncated \d+ bytes\]$/;
|
|
251
|
+
var TRUNCATION_MARKER = /\n\[truncated\]$/;
|
|
252
|
+
function canonicalJson(value) {
|
|
253
|
+
const sort = (item) => {
|
|
254
|
+
if (Array.isArray(item)) return item.map(sort);
|
|
255
|
+
if (!item || typeof item !== "object") return item;
|
|
256
|
+
return Object.fromEntries(
|
|
257
|
+
Object.entries(item).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => [key, sort(entry)])
|
|
258
|
+
);
|
|
259
|
+
};
|
|
260
|
+
const json2 = JSON.stringify(sort(value));
|
|
261
|
+
if (json2 === void 0) throw new TypeError("tool I/O value is not JSON-serializable");
|
|
262
|
+
return json2;
|
|
263
|
+
}
|
|
264
|
+
function serialize(value) {
|
|
265
|
+
if (value === void 0) return void 0;
|
|
266
|
+
if (typeof value === "string") {
|
|
267
|
+
const trimmed = value.trim();
|
|
268
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
269
|
+
try {
|
|
270
|
+
return { value: canonicalJson(JSON.parse(trimmed)), mimeType: "application/json" };
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return { value, mimeType: "text/plain" };
|
|
275
|
+
}
|
|
276
|
+
return { value: canonicalJson(value), mimeType: "application/json" };
|
|
277
|
+
}
|
|
278
|
+
function utf8Prefix(value, maxBytes) {
|
|
279
|
+
let bytes = 0;
|
|
280
|
+
let end = 0;
|
|
281
|
+
for (const char of value) {
|
|
282
|
+
const charBytes = Buffer.byteLength(char);
|
|
283
|
+
if (bytes + charBytes > maxBytes) break;
|
|
284
|
+
bytes += charBytes;
|
|
285
|
+
end += char.length;
|
|
286
|
+
}
|
|
287
|
+
return Buffer.from(value.slice(0, end), "utf8").toString("utf8");
|
|
288
|
+
}
|
|
289
|
+
function truncatedValue(value, bytes) {
|
|
290
|
+
const longestMarker = `
|
|
291
|
+
[truncated ${bytes} bytes]`;
|
|
292
|
+
const prefix = utf8Prefix(value, TOOL_IO_VALUE_MAX_BYTES - Buffer.byteLength(longestMarker));
|
|
293
|
+
const omittedBytes = bytes - Buffer.byteLength(prefix);
|
|
294
|
+
return `${prefix}
|
|
295
|
+
[truncated ${omittedBytes} bytes]`;
|
|
296
|
+
}
|
|
297
|
+
function valueAttributes(side, raw) {
|
|
298
|
+
const serialized = serialize(raw);
|
|
299
|
+
if (!serialized) return {};
|
|
300
|
+
const bytes = Buffer.byteLength(serialized.value);
|
|
301
|
+
const sha256 = createHash("sha256").update(serialized.value).digest("hex");
|
|
302
|
+
const truncated = bytes > TOOL_IO_VALUE_MAX_BYTES;
|
|
303
|
+
const value = truncated ? truncatedValue(serialized.value, bytes) : serialized.value;
|
|
304
|
+
return {
|
|
305
|
+
[`${side}.value`]: value,
|
|
306
|
+
[`${side}.mime_type`]: serialized.mimeType,
|
|
307
|
+
[`traces.${side}.bytes`]: bytes,
|
|
308
|
+
[`traces.${side}.sha256`]: sha256,
|
|
309
|
+
[`traces.${side}.truncated`]: truncated
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function toolIoAttributes(io) {
|
|
313
|
+
return {
|
|
314
|
+
...valueAttributes("input", io.input),
|
|
315
|
+
...valueAttributes("output", io.output),
|
|
316
|
+
...io.argsCaptured === void 0 ? {} : { "tool.args_captured": io.argsCaptured }
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function toolArgumentsFromAttributes(attributes) {
|
|
320
|
+
const declared = attributes["tool.args_captured"];
|
|
321
|
+
const explicit = typeof declared === "boolean" ? declared : declared === "true" ? true : declared === "false" ? false : void 0;
|
|
322
|
+
const argsCaptured = explicit ?? attributes["input.value"] !== void 0;
|
|
323
|
+
return {
|
|
324
|
+
args: argsCaptured ? attributes["input.value"] : void 0,
|
|
325
|
+
argsCaptured
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function normalizeToolIoAttributes(attributes) {
|
|
329
|
+
for (const side of TOOL_IO_SIDES) {
|
|
330
|
+
const valueKey = `${side}.value`;
|
|
331
|
+
const mimeKey = `${side}.mime_type`;
|
|
332
|
+
const bytesKey = `traces.${side}.bytes`;
|
|
333
|
+
const digestKey = `traces.${side}.sha256`;
|
|
334
|
+
const truncatedKey = `traces.${side}.truncated`;
|
|
335
|
+
const rawValue = attributes[valueKey];
|
|
336
|
+
if (typeof rawValue !== "string") {
|
|
337
|
+
delete attributes[mimeKey];
|
|
338
|
+
delete attributes[bytesKey];
|
|
339
|
+
delete attributes[digestKey];
|
|
340
|
+
delete attributes[truncatedKey];
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
let value = rawValue.replace(LEGACY_TRUNCATION_MARKER, "\n[truncated]").replace(COUNTED_TRUNCATION_MARKER, "\n[truncated]");
|
|
344
|
+
let truncated = TRUNCATION_MARKER.test(value);
|
|
345
|
+
const unboundedBytes = Buffer.byteLength(value);
|
|
346
|
+
if (unboundedBytes > TOOL_IO_VALUE_MAX_BYTES) {
|
|
347
|
+
value = truncatedValue(value, unboundedBytes);
|
|
348
|
+
truncated = true;
|
|
349
|
+
}
|
|
350
|
+
attributes[valueKey] = value;
|
|
351
|
+
attributes[bytesKey] = Buffer.byteLength(value);
|
|
352
|
+
attributes[digestKey] = createHash("sha256").update(value).digest("hex");
|
|
353
|
+
attributes[truncatedKey] = truncated;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function recordToolOutput(toolSpan, output) {
|
|
357
|
+
if (!toolSpan) return;
|
|
358
|
+
Object.assign(toolSpan.attributes, valueAttributes("output", output));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/runtime-store.ts
|
|
583
362
|
var KIND = {
|
|
584
363
|
AGENT: "agent",
|
|
585
364
|
LLM: "llm",
|
|
@@ -766,7 +545,7 @@ function stampSessionIntegrity(ref, spans) {
|
|
|
766
545
|
}
|
|
767
546
|
|
|
768
547
|
// src/json.ts
|
|
769
|
-
import { readdir, readFile
|
|
548
|
+
import { readdir, readFile } from "fs/promises";
|
|
770
549
|
var JsonSourceError = class extends Error {
|
|
771
550
|
sourcePath;
|
|
772
551
|
kind;
|
|
@@ -801,7 +580,7 @@ function isMissingJsonSource(error) {
|
|
|
801
580
|
async function readJsonFile(path) {
|
|
802
581
|
let raw;
|
|
803
582
|
try {
|
|
804
|
-
raw = await
|
|
583
|
+
raw = await readFile(path, "utf8");
|
|
805
584
|
} catch (error) {
|
|
806
585
|
throw new JsonSourceError(path, "read", error);
|
|
807
586
|
}
|
|
@@ -822,7 +601,7 @@ async function listJsonFiles(path) {
|
|
|
822
601
|
// src/adapters/amp.ts
|
|
823
602
|
import { readdir as readdir2, stat } from "fs/promises";
|
|
824
603
|
import { homedir } from "os";
|
|
825
|
-
import { basename, join
|
|
604
|
+
import { basename, join } from "path";
|
|
826
605
|
var SERVICE = "amp";
|
|
827
606
|
function textOf(content) {
|
|
828
607
|
if (typeof content === "string") return content.slice(0, CONTENT_CAP);
|
|
@@ -833,8 +612,8 @@ function textOf(content) {
|
|
|
833
612
|
var AmpAdapter = class {
|
|
834
613
|
harness = "amp";
|
|
835
614
|
threadsRoot() {
|
|
836
|
-
const base = process.env.XDG_DATA_HOME ??
|
|
837
|
-
return
|
|
615
|
+
const base = process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
|
|
616
|
+
return join(base, "amp", "threads");
|
|
838
617
|
}
|
|
839
618
|
async locate(opts = {}) {
|
|
840
619
|
let files;
|
|
@@ -847,7 +626,7 @@ var AmpAdapter = class {
|
|
|
847
626
|
const refs = [];
|
|
848
627
|
for (const f of files) {
|
|
849
628
|
if (!f.startsWith("T-") || !f.endsWith(".json")) continue;
|
|
850
|
-
const path =
|
|
629
|
+
const path = join(this.threadsRoot(), f);
|
|
851
630
|
let st;
|
|
852
631
|
try {
|
|
853
632
|
st = await stat(path);
|
|
@@ -1068,7 +847,7 @@ var INJECT_MARKERS = [
|
|
|
1068
847
|
// Prefix match: Codex adds attributes such as source="goal" to this tag.
|
|
1069
848
|
"<codex_internal_context",
|
|
1070
849
|
"<subagent_notification>",
|
|
1071
|
-
"# AGENTS.md instructions
|
|
850
|
+
"# AGENTS.md instructions",
|
|
1072
851
|
"<local-command-stdout>",
|
|
1073
852
|
"This session is being continued from a previous conversation",
|
|
1074
853
|
// Slash-command skill bodies are expanded into a user turn (no <command-name>
|
|
@@ -1143,7 +922,7 @@ var CORRECTIVE_REACTIONS = ["correction", "frustration", "jargon", "structure"];
|
|
|
1143
922
|
// src/adapters/claude.ts
|
|
1144
923
|
import { readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
1145
924
|
import { homedir as homedir2 } from "os";
|
|
1146
|
-
import { basename as basename2, join as
|
|
925
|
+
import { basename as basename2, join as join2 } from "path";
|
|
1147
926
|
var SERVICE2 = "claude-code";
|
|
1148
927
|
function asBlocks(content) {
|
|
1149
928
|
return Array.isArray(content) ? content : [];
|
|
@@ -1265,7 +1044,7 @@ var ClaudeAdapter = class {
|
|
|
1265
1044
|
harness = "claude-code";
|
|
1266
1045
|
aliases = ["claude", "claudish", "openclaw", "nanoclaw"];
|
|
1267
1046
|
root() {
|
|
1268
|
-
return
|
|
1047
|
+
return join2(homedir2(), ".claude", "projects");
|
|
1269
1048
|
}
|
|
1270
1049
|
async locate(opts = {}) {
|
|
1271
1050
|
const root = this.root();
|
|
@@ -1278,7 +1057,7 @@ var ClaudeAdapter = class {
|
|
|
1278
1057
|
}
|
|
1279
1058
|
const refs = [];
|
|
1280
1059
|
for (const dir of projectDirs) {
|
|
1281
|
-
const dirPath =
|
|
1060
|
+
const dirPath = join2(root, dir);
|
|
1282
1061
|
let files;
|
|
1283
1062
|
try {
|
|
1284
1063
|
files = await readdir3(dirPath);
|
|
@@ -1288,7 +1067,7 @@ var ClaudeAdapter = class {
|
|
|
1288
1067
|
}
|
|
1289
1068
|
for (const f of files) {
|
|
1290
1069
|
if (!f.endsWith(".jsonl")) continue;
|
|
1291
|
-
const path =
|
|
1070
|
+
const path = join2(dirPath, f);
|
|
1292
1071
|
let st;
|
|
1293
1072
|
try {
|
|
1294
1073
|
st = await stat2(path);
|
|
@@ -1347,15 +1126,15 @@ var ClaudeAdapter = class {
|
|
|
1347
1126
|
endTime: lastTimestamp,
|
|
1348
1127
|
service: SERVICE2,
|
|
1349
1128
|
agent: SERVICE2
|
|
1350
|
-
})
|
|
1351
|
-
...main.spans
|
|
1129
|
+
})
|
|
1352
1130
|
];
|
|
1131
|
+
appendAll(spans, main.spans);
|
|
1353
1132
|
await this.foldSubagents(ref, traceId, main, spans, options);
|
|
1354
1133
|
return spans;
|
|
1355
1134
|
}
|
|
1356
1135
|
/** Parse `<session>/subagents/agent-*.jsonl`, parenting each under its Agent call. */
|
|
1357
1136
|
async foldSubagents(ref, traceId, main, out, options) {
|
|
1358
|
-
const subDir =
|
|
1137
|
+
const subDir = join2(ref.path.replace(/\.jsonl$/, ""), "subagents");
|
|
1359
1138
|
let files;
|
|
1360
1139
|
try {
|
|
1361
1140
|
files = await readdir3(subDir);
|
|
@@ -1367,7 +1146,7 @@ var ClaudeAdapter = class {
|
|
|
1367
1146
|
for (const file of files) {
|
|
1368
1147
|
if (!file.endsWith(".jsonl")) continue;
|
|
1369
1148
|
const hash2 = basename2(file, ".jsonl");
|
|
1370
|
-
const metaPath =
|
|
1149
|
+
const metaPath = join2(subDir, `${hash2}.meta.json`);
|
|
1371
1150
|
let meta = {};
|
|
1372
1151
|
try {
|
|
1373
1152
|
meta = await readJsonFile(metaPath);
|
|
@@ -1384,13 +1163,13 @@ var ClaudeAdapter = class {
|
|
|
1384
1163
|
};
|
|
1385
1164
|
const state = createClaudeStream(ctx.startStep);
|
|
1386
1165
|
for await (const event of readJsonl(
|
|
1387
|
-
|
|
1166
|
+
join2(subDir, file),
|
|
1388
1167
|
sessionJsonlOptions(ref, options)
|
|
1389
1168
|
)) {
|
|
1390
1169
|
consumeClaudeEvent(event, ctx, state);
|
|
1391
1170
|
}
|
|
1392
1171
|
const parsed = finishClaudeStream(state);
|
|
1393
|
-
out
|
|
1172
|
+
appendAll(out, parsed.spans);
|
|
1394
1173
|
step = parsed.nextStep;
|
|
1395
1174
|
}
|
|
1396
1175
|
}
|
|
@@ -1399,7 +1178,7 @@ var ClaudeAdapter = class {
|
|
|
1399
1178
|
// src/adapters/codex.ts
|
|
1400
1179
|
import { readdir as readdir4, stat as stat3 } from "fs/promises";
|
|
1401
1180
|
import { homedir as homedir3 } from "os";
|
|
1402
|
-
import { basename as basename3, join as
|
|
1181
|
+
import { basename as basename3, join as join3 } from "path";
|
|
1403
1182
|
var SERVICE3 = "codex";
|
|
1404
1183
|
var SESSION_HEAD_LINES = 40;
|
|
1405
1184
|
function contentToString(content) {
|
|
@@ -1407,15 +1186,87 @@ function contentToString(content) {
|
|
|
1407
1186
|
if (Array.isArray(content)) {
|
|
1408
1187
|
return content.map((c) => c && typeof c === "object" && "text" in c ? String(c.text ?? "") : "").join("");
|
|
1409
1188
|
}
|
|
1410
|
-
return "";
|
|
1411
|
-
}
|
|
1412
|
-
function textOf3(content) {
|
|
1413
|
-
return capText(contentToString(content));
|
|
1189
|
+
return "";
|
|
1190
|
+
}
|
|
1191
|
+
function textOf3(content) {
|
|
1192
|
+
return capText(contentToString(content));
|
|
1193
|
+
}
|
|
1194
|
+
function currentTaskStartedAt(meta) {
|
|
1195
|
+
const timestamp = Date.parse(meta?.payload?.timestamp ?? meta?.timestamp ?? "");
|
|
1196
|
+
return Number.isFinite(timestamp) ? Math.floor(timestamp / 1e3) : void 0;
|
|
1197
|
+
}
|
|
1198
|
+
function isCurrentTaskStart(line, startedAt) {
|
|
1199
|
+
return line.type === "event_msg" && line.payload?.type === "task_started" && typeof line.payload.started_at === "number" && Math.abs(line.payload.started_at - startedAt) <= 2;
|
|
1200
|
+
}
|
|
1201
|
+
async function hasCurrentTaskStart(path, options, startedAt) {
|
|
1202
|
+
for await (const line of readJsonl(path, options)) {
|
|
1203
|
+
if (isCurrentTaskStart(line, startedAt)) return true;
|
|
1204
|
+
}
|
|
1205
|
+
return false;
|
|
1206
|
+
}
|
|
1207
|
+
function numericStatus(value) {
|
|
1208
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1209
|
+
if (typeof value === "string" && /^-?\d+$/.test(value.trim())) return Number(value);
|
|
1210
|
+
return void 0;
|
|
1211
|
+
}
|
|
1212
|
+
function explicitOutputError(value) {
|
|
1213
|
+
if (Array.isArray(value)) {
|
|
1214
|
+
let observedSuccess = false;
|
|
1215
|
+
for (const item of value) {
|
|
1216
|
+
const status = explicitOutputError(item);
|
|
1217
|
+
if (status === true) return true;
|
|
1218
|
+
if (status === false) observedSuccess = true;
|
|
1219
|
+
}
|
|
1220
|
+
return observedSuccess ? false : void 0;
|
|
1221
|
+
}
|
|
1222
|
+
if (value && typeof value === "object") {
|
|
1223
|
+
const row = value;
|
|
1224
|
+
for (const key of ["exit_code", "exitCode"]) {
|
|
1225
|
+
const code = numericStatus(row[key]);
|
|
1226
|
+
if (code !== void 0 && ("output" in row || "chunk_id" in row || "wall_time_seconds" in row)) {
|
|
1227
|
+
return code !== 0;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
for (const key of ["timed_out", "timedOut"]) {
|
|
1231
|
+
if (row[key] === true) return true;
|
|
1232
|
+
}
|
|
1233
|
+
if (typeof row.succeeded === "boolean" && ("value" in row || "error" in row)) {
|
|
1234
|
+
return !row.succeeded;
|
|
1235
|
+
}
|
|
1236
|
+
if ((row.type === "input_text" || row.type === "text") && typeof row.text === "string") {
|
|
1237
|
+
return explicitOutputError(row.text);
|
|
1238
|
+
}
|
|
1239
|
+
return void 0;
|
|
1240
|
+
}
|
|
1241
|
+
if (typeof value !== "string") return void 0;
|
|
1242
|
+
const text = value.trim();
|
|
1243
|
+
if (!text) return void 0;
|
|
1244
|
+
if (text.startsWith("{") && text.endsWith("}") || text.startsWith("[") && text.endsWith("]")) {
|
|
1245
|
+
try {
|
|
1246
|
+
const parsedStatus = explicitOutputError(JSON.parse(text));
|
|
1247
|
+
if (parsedStatus !== void 0) return parsedStatus;
|
|
1248
|
+
} catch {
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
const outputStart = text.indexOf("\nOutput:\n");
|
|
1252
|
+
const header = outputStart >= 0 ? text.slice(0, outputStart) : text;
|
|
1253
|
+
if (/^Chunk ID:/i.test(header)) {
|
|
1254
|
+
const exitCode = header.match(/^Process exited with code\s+(-?\d+)\b/im)?.[1];
|
|
1255
|
+
if (exitCode !== void 0) return Number(exitCode) !== 0;
|
|
1256
|
+
}
|
|
1257
|
+
if (/^Script completed\s*\nWall time:/i.test(header)) return false;
|
|
1258
|
+
if (/^Script failed\s*\nWall time:/i.test(header)) return true;
|
|
1259
|
+
const scriptExitCode = header.match(/^Script error:\s*\nExit code:\s*(-?\d+)\b/im)?.[1];
|
|
1260
|
+
if (scriptExitCode !== void 0) return Number(scriptExitCode) !== 0;
|
|
1261
|
+
const commandExitCode = text.match(/^Command failed with exit code\s+(-?\d+)\.?$/i)?.[1];
|
|
1262
|
+
if (commandExitCode !== void 0) return Number(commandExitCode) !== 0;
|
|
1263
|
+
if (/^<tool_error>[\s\S]*<\/tool_error>$/i.test(text)) return true;
|
|
1264
|
+
return void 0;
|
|
1414
1265
|
}
|
|
1415
1266
|
function outputIsError(output) {
|
|
1416
|
-
const
|
|
1417
|
-
const
|
|
1418
|
-
return { error, message: error ?
|
|
1267
|
+
const error = explicitOutputError(output) === true;
|
|
1268
|
+
const message = typeof output === "string" ? output : JSON.stringify(output ?? "");
|
|
1269
|
+
return { error, message: error ? message.slice(0, 500) : "" };
|
|
1419
1270
|
}
|
|
1420
1271
|
function singleNestedToolName(input) {
|
|
1421
1272
|
if (!input) return null;
|
|
@@ -1475,7 +1326,7 @@ async function* walkRollouts(root) {
|
|
|
1475
1326
|
throw error;
|
|
1476
1327
|
}
|
|
1477
1328
|
for (const y of years) {
|
|
1478
|
-
const yp =
|
|
1329
|
+
const yp = join3(root, y);
|
|
1479
1330
|
let months;
|
|
1480
1331
|
try {
|
|
1481
1332
|
months = await readdir4(yp);
|
|
@@ -1484,7 +1335,7 @@ async function* walkRollouts(root) {
|
|
|
1484
1335
|
throw error;
|
|
1485
1336
|
}
|
|
1486
1337
|
for (const m of months) {
|
|
1487
|
-
const mp =
|
|
1338
|
+
const mp = join3(yp, m);
|
|
1488
1339
|
let days;
|
|
1489
1340
|
try {
|
|
1490
1341
|
days = await readdir4(mp);
|
|
@@ -1493,7 +1344,7 @@ async function* walkRollouts(root) {
|
|
|
1493
1344
|
throw error;
|
|
1494
1345
|
}
|
|
1495
1346
|
for (const d of days) {
|
|
1496
|
-
const dp =
|
|
1347
|
+
const dp = join3(mp, d);
|
|
1497
1348
|
let files;
|
|
1498
1349
|
try {
|
|
1499
1350
|
files = await readdir4(dp);
|
|
@@ -1502,7 +1353,7 @@ async function* walkRollouts(root) {
|
|
|
1502
1353
|
throw error;
|
|
1503
1354
|
}
|
|
1504
1355
|
for (const f of files) {
|
|
1505
|
-
if (f.startsWith("rollout-") && f.endsWith(".jsonl")) yield
|
|
1356
|
+
if (f.startsWith("rollout-") && f.endsWith(".jsonl")) yield join3(dp, f);
|
|
1506
1357
|
}
|
|
1507
1358
|
}
|
|
1508
1359
|
}
|
|
@@ -1512,7 +1363,7 @@ var CodexAdapter = class {
|
|
|
1512
1363
|
harness = "codex";
|
|
1513
1364
|
aliases = ["codex-acp"];
|
|
1514
1365
|
root() {
|
|
1515
|
-
return process.env.CODEX_HOME ?
|
|
1366
|
+
return process.env.CODEX_HOME ? join3(process.env.CODEX_HOME, "sessions") : join3(homedir3(), ".codex", "sessions");
|
|
1516
1367
|
}
|
|
1517
1368
|
async locate(opts = {}) {
|
|
1518
1369
|
const refs = [];
|
|
@@ -1550,7 +1401,9 @@ var CodexAdapter = class {
|
|
|
1550
1401
|
const head = await takeJsonl(ref.path, SESSION_HEAD_LINES, jsonl2);
|
|
1551
1402
|
let first = head[0];
|
|
1552
1403
|
let meta = head.find((line) => line.type === "session_meta");
|
|
1553
|
-
|
|
1404
|
+
const taskStartedAt = currentTaskStartedAt(meta);
|
|
1405
|
+
const hasTaskBoundary = taskStartedAt !== void 0 && await hasCurrentTaskStart(ref.path, jsonl2, taskStartedAt);
|
|
1406
|
+
let model = hasTaskBoundary ? null : head.find((line) => line.type === "turn_context")?.payload?.model ?? null;
|
|
1554
1407
|
if (!first || !meta) {
|
|
1555
1408
|
for await (const line of readJsonl(ref.path, jsonl2)) {
|
|
1556
1409
|
first ??= line;
|
|
@@ -1591,7 +1444,12 @@ var CodexAdapter = class {
|
|
|
1591
1444
|
let sawUserTurn = false;
|
|
1592
1445
|
let lastTimestamp;
|
|
1593
1446
|
const awaitingModel = model ? [] : [root];
|
|
1447
|
+
let reachedCurrentTask = !hasTaskBoundary;
|
|
1594
1448
|
for await (const l of readJsonl(ref.path, jsonl2)) {
|
|
1449
|
+
if (!reachedCurrentTask) {
|
|
1450
|
+
if (isCurrentTaskStart(l, taskStartedAt)) reachedCurrentTask = true;
|
|
1451
|
+
else continue;
|
|
1452
|
+
}
|
|
1595
1453
|
lastTimestamp = l.timestamp;
|
|
1596
1454
|
const ts = l.timestamp ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
1597
1455
|
if (!model && l.type === "turn_context" && l.payload?.model) {
|
|
@@ -1762,13 +1620,13 @@ var CodexAdapter = class {
|
|
|
1762
1620
|
// src/adapters/copilot.ts
|
|
1763
1621
|
import { readdir as readdir5, stat as stat4 } from "fs/promises";
|
|
1764
1622
|
import { homedir as homedir4 } from "os";
|
|
1765
|
-
import { join as
|
|
1623
|
+
import { join as join4 } from "path";
|
|
1766
1624
|
var SERVICE4 = "github-copilot";
|
|
1767
1625
|
var CopilotAdapter = class {
|
|
1768
1626
|
harness = "github-copilot";
|
|
1769
1627
|
aliases = ["copilot"];
|
|
1770
1628
|
root() {
|
|
1771
|
-
return
|
|
1629
|
+
return join4(process.env.COPILOT_HOME ?? join4(homedir4(), ".copilot"), "session-state");
|
|
1772
1630
|
}
|
|
1773
1631
|
async locate(opts = {}) {
|
|
1774
1632
|
let dirs;
|
|
@@ -1780,7 +1638,7 @@ var CopilotAdapter = class {
|
|
|
1780
1638
|
}
|
|
1781
1639
|
const refs = [];
|
|
1782
1640
|
for (const id of dirs) {
|
|
1783
|
-
const path =
|
|
1641
|
+
const path = join4(this.root(), id, "events.jsonl");
|
|
1784
1642
|
let st;
|
|
1785
1643
|
try {
|
|
1786
1644
|
st = await stat4(path);
|
|
@@ -1886,7 +1744,7 @@ var CopilotAdapter = class {
|
|
|
1886
1744
|
// src/adapters/factory.ts
|
|
1887
1745
|
import { readdir as readdir6, stat as stat5 } from "fs/promises";
|
|
1888
1746
|
import { homedir as homedir5 } from "os";
|
|
1889
|
-
import { basename as basename4, join as
|
|
1747
|
+
import { basename as basename4, join as join5 } from "path";
|
|
1890
1748
|
var SERVICE5 = "factory";
|
|
1891
1749
|
function textOf4(content) {
|
|
1892
1750
|
return capText(
|
|
@@ -1897,7 +1755,7 @@ var FactoryAdapter = class {
|
|
|
1897
1755
|
harness = "factory";
|
|
1898
1756
|
aliases = ["factory-droids", "droid"];
|
|
1899
1757
|
root() {
|
|
1900
|
-
return
|
|
1758
|
+
return join5(homedir5(), ".factory", "sessions");
|
|
1901
1759
|
}
|
|
1902
1760
|
async locate(opts = {}) {
|
|
1903
1761
|
const root = this.root();
|
|
@@ -1910,7 +1768,7 @@ var FactoryAdapter = class {
|
|
|
1910
1768
|
}
|
|
1911
1769
|
const refs = [];
|
|
1912
1770
|
for (const dir of dirs) {
|
|
1913
|
-
const dp =
|
|
1771
|
+
const dp = join5(root, dir);
|
|
1914
1772
|
let files;
|
|
1915
1773
|
try {
|
|
1916
1774
|
files = await readdir6(dp);
|
|
@@ -1922,7 +1780,7 @@ var FactoryAdapter = class {
|
|
|
1922
1780
|
if (opts.cwd && !cwd.startsWith(opts.cwd)) continue;
|
|
1923
1781
|
for (const f of files) {
|
|
1924
1782
|
if (!f.endsWith(".jsonl")) continue;
|
|
1925
|
-
const path =
|
|
1783
|
+
const path = join5(dp, f);
|
|
1926
1784
|
let st;
|
|
1927
1785
|
try {
|
|
1928
1786
|
st = await stat5(path);
|
|
@@ -2058,7 +1916,7 @@ var FactoryAdapter = class {
|
|
|
2058
1916
|
// src/adapters/forge.ts
|
|
2059
1917
|
import { readdir as readdir7, stat as stat6 } from "fs/promises";
|
|
2060
1918
|
import { homedir as homedir6 } from "os";
|
|
2061
|
-
import { basename as basename5, join as
|
|
1919
|
+
import { basename as basename5, join as join6 } from "path";
|
|
2062
1920
|
var SERVICE6 = "forge";
|
|
2063
1921
|
function tokens(t) {
|
|
2064
1922
|
if (!t) return null;
|
|
@@ -2072,7 +1930,7 @@ var ForgeAdapter = class {
|
|
|
2072
1930
|
aliases = ["forgecode"];
|
|
2073
1931
|
/** Directories scanned for `*-dump.json` exports. */
|
|
2074
1932
|
dumpDirs() {
|
|
2075
|
-
return [process.cwd(),
|
|
1933
|
+
return [process.cwd(), join6(homedir6(), ".forge")];
|
|
2076
1934
|
}
|
|
2077
1935
|
async locate(opts = {}) {
|
|
2078
1936
|
const refs = [];
|
|
@@ -2087,7 +1945,7 @@ var ForgeAdapter = class {
|
|
|
2087
1945
|
}
|
|
2088
1946
|
for (const f of files) {
|
|
2089
1947
|
if (!f.endsWith("-dump.json")) continue;
|
|
2090
|
-
const path =
|
|
1948
|
+
const path = join6(dir, f);
|
|
2091
1949
|
let st;
|
|
2092
1950
|
try {
|
|
2093
1951
|
st = await stat6(path);
|
|
@@ -2188,7 +2046,7 @@ var ForgeAdapter = class {
|
|
|
2188
2046
|
// src/adapters/gemini.ts
|
|
2189
2047
|
import { readdir as readdir8, stat as stat7 } from "fs/promises";
|
|
2190
2048
|
import { homedir as homedir7 } from "os";
|
|
2191
|
-
import { join as
|
|
2049
|
+
import { join as join7 } from "path";
|
|
2192
2050
|
function iso(ts, fallback) {
|
|
2193
2051
|
if (typeof ts === "number") return new Date(ts).toISOString();
|
|
2194
2052
|
if (typeof ts === "string" && ts.length > 0) return ts;
|
|
@@ -2213,7 +2071,7 @@ var GeminiFamilyAdapter = class {
|
|
|
2213
2071
|
this.homeDirName = cfg.homeDirName;
|
|
2214
2072
|
}
|
|
2215
2073
|
chatsRoot() {
|
|
2216
|
-
return
|
|
2074
|
+
return join7(homedir7(), this.homeDirName, "tmp");
|
|
2217
2075
|
}
|
|
2218
2076
|
/** `<home>/projects.json` maps `{ <absolute-cwd>: <projectName> }`. The session
|
|
2219
2077
|
* dir name (`tmp/<projectHash>/`) is that projectName for registered projects,
|
|
@@ -2223,7 +2081,7 @@ var GeminiFamilyAdapter = class {
|
|
|
2223
2081
|
const nameToPath = /* @__PURE__ */ new Map();
|
|
2224
2082
|
try {
|
|
2225
2083
|
const projects = (await readJsonFile(
|
|
2226
|
-
|
|
2084
|
+
join7(homedir7(), this.homeDirName, "projects.json")
|
|
2227
2085
|
)).projects ?? {};
|
|
2228
2086
|
for (const [path, name] of Object.entries(projects)) nameToPath.set(name, path);
|
|
2229
2087
|
} catch (error) {
|
|
@@ -2244,7 +2102,7 @@ var GeminiFamilyAdapter = class {
|
|
|
2244
2102
|
const refs = [];
|
|
2245
2103
|
for (const pd of projectDirs) {
|
|
2246
2104
|
const cwd = projectCwds.get(pd) ?? null;
|
|
2247
|
-
const chatsDir =
|
|
2105
|
+
const chatsDir = join7(root, pd, "chats");
|
|
2248
2106
|
let files;
|
|
2249
2107
|
try {
|
|
2250
2108
|
files = await readdir8(chatsDir);
|
|
@@ -2254,7 +2112,7 @@ var GeminiFamilyAdapter = class {
|
|
|
2254
2112
|
}
|
|
2255
2113
|
for (const f of files) {
|
|
2256
2114
|
if (!f.startsWith("session-") || !f.endsWith(".json")) continue;
|
|
2257
|
-
const path =
|
|
2115
|
+
const path = join7(chatsDir, f);
|
|
2258
2116
|
let st;
|
|
2259
2117
|
try {
|
|
2260
2118
|
st = await stat7(path);
|
|
@@ -2363,7 +2221,7 @@ var GeminiAdapter = class extends GeminiFamilyAdapter {
|
|
|
2363
2221
|
// src/adapters/opencode.ts
|
|
2364
2222
|
import { readdir as readdir9, stat as stat8 } from "fs/promises";
|
|
2365
2223
|
import { homedir as homedir8 } from "os";
|
|
2366
|
-
import { join as
|
|
2224
|
+
import { join as join8 } from "path";
|
|
2367
2225
|
var SERVICE7 = "opencode";
|
|
2368
2226
|
function isoOf(ms) {
|
|
2369
2227
|
return new Date(ms ?? 0).toISOString();
|
|
@@ -2376,11 +2234,11 @@ function textOf7(parts) {
|
|
|
2376
2234
|
var OpencodeAdapter = class {
|
|
2377
2235
|
harness = "opencode";
|
|
2378
2236
|
storage() {
|
|
2379
|
-
const base = process.env.XDG_DATA_HOME ??
|
|
2380
|
-
return
|
|
2237
|
+
const base = process.env.XDG_DATA_HOME ?? join8(homedir8(), ".local", "share");
|
|
2238
|
+
return join8(base, "opencode", "storage");
|
|
2381
2239
|
}
|
|
2382
2240
|
async locate(opts = {}) {
|
|
2383
|
-
const msgRoot =
|
|
2241
|
+
const msgRoot = join8(this.storage(), "message");
|
|
2384
2242
|
let sessionDirs;
|
|
2385
2243
|
try {
|
|
2386
2244
|
sessionDirs = await readdir9(msgRoot);
|
|
@@ -2390,7 +2248,7 @@ var OpencodeAdapter = class {
|
|
|
2390
2248
|
}
|
|
2391
2249
|
const refs = [];
|
|
2392
2250
|
for (const sid of sessionDirs) {
|
|
2393
|
-
const dir =
|
|
2251
|
+
const dir = join8(msgRoot, sid);
|
|
2394
2252
|
let st;
|
|
2395
2253
|
try {
|
|
2396
2254
|
st = await stat8(dir);
|
|
@@ -2408,11 +2266,11 @@ var OpencodeAdapter = class {
|
|
|
2408
2266
|
async parse(ref) {
|
|
2409
2267
|
const traceId = ref.sessionId;
|
|
2410
2268
|
const rootId = `root:${traceId}`;
|
|
2411
|
-
const partRoot =
|
|
2269
|
+
const partRoot = join8(this.storage(), "part");
|
|
2412
2270
|
const msgFiles = await listJsonFiles(ref.path);
|
|
2413
2271
|
const messages = [];
|
|
2414
2272
|
for (const f of msgFiles) {
|
|
2415
|
-
messages.push(await readJsonFile(
|
|
2273
|
+
messages.push(await readJsonFile(join8(ref.path, f)));
|
|
2416
2274
|
}
|
|
2417
2275
|
messages.sort((a, b) => (a.time?.created ?? 0) - (b.time?.created ?? 0));
|
|
2418
2276
|
const spans = [
|
|
@@ -2432,7 +2290,7 @@ var OpencodeAdapter = class {
|
|
|
2432
2290
|
for (const msg of messages) {
|
|
2433
2291
|
const mid = msg.id ?? `m${step}`;
|
|
2434
2292
|
const llmId = `llm:${mid}`;
|
|
2435
|
-
const pdir =
|
|
2293
|
+
const pdir = join8(partRoot, mid);
|
|
2436
2294
|
const parts = [];
|
|
2437
2295
|
let partFiles = [];
|
|
2438
2296
|
try {
|
|
@@ -2441,7 +2299,7 @@ var OpencodeAdapter = class {
|
|
|
2441
2299
|
if (!isMissingJsonSource(error)) throw error;
|
|
2442
2300
|
}
|
|
2443
2301
|
for (const pf of partFiles) {
|
|
2444
|
-
parts.push(await readJsonFile(
|
|
2302
|
+
parts.push(await readJsonFile(join8(pdir, pf)));
|
|
2445
2303
|
}
|
|
2446
2304
|
const turnText = textOf7(parts);
|
|
2447
2305
|
const isUser = msg.role === "user";
|
|
@@ -2519,7 +2377,7 @@ var OpencodeAdapter = class {
|
|
|
2519
2377
|
// src/adapters/pi.ts
|
|
2520
2378
|
import { readdir as readdir10, stat as stat9 } from "fs/promises";
|
|
2521
2379
|
import { homedir as homedir9 } from "os";
|
|
2522
|
-
import { basename as basename6, join as
|
|
2380
|
+
import { basename as basename6, join as join9 } from "path";
|
|
2523
2381
|
var SERVICE8 = "pi";
|
|
2524
2382
|
function isToolBlock(b) {
|
|
2525
2383
|
return typeof b.type === "string" && /tool[_-]?call|tool[_-]?use|^tool$/i.test(b.type);
|
|
@@ -2536,7 +2394,7 @@ function textOf8(content) {
|
|
|
2536
2394
|
var PiAdapter = class {
|
|
2537
2395
|
harness = "pi";
|
|
2538
2396
|
root() {
|
|
2539
|
-
return
|
|
2397
|
+
return join9(homedir9(), ".pi", "agent", "sessions");
|
|
2540
2398
|
}
|
|
2541
2399
|
async locate(opts = {}) {
|
|
2542
2400
|
const root = this.root();
|
|
@@ -2549,7 +2407,7 @@ var PiAdapter = class {
|
|
|
2549
2407
|
}
|
|
2550
2408
|
const refs = [];
|
|
2551
2409
|
for (const dir of dirs) {
|
|
2552
|
-
const dp =
|
|
2410
|
+
const dp = join9(root, dir);
|
|
2553
2411
|
let files;
|
|
2554
2412
|
try {
|
|
2555
2413
|
files = await readdir10(dp);
|
|
@@ -2561,7 +2419,7 @@ var PiAdapter = class {
|
|
|
2561
2419
|
if (opts.cwd && !cwd.startsWith(opts.cwd)) continue;
|
|
2562
2420
|
for (const f of files) {
|
|
2563
2421
|
if (!f.endsWith(".jsonl")) continue;
|
|
2564
|
-
const path =
|
|
2422
|
+
const path = join9(dp, f);
|
|
2565
2423
|
let st;
|
|
2566
2424
|
try {
|
|
2567
2425
|
st = await stat9(path);
|
|
@@ -2696,7 +2554,7 @@ var PiAdapter = class {
|
|
|
2696
2554
|
// src/adapters/qwen.ts
|
|
2697
2555
|
import { readdir as readdir11, stat as stat10 } from "fs/promises";
|
|
2698
2556
|
import { homedir as homedir10 } from "os";
|
|
2699
|
-
import { basename as basename7, join as
|
|
2557
|
+
import { basename as basename7, join as join10 } from "path";
|
|
2700
2558
|
var SERVICE9 = "qwen";
|
|
2701
2559
|
function textOf9(parts) {
|
|
2702
2560
|
if (!parts) return "";
|
|
@@ -2706,7 +2564,7 @@ var QwenAdapter = class {
|
|
|
2706
2564
|
harness = "qwen";
|
|
2707
2565
|
aliases = ["qwen-code"];
|
|
2708
2566
|
root() {
|
|
2709
|
-
return
|
|
2567
|
+
return join10(homedir10(), ".qwen", "projects");
|
|
2710
2568
|
}
|
|
2711
2569
|
async locate(opts = {}) {
|
|
2712
2570
|
const root = this.root();
|
|
@@ -2719,7 +2577,7 @@ var QwenAdapter = class {
|
|
|
2719
2577
|
}
|
|
2720
2578
|
const refs = [];
|
|
2721
2579
|
for (const pd of projectDirs) {
|
|
2722
|
-
const chatsDir =
|
|
2580
|
+
const chatsDir = join10(root, pd, "chats");
|
|
2723
2581
|
let files;
|
|
2724
2582
|
try {
|
|
2725
2583
|
files = await readdir11(chatsDir);
|
|
@@ -2730,7 +2588,7 @@ var QwenAdapter = class {
|
|
|
2730
2588
|
if (opts.cwd) continue;
|
|
2731
2589
|
for (const f of files) {
|
|
2732
2590
|
if (!f.endsWith(".jsonl")) continue;
|
|
2733
|
-
const path =
|
|
2591
|
+
const path = join10(chatsDir, f);
|
|
2734
2592
|
let st;
|
|
2735
2593
|
try {
|
|
2736
2594
|
st = await stat10(path);
|
|
@@ -2896,7 +2754,7 @@ function selectAdapters(opts) {
|
|
|
2896
2754
|
|
|
2897
2755
|
// src/repo.ts
|
|
2898
2756
|
import { stat as stat11 } from "fs/promises";
|
|
2899
|
-
import { dirname, isAbsolute, join as
|
|
2757
|
+
import { dirname, isAbsolute, join as join11, normalize, parse as parsePath } from "path";
|
|
2900
2758
|
function normalizeRemote(url) {
|
|
2901
2759
|
const raw = url.trim();
|
|
2902
2760
|
if (!raw) return null;
|
|
@@ -3017,7 +2875,7 @@ async function nearestExistingDir(path) {
|
|
|
3017
2875
|
async function findGitRoot(path) {
|
|
3018
2876
|
let cur = await nearestExistingDir(path);
|
|
3019
2877
|
while (cur) {
|
|
3020
|
-
if (await pathStat(
|
|
2878
|
+
if (await pathStat(join11(cur, ".git"))) return cur;
|
|
3021
2879
|
const parent = dirname(cur);
|
|
3022
2880
|
if (parent === cur) return null;
|
|
3023
2881
|
cur = parent;
|
|
@@ -3035,7 +2893,7 @@ async function repairDashedPath(cwd) {
|
|
|
3035
2893
|
while (index < parts.length) {
|
|
3036
2894
|
const nextPart = parts[index];
|
|
3037
2895
|
if (!nextPart) break;
|
|
3038
|
-
const exact =
|
|
2896
|
+
const exact = join11(current, nextPart);
|
|
3039
2897
|
if (!await directoryExists(exact)) break;
|
|
3040
2898
|
current = exact;
|
|
3041
2899
|
index += 1;
|
|
@@ -3045,7 +2903,7 @@ async function repairDashedPath(cwd) {
|
|
|
3045
2903
|
let consumed = 0;
|
|
3046
2904
|
for (let n = parts.length - index; n >= 1; n -= 1) {
|
|
3047
2905
|
const segment = parts.slice(index, index + n).join("-");
|
|
3048
|
-
const candidate =
|
|
2906
|
+
const candidate = join11(current, segment);
|
|
3049
2907
|
if (await directoryExists(candidate)) {
|
|
3050
2908
|
match = candidate;
|
|
3051
2909
|
consumed = n;
|
|
@@ -3316,7 +3174,7 @@ async function* scanSessions(opts) {
|
|
|
3316
3174
|
// src/evidence.ts
|
|
3317
3175
|
import { mkdtemp, writeFile } from "fs/promises";
|
|
3318
3176
|
import { tmpdir } from "os";
|
|
3319
|
-
import { join as
|
|
3177
|
+
import { join as join12 } from "path";
|
|
3320
3178
|
import {
|
|
3321
3179
|
OPENINFERENCE_SPAN_KIND as OPENINFERENCE_SPAN_KIND2,
|
|
3322
3180
|
TOOL_NAME as TOOL_NAME2
|
|
@@ -3428,7 +3286,7 @@ function serializePolicyEvidence(records) {
|
|
|
3428
3286
|
`;
|
|
3429
3287
|
}
|
|
3430
3288
|
async function writePolicyEvidenceFile(records, outPath) {
|
|
3431
|
-
const path = outPath ??
|
|
3289
|
+
const path = outPath ?? join12(await mkdtemp(join12(tmpdir(), "traces-evidence-")), "policy-evidence.jsonl");
|
|
3432
3290
|
await writeFile(path, serializePolicyEvidence(records), "utf8");
|
|
3433
3291
|
return path;
|
|
3434
3292
|
}
|
|
@@ -3626,9 +3484,9 @@ async function applyRedactor(spans, redactor) {
|
|
|
3626
3484
|
|
|
3627
3485
|
// src/file-export.ts
|
|
3628
3486
|
import { createHash as createHash4 } from "crypto";
|
|
3629
|
-
import { mkdtemp as mkdtemp2, readFile as
|
|
3487
|
+
import { mkdtemp as mkdtemp2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
3630
3488
|
import { tmpdir as tmpdir2 } from "os";
|
|
3631
|
-
import { join as
|
|
3489
|
+
import { join as join13 } from "path";
|
|
3632
3490
|
import {
|
|
3633
3491
|
firstNumberAttr,
|
|
3634
3492
|
LLM_CACHED_TOKEN_ATTR_KEYS,
|
|
@@ -4216,7 +4074,7 @@ async function exportTraceEvidenceFile(inputPath, opts = {}) {
|
|
|
4216
4074
|
sourcePath: inputPath,
|
|
4217
4075
|
format
|
|
4218
4076
|
});
|
|
4219
|
-
spans
|
|
4077
|
+
appendAll(spans, converted.spans);
|
|
4220
4078
|
redactionCount += converted.redactionCount;
|
|
4221
4079
|
for (const [rule, count2] of Object.entries(converted.redactionsByRule)) {
|
|
4222
4080
|
redactionsByRule[rule] = (redactionsByRule[rule] ?? 0) + count2;
|
|
@@ -4232,20 +4090,20 @@ async function exportTraceEvidenceFile(inputPath, opts = {}) {
|
|
|
4232
4090
|
}
|
|
4233
4091
|
return { format, spans, redactionCount, redactionsByRule };
|
|
4234
4092
|
}
|
|
4235
|
-
const text = await
|
|
4093
|
+
const text = await readFile2(inputPath, "utf8");
|
|
4236
4094
|
return exportTraceEvidenceText(text, { ...opts, sourcePath: inputPath });
|
|
4237
4095
|
}
|
|
4238
4096
|
async function writeTraceEvidenceExportFile(inputPath, outPath, opts = {}) {
|
|
4239
4097
|
const result = await exportTraceEvidenceFile(inputPath, opts);
|
|
4240
|
-
const path = outPath ??
|
|
4098
|
+
const path = outPath ?? join13(await mkdtemp2(join13(tmpdir2(), "traces-export-")), "spans.openinference.jsonl");
|
|
4241
4099
|
await writeFile2(path, serializeSpans(result.spans), "utf8");
|
|
4242
4100
|
return { ...result, path };
|
|
4243
4101
|
}
|
|
4244
4102
|
|
|
4245
4103
|
// src/inspect.ts
|
|
4246
|
-
import { mkdtemp as mkdtemp3, readFile as
|
|
4104
|
+
import { mkdtemp as mkdtemp3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
4247
4105
|
import { tmpdir as tmpdir3 } from "os";
|
|
4248
|
-
import { basename as basename8, join as
|
|
4106
|
+
import { basename as basename8, join as join14 } from "path";
|
|
4249
4107
|
var severityRank = {
|
|
4250
4108
|
high: 0,
|
|
4251
4109
|
medium: 1,
|
|
@@ -4529,16 +4387,289 @@ function assertSessionIndex(value) {
|
|
|
4529
4387
|
}
|
|
4530
4388
|
}
|
|
4531
4389
|
async function readSessionIndexFile(path) {
|
|
4532
|
-
const parsed = JSON.parse(await
|
|
4390
|
+
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
4533
4391
|
assertSessionIndex(parsed);
|
|
4534
4392
|
return parsed;
|
|
4535
4393
|
}
|
|
4536
4394
|
async function writeInspectionReportFile(report, outPath) {
|
|
4537
|
-
const path = outPath ??
|
|
4395
|
+
const path = outPath ?? join14(await mkdtemp3(join14(tmpdir3(), "traces-inspect-")), "inspection-report.md");
|
|
4538
4396
|
await writeFile3(path, renderInspectionReport(report), "utf8");
|
|
4539
4397
|
return path;
|
|
4540
4398
|
}
|
|
4541
4399
|
|
|
4400
|
+
// src/adoption.ts
|
|
4401
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
4402
|
+
import { join as join15 } from "path";
|
|
4403
|
+
function toolName(s) {
|
|
4404
|
+
const n = s.attributes["tool.name"];
|
|
4405
|
+
if (typeof n === "string") return n;
|
|
4406
|
+
if (s.name.startsWith("tool.")) return s.name.slice("tool.".length);
|
|
4407
|
+
return null;
|
|
4408
|
+
}
|
|
4409
|
+
function parseInput(s) {
|
|
4410
|
+
const captured = toolArgumentsFromAttributes(s.attributes);
|
|
4411
|
+
if (!captured.argsCaptured) return {};
|
|
4412
|
+
if (captured.args && typeof captured.args === "object") {
|
|
4413
|
+
return captured.args;
|
|
4414
|
+
}
|
|
4415
|
+
if (typeof captured.args !== "string") return {};
|
|
4416
|
+
try {
|
|
4417
|
+
const v = JSON.parse(captured.args);
|
|
4418
|
+
return v && typeof v === "object" ? v : {};
|
|
4419
|
+
} catch {
|
|
4420
|
+
return {};
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
function skillNameOf(input) {
|
|
4424
|
+
const v = input.skill ?? input.command;
|
|
4425
|
+
return typeof v === "string" && v.length > 0 ? v : "?";
|
|
4426
|
+
}
|
|
4427
|
+
function subagentTypeOf(input) {
|
|
4428
|
+
const v = input.subagent_type ?? input.agent_type ?? input.type ?? input.name;
|
|
4429
|
+
return typeof v === "string" && v.length > 0 ? v : "?";
|
|
4430
|
+
}
|
|
4431
|
+
function isSpawnAgentTool(name) {
|
|
4432
|
+
return name === "spawn_agent" || name.endsWith("__spawn_agent");
|
|
4433
|
+
}
|
|
4434
|
+
function skillTelemetryCapability(service) {
|
|
4435
|
+
if (service === "claude-code") return "supported";
|
|
4436
|
+
if (service === "codex") return "unsupported";
|
|
4437
|
+
return "unknown";
|
|
4438
|
+
}
|
|
4439
|
+
var MATERIALIZED_SKILL_RE = /<skills_instructions>|(?:^|\n)#{1,3}\s+(?:available\s+)?skills\b|base directory for this skill:/i;
|
|
4440
|
+
function hasMaterializedSkillEvidence(s) {
|
|
4441
|
+
if (s.name !== "message.developer" && s.attributes["tangle.actor"] !== "injected") return false;
|
|
4442
|
+
const content = s.attributes.content;
|
|
4443
|
+
return typeof content === "string" && MATERIALIZED_SKILL_RE.test(content);
|
|
4444
|
+
}
|
|
4445
|
+
var SKILL_DOCUMENT_READ_COMMAND = /\b(?:cat|sed|head|tail|less|more|awk)\b/;
|
|
4446
|
+
function skillDocumentNames(s) {
|
|
4447
|
+
if (s.attributes["openinference.span.kind"] !== "TOOL") return [];
|
|
4448
|
+
if (s.status.code !== "OK" || !SKILL_DOCUMENT_READ_COMMAND.test(String(s.attributes["input.value"] ?? ""))) return [];
|
|
4449
|
+
const input = s.attributes["input.value"];
|
|
4450
|
+
if (typeof input !== "string") return [];
|
|
4451
|
+
const names = /* @__PURE__ */ new Set();
|
|
4452
|
+
for (const match of input.matchAll(/(?:^|[\\/'"])([^\\/'"\s]+)[\\/]SKILL\.md\b/g)) {
|
|
4453
|
+
const name = match[1];
|
|
4454
|
+
if (/^[A-Za-z][A-Za-z0-9-]*$/.test(name)) names.add(name);
|
|
4455
|
+
}
|
|
4456
|
+
return [...names];
|
|
4457
|
+
}
|
|
4458
|
+
function telemetryStatus(measured, unsupported, unknown) {
|
|
4459
|
+
if (measured > 0 && unsupported === 0 && unknown === 0) return "measured";
|
|
4460
|
+
if (measured > 0) return "partial";
|
|
4461
|
+
if (unsupported > 0 && unknown === 0) return "unsupported";
|
|
4462
|
+
return "unknown";
|
|
4463
|
+
}
|
|
4464
|
+
function skillNamesFromRun(record) {
|
|
4465
|
+
const names = [];
|
|
4466
|
+
if (Array.isArray(record.skills)) {
|
|
4467
|
+
for (const skill of record.skills) if (typeof skill === "string" && skill) names.push(skill);
|
|
4468
|
+
}
|
|
4469
|
+
if (typeof record.skill === "string" && record.skill) names.push(record.skill);
|
|
4470
|
+
return names.length > 0 ? names : ["?"];
|
|
4471
|
+
}
|
|
4472
|
+
function sessionIdFromRun(record) {
|
|
4473
|
+
for (const key of ["sessionId", "session_id", "traceId", "trace_id"]) {
|
|
4474
|
+
if (typeof record[key] === "string" && record[key]) return record[key];
|
|
4475
|
+
}
|
|
4476
|
+
return void 0;
|
|
4477
|
+
}
|
|
4478
|
+
function addCounts(target, names) {
|
|
4479
|
+
for (const name of names) target[name] = (target[name] ?? 0) + 1;
|
|
4480
|
+
}
|
|
4481
|
+
function countSkillRunsJsonl(raw) {
|
|
4482
|
+
const out = {};
|
|
4483
|
+
for (const line of raw.split("\n")) {
|
|
4484
|
+
const t = line.trim();
|
|
4485
|
+
if (!t) continue;
|
|
4486
|
+
let o;
|
|
4487
|
+
try {
|
|
4488
|
+
o = JSON.parse(t);
|
|
4489
|
+
} catch {
|
|
4490
|
+
continue;
|
|
4491
|
+
}
|
|
4492
|
+
if (!o || typeof o !== "object") continue;
|
|
4493
|
+
const rec = o;
|
|
4494
|
+
addCounts(out, skillNamesFromRun(rec));
|
|
4495
|
+
}
|
|
4496
|
+
return out;
|
|
4497
|
+
}
|
|
4498
|
+
async function readLoopRuns(cwds, sessionIds2) {
|
|
4499
|
+
const linkedCounts = {};
|
|
4500
|
+
const unlinkedCounts = {};
|
|
4501
|
+
let filesRead = 0;
|
|
4502
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4503
|
+
for (const cwd of cwds) {
|
|
4504
|
+
if (!cwd || seen.has(cwd)) continue;
|
|
4505
|
+
seen.add(cwd);
|
|
4506
|
+
const path = join15(cwd, ".evolve", "skill-runs.jsonl");
|
|
4507
|
+
let raw;
|
|
4508
|
+
try {
|
|
4509
|
+
raw = await readFile4(path, "utf8");
|
|
4510
|
+
} catch {
|
|
4511
|
+
continue;
|
|
4512
|
+
}
|
|
4513
|
+
filesRead += 1;
|
|
4514
|
+
for (const line of raw.split("\n")) {
|
|
4515
|
+
if (!line.trim()) continue;
|
|
4516
|
+
let value;
|
|
4517
|
+
try {
|
|
4518
|
+
value = JSON.parse(line);
|
|
4519
|
+
} catch {
|
|
4520
|
+
continue;
|
|
4521
|
+
}
|
|
4522
|
+
if (!value || typeof value !== "object") continue;
|
|
4523
|
+
const record = value;
|
|
4524
|
+
const target = sessionIds2.has(sessionIdFromRun(record) ?? "") ? linkedCounts : unlinkedCounts;
|
|
4525
|
+
addCounts(target, skillNamesFromRun(record));
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
return { linkedCounts, unlinkedCounts, filesRead };
|
|
4529
|
+
}
|
|
4530
|
+
async function analyzeAdoption(spans, opts = {}) {
|
|
4531
|
+
const { sessionByTrace, conflicts } = indexSessionIdsByTrace(spans);
|
|
4532
|
+
const identifiedSessions = new Set(sessionByTrace.values());
|
|
4533
|
+
const unassignedTraceIds = new Set(
|
|
4534
|
+
spans.map((span2) => span2.trace_id).filter((traceId) => !sessionByTrace.has(traceId))
|
|
4535
|
+
);
|
|
4536
|
+
const executionGroup = (span2) => sessionByTrace.get(span2.trace_id) ?? `trace:${span2.trace_id}`;
|
|
4537
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
4538
|
+
const sessionsWithSkill = /* @__PURE__ */ new Set();
|
|
4539
|
+
const sessionsWithSubagent = /* @__PURE__ */ new Set();
|
|
4540
|
+
const sessionsWithMaterializedSkills = /* @__PURE__ */ new Set();
|
|
4541
|
+
const sessionsWithSkillFileReference = /* @__PURE__ */ new Set();
|
|
4542
|
+
const skillInvocations = {};
|
|
4543
|
+
const skillDocumentReads = {};
|
|
4544
|
+
const subagentSpawns = {};
|
|
4545
|
+
const canonicalSubagentSessions = /* @__PURE__ */ new Set();
|
|
4546
|
+
const fallbackSubagents = /* @__PURE__ */ new Map();
|
|
4547
|
+
const sessionCapabilities = /* @__PURE__ */ new Map();
|
|
4548
|
+
for (const s of spans) {
|
|
4549
|
+
const group = executionGroup(s);
|
|
4550
|
+
sessions.add(group);
|
|
4551
|
+
const observedCapability = skillTelemetryCapability(s.attributes["service.name"]);
|
|
4552
|
+
const currentCapability = sessionCapabilities.get(group) ?? "unknown";
|
|
4553
|
+
if (observedCapability === "supported" || currentCapability === "unknown") {
|
|
4554
|
+
sessionCapabilities.set(group, observedCapability);
|
|
4555
|
+
}
|
|
4556
|
+
if (hasMaterializedSkillEvidence(s)) sessionsWithMaterializedSkills.add(group);
|
|
4557
|
+
const skillDocuments = skillDocumentNames(s);
|
|
4558
|
+
if (skillDocuments.length > 0) {
|
|
4559
|
+
sessionsWithSkillFileReference.add(group);
|
|
4560
|
+
addCounts(skillDocumentReads, skillDocuments);
|
|
4561
|
+
}
|
|
4562
|
+
const tn = toolName(s);
|
|
4563
|
+
if (tn === "Skill") {
|
|
4564
|
+
sessionCapabilities.set(group, "supported");
|
|
4565
|
+
const name = skillNameOf(parseInput(s));
|
|
4566
|
+
skillInvocations[name] = (skillInvocations[name] ?? 0) + 1;
|
|
4567
|
+
sessionsWithSkill.add(group);
|
|
4568
|
+
} else if (tn === "Task" || tn === "Agent") {
|
|
4569
|
+
const type = subagentTypeOf(parseInput(s));
|
|
4570
|
+
subagentSpawns[type] = (subagentSpawns[type] ?? 0) + 1;
|
|
4571
|
+
sessionsWithSubagent.add(group);
|
|
4572
|
+
canonicalSubagentSessions.add(group);
|
|
4573
|
+
} else if (tn && isSpawnAgentTool(tn)) {
|
|
4574
|
+
const types = fallbackSubagents.get(group) ?? [];
|
|
4575
|
+
types.push(subagentTypeOf(parseInput(s)));
|
|
4576
|
+
fallbackSubagents.set(group, types);
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4579
|
+
for (const [traceId, types] of fallbackSubagents) {
|
|
4580
|
+
if (canonicalSubagentSessions.has(traceId)) continue;
|
|
4581
|
+
sessionsWithSubagent.add(traceId);
|
|
4582
|
+
for (const type of types) {
|
|
4583
|
+
subagentSpawns[type] = (subagentSpawns[type] ?? 0) + 1;
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4586
|
+
const {
|
|
4587
|
+
linkedCounts: loopDispatchedRuns,
|
|
4588
|
+
unlinkedCounts: unlinkedLoopDispatchedRuns,
|
|
4589
|
+
filesRead
|
|
4590
|
+
} = await readLoopRuns(opts.cwds ?? [], identifiedSessions);
|
|
4591
|
+
const totalSkillInvocations = Object.values(skillInvocations).reduce((a, b) => a + b, 0);
|
|
4592
|
+
const totalSkillDocumentReads = Object.values(skillDocumentReads).reduce((a, b) => a + b, 0);
|
|
4593
|
+
const totalSubagentSpawns = Object.values(subagentSpawns).reduce((a, b) => a + b, 0);
|
|
4594
|
+
const totalLoopDispatchedRuns = Object.values(loopDispatchedRuns).reduce((a, b) => a + b, 0);
|
|
4595
|
+
const totalUnlinkedLoopDispatchedRuns = Object.values(unlinkedLoopDispatchedRuns).reduce((a, b) => a + b, 0);
|
|
4596
|
+
let skillTelemetrySessions = 0;
|
|
4597
|
+
let skillTelemetryUnsupportedSessions = 0;
|
|
4598
|
+
let skillTelemetryUnknownSessions = 0;
|
|
4599
|
+
for (const traceId of sessions) {
|
|
4600
|
+
const capability = sessionCapabilities.get(traceId) ?? "unknown";
|
|
4601
|
+
if (capability === "supported") skillTelemetrySessions += 1;
|
|
4602
|
+
else if (capability === "unsupported") skillTelemetryUnsupportedSessions += 1;
|
|
4603
|
+
else skillTelemetryUnknownSessions += 1;
|
|
4604
|
+
}
|
|
4605
|
+
return {
|
|
4606
|
+
executionGroupCount: sessions.size,
|
|
4607
|
+
identifiedSessionCount: identifiedSessions.size,
|
|
4608
|
+
unassignedTraceCount: unassignedTraceIds.size,
|
|
4609
|
+
sessionIdentityConflicts: conflicts,
|
|
4610
|
+
sessionsWithSkill: sessionsWithSkill.size,
|
|
4611
|
+
skillPenetration: skillTelemetrySessions === 0 ? null : sessionsWithSkill.size / skillTelemetrySessions,
|
|
4612
|
+
skillTelemetryStatus: telemetryStatus(
|
|
4613
|
+
skillTelemetrySessions,
|
|
4614
|
+
skillTelemetryUnsupportedSessions,
|
|
4615
|
+
skillTelemetryUnknownSessions
|
|
4616
|
+
),
|
|
4617
|
+
skillTelemetrySessions,
|
|
4618
|
+
sessionsWithMaterializedSkills: sessionsWithMaterializedSkills.size,
|
|
4619
|
+
sessionsWithSkillFileReference: sessionsWithSkillFileReference.size,
|
|
4620
|
+
skillDocumentReads,
|
|
4621
|
+
totalSkillDocumentReads,
|
|
4622
|
+
skillInvocations,
|
|
4623
|
+
totalSkillInvocations,
|
|
4624
|
+
subagentSpawns,
|
|
4625
|
+
totalSubagentSpawns,
|
|
4626
|
+
sessionsWithSubagent: sessionsWithSubagent.size,
|
|
4627
|
+
loopDispatchedRuns,
|
|
4628
|
+
totalLoopDispatchedRuns,
|
|
4629
|
+
skillRunFilesRead: filesRead,
|
|
4630
|
+
unlinkedLoopDispatchedRuns,
|
|
4631
|
+
totalUnlinkedLoopDispatchedRuns
|
|
4632
|
+
};
|
|
4633
|
+
}
|
|
4634
|
+
|
|
4635
|
+
// src/analyze.ts
|
|
4636
|
+
import { buildDefaultAnalystRegistry } from "@tangle-network/agent-eval/analyst";
|
|
4637
|
+
import { OtlpFileTraceStore } from "@tangle-network/agent-eval/traces";
|
|
4638
|
+
var GENERATED_TRACE_FILE_CEILING = 512 * 1024 * 1024;
|
|
4639
|
+
async function analyzeSpans(spans, opts = {}) {
|
|
4640
|
+
if (spans.length === 0) throw new Error("analyzeSpans: no spans to analyze");
|
|
4641
|
+
const otlpPath = await writeOtlpFile(spans, opts.otlpOutPath);
|
|
4642
|
+
const runId = opts.runId ?? `traces-${Date.now()}`;
|
|
4643
|
+
const execution = summarizeSpanExecution(spans, {
|
|
4644
|
+
experimentId: runId
|
|
4645
|
+
});
|
|
4646
|
+
const detStore = new OtlpFileTraceStore({
|
|
4647
|
+
path: otlpPath,
|
|
4648
|
+
maxFileBytes: GENERATED_TRACE_FILE_CEILING,
|
|
4649
|
+
perCallByteCeiling: GENERATED_TRACE_FILE_CEILING
|
|
4650
|
+
});
|
|
4651
|
+
await detStore.ensureIndexed();
|
|
4652
|
+
const detRegistry = opts.registry ?? buildDefaultAnalystRegistry({ registry: { log: opts.log } });
|
|
4653
|
+
const result = await detRegistry.run(runId, { traceStore: detStore });
|
|
4654
|
+
if (opts.ai) {
|
|
4655
|
+
const agStore = new OtlpFileTraceStore({ path: otlpPath, maxFileBytes: GENERATED_TRACE_FILE_CEILING });
|
|
4656
|
+
await agStore.ensureIndexed();
|
|
4657
|
+
const agRegistry = buildDefaultAnalystRegistry({
|
|
4658
|
+
ai: opts.ai,
|
|
4659
|
+
model: opts.model,
|
|
4660
|
+
includeBehavioral: false,
|
|
4661
|
+
registry: { log: opts.log }
|
|
4662
|
+
});
|
|
4663
|
+
const agResult = await agRegistry.run(runId, { traceStore: agStore }, {
|
|
4664
|
+
budget: opts.budgetUsd != null ? { totalUsd: opts.budgetUsd } : void 0
|
|
4665
|
+
});
|
|
4666
|
+
result.findings.push(...agResult.findings);
|
|
4667
|
+
result.per_analyst.push(...agResult.per_analyst);
|
|
4668
|
+
result.total_cost_usd += agResult.total_cost_usd;
|
|
4669
|
+
}
|
|
4670
|
+
return { otlpPath, execution, result };
|
|
4671
|
+
}
|
|
4672
|
+
|
|
4542
4673
|
// src/reactions.ts
|
|
4543
4674
|
var REACTIONS = ["correction", "frustration", "jargon", "structure", "praise"];
|
|
4544
4675
|
function emptySignals() {
|
|
@@ -4681,15 +4812,17 @@ function renderExecution(report) {
|
|
|
4681
4812
|
const execution = report.execution;
|
|
4682
4813
|
const lines = ["## execution facts", ""];
|
|
4683
4814
|
lines.push(
|
|
4684
|
-
`- **
|
|
4815
|
+
`- **Sessions:** ${count(execution.durationMs.n)} | **Model-call runs:** ${count(execution.modelCalls.runs)} | **Model-call events:** ${count(execution.modelCalls.events)} | **Sessions with tool errors:** ${count(execution.failures.runs)}/${count(execution.failures.reportingRuns)} (${decimal(execution.failures.fraction * 100, 2)}%)`
|
|
4685
4816
|
);
|
|
4686
4817
|
lines.push("- **Task quality:** not measured; these traces do not include comparable task outcome labels.");
|
|
4687
4818
|
lines.push("");
|
|
4688
|
-
lines.push("| Time |
|
|
4819
|
+
lines.push("| Time | Sessions measured | Min | Mean | p50 | p95 | Max |");
|
|
4689
4820
|
lines.push("|---|---:|---:|---:|---:|---:|---:|");
|
|
4690
|
-
lines.push(distributionRow("
|
|
4821
|
+
lines.push(distributionRow("Recorded session interval", execution.durationMs, "ms"));
|
|
4691
4822
|
lines.push(distributionRow("Queue", execution.queueMs, "ms"));
|
|
4692
4823
|
lines.push("");
|
|
4824
|
+
lines.push("Recorded session interval is first-to-last trace time and can include idle time; it is not active agent runtime.");
|
|
4825
|
+
lines.push("");
|
|
4693
4826
|
lines.push("### Direct model usage");
|
|
4694
4827
|
lines.push("");
|
|
4695
4828
|
lines.push("| Token category | Total | Runs measured | Mean | p50 | p95 | Max |");
|
|
@@ -5030,25 +5163,38 @@ function renderAdoption(ar) {
|
|
|
5030
5163
|
);
|
|
5031
5164
|
}
|
|
5032
5165
|
lines.push(
|
|
5033
|
-
`- **Materialized skill catalogs/instructions:** ${ar.sessionsWithMaterializedSkills}/${ar.executionGroupCount} observed group(s) \xB7 **
|
|
5166
|
+
`- **Materialized skill catalogs/instructions:** ${ar.sessionsWithMaterializedSkills}/${ar.executionGroupCount} observed group(s) \xB7 **Sessions with successful skill-document reads:** ${ar.sessionsWithSkillFileReference}/${ar.executionGroupCount}`
|
|
5034
5167
|
);
|
|
5035
5168
|
lines.push(
|
|
5036
|
-
`- **Explicit skill invocations:** ${ar.totalSkillInvocations} (Skill tool spans) \xB7 **
|
|
5169
|
+
`- **Explicit skill invocations:** ${ar.totalSkillInvocations} (Skill tool spans) \xB7 **Session-linked loop runs:** ${ar.totalLoopDispatchedRuns}`
|
|
5037
5170
|
);
|
|
5171
|
+
if (ar.totalUnlinkedLoopDispatchedRuns > 0) {
|
|
5172
|
+
lines.push(
|
|
5173
|
+
`- **Unlinked repository history:** ${ar.totalUnlinkedLoopDispatchedRuns} run(s) from ${ar.skillRunFilesRead} \`.evolve/skill-runs.jsonl\` file(s); not attributed to these sessions.`
|
|
5174
|
+
);
|
|
5175
|
+
}
|
|
5176
|
+
if (ar.totalSkillDocumentReads > 0) {
|
|
5177
|
+
lines.push(`- **Successful skill-document reads:** ${ar.totalSkillDocumentReads}; inspection is not outcome evidence.`);
|
|
5178
|
+
}
|
|
5038
5179
|
lines.push(
|
|
5039
5180
|
`- **Subagent spawns observed:** ${ar.totalSubagentSpawns} across ${ar.sessionsWithSubagent} observed group(s); capture completeness depends on source telemetry.`
|
|
5040
5181
|
);
|
|
5041
5182
|
lines.push("");
|
|
5042
5183
|
const skillRows = Object.entries(ar.skillInvocations).sort((a, b) => b[1] - a[1]);
|
|
5184
|
+
const documentRows = Object.entries(ar.skillDocumentReads).sort((a, b) => b[1] - a[1]);
|
|
5043
5185
|
const loopRows = Object.entries(ar.loopDispatchedRuns).sort((a, b) => b[1] - a[1]);
|
|
5044
|
-
if (skillRows.length > 0 || loopRows.length > 0) {
|
|
5045
|
-
lines.push("| Skill | Explicit invocations |
|
|
5046
|
-
lines.push("
|
|
5047
|
-
const names = [.../* @__PURE__ */ new Set([
|
|
5048
|
-
(
|
|
5186
|
+
if (skillRows.length > 0 || documentRows.length > 0 || loopRows.length > 0) {
|
|
5187
|
+
lines.push("| Skill | Explicit invocations | Documents read | Session-linked loop runs |");
|
|
5188
|
+
lines.push("|---|---|---|---|");
|
|
5189
|
+
const names = [.../* @__PURE__ */ new Set([
|
|
5190
|
+
...skillRows.map((r) => r[0]),
|
|
5191
|
+
...documentRows.map((r) => r[0]),
|
|
5192
|
+
...loopRows.map((r) => r[0])
|
|
5193
|
+
])].sort(
|
|
5194
|
+
(a, b) => (ar.skillInvocations[b] ?? 0) + (ar.skillDocumentReads[b] ?? 0) + (ar.loopDispatchedRuns[b] ?? 0) - (ar.skillInvocations[a] ?? 0) - (ar.skillDocumentReads[a] ?? 0) - (ar.loopDispatchedRuns[a] ?? 0)
|
|
5049
5195
|
);
|
|
5050
5196
|
for (const n of names) {
|
|
5051
|
-
lines.push(`| \`${n}\` | ${ar.skillInvocations[n] ?? 0} | ${ar.loopDispatchedRuns[n] ?? 0} |`);
|
|
5197
|
+
lines.push(`| \`${n}\` | ${ar.skillInvocations[n] ?? 0} | ${ar.skillDocumentReads[n] ?? 0} | ${ar.loopDispatchedRuns[n] ?? 0} |`);
|
|
5052
5198
|
}
|
|
5053
5199
|
lines.push("");
|
|
5054
5200
|
}
|
|
@@ -6448,25 +6594,23 @@ async function executeUpload(plan, opts = {}) {
|
|
|
6448
6594
|
}
|
|
6449
6595
|
|
|
6450
6596
|
export {
|
|
6451
|
-
ATTR,
|
|
6452
|
-
INGEST_SOURCE_CLI,
|
|
6453
|
-
DEFAULT_HARNESS,
|
|
6454
|
-
sessionIdFromAttributes,
|
|
6455
|
-
indexSessionIdsByTrace,
|
|
6456
|
-
stampSessionIdentity,
|
|
6457
|
-
countSkillRunsJsonl,
|
|
6458
|
-
analyzeAdoption,
|
|
6459
6597
|
span,
|
|
6460
6598
|
toOpenInferenceSpan,
|
|
6461
6599
|
serializeSpans,
|
|
6462
6600
|
writeOtlpFile,
|
|
6463
|
-
summarizeOtlpExecution,
|
|
6464
|
-
summarizeSpanExecution,
|
|
6465
|
-
analyzeSpans,
|
|
6466
6601
|
CONTENT_CAP,
|
|
6467
6602
|
capText,
|
|
6468
6603
|
ACTOR_ATTR,
|
|
6469
6604
|
userPromptSpan,
|
|
6605
|
+
appendAll,
|
|
6606
|
+
ATTR,
|
|
6607
|
+
INGEST_SOURCE_CLI,
|
|
6608
|
+
DEFAULT_HARNESS,
|
|
6609
|
+
sessionIdFromAttributes,
|
|
6610
|
+
indexSessionIdsByTrace,
|
|
6611
|
+
stampSessionIdentity,
|
|
6612
|
+
summarizeOtlpExecution,
|
|
6613
|
+
summarizeSpanExecution,
|
|
6470
6614
|
parseIsoToEpochMs,
|
|
6471
6615
|
parseSince,
|
|
6472
6616
|
toRuntimeStore,
|
|
@@ -6532,14 +6676,12 @@ export {
|
|
|
6532
6676
|
serializeInspectionReport,
|
|
6533
6677
|
readSessionIndexFile,
|
|
6534
6678
|
writeInspectionReportFile,
|
|
6679
|
+
countSkillRunsJsonl,
|
|
6680
|
+
analyzeAdoption,
|
|
6681
|
+
analyzeSpans,
|
|
6535
6682
|
REACTIONS,
|
|
6536
6683
|
analyzeReactions,
|
|
6537
6684
|
CORRUPTION_RECEIPT_DISPLAY_LIMIT,
|
|
6538
|
-
summarizeDeterministicSignals,
|
|
6539
|
-
renderReport,
|
|
6540
|
-
renderPipelines,
|
|
6541
|
-
renderReactions,
|
|
6542
|
-
renderAdoption,
|
|
6543
6685
|
buildTraceFindingPacket,
|
|
6544
6686
|
loadTracesConfig,
|
|
6545
6687
|
mergeTracesConfig,
|