session-steward 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +13 -0
- package/bin/session-steward-cli.mjs +14 -0
- package/dist/assets/index-B6C5qvGV.js +9 -0
- package/dist/assets/index-B8U6eLCA.css +2 -0
- package/dist/index.html +2 -2
- package/lib/cli.mjs +134 -2
- package/lib/providers/claude-code/events.mjs +498 -0
- package/lib/providers/claude-code/index.mjs +2 -0
- package/lib/providers/codex/events.mjs +731 -0
- package/lib/providers/codex/index.mjs +2 -0
- package/lib/server.mjs +62 -1
- package/lib/session-event-reader.mjs +126 -0
- package/lib/session-events.mjs +307 -0
- package/lib/storage/jsonl.mjs +148 -0
- package/package.json +6 -2
- package/dist/assets/index-BQ6SUqXr.css +0 -2
- package/dist/assets/index-QhQbSn0H.js +0 -9
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSessionEvent,
|
|
3
|
+
createSessionEventCoverage,
|
|
4
|
+
createSessionEventHeader,
|
|
5
|
+
createSessionEventsResult,
|
|
6
|
+
SESSION_EVENT_KIND,
|
|
7
|
+
SESSION_EVENT_REASON,
|
|
8
|
+
} from "../../session-events.mjs";
|
|
9
|
+
import {
|
|
10
|
+
createSessionEventReadState,
|
|
11
|
+
createUnmappedSessionEventTracker,
|
|
12
|
+
isInjectedSessionAsk,
|
|
13
|
+
} from "../../session-event-reader.mjs";
|
|
14
|
+
import { visitJsonlSnapshotEntries } from "../../storage/jsonl.mjs";
|
|
15
|
+
import { getSessionRecord } from "./store.mjs";
|
|
16
|
+
|
|
17
|
+
const MAX_PENDING_DECISIONS = 2_048;
|
|
18
|
+
const PROVIDER_ID = "claude-code";
|
|
19
|
+
const READ_ONLY_TOOLS = new Set(["Glob", "Grep", "Read", "WebFetch", "WebSearch"]);
|
|
20
|
+
const RECORD_CLASSIFICATION = Object.freeze({
|
|
21
|
+
RECOGNIZED: "recognized",
|
|
22
|
+
SKIPPED: "skipped",
|
|
23
|
+
UNMAPPED: "unmapped",
|
|
24
|
+
UNPARSEABLE: "unparseable",
|
|
25
|
+
});
|
|
26
|
+
const SKIPPED_CONTENT_TYPES = new Set(["attachment", "image", "thinking"]);
|
|
27
|
+
const SKIPPED_RECORD_TYPES = new Set([
|
|
28
|
+
"attachment",
|
|
29
|
+
"custom-title",
|
|
30
|
+
"file-history-snapshot",
|
|
31
|
+
"frame-link",
|
|
32
|
+
"last-prompt",
|
|
33
|
+
"mode",
|
|
34
|
+
"queue-operation",
|
|
35
|
+
"summary",
|
|
36
|
+
"system",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
function asTimestamp(value) {
|
|
40
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
41
|
+
if (typeof value !== "string") return null;
|
|
42
|
+
const timestamp = Date.parse(value);
|
|
43
|
+
return Number.isFinite(timestamp) ? timestamp : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function contentText(value) {
|
|
47
|
+
if (typeof value === "string") return value;
|
|
48
|
+
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
return value
|
|
51
|
+
.map((part) => contentText(part))
|
|
52
|
+
.filter((part) => part !== "")
|
|
53
|
+
.join("\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!value || typeof value !== "object") return "";
|
|
57
|
+
if (typeof value.text === "string") return value.text;
|
|
58
|
+
if (typeof value.content === "string" || Array.isArray(value.content)) {
|
|
59
|
+
return contentText(value.content);
|
|
60
|
+
}
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function emptyResult({ cwd = null, origin = null, reason }) {
|
|
65
|
+
return createSessionEventsResult({
|
|
66
|
+
coverage: createSessionEventCoverage(),
|
|
67
|
+
events: [],
|
|
68
|
+
header: createSessionEventHeader({ cwd, origin, provider: PROVIDER_ID }),
|
|
69
|
+
reason,
|
|
70
|
+
window: {
|
|
71
|
+
complete: true,
|
|
72
|
+
end: null,
|
|
73
|
+
outcomesMayBeUnresolved: false,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function messageParts(record) {
|
|
79
|
+
const content = record.message?.content;
|
|
80
|
+
if (typeof content === "string") return [{ text: content, type: "text" }];
|
|
81
|
+
return Array.isArray(content) ? content : [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function planSteps(input) {
|
|
85
|
+
const todos = Array.isArray(input?.todos) ? input.todos : [];
|
|
86
|
+
return todos
|
|
87
|
+
.filter((todo) => typeof todo?.status === "string")
|
|
88
|
+
.map((todo) => ({
|
|
89
|
+
status: todo.status,
|
|
90
|
+
text: typeof todo.content === "string"
|
|
91
|
+
? todo.content
|
|
92
|
+
: typeof todo.activeForm === "string"
|
|
93
|
+
? todo.activeForm
|
|
94
|
+
: "Untitled task",
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function questionText(input) {
|
|
99
|
+
if (typeof input?.question === "string") return input.question;
|
|
100
|
+
if (!Array.isArray(input?.questions)) return "Question";
|
|
101
|
+
return input.questions
|
|
102
|
+
.map((question) => question?.question)
|
|
103
|
+
.filter((question) => typeof question === "string")
|
|
104
|
+
.join("\n") || "Question";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function readSessionEvents({
|
|
108
|
+
claudeHome,
|
|
109
|
+
desktopDataHome,
|
|
110
|
+
id,
|
|
111
|
+
limit,
|
|
112
|
+
maxLineBytes,
|
|
113
|
+
mode,
|
|
114
|
+
signal,
|
|
115
|
+
}) {
|
|
116
|
+
const record = await getSessionRecord({ claudeHome, desktopDataHome, id });
|
|
117
|
+
if (!record) return null;
|
|
118
|
+
|
|
119
|
+
const origin = record.surface ?? record.recordSource ?? null;
|
|
120
|
+
if (!record.rolloutPath) {
|
|
121
|
+
return emptyResult({
|
|
122
|
+
cwd: record.cwd || null,
|
|
123
|
+
origin,
|
|
124
|
+
reason: SESSION_EVENT_REASON.NO_TRANSCRIPT_PATH,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const coverage = createSessionEventCoverage();
|
|
129
|
+
const pendingDecisionIds = [];
|
|
130
|
+
const readState = createSessionEventReadState({ limit, mode });
|
|
131
|
+
const unmappedTypes = createUnmappedSessionEventTracker();
|
|
132
|
+
let acceptingInjectedAsks = true;
|
|
133
|
+
let header = createSessionEventHeader({
|
|
134
|
+
cwd: record.cwd || null,
|
|
135
|
+
origin,
|
|
136
|
+
provider: PROVIDER_ID,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
function addEvent(event, pendingId = null) {
|
|
140
|
+
return readState.add(event, { pendingId });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function rememberDecision(pendingId) {
|
|
144
|
+
if (!pendingId) return;
|
|
145
|
+
pendingDecisionIds.push(pendingId);
|
|
146
|
+
if (pendingDecisionIds.length > MAX_PENDING_DECISIONS) pendingDecisionIds.shift();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function resolveDecision(answer) {
|
|
150
|
+
while (pendingDecisionIds.length > 0) {
|
|
151
|
+
const pendingId = pendingDecisionIds.shift();
|
|
152
|
+
if (readState.resolve(pendingId, (event) => {
|
|
153
|
+
if (event.kind === SESSION_EVENT_KIND.DECIDED) event.answer = answer;
|
|
154
|
+
})) return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function toolEvent(part, atMs, sequence) {
|
|
161
|
+
const input = part.input && typeof part.input === "object" ? part.input : {};
|
|
162
|
+
const name = typeof part.name === "string" ? part.name : "unknown tool";
|
|
163
|
+
const pendingId = typeof part.id === "string" ? part.id : null;
|
|
164
|
+
|
|
165
|
+
if (name === "Edit" || name === "Write") {
|
|
166
|
+
return {
|
|
167
|
+
event: createSessionEvent({
|
|
168
|
+
atMs,
|
|
169
|
+
files: typeof input.file_path === "string" ? [input.file_path] : [],
|
|
170
|
+
kind: SESSION_EVENT_KIND.EDIT,
|
|
171
|
+
sequence,
|
|
172
|
+
}),
|
|
173
|
+
pendingId,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (name === "Bash") {
|
|
178
|
+
return {
|
|
179
|
+
event: createSessionEvent({
|
|
180
|
+
atMs,
|
|
181
|
+
command: typeof input.command === "string" ? input.command : null,
|
|
182
|
+
kind: SESSION_EVENT_KIND.RAN,
|
|
183
|
+
sequence,
|
|
184
|
+
workdir: typeof input.workdir === "string" ? input.workdir : record.cwd || null,
|
|
185
|
+
}),
|
|
186
|
+
pendingId,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (name === "AskUserQuestion") {
|
|
191
|
+
return {
|
|
192
|
+
decision: true,
|
|
193
|
+
event: createSessionEvent({
|
|
194
|
+
atMs,
|
|
195
|
+
kind: SESSION_EVENT_KIND.DECIDED,
|
|
196
|
+
question: questionText(input),
|
|
197
|
+
sequence,
|
|
198
|
+
}),
|
|
199
|
+
pendingId,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (name === "TodoWrite") {
|
|
204
|
+
return {
|
|
205
|
+
event: createSessionEvent({
|
|
206
|
+
atMs,
|
|
207
|
+
kind: SESSION_EVENT_KIND.PLAN,
|
|
208
|
+
sequence,
|
|
209
|
+
steps: planSteps(input),
|
|
210
|
+
}),
|
|
211
|
+
pendingId: null,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!READ_ONLY_TOOLS.has(name) && typeof input.file_path === "string") {
|
|
216
|
+
return {
|
|
217
|
+
event: createSessionEvent({
|
|
218
|
+
atMs,
|
|
219
|
+
files: [input.file_path],
|
|
220
|
+
kind: SESSION_EVENT_KIND.EDIT,
|
|
221
|
+
sequence,
|
|
222
|
+
}),
|
|
223
|
+
pendingId,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (typeof input.command === "string") {
|
|
228
|
+
return {
|
|
229
|
+
event: createSessionEvent({
|
|
230
|
+
atMs,
|
|
231
|
+
command: input.command,
|
|
232
|
+
kind: SESSION_EVENT_KIND.RAN,
|
|
233
|
+
sequence,
|
|
234
|
+
workdir: typeof input.workdir === "string" ? input.workdir : record.cwd || null,
|
|
235
|
+
}),
|
|
236
|
+
pendingId,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
event: createSessionEvent({
|
|
242
|
+
atMs,
|
|
243
|
+
command: name,
|
|
244
|
+
kind: SESSION_EVENT_KIND.RAN,
|
|
245
|
+
sequence,
|
|
246
|
+
unclassified: true,
|
|
247
|
+
workdir: record.cwd || null,
|
|
248
|
+
}),
|
|
249
|
+
pendingId,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function resolveToolResult(part) {
|
|
254
|
+
const pendingId = part.tool_use_id;
|
|
255
|
+
const answer = contentText(part.content).trim();
|
|
256
|
+
const failed = part.is_error === true;
|
|
257
|
+
return readState.resolve(pendingId, (event) => {
|
|
258
|
+
if (event.kind === SESSION_EVENT_KIND.DECIDED) {
|
|
259
|
+
event.answer = answer || null;
|
|
260
|
+
} else if (event.kind === SESSION_EVENT_KIND.EDIT) {
|
|
261
|
+
event.applied = !failed;
|
|
262
|
+
} else if (event.kind === SESSION_EVENT_KIND.RAN) {
|
|
263
|
+
event.error = failed ? answer || "Tool failed." : null;
|
|
264
|
+
event.failed = failed;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function updateHeader(recordValue) {
|
|
270
|
+
const branch = recordValue.gitBranch ?? recordValue.git?.branch ?? header.git?.branch ?? null;
|
|
271
|
+
const commit = recordValue.git?.commit ?? recordValue.git?.commit_hash ?? header.git?.commit ?? null;
|
|
272
|
+
const repository = recordValue.git?.repository
|
|
273
|
+
?? recordValue.git?.repository_url
|
|
274
|
+
?? header.git?.repository
|
|
275
|
+
?? null;
|
|
276
|
+
const hasGit = branch !== null || commit !== null || repository !== null;
|
|
277
|
+
header = createSessionEventHeader({
|
|
278
|
+
cwd: recordValue.cwd ?? header.cwd,
|
|
279
|
+
git: hasGit ? { branch, commit, repository } : null,
|
|
280
|
+
model: recordValue.message?.model ?? recordValue.model ?? header.model,
|
|
281
|
+
origin: recordValue.entrypoint ?? header.origin,
|
|
282
|
+
provider: PROVIDER_ID,
|
|
283
|
+
version: recordValue.version ?? header.version,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function handleRecord(recordValue, sequence) {
|
|
288
|
+
updateHeader(recordValue);
|
|
289
|
+
const atMs = asTimestamp(recordValue.timestamp ?? recordValue.createdAt);
|
|
290
|
+
const parts = messageParts(recordValue);
|
|
291
|
+
|
|
292
|
+
if (recordValue.type === "user") {
|
|
293
|
+
if (recordValue.isCompactSummary === true) {
|
|
294
|
+
acceptingInjectedAsks = false;
|
|
295
|
+
const text = contentText(recordValue.message?.content);
|
|
296
|
+
if (!text) {
|
|
297
|
+
return { classification: RECORD_CLASSIFICATION.UNPARSEABLE, stop: false };
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
classification: RECORD_CLASSIFICATION.RECOGNIZED,
|
|
301
|
+
stop: addEvent(createSessionEvent({
|
|
302
|
+
atMs,
|
|
303
|
+
kind: SESSION_EVENT_KIND.SUMMARY,
|
|
304
|
+
sequence,
|
|
305
|
+
text,
|
|
306
|
+
})),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let recognized = false;
|
|
311
|
+
let resolvedDecision = false;
|
|
312
|
+
let unparseable = parts.length === 0;
|
|
313
|
+
let unmappedType = null;
|
|
314
|
+
|
|
315
|
+
for (const part of parts) {
|
|
316
|
+
if (typeof part?.type !== "string") {
|
|
317
|
+
unparseable = true;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (part?.type === "text" && typeof part.text === "string") continue;
|
|
321
|
+
if (part?.type !== "tool_result") {
|
|
322
|
+
if (!SKIPPED_CONTENT_TYPES.has(part?.type)) {
|
|
323
|
+
unmappedType = `user:${part?.type ?? "missing content type"}`;
|
|
324
|
+
}
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const resolved = resolveToolResult(part);
|
|
328
|
+
if (part.is_error === true) recognized = true;
|
|
329
|
+
if (part.is_error === true && !resolved) unparseable = true;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const text = parts
|
|
333
|
+
.filter((part) => part?.type === "text" && typeof part.text === "string")
|
|
334
|
+
.map((part) => part.text)
|
|
335
|
+
.join("\n");
|
|
336
|
+
|
|
337
|
+
if (text) {
|
|
338
|
+
resolvedDecision = resolveDecision(text);
|
|
339
|
+
recognized = true;
|
|
340
|
+
if (resolvedDecision) acceptingInjectedAsks = false;
|
|
341
|
+
if (!resolvedDecision) {
|
|
342
|
+
const injected = acceptingInjectedAsks && isInjectedSessionAsk(text);
|
|
343
|
+
if (!injected) acceptingInjectedAsks = false;
|
|
344
|
+
return {
|
|
345
|
+
classification: unmappedType
|
|
346
|
+
? RECORD_CLASSIFICATION.UNMAPPED
|
|
347
|
+
: unparseable
|
|
348
|
+
? RECORD_CLASSIFICATION.UNPARSEABLE
|
|
349
|
+
: RECORD_CLASSIFICATION.RECOGNIZED,
|
|
350
|
+
stop: addEvent(createSessionEvent({
|
|
351
|
+
atMs,
|
|
352
|
+
injected,
|
|
353
|
+
kind: SESSION_EVENT_KIND.ASK,
|
|
354
|
+
sequence,
|
|
355
|
+
text,
|
|
356
|
+
})),
|
|
357
|
+
unmappedType,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
classification: unmappedType
|
|
364
|
+
? RECORD_CLASSIFICATION.UNMAPPED
|
|
365
|
+
: unparseable
|
|
366
|
+
? RECORD_CLASSIFICATION.UNPARSEABLE
|
|
367
|
+
: recognized
|
|
368
|
+
? RECORD_CLASSIFICATION.RECOGNIZED
|
|
369
|
+
: RECORD_CLASSIFICATION.SKIPPED,
|
|
370
|
+
stop: false,
|
|
371
|
+
unmappedType,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (recordValue.type === "assistant") {
|
|
376
|
+
acceptingInjectedAsks = false;
|
|
377
|
+
let recognized = false;
|
|
378
|
+
let stop = false;
|
|
379
|
+
const unparseable = parts.length === 0 || parts.some((part) => (
|
|
380
|
+
typeof part?.type !== "string"
|
|
381
|
+
|| (part.type === "text" && typeof part.text !== "string")
|
|
382
|
+
));
|
|
383
|
+
const unmappedPart = parts.find((part) => (
|
|
384
|
+
typeof part?.type === "string"
|
|
385
|
+
&& part.type !== "text"
|
|
386
|
+
&& part?.type !== "tool_use"
|
|
387
|
+
&& !SKIPPED_CONTENT_TYPES.has(part?.type)
|
|
388
|
+
));
|
|
389
|
+
const unmappedType = unmappedPart
|
|
390
|
+
? `assistant:${unmappedPart?.type ?? "missing content type"}`
|
|
391
|
+
: null;
|
|
392
|
+
const text = parts
|
|
393
|
+
.filter((part) => part?.type === "text" && typeof part.text === "string")
|
|
394
|
+
.map((part) => part.text)
|
|
395
|
+
.join("\n");
|
|
396
|
+
|
|
397
|
+
if (text) {
|
|
398
|
+
recognized = true;
|
|
399
|
+
stop = addEvent(createSessionEvent({
|
|
400
|
+
atMs,
|
|
401
|
+
kind: SESSION_EVENT_KIND.SAID,
|
|
402
|
+
sequence,
|
|
403
|
+
text,
|
|
404
|
+
}));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (!stop) {
|
|
408
|
+
for (const part of parts) {
|
|
409
|
+
if (part?.type === "text" && typeof part.text === "string") continue;
|
|
410
|
+
if (part?.type !== "tool_use") {
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
const result = toolEvent(part, atMs, sequence);
|
|
414
|
+
recognized = true;
|
|
415
|
+
stop = addEvent(result.event, result.pendingId);
|
|
416
|
+
if (result.decision) rememberDecision(result.pendingId);
|
|
417
|
+
if (stop) break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
classification: unmappedType
|
|
423
|
+
? RECORD_CLASSIFICATION.UNMAPPED
|
|
424
|
+
: unparseable
|
|
425
|
+
? RECORD_CLASSIFICATION.UNPARSEABLE
|
|
426
|
+
: recognized
|
|
427
|
+
? RECORD_CLASSIFICATION.RECOGNIZED
|
|
428
|
+
: RECORD_CLASSIFICATION.SKIPPED,
|
|
429
|
+
stop,
|
|
430
|
+
unmappedType,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (typeof recordValue.type !== "string") {
|
|
435
|
+
return { classification: RECORD_CLASSIFICATION.UNPARSEABLE, stop: false };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
classification: SKIPPED_RECORD_TYPES.has(recordValue.type)
|
|
440
|
+
? RECORD_CLASSIFICATION.SKIPPED
|
|
441
|
+
: RECORD_CLASSIFICATION.UNMAPPED,
|
|
442
|
+
stop: false,
|
|
443
|
+
unmappedType: `claude:${recordValue.type ?? "missing record type"}`,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
let read;
|
|
448
|
+
|
|
449
|
+
try {
|
|
450
|
+
read = await visitJsonlSnapshotEntries(
|
|
451
|
+
record.rolloutPath,
|
|
452
|
+
(entry) => {
|
|
453
|
+
if (signal?.aborted) return false;
|
|
454
|
+
coverage.total += 1;
|
|
455
|
+
|
|
456
|
+
if (entry.oversized) {
|
|
457
|
+
coverage.oversized += 1;
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (!entry.parsed || typeof entry.parsed !== "object") {
|
|
462
|
+
coverage.unparseable += 1;
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const result = handleRecord(entry.parsed, entry.index);
|
|
467
|
+
coverage[result.classification] += 1;
|
|
468
|
+
if (result.classification === RECORD_CLASSIFICATION.UNMAPPED) {
|
|
469
|
+
unmappedTypes.add(result.unmappedType);
|
|
470
|
+
}
|
|
471
|
+
return !result.stop;
|
|
472
|
+
},
|
|
473
|
+
{ maxLineBytes },
|
|
474
|
+
);
|
|
475
|
+
} catch (error) {
|
|
476
|
+
if (error?.code === "ENOENT") {
|
|
477
|
+
return emptyResult({
|
|
478
|
+
cwd: record.cwd || null,
|
|
479
|
+
origin,
|
|
480
|
+
reason: SESSION_EVENT_REASON.TRANSCRIPT_MISSING,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
throw error;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const events = readState.values();
|
|
488
|
+
coverage.unmappedTypes = unmappedTypes.values();
|
|
489
|
+
return createSessionEventsResult({
|
|
490
|
+
coverage,
|
|
491
|
+
events,
|
|
492
|
+
header,
|
|
493
|
+
reason: events.length === 0 && read.complete
|
|
494
|
+
? SESSION_EVENT_REASON.NO_RECOGNIZED_EVENTS
|
|
495
|
+
: null,
|
|
496
|
+
window: readState.window(read),
|
|
497
|
+
});
|
|
498
|
+
}
|