spiracha 1.0.0 → 1.1.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.
Files changed (88) hide show
  1. package/AGENTS.md +28 -1
  2. package/README.md +47 -7
  3. package/apps/ui/AGENTS.md +70 -0
  4. package/apps/ui/README.md +72 -0
  5. package/apps/ui/dist/client/assets/_threadId-CAIeH5mq.js +1 -0
  6. package/apps/ui/dist/client/assets/analytics-BjYaHqXk.js +1 -0
  7. package/apps/ui/dist/client/assets/checkbox-wPoGG3of.js +1 -0
  8. package/apps/ui/dist/client/assets/data-table-6yDgAdtf.js +4 -0
  9. package/apps/ui/dist/client/assets/delete-confirm-dialog-DJUAk7ha.js +11 -0
  10. package/apps/ui/dist/client/assets/download-BhWd-Pm5.js +1 -0
  11. package/apps/ui/dist/client/assets/es2015-BlyMI4CF.js +41 -0
  12. package/apps/ui/dist/client/assets/formatters-BxjZwWSE.js +1 -0
  13. package/apps/ui/dist/client/assets/index-T01rPkb4.js +22 -0
  14. package/apps/ui/dist/client/assets/input-B3YN8gzg.js +1 -0
  15. package/apps/ui/dist/client/assets/metric-card-BWW7TWER.js +1 -0
  16. package/apps/ui/dist/client/assets/page-header-BZ8Gnxgs.js +1 -0
  17. package/apps/ui/dist/client/assets/projects._project-B7XcpoLt.js +1 -0
  18. package/apps/ui/dist/client/assets/projects._project-EfBhCHPY.js +1 -0
  19. package/apps/ui/dist/client/assets/projects.index-4vfIwLjw.js +1 -0
  20. package/apps/ui/dist/client/assets/projects.index-DzEZ4pAJ.js +1 -0
  21. package/apps/ui/dist/client/assets/routes-CWCCZykE.js +1 -0
  22. package/apps/ui/dist/client/assets/select-DLXGsyZ4.js +1 -0
  23. package/apps/ui/dist/client/assets/settings-b0Xthfae.js +1 -0
  24. package/apps/ui/dist/client/assets/styles-8Wtc8YJw.css +1 -0
  25. package/apps/ui/dist/client/assets/threads._threadId-CgtoCqTb.js +1 -0
  26. package/apps/ui/dist/client/assets/threads._threadId-DBiDb38K.js +7 -0
  27. package/apps/ui/dist/client/favicon.ico +0 -0
  28. package/apps/ui/dist/client/logo192.png +0 -0
  29. package/apps/ui/dist/client/logo512.png +0 -0
  30. package/apps/ui/dist/client/manifest.json +25 -0
  31. package/apps/ui/dist/client/robots.txt +3 -0
  32. package/apps/ui/dist/server/assets/__23tanstack-start-plugin-adapters-BzCA6dXo.js +5 -0
  33. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-BjsXNYgm.js +99 -0
  34. package/apps/ui/dist/server/assets/_threadId-B6SrBR9E.js +6 -0
  35. package/apps/ui/dist/server/assets/analytics-Br_fZB6a.js +139 -0
  36. package/apps/ui/dist/server/assets/button-CmTDnzOn.js +46 -0
  37. package/apps/ui/dist/server/assets/checkbox-C0hovF41.js +19 -0
  38. package/apps/ui/dist/server/assets/codex-queries-CAF6HYiG.js +109 -0
  39. package/apps/ui/dist/server/assets/codex-server-Cqh0hb93.js +1995 -0
  40. package/apps/ui/dist/server/assets/data-table-Cdct823O.js +189 -0
  41. package/apps/ui/dist/server/assets/delete-confirm-dialog-CWqcTXTF.js +139 -0
  42. package/apps/ui/dist/server/assets/download-CzHmFWGk.js +286 -0
  43. package/apps/ui/dist/server/assets/formatters-B6o5pTY9.js +72 -0
  44. package/apps/ui/dist/server/assets/input-B4tEzctc.js +46 -0
  45. package/apps/ui/dist/server/assets/loading-panel-DbLdvjtR.js +27 -0
  46. package/apps/ui/dist/server/assets/metric-card-ByEeLu0r.js +23 -0
  47. package/apps/ui/dist/server/assets/page-header-CxdZM86z.js +25 -0
  48. package/apps/ui/dist/server/assets/path-transforms-DD1e7rhY.js +31 -0
  49. package/apps/ui/dist/server/assets/projects._project-Bwf6iJC-.js +335 -0
  50. package/apps/ui/dist/server/assets/projects._project-CLSohrBp.js +26 -0
  51. package/apps/ui/dist/server/assets/projects._project-DdVSdfPe.js +18 -0
  52. package/apps/ui/dist/server/assets/projects.index-CaplpeMy.js +26 -0
  53. package/apps/ui/dist/server/assets/projects.index-DKeVeqUZ.js +171 -0
  54. package/apps/ui/dist/server/assets/router-ve2Hrl2Y.js +307 -0
  55. package/apps/ui/dist/server/assets/routes-BJyx5OmO.js +34 -0
  56. package/apps/ui/dist/server/assets/routes-pkOwjjYc.js +168 -0
  57. package/apps/ui/dist/server/assets/select-GW76p-ld.js +76 -0
  58. package/apps/ui/dist/server/assets/settings-MvWDgc1u.js +100 -0
  59. package/apps/ui/dist/server/assets/settings-store-DpEJEQ7M.js +52 -0
  60. package/apps/ui/dist/server/assets/sqlite-error-LZDrnxdd.js +13 -0
  61. package/apps/ui/dist/server/assets/start-BAvbjjfs.js +4 -0
  62. package/apps/ui/dist/server/assets/threads._threadId-BSSK4nkI.js +26 -0
  63. package/apps/ui/dist/server/assets/threads._threadId-D3PYZIwl.js +18 -0
  64. package/apps/ui/dist/server/assets/threads._threadId-D3xaWM86.js +1037 -0
  65. package/apps/ui/dist/server/assets/utils-C_uf36nf.js +8 -0
  66. package/apps/ui/dist/server/server.js +5678 -0
  67. package/package.json +47 -7
  68. package/src/export-chats.ts +1 -14
  69. package/src/lib/codex-analytics.ts +100 -0
  70. package/src/lib/codex-browser-db.ts +518 -0
  71. package/src/lib/codex-browser-export.ts +418 -0
  72. package/src/lib/codex-browser-types.ts +224 -0
  73. package/src/lib/codex-exporter-cli.ts +5 -0
  74. package/src/lib/codex-exporter-transcript.ts +143 -32
  75. package/src/lib/codex-exporter-types.ts +8 -0
  76. package/src/lib/codex-thread-cache.ts +58 -0
  77. package/src/lib/codex-thread-parser.ts +604 -0
  78. package/src/lib/interactive-cli.ts +5 -13
  79. package/src/lib/native-open.ts +54 -0
  80. package/src/lib/path-transforms.ts +45 -0
  81. package/src/lib/shared.ts +37 -1
  82. package/src/lib/sqlite-error.ts +14 -0
  83. package/src/lib/sqlite-retry.ts +39 -0
  84. package/src/lib/ui-cache.ts +96 -0
  85. package/src/lib/ui-export-files.ts +77 -0
  86. package/src/mcp-server.ts +1 -0
  87. package/src/spiracha.ts +14 -1
  88. package/src/ui-cli.ts +310 -0
