@bububuger/spanory 0.1.16 → 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 +5143 -1594
- package/package.json +11 -7
- package/dist/alert/evaluate.d.ts +0 -3
- package/dist/alert/evaluate.js +0 -197
- package/dist/env.d.ts +0 -2
- package/dist/env.js +0 -54
- 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 -21
- package/dist/report/aggregate.js +0 -245
- 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 -212
- 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 -644
package/dist/report/aggregate.js
DELETED
|
@@ -1,245 +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
|
-
export async function loadExportedEvents(inputPath) {
|
|
30
|
-
const inputStat = await stat(inputPath);
|
|
31
|
-
const files = [];
|
|
32
|
-
if (inputStat.isDirectory()) {
|
|
33
|
-
const names = await readdir(inputPath);
|
|
34
|
-
for (const name of names) {
|
|
35
|
-
if (name.endsWith('.json'))
|
|
36
|
-
files.push(path.join(inputPath, name));
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
files.push(inputPath);
|
|
41
|
-
}
|
|
42
|
-
const sessions = [];
|
|
43
|
-
for (const file of files) {
|
|
44
|
-
const raw = await readFile(file, 'utf-8');
|
|
45
|
-
const parsed = JSON.parse(raw);
|
|
46
|
-
if (!Array.isArray(parsed.events))
|
|
47
|
-
continue;
|
|
48
|
-
sessions.push({
|
|
49
|
-
file,
|
|
50
|
-
context: parsed.context ?? {},
|
|
51
|
-
events: parsed.events,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
return sessions;
|
|
55
|
-
}
|
|
56
|
-
export function summarizeSessions(sessions) {
|
|
57
|
-
return sessions.map((s) => {
|
|
58
|
-
const turns = s.events.filter((e) => e.category === 'turn');
|
|
59
|
-
const usage = turns.reduce((acc, e) => {
|
|
60
|
-
const u = usageFromEvent(e);
|
|
61
|
-
acc.input += u.input;
|
|
62
|
-
acc.output += u.output;
|
|
63
|
-
acc.total += u.total;
|
|
64
|
-
return acc;
|
|
65
|
-
}, { input: 0, output: 0, total: 0 });
|
|
66
|
-
return {
|
|
67
|
-
projectId: s.context.projectId ?? s.events[0]?.projectId,
|
|
68
|
-
sessionId: s.context.sessionId ?? s.events[0]?.sessionId,
|
|
69
|
-
runtime: s.events[0]?.runtime,
|
|
70
|
-
turns: turns.length,
|
|
71
|
-
events: s.events.length,
|
|
72
|
-
usage,
|
|
73
|
-
};
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
export function summarizeMcp(sessions) {
|
|
77
|
-
const agg = new Map();
|
|
78
|
-
for (const s of sessions) {
|
|
79
|
-
for (const e of s.events) {
|
|
80
|
-
if (e.category !== 'mcp')
|
|
81
|
-
continue;
|
|
82
|
-
const attrs = e.attributes ?? {};
|
|
83
|
-
const key = attrs['agentic.mcp.server.name'] ?? attrs['gen_ai.tool.name'] ?? e.name;
|
|
84
|
-
const cur = agg.get(key) ?? { server: key, calls: 0, sessions: new Set() };
|
|
85
|
-
cur.calls += 1;
|
|
86
|
-
cur.sessions.add(e.sessionId);
|
|
87
|
-
agg.set(key, cur);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return [...agg.values()].map((v) => ({ server: v.server, calls: v.calls, sessions: v.sessions.size }));
|
|
91
|
-
}
|
|
92
|
-
export function summarizeCommands(sessions) {
|
|
93
|
-
const agg = new Map();
|
|
94
|
-
for (const s of sessions) {
|
|
95
|
-
for (const e of s.events) {
|
|
96
|
-
if (e.category !== 'agent_command')
|
|
97
|
-
continue;
|
|
98
|
-
const command = e.attributes?.['agentic.command.name'] ?? e.name;
|
|
99
|
-
const cur = agg.get(command) ?? { command, calls: 0, sessions: new Set() };
|
|
100
|
-
cur.calls += 1;
|
|
101
|
-
cur.sessions.add(e.sessionId);
|
|
102
|
-
agg.set(command, cur);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return [...agg.values()].map((v) => ({ command: v.command, calls: v.calls, sessions: v.sessions.size }));
|
|
106
|
-
}
|
|
107
|
-
export function summarizeAgents(sessions) {
|
|
108
|
-
const out = [];
|
|
109
|
-
for (const s of sessions) {
|
|
110
|
-
const turns = s.events.filter((e) => e.category === 'turn');
|
|
111
|
-
const tasks = s.events.filter((e) => e.category === 'agent_task');
|
|
112
|
-
const shell = s.events.filter((e) => e.category === 'shell_command');
|
|
113
|
-
const mcp = s.events.filter((e) => e.category === 'mcp');
|
|
114
|
-
const usage = turns.reduce((acc, e) => {
|
|
115
|
-
const u = usageFromEvent(e);
|
|
116
|
-
acc.input += u.input;
|
|
117
|
-
acc.output += u.output;
|
|
118
|
-
acc.total += u.total;
|
|
119
|
-
return acc;
|
|
120
|
-
}, { input: 0, output: 0, total: 0 });
|
|
121
|
-
out.push({
|
|
122
|
-
sessionId: s.context.sessionId ?? s.events[0]?.sessionId,
|
|
123
|
-
projectId: s.context.projectId ?? s.events[0]?.projectId,
|
|
124
|
-
runtime: s.events[0]?.runtime,
|
|
125
|
-
turns: turns.length,
|
|
126
|
-
agentTasks: tasks.length,
|
|
127
|
-
shellCommands: shell.length,
|
|
128
|
-
mcpCalls: mcp.length,
|
|
129
|
-
usage,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
return out;
|
|
133
|
-
}
|
|
134
|
-
export function summarizeCache(sessions) {
|
|
135
|
-
return sessions.map((s) => {
|
|
136
|
-
const turns = s.events.filter((e) => e.category === 'turn');
|
|
137
|
-
let inputTokens = 0;
|
|
138
|
-
let cacheReadInputTokens = 0;
|
|
139
|
-
let cacheCreationInputTokens = 0;
|
|
140
|
-
const explicitHitRates = [];
|
|
141
|
-
for (const turn of turns) {
|
|
142
|
-
const attrs = turn.attributes ?? {};
|
|
143
|
-
inputTokens += toNumber(attrs['gen_ai.usage.input_tokens']);
|
|
144
|
-
cacheReadInputTokens += toNumber(attrs['gen_ai.usage.details.cache_read_input_tokens']);
|
|
145
|
-
cacheCreationInputTokens += toNumber(attrs['gen_ai.usage.details.cache_creation_input_tokens']);
|
|
146
|
-
const hitRate = toOptionalNumber(attrs['gen_ai.usage.details.cache_hit_rate']);
|
|
147
|
-
if (hitRate !== undefined)
|
|
148
|
-
explicitHitRates.push(hitRate);
|
|
149
|
-
}
|
|
150
|
-
const cacheHitRate = explicitHitRates.length > 0
|
|
151
|
-
? round6(explicitHitRates.reduce((acc, v) => acc + v, 0) / explicitHitRates.length)
|
|
152
|
-
: round6((inputTokens + cacheReadInputTokens) > 0 ? cacheReadInputTokens / (inputTokens + cacheReadInputTokens) : 0);
|
|
153
|
-
return {
|
|
154
|
-
projectId: s.context.projectId ?? s.events[0]?.projectId,
|
|
155
|
-
sessionId: s.context.sessionId ?? s.events[0]?.sessionId,
|
|
156
|
-
runtime: s.events[0]?.runtime,
|
|
157
|
-
turns: turns.length,
|
|
158
|
-
inputTokens,
|
|
159
|
-
cacheReadInputTokens,
|
|
160
|
-
cacheCreationInputTokens,
|
|
161
|
-
cacheHitRate,
|
|
162
|
-
};
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
export function summarizeTools(sessions) {
|
|
166
|
-
const agg = new Map();
|
|
167
|
-
for (const s of sessions) {
|
|
168
|
-
for (const e of s.events) {
|
|
169
|
-
if (!['tool', 'mcp', 'shell_command', 'agent_task'].includes(e.category))
|
|
170
|
-
continue;
|
|
171
|
-
const attrs = e.attributes ?? {};
|
|
172
|
-
const tool = attrs['gen_ai.tool.name'] ?? e.name;
|
|
173
|
-
const key = `${e.category}:${tool}`;
|
|
174
|
-
const cur = agg.get(key) ?? {
|
|
175
|
-
category: e.category,
|
|
176
|
-
tool,
|
|
177
|
-
calls: 0,
|
|
178
|
-
sessions: new Set(),
|
|
179
|
-
};
|
|
180
|
-
cur.calls += 1;
|
|
181
|
-
cur.sessions.add(e.sessionId);
|
|
182
|
-
agg.set(key, cur);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return [...agg.values()]
|
|
186
|
-
.map((v) => ({
|
|
187
|
-
category: v.category,
|
|
188
|
-
tool: v.tool,
|
|
189
|
-
calls: v.calls,
|
|
190
|
-
sessions: v.sessions.size,
|
|
191
|
-
}))
|
|
192
|
-
.sort((a, b) => {
|
|
193
|
-
if (b.calls !== a.calls)
|
|
194
|
-
return b.calls - a.calls;
|
|
195
|
-
if (a.category !== b.category)
|
|
196
|
-
return a.category.localeCompare(b.category);
|
|
197
|
-
return a.tool.localeCompare(b.tool);
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
export function summarizeTurnDiff(sessions) {
|
|
201
|
-
const rows = [];
|
|
202
|
-
for (const s of sessions) {
|
|
203
|
-
const turns = s.events
|
|
204
|
-
.filter((e) => e.category === 'turn')
|
|
205
|
-
.slice()
|
|
206
|
-
.sort((a, b) => {
|
|
207
|
-
const ao = parseTurnOrdinal(a.turnId);
|
|
208
|
-
const bo = parseTurnOrdinal(b.turnId);
|
|
209
|
-
if (ao === undefined && bo === undefined)
|
|
210
|
-
return String(a.turnId ?? '').localeCompare(String(b.turnId ?? ''));
|
|
211
|
-
if (ao === undefined)
|
|
212
|
-
return 1;
|
|
213
|
-
if (bo === undefined)
|
|
214
|
-
return -1;
|
|
215
|
-
return ao - bo;
|
|
216
|
-
});
|
|
217
|
-
for (let i = 0; i < turns.length; i += 1) {
|
|
218
|
-
const turn = turns[i];
|
|
219
|
-
const attrs = turn.attributes ?? {};
|
|
220
|
-
const input = String(turn.input ?? '');
|
|
221
|
-
const prevInput = String(turns[i - 1]?.input ?? '');
|
|
222
|
-
const charDelta = toOptionalNumber(attrs['agentic.turn.diff.char_delta'])
|
|
223
|
-
?? (i === 0 ? 0 : input.length - prevInput.length);
|
|
224
|
-
const lineDelta = toOptionalNumber(attrs['agentic.turn.diff.line_delta'])
|
|
225
|
-
?? (i === 0 ? 0 : (input ? input.split(/\r?\n/).length : 0) - (prevInput ? prevInput.split(/\r?\n/).length : 0));
|
|
226
|
-
const similarity = toOptionalNumber(attrs['agentic.turn.diff.similarity']) ?? (i === 0 ? 1 : undefined);
|
|
227
|
-
const changed = typeof attrs['agentic.turn.diff.changed'] === 'boolean'
|
|
228
|
-
? attrs['agentic.turn.diff.changed']
|
|
229
|
-
: (i === 0 ? false : input !== prevInput);
|
|
230
|
-
rows.push({
|
|
231
|
-
projectId: s.context.projectId ?? turn.projectId,
|
|
232
|
-
sessionId: s.context.sessionId ?? turn.sessionId,
|
|
233
|
-
runtime: turn.runtime,
|
|
234
|
-
turnId: turn.turnId,
|
|
235
|
-
inputHash: attrs['agentic.turn.input.hash'] ?? '',
|
|
236
|
-
prevHash: attrs['agentic.turn.input.prev_hash'] ?? '',
|
|
237
|
-
charDelta,
|
|
238
|
-
lineDelta,
|
|
239
|
-
similarity,
|
|
240
|
-
changed,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return rows;
|
|
245
|
-
}
|
|
@@ -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
|
-
};
|