@bububuger/spanory 0.1.18 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +5109 -1798
- package/package.json +6 -4
- package/dist/alert/evaluate.d.ts +0 -3
- package/dist/alert/evaluate.js +0 -348
- package/dist/env.d.ts +0 -6
- package/dist/env.js +0 -82
- package/dist/issue/state.d.ts +0 -39
- package/dist/issue/state.js +0 -151
- package/dist/otlp.d.ts +0 -5
- package/dist/otlp.js +0 -12
- package/dist/report/aggregate.d.ts +0 -22
- package/dist/report/aggregate.js +0 -371
- package/dist/runtime/claude/adapter.d.ts +0 -18
- package/dist/runtime/claude/adapter.js +0 -222
- package/dist/runtime/codex/adapter.d.ts +0 -18
- package/dist/runtime/codex/adapter.js +0 -493
- package/dist/runtime/codex/proxy.d.ts +0 -9
- package/dist/runtime/codex/proxy.js +0 -215
- package/dist/runtime/openclaw/adapter.d.ts +0 -18
- package/dist/runtime/openclaw/adapter.js +0 -380
- package/dist/runtime/shared/capabilities.d.ts +0 -30
- package/dist/runtime/shared/capabilities.js +0 -39
- package/dist/runtime/shared/file-settle.d.ts +0 -19
- package/dist/runtime/shared/file-settle.js +0 -44
- package/dist/runtime/shared/normalize.d.ts +0 -9
- package/dist/runtime/shared/normalize.js +0 -1016
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export declare function loadExportedEvents(inputPath: any): Promise<any[]>;
|
|
2
|
-
export declare function summarizeSessions(sessions: any): any;
|
|
3
|
-
export declare function summarizeMcp(sessions: any): {
|
|
4
|
-
server: any;
|
|
5
|
-
calls: any;
|
|
6
|
-
sessions: any;
|
|
7
|
-
}[];
|
|
8
|
-
export declare function summarizeCommands(sessions: any): {
|
|
9
|
-
command: any;
|
|
10
|
-
calls: any;
|
|
11
|
-
sessions: any;
|
|
12
|
-
}[];
|
|
13
|
-
export declare function summarizeAgents(sessions: any): any[];
|
|
14
|
-
export declare function summarizeCache(sessions: any): any;
|
|
15
|
-
export declare function summarizeTools(sessions: any): {
|
|
16
|
-
category: any;
|
|
17
|
-
tool: any;
|
|
18
|
-
calls: any;
|
|
19
|
-
sessions: any;
|
|
20
|
-
}[];
|
|
21
|
-
export declare function summarizeTurnDiff(sessions: any): any[];
|
|
22
|
-
export declare function summarizeContext(sessions: any): any;
|
package/dist/report/aggregate.js
DELETED
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
function toNumber(value) {
|
|
5
|
-
const n = Number(value);
|
|
6
|
-
return Number.isFinite(n) ? n : 0;
|
|
7
|
-
}
|
|
8
|
-
function toOptionalNumber(value) {
|
|
9
|
-
const n = Number(value);
|
|
10
|
-
return Number.isFinite(n) ? n : undefined;
|
|
11
|
-
}
|
|
12
|
-
function round6(value) {
|
|
13
|
-
return Number(Number(value).toFixed(6));
|
|
14
|
-
}
|
|
15
|
-
function parseTurnOrdinal(turnId) {
|
|
16
|
-
const m = String(turnId ?? '').match(/^turn-(\d+)$/);
|
|
17
|
-
if (!m)
|
|
18
|
-
return undefined;
|
|
19
|
-
const n = Number(m[1]);
|
|
20
|
-
return Number.isFinite(n) ? n : undefined;
|
|
21
|
-
}
|
|
22
|
-
function usageFromEvent(event) {
|
|
23
|
-
const attrs = event.attributes ?? {};
|
|
24
|
-
const input = toNumber(attrs['gen_ai.usage.input_tokens']);
|
|
25
|
-
const output = toNumber(attrs['gen_ai.usage.output_tokens']);
|
|
26
|
-
const total = toNumber(attrs['gen_ai.usage.total_tokens']) || input + output;
|
|
27
|
-
return { input, output, total };
|
|
28
|
-
}
|
|
29
|
-
function parseJsonObject(value) {
|
|
30
|
-
if (typeof value !== 'string' || !value.trim())
|
|
31
|
-
return null;
|
|
32
|
-
try {
|
|
33
|
-
const parsed = JSON.parse(value);
|
|
34
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
35
|
-
return parsed;
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
// ignore parse errors
|
|
39
|
-
}
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
function parseJsonArray(value) {
|
|
43
|
-
if (typeof value !== 'string' || !value.trim())
|
|
44
|
-
return [];
|
|
45
|
-
try {
|
|
46
|
-
const parsed = JSON.parse(value);
|
|
47
|
-
if (Array.isArray(parsed))
|
|
48
|
-
return parsed;
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
// ignore parse errors
|
|
52
|
-
}
|
|
53
|
-
return [];
|
|
54
|
-
}
|
|
55
|
-
export async function loadExportedEvents(inputPath) {
|
|
56
|
-
const inputStat = await stat(inputPath);
|
|
57
|
-
const files = [];
|
|
58
|
-
if (inputStat.isDirectory()) {
|
|
59
|
-
const names = await readdir(inputPath);
|
|
60
|
-
for (const name of names) {
|
|
61
|
-
if (name.endsWith('.json'))
|
|
62
|
-
files.push(path.join(inputPath, name));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
files.push(inputPath);
|
|
67
|
-
}
|
|
68
|
-
const sessions = [];
|
|
69
|
-
for (const file of files) {
|
|
70
|
-
const raw = await readFile(file, 'utf-8');
|
|
71
|
-
const parsed = JSON.parse(raw);
|
|
72
|
-
if (!Array.isArray(parsed.events))
|
|
73
|
-
continue;
|
|
74
|
-
sessions.push({
|
|
75
|
-
file,
|
|
76
|
-
context: parsed.context ?? {},
|
|
77
|
-
events: parsed.events,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
return sessions;
|
|
81
|
-
}
|
|
82
|
-
export function summarizeSessions(sessions) {
|
|
83
|
-
return sessions.map((s) => {
|
|
84
|
-
const turns = s.events.filter((e) => e.category === 'turn');
|
|
85
|
-
const usage = turns.reduce((acc, e) => {
|
|
86
|
-
const u = usageFromEvent(e);
|
|
87
|
-
acc.input += u.input;
|
|
88
|
-
acc.output += u.output;
|
|
89
|
-
acc.total += u.total;
|
|
90
|
-
return acc;
|
|
91
|
-
}, { input: 0, output: 0, total: 0 });
|
|
92
|
-
return {
|
|
93
|
-
projectId: s.context.projectId ?? s.events[0]?.projectId,
|
|
94
|
-
sessionId: s.context.sessionId ?? s.events[0]?.sessionId,
|
|
95
|
-
runtime: s.events[0]?.runtime,
|
|
96
|
-
turns: turns.length,
|
|
97
|
-
events: s.events.length,
|
|
98
|
-
usage,
|
|
99
|
-
};
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
export function summarizeMcp(sessions) {
|
|
103
|
-
const agg = new Map();
|
|
104
|
-
for (const s of sessions) {
|
|
105
|
-
for (const e of s.events) {
|
|
106
|
-
if (e.category !== 'mcp')
|
|
107
|
-
continue;
|
|
108
|
-
const attrs = e.attributes ?? {};
|
|
109
|
-
const key = attrs['agentic.mcp.server.name'] ?? attrs['gen_ai.tool.name'] ?? e.name;
|
|
110
|
-
const cur = agg.get(key) ?? { server: key, calls: 0, sessions: new Set() };
|
|
111
|
-
cur.calls += 1;
|
|
112
|
-
cur.sessions.add(e.sessionId);
|
|
113
|
-
agg.set(key, cur);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return [...agg.values()].map((v) => ({ server: v.server, calls: v.calls, sessions: v.sessions.size }));
|
|
117
|
-
}
|
|
118
|
-
export function summarizeCommands(sessions) {
|
|
119
|
-
const agg = new Map();
|
|
120
|
-
for (const s of sessions) {
|
|
121
|
-
for (const e of s.events) {
|
|
122
|
-
if (e.category !== 'agent_command')
|
|
123
|
-
continue;
|
|
124
|
-
const command = e.attributes?.['agentic.command.name'] ?? e.name;
|
|
125
|
-
const cur = agg.get(command) ?? { command, calls: 0, sessions: new Set() };
|
|
126
|
-
cur.calls += 1;
|
|
127
|
-
cur.sessions.add(e.sessionId);
|
|
128
|
-
agg.set(command, cur);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return [...agg.values()].map((v) => ({ command: v.command, calls: v.calls, sessions: v.sessions.size }));
|
|
132
|
-
}
|
|
133
|
-
export function summarizeAgents(sessions) {
|
|
134
|
-
const out = [];
|
|
135
|
-
for (const s of sessions) {
|
|
136
|
-
const turns = s.events.filter((e) => e.category === 'turn');
|
|
137
|
-
const tasks = s.events.filter((e) => e.category === 'agent_task');
|
|
138
|
-
const shell = s.events.filter((e) => e.category === 'shell_command');
|
|
139
|
-
const mcp = s.events.filter((e) => e.category === 'mcp');
|
|
140
|
-
const usage = turns.reduce((acc, e) => {
|
|
141
|
-
const u = usageFromEvent(e);
|
|
142
|
-
acc.input += u.input;
|
|
143
|
-
acc.output += u.output;
|
|
144
|
-
acc.total += u.total;
|
|
145
|
-
return acc;
|
|
146
|
-
}, { input: 0, output: 0, total: 0 });
|
|
147
|
-
out.push({
|
|
148
|
-
sessionId: s.context.sessionId ?? s.events[0]?.sessionId,
|
|
149
|
-
projectId: s.context.projectId ?? s.events[0]?.projectId,
|
|
150
|
-
runtime: s.events[0]?.runtime,
|
|
151
|
-
turns: turns.length,
|
|
152
|
-
agentTasks: tasks.length,
|
|
153
|
-
shellCommands: shell.length,
|
|
154
|
-
mcpCalls: mcp.length,
|
|
155
|
-
usage,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
return out;
|
|
159
|
-
}
|
|
160
|
-
export function summarizeCache(sessions) {
|
|
161
|
-
return sessions.map((s) => {
|
|
162
|
-
const turns = s.events.filter((e) => e.category === 'turn');
|
|
163
|
-
let inputTokens = 0;
|
|
164
|
-
let cacheReadInputTokens = 0;
|
|
165
|
-
let cacheCreationInputTokens = 0;
|
|
166
|
-
const explicitHitRates = [];
|
|
167
|
-
for (const turn of turns) {
|
|
168
|
-
const attrs = turn.attributes ?? {};
|
|
169
|
-
inputTokens += toNumber(attrs['gen_ai.usage.input_tokens']);
|
|
170
|
-
cacheReadInputTokens += toNumber(attrs['gen_ai.usage.cache_read.input_tokens']);
|
|
171
|
-
cacheCreationInputTokens += toNumber(attrs['gen_ai.usage.cache_creation.input_tokens']);
|
|
172
|
-
const hitRate = toOptionalNumber(attrs['gen_ai.usage.details.cache_hit_rate']);
|
|
173
|
-
if (hitRate !== undefined)
|
|
174
|
-
explicitHitRates.push(hitRate);
|
|
175
|
-
}
|
|
176
|
-
const cacheHitRate = explicitHitRates.length > 0
|
|
177
|
-
? round6(explicitHitRates.reduce((acc, v) => acc + v, 0) / explicitHitRates.length)
|
|
178
|
-
: round6((inputTokens + cacheReadInputTokens) > 0 ? cacheReadInputTokens / (inputTokens + cacheReadInputTokens) : 0);
|
|
179
|
-
return {
|
|
180
|
-
projectId: s.context.projectId ?? s.events[0]?.projectId,
|
|
181
|
-
sessionId: s.context.sessionId ?? s.events[0]?.sessionId,
|
|
182
|
-
runtime: s.events[0]?.runtime,
|
|
183
|
-
turns: turns.length,
|
|
184
|
-
inputTokens,
|
|
185
|
-
cacheReadInputTokens,
|
|
186
|
-
cacheCreationInputTokens,
|
|
187
|
-
cacheHitRate,
|
|
188
|
-
};
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
export function summarizeTools(sessions) {
|
|
192
|
-
const agg = new Map();
|
|
193
|
-
for (const s of sessions) {
|
|
194
|
-
for (const e of s.events) {
|
|
195
|
-
if (!['tool', 'mcp', 'shell_command', 'agent_task'].includes(e.category))
|
|
196
|
-
continue;
|
|
197
|
-
const attrs = e.attributes ?? {};
|
|
198
|
-
const tool = attrs['gen_ai.tool.name'] ?? e.name;
|
|
199
|
-
const key = `${e.category}:${tool}`;
|
|
200
|
-
const cur = agg.get(key) ?? {
|
|
201
|
-
category: e.category,
|
|
202
|
-
tool,
|
|
203
|
-
calls: 0,
|
|
204
|
-
sessions: new Set(),
|
|
205
|
-
};
|
|
206
|
-
cur.calls += 1;
|
|
207
|
-
cur.sessions.add(e.sessionId);
|
|
208
|
-
agg.set(key, cur);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return [...agg.values()]
|
|
212
|
-
.map((v) => ({
|
|
213
|
-
category: v.category,
|
|
214
|
-
tool: v.tool,
|
|
215
|
-
calls: v.calls,
|
|
216
|
-
sessions: v.sessions.size,
|
|
217
|
-
}))
|
|
218
|
-
.sort((a, b) => {
|
|
219
|
-
if (b.calls !== a.calls)
|
|
220
|
-
return b.calls - a.calls;
|
|
221
|
-
if (a.category !== b.category)
|
|
222
|
-
return a.category.localeCompare(b.category);
|
|
223
|
-
return a.tool.localeCompare(b.tool);
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
export function summarizeTurnDiff(sessions) {
|
|
227
|
-
const rows = [];
|
|
228
|
-
for (const s of sessions) {
|
|
229
|
-
const turns = s.events
|
|
230
|
-
.filter((e) => e.category === 'turn')
|
|
231
|
-
.slice()
|
|
232
|
-
.sort((a, b) => {
|
|
233
|
-
const ao = parseTurnOrdinal(a.turnId);
|
|
234
|
-
const bo = parseTurnOrdinal(b.turnId);
|
|
235
|
-
if (ao === undefined && bo === undefined)
|
|
236
|
-
return String(a.turnId ?? '').localeCompare(String(b.turnId ?? ''));
|
|
237
|
-
if (ao === undefined)
|
|
238
|
-
return 1;
|
|
239
|
-
if (bo === undefined)
|
|
240
|
-
return -1;
|
|
241
|
-
return ao - bo;
|
|
242
|
-
});
|
|
243
|
-
for (let i = 0; i < turns.length; i += 1) {
|
|
244
|
-
const turn = turns[i];
|
|
245
|
-
const attrs = turn.attributes ?? {};
|
|
246
|
-
const input = String(turn.input ?? '');
|
|
247
|
-
const prevInput = String(turns[i - 1]?.input ?? '');
|
|
248
|
-
const charDelta = toOptionalNumber(attrs['agentic.turn.diff.char_delta'])
|
|
249
|
-
?? (i === 0 ? 0 : input.length - prevInput.length);
|
|
250
|
-
const lineDelta = toOptionalNumber(attrs['agentic.turn.diff.line_delta'])
|
|
251
|
-
?? (i === 0 ? 0 : (input ? input.split(/\r?\n/).length : 0) - (prevInput ? prevInput.split(/\r?\n/).length : 0));
|
|
252
|
-
const similarity = toOptionalNumber(attrs['agentic.turn.diff.similarity']) ?? (i === 0 ? 1 : undefined);
|
|
253
|
-
const changed = typeof attrs['agentic.turn.diff.changed'] === 'boolean'
|
|
254
|
-
? attrs['agentic.turn.diff.changed']
|
|
255
|
-
: (i === 0 ? false : input !== prevInput);
|
|
256
|
-
rows.push({
|
|
257
|
-
projectId: s.context.projectId ?? turn.projectId,
|
|
258
|
-
sessionId: s.context.sessionId ?? turn.sessionId,
|
|
259
|
-
runtime: turn.runtime,
|
|
260
|
-
turnId: turn.turnId,
|
|
261
|
-
inputHash: attrs['agentic.turn.input.hash'] ?? '',
|
|
262
|
-
prevHash: attrs['agentic.turn.input.prev_hash'] ?? '',
|
|
263
|
-
charDelta,
|
|
264
|
-
lineDelta,
|
|
265
|
-
similarity,
|
|
266
|
-
changed,
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return rows;
|
|
271
|
-
}
|
|
272
|
-
export function summarizeContext(sessions) {
|
|
273
|
-
return sessions.map((s) => {
|
|
274
|
-
const events = s.events ?? [];
|
|
275
|
-
const snapshots = events.filter((e) => e?.attributes?.['agentic.context.event_type'] === 'context_snapshot');
|
|
276
|
-
const boundaries = events.filter((e) => e?.attributes?.['agentic.context.event_type'] === 'context_boundary');
|
|
277
|
-
const attributions = events.filter((e) => e?.attributes?.['agentic.context.event_type'] === 'context_source_attribution');
|
|
278
|
-
let maxFillRatio = 0;
|
|
279
|
-
let maxDeltaTokens = 0;
|
|
280
|
-
for (const snapshot of snapshots) {
|
|
281
|
-
const attrs = snapshot.attributes ?? {};
|
|
282
|
-
const fillRatio = toOptionalNumber(attrs['agentic.context.fill_ratio']) ?? 0;
|
|
283
|
-
const deltaTokens = toOptionalNumber(attrs['agentic.context.delta_tokens']) ?? 0;
|
|
284
|
-
maxFillRatio = Math.max(maxFillRatio, fillRatio);
|
|
285
|
-
maxDeltaTokens = Math.max(maxDeltaTokens, deltaTokens);
|
|
286
|
-
}
|
|
287
|
-
const compactCount = boundaries.filter((e) => String(e?.attributes?.['agentic.context.boundary_kind'] ?? '') === 'compact_after').length;
|
|
288
|
-
const last5 = snapshots.slice(-5);
|
|
289
|
-
let unknownTokens = 0;
|
|
290
|
-
let totalTokens = 0;
|
|
291
|
-
for (const snapshot of last5) {
|
|
292
|
-
const composition = parseJsonObject(snapshot?.attributes?.['agentic.context.composition']);
|
|
293
|
-
if (!composition)
|
|
294
|
-
continue;
|
|
295
|
-
for (const [kind, raw] of Object.entries(composition)) {
|
|
296
|
-
const value = Number(raw);
|
|
297
|
-
if (!Number.isFinite(value) || value <= 0)
|
|
298
|
-
continue;
|
|
299
|
-
totalTokens += value;
|
|
300
|
-
if (kind === 'unknown')
|
|
301
|
-
unknownTokens += value;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
const unknownDeltaShareWindow5 = totalTokens > 0 ? round6(unknownTokens / totalTokens) : 0;
|
|
305
|
-
let unknownTopStreak = 0;
|
|
306
|
-
let runningUnknown = 0;
|
|
307
|
-
for (const snapshot of snapshots) {
|
|
308
|
-
const topSources = parseJsonArray(snapshot?.attributes?.['agentic.context.top_sources']);
|
|
309
|
-
const top = String(topSources[0] ?? '').trim();
|
|
310
|
-
if (top === 'unknown') {
|
|
311
|
-
runningUnknown += 1;
|
|
312
|
-
unknownTopStreak = Math.max(unknownTopStreak, runningUnknown);
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
runningUnknown = 0;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
let highPollutionSourceStreak = 0;
|
|
319
|
-
const turnOrder = [];
|
|
320
|
-
const highByTurn = new Map();
|
|
321
|
-
for (const event of attributions) {
|
|
322
|
-
const attrs = event?.attributes ?? {};
|
|
323
|
-
const turnId = String(event?.turnId ?? '');
|
|
324
|
-
if (!turnId)
|
|
325
|
-
continue;
|
|
326
|
-
if (!highByTurn.has(turnId)) {
|
|
327
|
-
highByTurn.set(turnId, []);
|
|
328
|
-
turnOrder.push(turnId);
|
|
329
|
-
}
|
|
330
|
-
const sourceKind = String(attrs['agentic.context.source_kind'] ?? '').trim();
|
|
331
|
-
const score = Number(attrs['agentic.context.pollution_score']);
|
|
332
|
-
if (!sourceKind || !Number.isFinite(score) || score < 80)
|
|
333
|
-
continue;
|
|
334
|
-
highByTurn.get(turnId).push({ sourceKind, score });
|
|
335
|
-
}
|
|
336
|
-
let runningSource = '';
|
|
337
|
-
let runningCount = 0;
|
|
338
|
-
for (const turnId of turnOrder) {
|
|
339
|
-
const items = highByTurn.get(turnId) ?? [];
|
|
340
|
-
if (!items.length) {
|
|
341
|
-
runningSource = '';
|
|
342
|
-
runningCount = 0;
|
|
343
|
-
continue;
|
|
344
|
-
}
|
|
345
|
-
items.sort((a, b) => b.score - a.score);
|
|
346
|
-
const topSource = items[0].sourceKind;
|
|
347
|
-
if (topSource === runningSource) {
|
|
348
|
-
runningCount += 1;
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
runningSource = topSource;
|
|
352
|
-
runningCount = 1;
|
|
353
|
-
}
|
|
354
|
-
highPollutionSourceStreak = Math.max(highPollutionSourceStreak, runningCount);
|
|
355
|
-
}
|
|
356
|
-
return {
|
|
357
|
-
projectId: s.context.projectId ?? events[0]?.projectId,
|
|
358
|
-
sessionId: s.context.sessionId ?? events[0]?.sessionId,
|
|
359
|
-
runtime: events[0]?.runtime,
|
|
360
|
-
snapshots: snapshots.length,
|
|
361
|
-
boundaries: boundaries.length,
|
|
362
|
-
compactCount,
|
|
363
|
-
attributions: attributions.length,
|
|
364
|
-
maxFillRatio: round6(maxFillRatio),
|
|
365
|
-
maxDeltaTokens,
|
|
366
|
-
unknownDeltaShareWindow5,
|
|
367
|
-
unknownTopStreak,
|
|
368
|
-
highPollutionSourceStreak,
|
|
369
|
-
};
|
|
370
|
-
});
|
|
371
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export declare const claudeCodeAdapter: {
|
|
2
|
-
runtimeName: string;
|
|
3
|
-
capabilities: {
|
|
4
|
-
turnDetection: boolean;
|
|
5
|
-
toolCallAttribution: boolean;
|
|
6
|
-
toolResultCorrelation: boolean;
|
|
7
|
-
modelName: boolean;
|
|
8
|
-
usageDetails: boolean;
|
|
9
|
-
slashCommandExtraction: boolean;
|
|
10
|
-
mcpServerExtraction: boolean;
|
|
11
|
-
};
|
|
12
|
-
resolveContextFromHook(payload: any): {
|
|
13
|
-
projectId: any;
|
|
14
|
-
sessionId: any;
|
|
15
|
-
transcriptPath: any;
|
|
16
|
-
};
|
|
17
|
-
collectEvents(context: any): Promise<any[]>;
|
|
18
|
-
};
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import { readdir, readFile } from 'node:fs/promises';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { RUNTIME_CAPABILITIES } from '../shared/capabilities.js';
|
|
5
|
-
import { normalizeTranscriptMessages, parseProjectIdFromTranscriptPath, pickUsage } from '../shared/normalize.js';
|
|
6
|
-
const INFER_WINDOW_EPSILON_MS = 1200;
|
|
7
|
-
function parseTimestamp(entry) {
|
|
8
|
-
const raw = entry?.timestamp;
|
|
9
|
-
const date = raw ? new Date(raw) : new Date();
|
|
10
|
-
if (Number.isNaN(date.getTime()))
|
|
11
|
-
return new Date();
|
|
12
|
-
return date;
|
|
13
|
-
}
|
|
14
|
-
function normalizeIsSidechain(entry) {
|
|
15
|
-
const raw = entry?.isSidechain ?? entry?.is_sidechain ?? entry?.message?.isSidechain ?? entry?.message?.is_sidechain;
|
|
16
|
-
return raw === true;
|
|
17
|
-
}
|
|
18
|
-
function normalizeAgentId(entry) {
|
|
19
|
-
return entry?.agentId ?? entry?.agent_id ?? entry?.message?.agentId ?? entry?.message?.agent_id;
|
|
20
|
-
}
|
|
21
|
-
function extractToolUses(content) {
|
|
22
|
-
if (!Array.isArray(content))
|
|
23
|
-
return [];
|
|
24
|
-
return content.filter((block) => block && typeof block === 'object' && block.type === 'tool_use');
|
|
25
|
-
}
|
|
26
|
-
function extractToolResults(content) {
|
|
27
|
-
if (!Array.isArray(content))
|
|
28
|
-
return [];
|
|
29
|
-
return content.filter((block) => block && typeof block === 'object' && block.type === 'tool_result');
|
|
30
|
-
}
|
|
31
|
-
function isToolResultOnlyContent(content) {
|
|
32
|
-
return Array.isArray(content)
|
|
33
|
-
&& content.length > 0
|
|
34
|
-
&& content.every((block) => block && typeof block === 'object' && block.type === 'tool_result');
|
|
35
|
-
}
|
|
36
|
-
function isPromptUserMessage(message) {
|
|
37
|
-
if (!message || message.role !== 'user' || message.isMeta)
|
|
38
|
-
return false;
|
|
39
|
-
const { content } = message;
|
|
40
|
-
if (typeof content === 'string')
|
|
41
|
-
return content.trim().length > 0;
|
|
42
|
-
if (!Array.isArray(content))
|
|
43
|
-
return false;
|
|
44
|
-
if (isToolResultOnlyContent(content))
|
|
45
|
-
return false;
|
|
46
|
-
return content.length > 0;
|
|
47
|
-
}
|
|
48
|
-
function firstNonEmpty(values) {
|
|
49
|
-
for (const value of values) {
|
|
50
|
-
const text = String(value ?? '').trim();
|
|
51
|
-
if (text)
|
|
52
|
-
return text;
|
|
53
|
-
}
|
|
54
|
-
return '';
|
|
55
|
-
}
|
|
56
|
-
function findChildSessionHints(messages) {
|
|
57
|
-
const hasSidechainSignal = messages.some((m) => m?.isSidechain === true || String(m?.agentId ?? '').trim().length > 0);
|
|
58
|
-
if (!hasSidechainSignal)
|
|
59
|
-
return null;
|
|
60
|
-
const hasParentLink = messages.some((m) => String(m?.parentSessionId ?? m?.parent_session_id ?? '').trim().length > 0
|
|
61
|
-
|| String(m?.parentTurnId ?? m?.parent_turn_id ?? '').trim().length > 0
|
|
62
|
-
|| String(m?.parentToolCallId ?? m?.parent_tool_call_id ?? '').trim().length > 0);
|
|
63
|
-
if (hasParentLink)
|
|
64
|
-
return null;
|
|
65
|
-
const sorted = [...messages].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
66
|
-
const childStartedAt = sorted[0]?.timestamp;
|
|
67
|
-
if (!childStartedAt)
|
|
68
|
-
return null;
|
|
69
|
-
return {
|
|
70
|
-
childStartedAt,
|
|
71
|
-
agentId: firstNonEmpty(messages.map((m) => m?.agentId)),
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function extractTaskWindows(messages, sessionId) {
|
|
75
|
-
const sorted = [...messages].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
76
|
-
const windows = [];
|
|
77
|
-
const byCallId = new Map();
|
|
78
|
-
let turnIndex = 0;
|
|
79
|
-
let currentTurnId = 'turn-1';
|
|
80
|
-
for (const msg of sorted) {
|
|
81
|
-
if (isPromptUserMessage(msg)) {
|
|
82
|
-
turnIndex += 1;
|
|
83
|
-
currentTurnId = `turn-${turnIndex}`;
|
|
84
|
-
}
|
|
85
|
-
if (msg.role === 'assistant') {
|
|
86
|
-
for (const tu of extractToolUses(msg.content)) {
|
|
87
|
-
const toolName = String(tu.name ?? '').trim();
|
|
88
|
-
if (toolName !== 'Task')
|
|
89
|
-
continue;
|
|
90
|
-
const callId = String(tu.id ?? '').trim();
|
|
91
|
-
if (!callId)
|
|
92
|
-
continue;
|
|
93
|
-
const window = {
|
|
94
|
-
parentSessionId: sessionId,
|
|
95
|
-
parentTurnId: currentTurnId,
|
|
96
|
-
parentToolCallId: callId,
|
|
97
|
-
startedAtMs: msg.timestamp.getTime(),
|
|
98
|
-
endedAtMs: msg.timestamp.getTime(),
|
|
99
|
-
};
|
|
100
|
-
windows.push(window);
|
|
101
|
-
byCallId.set(callId, window);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
if (msg.role === 'user') {
|
|
105
|
-
for (const tr of extractToolResults(msg.content)) {
|
|
106
|
-
const callId = String(tr.tool_use_id ?? tr.toolUseId ?? '').trim();
|
|
107
|
-
if (!callId)
|
|
108
|
-
continue;
|
|
109
|
-
const window = byCallId.get(callId);
|
|
110
|
-
if (window) {
|
|
111
|
-
window.endedAtMs = Math.max(window.endedAtMs, msg.timestamp.getTime());
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return windows;
|
|
117
|
-
}
|
|
118
|
-
async function inferParentLinkFromSiblingSessions({ transcriptPath, messages }) {
|
|
119
|
-
const hints = findChildSessionHints(messages);
|
|
120
|
-
if (!hints)
|
|
121
|
-
return messages;
|
|
122
|
-
const currentSessionId = path.basename(transcriptPath, '.jsonl');
|
|
123
|
-
const dir = path.dirname(transcriptPath);
|
|
124
|
-
let entries = [];
|
|
125
|
-
try {
|
|
126
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
return messages;
|
|
130
|
-
}
|
|
131
|
-
const candidates = [];
|
|
132
|
-
for (const entry of entries) {
|
|
133
|
-
if (!entry.isFile() || !entry.name.endsWith('.jsonl'))
|
|
134
|
-
continue;
|
|
135
|
-
const siblingSessionId = entry.name.slice(0, -'.jsonl'.length);
|
|
136
|
-
if (siblingSessionId === currentSessionId)
|
|
137
|
-
continue;
|
|
138
|
-
const siblingPath = path.join(dir, entry.name);
|
|
139
|
-
const siblingMessages = await readClaudeTranscript(siblingPath);
|
|
140
|
-
const windows = extractTaskWindows(siblingMessages, siblingSessionId);
|
|
141
|
-
for (const window of windows) {
|
|
142
|
-
const childAtMs = hints.childStartedAt.getTime();
|
|
143
|
-
const lower = window.startedAtMs - INFER_WINDOW_EPSILON_MS;
|
|
144
|
-
const upper = window.endedAtMs + INFER_WINDOW_EPSILON_MS;
|
|
145
|
-
if (childAtMs < lower || childAtMs > upper)
|
|
146
|
-
continue;
|
|
147
|
-
candidates.push({
|
|
148
|
-
...window,
|
|
149
|
-
score: Math.abs(childAtMs - window.startedAtMs),
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (candidates.length === 0)
|
|
154
|
-
return messages;
|
|
155
|
-
candidates.sort((a, b) => a.score - b.score);
|
|
156
|
-
const best = candidates[0];
|
|
157
|
-
return messages.map((msg) => ({
|
|
158
|
-
...msg,
|
|
159
|
-
parentSessionId: best.parentSessionId,
|
|
160
|
-
parentTurnId: best.parentTurnId,
|
|
161
|
-
parentToolCallId: best.parentToolCallId,
|
|
162
|
-
parentLinkConfidence: 'inferred',
|
|
163
|
-
}));
|
|
164
|
-
}
|
|
165
|
-
async function readClaudeTranscript(transcriptPath) {
|
|
166
|
-
const raw = await readFile(transcriptPath, 'utf-8');
|
|
167
|
-
const lines = raw.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
168
|
-
const messages = [];
|
|
169
|
-
for (const line of lines) {
|
|
170
|
-
try {
|
|
171
|
-
const entry = JSON.parse(line);
|
|
172
|
-
messages.push({
|
|
173
|
-
role: entry.type,
|
|
174
|
-
isMeta: entry.isMeta ?? false,
|
|
175
|
-
isSidechain: normalizeIsSidechain(entry),
|
|
176
|
-
agentId: normalizeAgentId(entry),
|
|
177
|
-
parentSessionId: entry?.parentSessionId ?? entry?.parent_session_id,
|
|
178
|
-
parentTurnId: entry?.parentTurnId ?? entry?.parent_turn_id,
|
|
179
|
-
parentToolCallId: entry?.parentToolCallId ?? entry?.parent_tool_call_id,
|
|
180
|
-
content: entry?.message?.content ?? entry.content ?? '',
|
|
181
|
-
model: entry?.message?.model ?? entry.model,
|
|
182
|
-
usage: pickUsage(entry?.message?.usage ?? entry?.usage ?? entry?.message_usage),
|
|
183
|
-
runtimeVersion: entry?.version,
|
|
184
|
-
messageId: entry?.message?.id,
|
|
185
|
-
toolUseResult: entry?.toolUseResult,
|
|
186
|
-
sourceToolUseId: entry?.sourceToolUseID ?? entry?.sourceToolUseId,
|
|
187
|
-
timestamp: parseTimestamp(entry),
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
// ignore malformed lines
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
messages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
195
|
-
return messages;
|
|
196
|
-
}
|
|
197
|
-
export const claudeCodeAdapter = {
|
|
198
|
-
runtimeName: 'claude-code',
|
|
199
|
-
capabilities: RUNTIME_CAPABILITIES['claude-code'],
|
|
200
|
-
resolveContextFromHook(payload) {
|
|
201
|
-
const sessionId = payload.sessionId;
|
|
202
|
-
const transcriptPath = payload.transcriptPath;
|
|
203
|
-
if (!sessionId || !transcriptPath)
|
|
204
|
-
return null;
|
|
205
|
-
const projectId = parseProjectIdFromTranscriptPath(transcriptPath, '/.claude/projects/');
|
|
206
|
-
if (!projectId)
|
|
207
|
-
return null;
|
|
208
|
-
return { projectId, sessionId, transcriptPath };
|
|
209
|
-
},
|
|
210
|
-
async collectEvents(context) {
|
|
211
|
-
const transcriptPath = context.transcriptPath ??
|
|
212
|
-
path.join(process.env.HOME || '', '.claude', 'projects', context.projectId, `${context.sessionId}.jsonl`);
|
|
213
|
-
const loaded = await readClaudeTranscript(transcriptPath);
|
|
214
|
-
const messages = await inferParentLinkFromSiblingSessions({ transcriptPath, messages: loaded });
|
|
215
|
-
return normalizeTranscriptMessages({
|
|
216
|
-
runtime: 'claude-code',
|
|
217
|
-
projectId: context.projectId,
|
|
218
|
-
sessionId: context.sessionId,
|
|
219
|
-
messages,
|
|
220
|
-
});
|
|
221
|
-
},
|
|
222
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export declare const codexAdapter: {
|
|
2
|
-
runtimeName: string;
|
|
3
|
-
capabilities: {
|
|
4
|
-
turnDetection: boolean;
|
|
5
|
-
toolCallAttribution: boolean;
|
|
6
|
-
toolResultCorrelation: boolean;
|
|
7
|
-
modelName: boolean;
|
|
8
|
-
usageDetails: boolean;
|
|
9
|
-
slashCommandExtraction: boolean;
|
|
10
|
-
mcpServerExtraction: boolean;
|
|
11
|
-
};
|
|
12
|
-
resolveContextFromHook(payload: any): {
|
|
13
|
-
transcriptPath?: any;
|
|
14
|
-
projectId: string;
|
|
15
|
-
sessionId: any;
|
|
16
|
-
};
|
|
17
|
-
collectEvents(context: any): Promise<any>;
|
|
18
|
-
};
|