@tangle-network/traces 0.8.25 → 0.8.27
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/dist/{chunk-SV7GW5LB.js → chunk-EZDMNNW4.js} +112 -6
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -22,9 +22,12 @@ function skillNameOf(input) {
|
|
|
22
22
|
return typeof v === "string" && v.length > 0 ? v : "?";
|
|
23
23
|
}
|
|
24
24
|
function subagentTypeOf(input) {
|
|
25
|
-
const v = input.subagent_type;
|
|
25
|
+
const v = input.subagent_type ?? input.agent_type ?? input.type ?? input.name;
|
|
26
26
|
return typeof v === "string" && v.length > 0 ? v : "?";
|
|
27
27
|
}
|
|
28
|
+
function isSpawnAgentTool(name) {
|
|
29
|
+
return name === "spawn_agent" || name.endsWith("__spawn_agent");
|
|
30
|
+
}
|
|
28
31
|
function countSkillRunsJsonl(raw) {
|
|
29
32
|
const out = {};
|
|
30
33
|
for (const line of raw.split("\n")) {
|
|
@@ -73,6 +76,8 @@ async function analyzeAdoption(spans, opts = {}) {
|
|
|
73
76
|
const sessionsWithSubagent = /* @__PURE__ */ new Set();
|
|
74
77
|
const skillInvocations = {};
|
|
75
78
|
const subagentSpawns = {};
|
|
79
|
+
const canonicalSubagentSessions = /* @__PURE__ */ new Set();
|
|
80
|
+
const fallbackSubagents = /* @__PURE__ */ new Map();
|
|
76
81
|
for (const s of spans) {
|
|
77
82
|
sessions.add(s.trace_id);
|
|
78
83
|
const tn = toolName(s);
|
|
@@ -84,6 +89,18 @@ async function analyzeAdoption(spans, opts = {}) {
|
|
|
84
89
|
const type = subagentTypeOf(parseInput(s));
|
|
85
90
|
subagentSpawns[type] = (subagentSpawns[type] ?? 0) + 1;
|
|
86
91
|
sessionsWithSubagent.add(s.trace_id);
|
|
92
|
+
canonicalSubagentSessions.add(s.trace_id);
|
|
93
|
+
} else if (tn && isSpawnAgentTool(tn)) {
|
|
94
|
+
const types = fallbackSubagents.get(s.trace_id) ?? [];
|
|
95
|
+
types.push(subagentTypeOf(parseInput(s)));
|
|
96
|
+
fallbackSubagents.set(s.trace_id, types);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const [traceId, types] of fallbackSubagents) {
|
|
100
|
+
if (canonicalSubagentSessions.has(traceId)) continue;
|
|
101
|
+
sessionsWithSubagent.add(traceId);
|
|
102
|
+
for (const type of types) {
|
|
103
|
+
subagentSpawns[type] = (subagentSpawns[type] ?? 0) + 1;
|
|
87
104
|
}
|
|
88
105
|
}
|
|
89
106
|
const { counts: loopDispatchedRuns, filesRead } = await readLoopRuns(opts.cwds ?? []);
|
|
@@ -297,7 +314,9 @@ import { computeToolUseMetrics } from "@tangle-network/agent-eval";
|
|
|
297
314
|
import { stuckLoopView } from "@tangle-network/agent-eval/pipelines";
|
|
298
315
|
async function runPipelines(spans, opts = {}) {
|
|
299
316
|
const { store, runIds } = await toRuntimeStore(spans);
|
|
300
|
-
const
|
|
317
|
+
const loopEligible = spans.filter((item) => item.attributes["traces.expected_blocking"] !== true);
|
|
318
|
+
const { store: loopStore } = await toRuntimeStore(loopEligible);
|
|
319
|
+
const stuckLoops = await stuckLoopView(loopStore, { minOccurrences: opts.minLoopOccurrences ?? 3 });
|
|
301
320
|
const toolUse = await Promise.all(runIds.map((runId) => computeToolUseMetrics(store, runId)));
|
|
302
321
|
return { stuckLoops, toolUse };
|
|
303
322
|
}
|
|
@@ -469,6 +488,10 @@ var INJECT_MARKERS = [
|
|
|
469
488
|
// not a person. (Observed in Claude Code task/loop sessions.)
|
|
470
489
|
"<task-notification>",
|
|
471
490
|
"<task-prompt>",
|
|
491
|
+
// Prefix match: Codex adds attributes such as source="goal" to this tag.
|
|
492
|
+
"<codex_internal_context",
|
|
493
|
+
"<subagent_notification>",
|
|
494
|
+
"# AGENTS.md instructions for ",
|
|
472
495
|
"<local-command-stdout>",
|
|
473
496
|
"This session is being continued from a previous conversation",
|
|
474
497
|
// Slash-command skill bodies are expanded into a user turn (no <command-name>
|
|
@@ -808,6 +831,33 @@ function outputIsError(output) {
|
|
|
808
831
|
const error = /"success"\s*:\s*false|exit(?:_| )code["\s:]+[1-9]|\bcommand failed\b|\bENOENT\b|\berror:/i.test(s);
|
|
809
832
|
return { error, message: error ? s.slice(0, 500) : "" };
|
|
810
833
|
}
|
|
834
|
+
function singleNestedToolName(input) {
|
|
835
|
+
if (!input) return null;
|
|
836
|
+
const names = [...input.matchAll(/\btools\.([A-Za-z][A-Za-z0-9_]*)\s*\(/g)].map((match) => match[1]);
|
|
837
|
+
const unique = [...new Set(names)];
|
|
838
|
+
return unique.length === 1 ? unique[0] : null;
|
|
839
|
+
}
|
|
840
|
+
function toolInputToString(input) {
|
|
841
|
+
if (typeof input === "string") return input;
|
|
842
|
+
if (input == null) return void 0;
|
|
843
|
+
return JSON.stringify(input);
|
|
844
|
+
}
|
|
845
|
+
var verificationCommand = /\b(?:pnpm|npm|yarn|bun)\s+(?:run\s+)?(?:test|typecheck|lint|build|check)(?::[A-Za-z0-9:_-]+)?\b|\b(?:vitest|jest|pytest|tsc|biome|eslint|sha256sum|pdfinfo|pdftotext)\b|\bgo\s+test\b|\bcargo\s+(?:test|check|clippy|build)\b|\bgit\s+(?:status|diff|show|merge-tree)\b|\bgh-drew\s+pr\s+(?:view|checks)\b/i;
|
|
846
|
+
function hasReadOnlyCurl(input) {
|
|
847
|
+
if (!/\bcurl\b/i.test(input)) return false;
|
|
848
|
+
const method = input.match(/(?:^|\s)-X(?:=|\s*)([A-Za-z]+)\b/)?.[1] ?? input.match(/(?:^|\s)--request(?:=|\s+)([A-Za-z]+)\b/i)?.[1];
|
|
849
|
+
if (method && !/^(?:GET|HEAD)$/i.test(method)) return false;
|
|
850
|
+
return !/(?:^|\s)-(?:d|F|T)(?:\S*|\s+\S+)|(?:^|\s)--(?:data(?:-ascii|-binary|-raw|-urlencode)?|form(?:-string)?|json|upload-file)(?:=|\s)/i.test(input);
|
|
851
|
+
}
|
|
852
|
+
function classifyNestedTool(name, input) {
|
|
853
|
+
return name === "exec_command" && input && (verificationCommand.test(input) || hasReadOnlyCurl(input)) ? "exec_command.verify" : name;
|
|
854
|
+
}
|
|
855
|
+
function isExpectedBlockingTool(name, input) {
|
|
856
|
+
if (!input) return false;
|
|
857
|
+
if (name === "wait") return /\bcell_id\b["']?\s*:/.test(input);
|
|
858
|
+
if (name === "write_stdin") return /\bsession_id\b["']?\s*:/.test(input);
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
811
861
|
async function* walkRollouts(root) {
|
|
812
862
|
let years;
|
|
813
863
|
try {
|
|
@@ -901,6 +951,7 @@ var CodexAdapter = class {
|
|
|
901
951
|
})
|
|
902
952
|
];
|
|
903
953
|
const toolByCallId = /* @__PURE__ */ new Map();
|
|
954
|
+
const subagentByThreadId = /* @__PURE__ */ new Map();
|
|
904
955
|
let step = 0;
|
|
905
956
|
let lastLlm = rootId;
|
|
906
957
|
let sawUserTurn = false;
|
|
@@ -929,9 +980,14 @@ var CodexAdapter = class {
|
|
|
929
980
|
lastLlm = id;
|
|
930
981
|
step += 1;
|
|
931
982
|
}
|
|
932
|
-
} else if (l.type === "response_item" && l.payload?.type === "function_call") {
|
|
933
|
-
const
|
|
983
|
+
} else if (l.type === "response_item" && (l.payload?.type === "function_call" || l.payload?.type === "custom_tool_call")) {
|
|
984
|
+
const outerName = l.payload.name ?? "tool";
|
|
934
985
|
const callId = l.payload.call_id ?? `${step}`;
|
|
986
|
+
const input = toolInputToString(
|
|
987
|
+
l.payload.type === "custom_tool_call" ? l.payload.input : l.payload.arguments
|
|
988
|
+
);
|
|
989
|
+
const nestedName = l.payload.type === "custom_tool_call" ? singleNestedToolName(input) : null;
|
|
990
|
+
const name = classifyNestedTool(nestedName ?? outerName, input);
|
|
935
991
|
const toolSpan = span({
|
|
936
992
|
traceId,
|
|
937
993
|
spanId: `tool:${callId}`,
|
|
@@ -943,18 +999,68 @@ var CodexAdapter = class {
|
|
|
943
999
|
agent: SERVICE3,
|
|
944
1000
|
tool: name,
|
|
945
1001
|
step,
|
|
946
|
-
content:
|
|
1002
|
+
content: input ?? null,
|
|
1003
|
+
extra: {
|
|
1004
|
+
"traces.codex.call_type": l.payload.type,
|
|
1005
|
+
...name !== outerName ? { "traces.codex.outer_tool_name": outerName } : {},
|
|
1006
|
+
...nestedName ? { "traces.codex.nested_tool_name": nestedName } : {},
|
|
1007
|
+
...isExpectedBlockingTool(name, input) ? { "traces.expected_blocking": true } : {}
|
|
1008
|
+
}
|
|
947
1009
|
});
|
|
948
1010
|
spans.push(toolSpan);
|
|
949
1011
|
toolByCallId.set(callId, toolSpan);
|
|
950
1012
|
step += 1;
|
|
951
|
-
} else if (l.type === "response_item" && l.payload?.type === "function_call_output") {
|
|
1013
|
+
} else if (l.type === "response_item" && (l.payload?.type === "function_call_output" || l.payload?.type === "custom_tool_call_output")) {
|
|
952
1014
|
const t = toolByCallId.get(l.payload.call_id ?? "");
|
|
953
1015
|
if (t) {
|
|
954
1016
|
const { error, message } = outputIsError(l.payload.output);
|
|
955
1017
|
t.end_time = ts;
|
|
956
1018
|
t.status = error ? { code: "ERROR", message } : { code: "OK" };
|
|
957
1019
|
}
|
|
1020
|
+
} else if (l.type === "event_msg" && l.payload?.type === "sub_agent_activity") {
|
|
1021
|
+
const threadId = l.payload.agent_thread_id;
|
|
1022
|
+
const occurredAtMs = l.payload.occurred_at_ms;
|
|
1023
|
+
const eventTime = typeof occurredAtMs === "number" && Number.isFinite(occurredAtMs) ? new Date(occurredAtMs).toISOString() : ts;
|
|
1024
|
+
if (l.payload.kind === "started" && threadId && !subagentByThreadId.has(threadId)) {
|
|
1025
|
+
const agentPath = l.payload.agent_path ?? "subagent";
|
|
1026
|
+
const subagentType = agentPath.split("/").filter(Boolean).at(-1) ?? "subagent";
|
|
1027
|
+
const toolSpan = span({
|
|
1028
|
+
traceId,
|
|
1029
|
+
spanId: `subagent:${threadId}`,
|
|
1030
|
+
parentSpanId: lastLlm,
|
|
1031
|
+
name: "tool.Agent",
|
|
1032
|
+
kind: "TOOL",
|
|
1033
|
+
startTime: eventTime,
|
|
1034
|
+
service: SERVICE3,
|
|
1035
|
+
agent: SERVICE3,
|
|
1036
|
+
tool: "Agent",
|
|
1037
|
+
step,
|
|
1038
|
+
content: JSON.stringify({
|
|
1039
|
+
subagent_type: subagentType,
|
|
1040
|
+
agent_path: agentPath,
|
|
1041
|
+
agent_thread_id: threadId
|
|
1042
|
+
}),
|
|
1043
|
+
extra: {
|
|
1044
|
+
"traces.codex.subagent_path": agentPath,
|
|
1045
|
+
"traces.codex.subagent_thread_id": threadId
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
spans.push(toolSpan);
|
|
1049
|
+
subagentByThreadId.set(threadId, toolSpan);
|
|
1050
|
+
step += 1;
|
|
1051
|
+
} else if (l.payload.kind === "completed" && threadId) {
|
|
1052
|
+
const toolSpan = subagentByThreadId.get(threadId);
|
|
1053
|
+
if (toolSpan) {
|
|
1054
|
+
toolSpan.end_time = eventTime;
|
|
1055
|
+
toolSpan.status = { code: "OK" };
|
|
1056
|
+
}
|
|
1057
|
+
} else if (["interrupted", "failed", "timed_out"].includes(l.payload.kind ?? "") && threadId) {
|
|
1058
|
+
const toolSpan = subagentByThreadId.get(threadId);
|
|
1059
|
+
if (toolSpan) {
|
|
1060
|
+
toolSpan.end_time = eventTime;
|
|
1061
|
+
toolSpan.status = { code: "ERROR", message: `subagent ${l.payload.kind}` };
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
958
1064
|
} else if (l.type === "response_item" && l.payload?.type === "message" && l.payload.role === "user") {
|
|
959
1065
|
const prompt = textOf3(l.payload.content);
|
|
960
1066
|
if (prompt) {
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -314,7 +314,8 @@ declare class ClaudeAdapter implements HarnessTraceAdapter {
|
|
|
314
314
|
* Line types: `session_meta` (id + cwd), `turn_context`, `event_msg`
|
|
315
315
|
* (carries `token_count` with per-turn `last_token_usage`), and
|
|
316
316
|
* `response_item` (the OpenAI Responses items: `message`, `reasoning`,
|
|
317
|
-
*
|
|
317
|
+
* function/custom tool calls, and their outputs). Current Codex builds also
|
|
318
|
+
* emit `sub_agent_activity` events for delegated agents.
|
|
318
319
|
*
|
|
319
320
|
* Token trajectory comes from the `token_count` deltas (Codex puts usage
|
|
320
321
|
* on events, not on the message). Tools come from `function_call`, with
|
|
@@ -629,7 +630,9 @@ declare const CORRECTIVE_REACTIONS: readonly Reaction[];
|
|
|
629
630
|
* error rates above, so adding it would only emit a misleading waste %.
|
|
630
631
|
*
|
|
631
632
|
* Both are cheap ($0, deterministic), so they're safe to run continuously in
|
|
632
|
-
* `watch` mode, over the OTLP spans traces already produces.
|
|
633
|
+
* `watch` mode, over the OTLP spans traces already produces. Calls explicitly
|
|
634
|
+
* marked as expected blocking stay in usage totals but are excluded from
|
|
635
|
+
* stuck-loop findings.
|
|
633
636
|
*/
|
|
634
637
|
|
|
635
638
|
interface PipelineReport {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/traces",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.27",
|
|
4
4
|
"description": "Point it at your coding-agent session traces (Claude Code, Codex, OpenCode, Gemini, Pi, …) and get failure-mode + efficiency findings. CLI + SDK over the @tangle-network/agent-eval analyst suite — observe live sessions, run your own analysts, redact, and upload to the Tangle Intelligence Platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|