@@ -1,4 +1,7 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import { rm } from 'node:fs/promises';
1
3
  import path from 'node:path';
4
+ import { finished } from 'node:stream/promises';
2
5
  import { matchesFilters, toCodexRelativePath } from './codex-exporter-db';
3
6
  import type { CodexCliOptions, ExportTarget, MessageRecord, SessionMeta, ToolRecord } from './codex-exporter-types';
4
7
  import {
@@ -6,8 +9,11 @@ import {
6
9
  asString,
7
10
  cleanExtractedText,
8
11
  cleanInlineTitle,
12
+ createExportWriteStream,
9
13
  type ExportFormat,
14
+ finalizeExportWriteStream,
10
15
  formatInlineLiteral,
16
+ formatModelLabel,
11
17
  type JsonValue,
12
18
  type MetadataEntry,
13
19
  readJsonlObjects,
@@ -49,7 +55,64 @@ export const convertSessionFile = async (target: ExportTarget, options: CodexCli
49
55
  return parts.join('\n').trimEnd() + '\n';
50
56
  };
51
57
 
58
+ type TranscriptTextTransform = (text: string) => string;
59
+
60
+ export const writeSessionFileExport = async (
61
+ target: ExportTarget,
62
+ options: CodexCliOptions,
63
+ outputPath: string,
64
+ transform: TranscriptTextTransform = (text) => text,
65
+ ): Promise<boolean> => {
66
+ const transcriptOutputPath = `${outputPath}.transcript.tmp`;
67
+ const transcriptStream = await createExportWriteStream(transcriptOutputPath);
68
+ const state: CodexTranscriptState = {
69
+ assistantModel: target.thread?.model ?? null,
70
+ sections: [],
71
+ sessionMeta: {},
72
+ startedTranscript: false,
73
+ };
74
+ let wroteSection = false;
75
+
76
+ try {
77
+ for await (const parsed of readJsonlObjects(target.sessionFile)) {
78
+ captureSessionMeta(parsed, state.sessionMeta);
79
+ const block = renderCodexTranscriptRecord(parsed, options, state);
80
+ if (!block) {
81
+ continue;
82
+ }
83
+
84
+ transcriptStream.write(transform(wroteSection ? `${getSectionSeparator(options)}${block}` : block));
85
+ wroteSection = true;
86
+ }
87
+ } catch (error) {
88
+ transcriptStream.destroy();
89
+ throw error;
90
+ }
91
+
92
+ await finalizeExportWriteStream(transcriptStream);
93
+
94
+ if (!matchesFilters(target.thread?.cwd ?? state.sessionMeta.cwd ?? null, options) || !wroteSection) {
95
+ await rm(transcriptOutputPath, { force: true });
96
+ return false;
97
+ }
98
+
99
+ const outputStream = await createExportWriteStream(outputPath);
100
+ const prefix = buildStreamExportPrefix(target, state.sessionMeta, options);
101
+ if (prefix) {
102
+ outputStream.write(transform(prefix));
103
+ }
104
+
105
+ const transcriptReadStream = createReadStream(transcriptOutputPath, { encoding: 'utf8' });
106
+ transcriptReadStream.pipe(outputStream, { end: false });
107
+ await finished(transcriptReadStream);
108
+ outputStream.write('\n');
109
+ await finalizeExportWriteStream(outputStream);
110
+ await rm(transcriptOutputPath, { force: true });
111
+ return true;
112
+ };
113
+
52
114
  type CodexTranscriptState = {
115
+ assistantModel: string | null;
53
116
  sessionMeta: SessionMeta;
54
117
  sections: string[];
55
118
  startedTranscript: boolean;
@@ -57,6 +120,7 @@ type CodexTranscriptState = {
57
120
 
58
121
  const collectCodexTranscript = async (sessionFile: string, options: CodexCliOptions): Promise<CodexTranscriptState> => {
59
122
  const state: CodexTranscriptState = {
123
+ assistantModel: null,
60
124
  sections: [],
61
125
  sessionMeta: {},
62
126
  startedTranscript: false,
@@ -69,46 +133,52 @@ const collectCodexTranscript = async (sessionFile: string, options: CodexCliOpti
69
133
  return state;
70
134
  };
71
135
 
136
+ const getSectionSeparator = (options: CodexCliOptions) => {
137
+ return options.optimized ? '\n\n' : '\n';
138
+ };
139
+
72
140
  const processCodexTranscriptRecord = (
73
141
  parsed: Record<string, JsonValue>,
74
142
  options: CodexCliOptions,
75
143
  state: CodexTranscriptState,
76
144
  ) => {
77
145
  captureSessionMeta(parsed, state.sessionMeta);
146
+ const block = renderCodexTranscriptRecord(parsed, options, state);
147
+ if (block) {
148
+ state.sections.push(block);
149
+ }
150
+ };
78
151
 
152
+ const renderCodexTranscriptRecord = (
153
+ parsed: Record<string, JsonValue>,
154
+ options: CodexCliOptions,
155
+ state: CodexTranscriptState,
156
+ ) => {
79
157
  const message = extractMessageRecord(parsed);
80
158
  if (message) {
81
- processCodexMessageRecord(message, options, state);
82
- return;
159
+ return processCodexMessageRecord(message, options, state);
83
160
  }
84
161
 
85
162
  if (!options.includeTools) {
86
- return;
163
+ return '';
87
164
  }
88
165
 
89
166
  const tool = extractToolRecord(parsed);
90
167
  if (!tool) {
91
- return;
168
+ return '';
92
169
  }
93
170
 
94
- const block = options.optimized
171
+ return options.optimized
95
172
  ? renderCompactToolBlock(tool, options.outputFormat)
96
173
  : renderToolBlock(tool, options.outputFormat);
97
- if (block) {
98
- state.sections.push(block);
99
- }
100
174
  };
101
175
 
102
176
  const processCodexMessageRecord = (message: MessageRecord, options: CodexCliOptions, state: CodexTranscriptState) => {
103
177
  if (options.optimized) {
104
- processOptimizedCodexMessageRecord(message, options, state);
105
- return;
178
+ return processOptimizedCodexMessageRecord(message, options, state);
106
179
  }
107
180
 
108
- const block = renderMessageBlock(message, options.outputFormat);
109
- if (block) {
110
- state.sections.push(block);
111
- }
181
+ return renderMessageBlock(message, options.outputFormat, state.assistantModel, options.includeCommentary);
112
182
  };
113
183
 
114
184
  const processOptimizedCodexMessageRecord = (
@@ -117,25 +187,44 @@ const processOptimizedCodexMessageRecord = (
117
187
  state: CodexTranscriptState,
118
188
  ) => {
119
189
  if (message.role !== 'user' && message.role !== 'assistant') {
120
- return;
190
+ return '';
191
+ }
192
+
193
+ if (message.role === 'assistant' && message.phase === 'commentary' && !options.includeCommentary) {
194
+ return '';
121
195
  }
122
196
 
123
197
  const compact = compactMessageText(message, true);
124
198
  if (!compact) {
125
- return;
199
+ return '';
126
200
  }
127
201
 
128
202
  if (!state.startedTranscript) {
129
203
  if (shouldSkipOptimizedPrelude(message.role, compact)) {
130
- return;
204
+ return '';
131
205
  }
132
206
  state.startedTranscript = true;
133
207
  }
134
208
 
135
- const rendered = renderCompactBlock(message, compact, options.outputFormat);
136
- if (rendered) {
137
- state.sections.push(rendered);
209
+ return renderCompactBlock(message, compact, options.outputFormat, state.assistantModel);
210
+ };
211
+
212
+ const buildStreamExportPrefix = (target: ExportTarget, sessionMeta: SessionMeta, options: CodexCliOptions) => {
213
+ if (options.optimized) {
214
+ return '';
138
215
  }
216
+
217
+ const title = getTitle(target, sessionMeta);
218
+ const metadata = buildMetadataEntries(target, sessionMeta, options);
219
+ const parts = [
220
+ renderDocumentTitle(title, options.outputFormat),
221
+ '',
222
+ renderMetadataBlock(metadata, options.outputFormat),
223
+ ]
224
+ .filter(Boolean)
225
+ .join('\n');
226
+
227
+ return `${parts}\n`;
139
228
  };
140
229
 
141
230
  export const compactMessageText = (message: MessageRecord, optimized: boolean): string => {
@@ -183,17 +272,18 @@ export const formatToolOutputSummary = (outputText: string, outputFormat: Export
183
272
 
184
273
  export const parseExecCommandArguments = (argumentsText?: string) => {
185
274
  if (!argumentsText) {
186
- return { cmd: null as string | null, workdir: null as string | null };
275
+ return { argumentsParseFailed: false, cmd: null as string | null, workdir: null as string | null };
187
276
  }
188
277
 
189
278
  try {
190
279
  const parsed = JSON.parse(argumentsText) as Record<string, unknown>;
191
280
  return {
281
+ argumentsParseFailed: false,
192
282
  cmd: typeof parsed.cmd === 'string' ? parsed.cmd : null,
193
283
  workdir: typeof parsed.workdir === 'string' ? parsed.workdir : null,
194
284
  };
195
285
  } catch {
196
- return { cmd: null as string | null, workdir: null as string | null };
286
+ return { argumentsParseFailed: true, cmd: null as string | null, workdir: null as string | null };
197
287
  }
198
288
  };
199
289
 
@@ -397,7 +487,11 @@ const extractMessageRecord = (parsed: Record<string, JsonValue>): MessageRecord
397
487
  }
398
488
 
399
489
  const payload = asObject(parsed.payload);
400
- if (!payload || payload.type !== 'message') {
490
+ if (!payload) {
491
+ return null;
492
+ }
493
+
494
+ if (payload.type !== 'message' && payload.type !== 'agent_message' && payload.type !== 'user_message') {
401
495
  return null;
402
496
  }
403
497
 
@@ -405,15 +499,17 @@ const extractMessageRecord = (parsed: Record<string, JsonValue>): MessageRecord
405
499
  };
406
500
 
407
501
  const normalizeMessage = (value: Record<string, JsonValue>): MessageRecord | null => {
408
- const role = asString(value.role);
409
- const content = value.content;
502
+ const type = asString(value.type);
503
+ const role =
504
+ asString(value.role) ?? (type === 'agent_message' ? 'assistant' : type === 'user_message' ? 'user' : null);
505
+ const content = value.content ?? asString(value.message);
410
506
  const phase = asString(value.phase);
411
507
 
412
508
  if (!role || content === undefined) {
413
509
  return null;
414
510
  }
415
511
 
416
- return { content, phase: phase ?? undefined, role };
512
+ return { content, model: asString(value.model), phase: phase ?? undefined, role };
417
513
  };
418
514
 
419
515
  const extractToolRecord = (parsed: Record<string, JsonValue>): ToolRecord | null => {
@@ -462,17 +558,26 @@ const extractToolRecord = (parsed: Record<string, JsonValue>): ToolRecord | null
462
558
  return null;
463
559
  };
464
560
 
465
- const renderMessageBlock = (message: MessageRecord, outputFormat: ExportFormat): string => {
561
+ const renderMessageBlock = (
562
+ message: MessageRecord,
563
+ outputFormat: ExportFormat,
564
+ assistantModel: string | null,
565
+ includeCommentary: boolean,
566
+ ): string => {
466
567
  if (message.role !== 'user' && message.role !== 'assistant') {
467
568
  return '';
468
569
  }
469
570
 
571
+ if (message.role === 'assistant' && message.phase === 'commentary' && !includeCommentary) {
572
+ return '';
573
+ }
574
+
470
575
  const text = cleanExtractedText(extractText(message.content)).trim();
471
576
  if (!text || shouldSkipMessage(message.role, text)) {
472
577
  return '';
473
578
  }
474
579
 
475
- const title = message.role === 'user' ? 'User' : 'Assistant';
580
+ const title = message.role === 'user' ? 'User' : formatModelLabel(message.model ?? assistantModel);
476
581
  const body = message.phase ? `Phase: ${message.phase}\n\n${text}` : text;
477
582
 
478
583
  return renderSection(title, body, outputFormat);
@@ -488,8 +593,13 @@ const renderToolBlock = (tool: ToolRecord, outputFormat: ExportFormat): string =
488
593
  return summary ? renderSection('Tool Output', summary, outputFormat) : '';
489
594
  };
490
595
 
491
- const renderCompactBlock = (message: MessageRecord, text: string, outputFormat: ExportFormat): string => {
492
- const prefix = message.role === 'user' ? 'U:' : 'A:';
596
+ const renderCompactBlock = (
597
+ message: MessageRecord,
598
+ text: string,
599
+ outputFormat: ExportFormat,
600
+ assistantModel: string | null,
601
+ ): string => {
602
+ const prefix = message.role === 'user' ? 'U:' : `${formatModelLabel(message.model ?? assistantModel)}:`;
493
603
  const lines = text.split('\n');
494
604
  const [firstLine, ...rest] = lines;
495
605
 
@@ -525,11 +635,12 @@ const stripPreviewBlock = (text: string): string => {
525
635
 
526
636
  const first = parts[0];
527
637
  const second = parts[1];
638
+ const isTranscriptHeading = (value: string) => /^##\s+.+$/i.test(value);
528
639
  const looksLikePreview =
529
640
  !/^([UA]):/i.test(first) &&
530
- !/^##\s+(User|Assistant)\s*$/i.test(first) &&
641
+ !isTranscriptHeading(first) &&
531
642
  /^([UA]):/i.test(second) === false &&
532
- /^##\s+(User|Assistant)\s*$/i.test(second);
643
+ isTranscriptHeading(second);
533
644
 
534
645
  if (!looksLikePreview) {
535
646
  return text.trim();
@@ -10,6 +10,7 @@ export type CodexCliOptions = {
10
10
  projectFilter: string | null;
11
11
  threadIds: string[];
12
12
  optimized: boolean;
13
+ includeCommentary: boolean;
13
14
  includeTools: boolean;
14
15
  outputFormat: ExportFormat;
15
16
  flat: boolean;
@@ -35,11 +36,14 @@ export type SessionMeta = {
35
36
  source?: string;
36
37
  originator?: string;
37
38
  cli_version?: string;
39
+ thread_source?: string;
40
+ model_provider?: string;
38
41
  };
39
42
 
40
43
  export type MessageRecord = {
41
44
  role: string;
42
45
  content: JsonValue;
46
+ model?: string | null;
43
47
  phase?: string;
44
48
  };
45
49
 
@@ -77,6 +81,10 @@ export type ThreadRow = {
77
81
  model: string | null;
78
82
  reasoning_effort: string | null;
79
83
  agent_path: string | null;
84
+ created_at_ms: number | null;
85
+ updated_at_ms: number | null;
86
+ thread_source: string | null;
87
+ preview: string;
80
88
  };
81
89
 
82
90
  export type SpawnEdgeRow = {
@@ -0,0 +1,58 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import type { ParsedCodexTranscript } from './codex-browser-types';
4
+ import { parseCodexTranscriptFile } from './codex-thread-parser';
5
+ import { getFileFingerprint, hashCacheKeyParts, withCachedJson } from './ui-cache';
6
+
7
+ export const LARGE_THREAD_SIZE_BYTES = 100 * 1024 * 1024;
8
+ export const LARGE_THREAD_PREVIEW_EVENT_LIMIT = 200;
9
+
10
+ export const getCachedParsedCodexTranscript = async (sessionFile: string): Promise<ParsedCodexTranscript> => {
11
+ const fingerprint = await getFileFingerprint(sessionFile);
12
+ const key = `thread-${hashCacheKeyParts(path.basename(sessionFile), fingerprint)}`;
13
+
14
+ return withCachedJson(key, async () => parseCodexTranscriptFile(sessionFile));
15
+ };
16
+
17
+ type CachedThreadTranscriptPreviewOptions = {
18
+ largeTranscriptThresholdBytes?: number;
19
+ previewEventLimit?: number;
20
+ };
21
+
22
+ export const getThreadRolloutLoadState = async (
23
+ sessionFile: string,
24
+ largeTranscriptThresholdBytes = LARGE_THREAD_SIZE_BYTES,
25
+ ) => {
26
+ const metadata = await stat(sessionFile);
27
+
28
+ return {
29
+ fileSizeBytes: metadata.size,
30
+ shouldDeferTranscriptLoad: metadata.size > largeTranscriptThresholdBytes,
31
+ };
32
+ };
33
+
34
+ export const getCachedThreadTranscriptPreview = async (
35
+ sessionFile: string,
36
+ options: CachedThreadTranscriptPreviewOptions = {},
37
+ ): Promise<ParsedCodexTranscript> => {
38
+ const threshold = options.largeTranscriptThresholdBytes ?? LARGE_THREAD_SIZE_BYTES;
39
+ const previewEventLimit = options.previewEventLimit ?? LARGE_THREAD_PREVIEW_EVENT_LIMIT;
40
+ const fingerprint = await getFileFingerprint(sessionFile);
41
+ const { fileSizeBytes, shouldDeferTranscriptLoad } = await getThreadRolloutLoadState(sessionFile, threshold);
42
+ const key = `thread-preview-${hashCacheKeyParts(path.basename(sessionFile), fingerprint, String(threshold), String(previewEventLimit))}`;
43
+
44
+ return withCachedJson(key, async () => {
45
+ if (!shouldDeferTranscriptLoad) {
46
+ return parseCodexTranscriptFile(sessionFile, {
47
+ sourceFileSizeBytes: fileSizeBytes,
48
+ });
49
+ }
50
+
51
+ return parseCodexTranscriptFile(sessionFile, {
52
+ includeRaw: false,
53
+ maxEvents: previewEventLimit,
54
+ maxTurnContexts: 0,
55
+ sourceFileSizeBytes: fileSizeBytes,
56
+ });
57
+ });
58
+ };