poe-code 4.0.31 → 4.0.32

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.
@@ -0,0 +1,263 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+ import { mapConcurrent } from "./concurrency.js";
4
+ import { readHead } from "./head.js";
5
+ const MANIFEST_VERSION = 1;
6
+ const HOT_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
7
+ const IO_CONCURRENCY = 32;
8
+ function isMissingFile(error) {
9
+ return (typeof error === "object" &&
10
+ error !== null &&
11
+ Object.prototype.hasOwnProperty.call(error, "code") &&
12
+ error.code === "ENOENT");
13
+ }
14
+ function shardFileName(directory) {
15
+ return `${createHash("sha256").update(directory).digest("hex").slice(0, 16)}.jsonl`;
16
+ }
17
+ function parseShardLines(contents) {
18
+ const records = [];
19
+ for (const line of contents.split("\n")) {
20
+ if (line.trim().length === 0) {
21
+ continue;
22
+ }
23
+ try {
24
+ const value = JSON.parse(line);
25
+ if (typeof value === "object" &&
26
+ value !== null &&
27
+ typeof value.path === "string" &&
28
+ typeof value.id === "string") {
29
+ records.push(value);
30
+ }
31
+ }
32
+ catch {
33
+ // Corrupt shard lines are dropped; the next sync re-derives them.
34
+ }
35
+ }
36
+ return records;
37
+ }
38
+ function referenceFromRecord(record) {
39
+ return {
40
+ source: record.source,
41
+ id: record.id,
42
+ path: record.path,
43
+ ...(record.cwd !== undefined ? { cwd: record.cwd } : {}),
44
+ ...(record.updatedAtMs > 0 ? { updatedAt: new Date(record.updatedAtMs) } : {}),
45
+ ...(record.title !== undefined ? { title: record.title } : {}),
46
+ ...(record.metadata !== undefined ? { metadata: record.metadata } : {})
47
+ };
48
+ }
49
+ export async function openTraceIndex(options) {
50
+ const { dir, fs } = options;
51
+ const manifestPath = path.join(dir, "manifest.json");
52
+ const shardsDir = path.join(dir, "shards");
53
+ const writeAtomic = async (filePath, data) => {
54
+ const rename = fs.rename;
55
+ if (typeof rename === "function") {
56
+ const tempPath = `${filePath}.tmp`;
57
+ await fs.writeFile(tempPath, data);
58
+ await rename.call(fs, tempPath, filePath);
59
+ return;
60
+ }
61
+ await fs.writeFile(filePath, data);
62
+ };
63
+ const loadManifest = async () => {
64
+ try {
65
+ const parsed = JSON.parse(await fs.readFile(manifestPath, "utf8"));
66
+ if (parsed.version === MANIFEST_VERSION && typeof parsed.shards === "object") {
67
+ return parsed;
68
+ }
69
+ }
70
+ catch {
71
+ // Missing or corrupt manifest: start empty and re-derive on sync.
72
+ }
73
+ return { version: MANIFEST_VERSION, shards: {} };
74
+ };
75
+ const loadShard = async (shard) => {
76
+ try {
77
+ return parseShardLines(await fs.readFile(path.join(shardsDir, shard.file), "utf8"));
78
+ }
79
+ catch (error) {
80
+ if (isMissingFile(error)) {
81
+ return [];
82
+ }
83
+ throw error;
84
+ }
85
+ };
86
+ const sync = async (syncOptions) => {
87
+ const now = syncOptions.now ?? Date.now;
88
+ const stats = {
89
+ scannedDirs: 0,
90
+ statted: 0,
91
+ headReads: 0,
92
+ added: 0,
93
+ updated: 0,
94
+ removed: 0
95
+ };
96
+ await fs.mkdir(shardsDir, { recursive: true });
97
+ const manifest = await loadManifest();
98
+ let manifestDirty = false;
99
+ const scannedSources = new Set();
100
+ const seenDirectories = new Set();
101
+ const hotCutoffMs = now() - HOT_WINDOW_MS;
102
+ for (const reader of syncOptions.readers) {
103
+ const scan = reader.scan?.bind(reader);
104
+ const readHeadMetadata = reader.readHeadMetadata?.bind(reader);
105
+ if (scan === undefined || readHeadMetadata === undefined) {
106
+ continue;
107
+ }
108
+ scannedSources.add(reader.id);
109
+ for await (const { directory, files } of scan({ homeDir: syncOptions.homeDir, fs })) {
110
+ stats.scannedDirs += 1;
111
+ seenDirectories.add(directory);
112
+ const shardEntry = manifest.shards[directory];
113
+ const existing = shardEntry === undefined ? [] : await loadShard(shardEntry);
114
+ const byPath = new Map(existing.map((record) => [record.path, record]));
115
+ const fileSet = new Set(files);
116
+ const toStat = files.filter((filePath) => {
117
+ const record = byPath.get(filePath);
118
+ return record === undefined || record.updatedAtMs >= hotCutoffMs;
119
+ });
120
+ stats.statted += toStat.length;
121
+ const statResults = await mapConcurrent(toStat, IO_CONCURRENCY, async (filePath) => ({
122
+ filePath,
123
+ stat: await fs.stat(filePath).catch(() => undefined)
124
+ }));
125
+ const changed = statResults.filter(({ filePath, stat }) => {
126
+ if (stat === undefined || !stat.isFile()) {
127
+ return false;
128
+ }
129
+ const record = byPath.get(filePath);
130
+ const mtimeMs = stat.mtime?.getTime() ?? 0;
131
+ const size = stat.size ?? -1;
132
+ return record === undefined || record.mtimeMs !== mtimeMs || record.size !== size;
133
+ });
134
+ const updatedRecords = await mapConcurrent(changed, IO_CONCURRENCY, async ({ filePath, stat }) => {
135
+ stats.headReads += 1;
136
+ const head = await readHead(fs, filePath).catch(() => undefined);
137
+ if (head === undefined) {
138
+ return undefined;
139
+ }
140
+ const metadata = readHeadMetadata(head, filePath);
141
+ if (metadata === undefined) {
142
+ return undefined;
143
+ }
144
+ const mtimeMs = stat?.mtime?.getTime() ?? 0;
145
+ return {
146
+ path: filePath,
147
+ source: reader.id,
148
+ id: metadata.id,
149
+ ...(metadata.cwd !== undefined ? { cwd: metadata.cwd } : {}),
150
+ ...(metadata.title !== undefined ? { title: metadata.title } : {}),
151
+ ...(metadata.metadata !== undefined ? { metadata: metadata.metadata } : {}),
152
+ updatedAtMs: mtimeMs > 0 ? mtimeMs : (metadata.updatedAt?.getTime() ?? 0),
153
+ mtimeMs,
154
+ size: stat?.size ?? -1
155
+ };
156
+ });
157
+ const removedCount = existing.filter((record) => !fileSet.has(record.path)).length;
158
+ const freshRecords = updatedRecords.filter((record) => record !== undefined);
159
+ if (freshRecords.length === 0 && removedCount === 0) {
160
+ syncOptions.onProgress?.({ scannedDirs: stats.scannedDirs, headReads: stats.headReads });
161
+ continue;
162
+ }
163
+ for (const record of freshRecords) {
164
+ if (byPath.has(record.path)) {
165
+ stats.updated += 1;
166
+ }
167
+ else {
168
+ stats.added += 1;
169
+ }
170
+ byPath.set(record.path, record);
171
+ }
172
+ for (const record of existing) {
173
+ if (!fileSet.has(record.path)) {
174
+ byPath.delete(record.path);
175
+ stats.removed += 1;
176
+ }
177
+ }
178
+ const records = [...byPath.values()].sort((a, b) => b.updatedAtMs - a.updatedAtMs);
179
+ const file = shardEntry?.file ?? shardFileName(directory);
180
+ if (records.length === 0) {
181
+ delete manifest.shards[directory];
182
+ }
183
+ else {
184
+ await writeAtomic(path.join(shardsDir, file), records.map((record) => JSON.stringify(record)).join("\n") + "\n");
185
+ manifest.shards[directory] = {
186
+ file,
187
+ source: reader.id,
188
+ maxUpdatedAtMs: records[0]?.updatedAtMs ?? 0,
189
+ count: records.length
190
+ };
191
+ }
192
+ manifestDirty = true;
193
+ syncOptions.onProgress?.({ scannedDirs: stats.scannedDirs, headReads: stats.headReads });
194
+ }
195
+ }
196
+ for (const [directory, shard] of Object.entries(manifest.shards)) {
197
+ if (scannedSources.has(shard.source) && !seenDirectories.has(directory)) {
198
+ stats.removed += shard.count;
199
+ delete manifest.shards[directory];
200
+ manifestDirty = true;
201
+ const unlink = fs.unlink;
202
+ if (typeof unlink === "function") {
203
+ await unlink.call(fs, path.join(shardsDir, shard.file)).catch(() => undefined);
204
+ }
205
+ }
206
+ }
207
+ if (manifestDirty) {
208
+ await writeAtomic(manifestPath, JSON.stringify({ ...manifest, version: MANIFEST_VERSION }));
209
+ }
210
+ return stats;
211
+ };
212
+ const query = async (queryOptions) => {
213
+ const manifest = await loadManifest();
214
+ const sinceMs = queryOptions.since?.getTime();
215
+ const limit = queryOptions.limit;
216
+ const entries = Object.values(manifest.shards)
217
+ .filter((shard) => queryOptions.sources === undefined || queryOptions.sources.includes(shard.source))
218
+ .sort((a, b) => b.maxUpdatedAtMs - a.maxUpdatedAtMs);
219
+ let results = [];
220
+ for (const entry of entries) {
221
+ if (sinceMs !== undefined && entry.maxUpdatedAtMs < sinceMs) {
222
+ break;
223
+ }
224
+ if (results.length >= limit) {
225
+ const cutoff = results[results.length - 1]?.updatedAtMs ?? 0;
226
+ if (entry.maxUpdatedAtMs <= cutoff) {
227
+ break;
228
+ }
229
+ }
230
+ for (const record of await loadShard(entry)) {
231
+ if (sinceMs !== undefined && record.updatedAtMs < sinceMs) {
232
+ continue;
233
+ }
234
+ if (queryOptions.allWorkspaces !== true &&
235
+ queryOptions.cwd !== undefined &&
236
+ record.cwd !== queryOptions.cwd) {
237
+ continue;
238
+ }
239
+ results.push(record);
240
+ }
241
+ results.sort((a, b) => b.updatedAtMs - a.updatedAtMs);
242
+ if (Number.isFinite(limit) && results.length > limit) {
243
+ results = results.slice(0, limit);
244
+ }
245
+ }
246
+ return results.map(referenceFromRecord);
247
+ };
248
+ const rebuild = async (syncOptions) => {
249
+ const unlink = fs.unlink;
250
+ if (typeof unlink === "function") {
251
+ const names = await fs.readdir(shardsDir).catch(() => []);
252
+ for (const name of names) {
253
+ await unlink.call(fs, path.join(shardsDir, name)).catch(() => undefined);
254
+ }
255
+ await unlink.call(fs, manifestPath).catch(() => undefined);
256
+ }
257
+ else {
258
+ await writeAtomic(manifestPath, JSON.stringify({ version: MANIFEST_VERSION, shards: {} }));
259
+ }
260
+ return sync(syncOptions);
261
+ };
262
+ return { sync, query, rebuild };
263
+ }
@@ -1,4 +1,6 @@
1
1
  export { collectHumanPrompts, collectHumanPromptsFromReaders, collectHumanPromptsWithStats } from "./collect.js";
2
2
  export { writeHumanPromptJsonl } from "./jsonl.js";
3
+ export { openTraceIndex } from "./index-store/store.js";
4
+ export type { TraceIndex, TraceIndexQueryOptions, TraceIndexSyncOptions, TraceIndexSyncStats } from "./index-store/store.js";
3
5
  export { claudeTraceReader, codexTraceReader, piTraceReader, poeCodeTraceReader, traceReaders } from "./readers/index.js";
4
- export type { AgentTraceFileSystem, AgentTraceSource, CollectHumanPromptsOptions, CollectHumanPromptsResult, HumanPromptRecord, NormalizedTrace, NormalizedTraceTurn, SqliteTraceDatabase, SqliteTraceDatabaseFactory, TraceDiscoverOptions, TraceReadOptions, TraceReader, TraceReference, TraceUsage } from "./types.js";
6
+ export type { AgentTraceFileSystem, AgentTraceSource, CollectHumanPromptsOptions, CollectHumanPromptsResult, HumanPromptRecord, NormalizedTrace, NormalizedTraceTurn, SqliteTraceDatabase, SqliteTraceDatabaseFactory, TraceDiscoverOptions, TraceHeadMetadata, TraceReadOptions, TraceReader, TraceReference, TraceScanDirectory, TraceScanOptions, TraceUsage } from "./types.js";
@@ -1,3 +1,4 @@
1
1
  export { collectHumanPrompts, collectHumanPromptsFromReaders, collectHumanPromptsWithStats } from "./collect.js";
2
2
  export { writeHumanPromptJsonl } from "./jsonl.js";
3
+ export { openTraceIndex } from "./index-store/store.js";
3
4
  export { claudeTraceReader, codexTraceReader, piTraceReader, poeCodeTraceReader, traceReaders } from "./readers/index.js";
@@ -1,4 +1,6 @@
1
1
  import path from "node:path";
2
+ import { mapConcurrent } from "../index-store/concurrency.js";
3
+ import { readHead } from "../index-store/head.js";
2
4
  import { asRecord, newestDate, parseDate, parseJsonLines } from "../line-json.js";
3
5
  function isMissingFile(error) {
4
6
  return (typeof error === "object" &&
@@ -547,6 +549,7 @@ function parseRecordLine(rawLine) {
547
549
  return undefined;
548
550
  }
549
551
  }
552
+ const DISCOVER_CONCURRENCY = 32;
550
553
  export const claudeTraceReader = {
551
554
  id: "claude",
552
555
  defaultRoots(homeDir) {
@@ -559,24 +562,40 @@ export const claudeTraceReader = {
559
562
  : [path.join(projectsRoot, encodeClaudeProjectPath(options.cwd))];
560
563
  const references = [];
561
564
  for (const directory of directories) {
562
- for (const filePath of await listJsonlFiles(options.fs, directory)) {
565
+ const files = await listJsonlFiles(options.fs, directory);
566
+ const discovered = await mapConcurrent(files, DISCOVER_CONCURRENCY, async (filePath) => {
563
567
  const updatedAt = await fileUpdatedAt(options.fs, filePath);
564
568
  if (options.since && updatedAt && updatedAt < options.since) {
565
- continue;
569
+ return undefined;
566
570
  }
567
- const metadata = traceHeadMetadata(await options.fs.readFile(filePath, "utf8"));
568
- references.push({
571
+ const metadata = traceHeadMetadata(await readHead(options.fs, filePath));
572
+ return {
569
573
  source: "claude",
570
574
  id: metadata.sessionId ?? fileId(filePath),
571
575
  path: filePath,
572
576
  ...(metadata.cwd ? { cwd: metadata.cwd } : {}),
573
577
  ...(updatedAt ? { updatedAt } : {}),
574
578
  ...(metadata.title ? { title: metadata.title } : {})
575
- });
576
- }
579
+ };
580
+ });
581
+ references.push(...discovered.filter((reference) => reference !== undefined));
577
582
  }
578
583
  return references;
579
584
  },
585
+ async *scan(options) {
586
+ const projectsRoot = path.join(options.homeDir, ".claude", "projects");
587
+ for (const directory of await listProjectDirectories(options.fs, projectsRoot)) {
588
+ yield { directory, files: await listJsonlFiles(options.fs, directory) };
589
+ }
590
+ },
591
+ readHeadMetadata(head, filePath) {
592
+ const metadata = traceHeadMetadata(head);
593
+ return {
594
+ id: metadata.sessionId ?? fileId(filePath),
595
+ ...(metadata.cwd ? { cwd: metadata.cwd } : {}),
596
+ ...(metadata.title ? { title: metadata.title } : {})
597
+ };
598
+ },
580
599
  async read(reference, options) {
581
600
  if (!reference.path) {
582
601
  throw new Error(`Claude trace ${reference.id} has no path.`);
@@ -1,4 +1,6 @@
1
1
  import path from "node:path";
2
+ import { mapConcurrent } from "../index-store/concurrency.js";
3
+ import { readHead } from "../index-store/head.js";
2
4
  import { asRecord, newestDate, parseDate, parseJsonLines } from "../line-json.js";
3
5
  function isMissingFile(error) {
4
6
  return (typeof error === "object" &&
@@ -272,6 +274,44 @@ function readTraceContents(filePath, contents) {
272
274
  turns
273
275
  };
274
276
  }
277
+ function headMetadataFromContents(head, filePath) {
278
+ let id = fileId(filePath);
279
+ let cwd;
280
+ let model;
281
+ let title;
282
+ const toolCalls = new Map();
283
+ for (const value of parseJsonLines(head)) {
284
+ const record = asRecord(value);
285
+ if (!record) {
286
+ continue;
287
+ }
288
+ if (record.type === "session") {
289
+ if (typeof record.id === "string") {
290
+ id = record.id;
291
+ }
292
+ if (typeof record.cwd === "string") {
293
+ cwd = record.cwd;
294
+ }
295
+ }
296
+ if (record.type === "model_change" && typeof record.modelId === "string") {
297
+ model = record.modelId;
298
+ }
299
+ const message = asRecord(record.message);
300
+ if (message?.role === "assistant" && typeof message.model === "string") {
301
+ model = message.model;
302
+ }
303
+ if (title === undefined) {
304
+ title = turnsFromMessage(record, toolCalls).find((turn) => turn.role === "human")?.text;
305
+ }
306
+ }
307
+ return {
308
+ id,
309
+ ...(cwd ? { cwd } : {}),
310
+ ...(title ? { title } : {}),
311
+ ...(model ? { metadata: { model } } : {})
312
+ };
313
+ }
314
+ const DISCOVER_CONCURRENCY = 32;
275
315
  export const piTraceReader = {
276
316
  id: "pi",
277
317
  defaultRoots(homeDir) {
@@ -284,26 +324,37 @@ export const piTraceReader = {
284
324
  : [path.join(sessionsRoot, encodeWorkspacePath(options.cwd))];
285
325
  const references = [];
286
326
  for (const directory of directories) {
287
- for (const filePath of await listJsonlFiles(options.fs, directory)) {
327
+ const files = await listJsonlFiles(options.fs, directory);
328
+ const discovered = await mapConcurrent(files, DISCOVER_CONCURRENCY, async (filePath) => {
288
329
  const stat = await options.fs.stat(filePath);
289
330
  const updatedAt = stat.mtime;
290
331
  if (options.since && updatedAt && updatedAt < options.since) {
291
- continue;
332
+ return undefined;
292
333
  }
293
- const trace = readTraceContents(filePath, await options.fs.readFile(filePath, "utf8"));
294
- references.push({
334
+ const metadata = headMetadataFromContents(await readHead(options.fs, filePath), filePath);
335
+ return {
295
336
  source: "pi",
296
- id: trace.id,
337
+ id: metadata.id,
297
338
  path: filePath,
298
- ...(trace.cwd ? { cwd: trace.cwd } : {}),
339
+ ...(metadata.cwd ? { cwd: metadata.cwd } : {}),
299
340
  ...(updatedAt ? { updatedAt } : {}),
300
- ...(trace.title ? { title: trace.title } : {}),
301
- ...(trace.model ? { metadata: { model: trace.model } } : {})
302
- });
303
- }
341
+ ...(metadata.title ? { title: metadata.title } : {}),
342
+ ...(metadata.metadata ? { metadata: metadata.metadata } : {})
343
+ };
344
+ });
345
+ references.push(...discovered.filter((reference) => reference !== undefined));
304
346
  }
305
347
  return references;
306
348
  },
349
+ async *scan(options) {
350
+ const sessionsRoot = path.join(options.homeDir, ".pi", "agent", "sessions");
351
+ for (const directory of await listSessionDirectories(options.fs, sessionsRoot)) {
352
+ yield { directory, files: await listJsonlFiles(options.fs, directory) };
353
+ }
354
+ },
355
+ readHeadMetadata(head, filePath) {
356
+ return headMetadataFromContents(head, filePath);
357
+ },
307
358
  async read(reference, options) {
308
359
  if (!reference.path) {
309
360
  throw new Error(`Pi trace ${reference.id} has no path.`);
@@ -1,4 +1,6 @@
1
1
  import path from "node:path";
2
+ import { mapConcurrent } from "../index-store/concurrency.js";
3
+ import { readHead } from "../index-store/head.js";
2
4
  import { asRecord, newestDate, parseDate, parseJsonLines } from "../line-json.js";
3
5
  const HEAD_SCAN_MAX_LINES = 50;
4
6
  const REDACTED_LOG_CONTENT = "[redacted]";
@@ -208,6 +210,7 @@ function turnFromRecord(record) {
208
210
  return undefined;
209
211
  }
210
212
  }
213
+ const DISCOVER_CONCURRENCY = 32;
211
214
  export const poeCodeTraceReader = {
212
215
  id: "poe-code",
213
216
  defaultRoots(homeDir) {
@@ -215,26 +218,41 @@ export const poeCodeTraceReader = {
215
218
  },
216
219
  async discover(options) {
217
220
  const root = path.join(options.homeDir, ".poe-code", "spawn-logs");
218
- const references = [];
219
- for (const filePath of await listJsonlFiles(options.fs, root)) {
221
+ const files = await listJsonlFiles(options.fs, root);
222
+ const discovered = await mapConcurrent(files, DISCOVER_CONCURRENCY, async (filePath) => {
220
223
  const parsed = parseLogFileName(filePath);
221
224
  if (!parsed) {
222
- continue;
225
+ return undefined;
223
226
  }
224
227
  const updatedAt = await updatedAtForFile(options.fs, filePath, parsed.timestamp);
225
228
  if (options.since && updatedAt && updatedAt < options.since) {
226
- continue;
229
+ return undefined;
227
230
  }
228
- const content = await options.fs.readFile(filePath, "utf8");
229
- references.push({
231
+ const head = await readHead(options.fs, filePath);
232
+ return {
230
233
  source: "poe-code",
231
234
  id: parsed.sessionId,
232
235
  path: filePath,
233
236
  ...(updatedAt ? { updatedAt } : {}),
234
- title: titleFromLogContent(content) ?? parsed.agent
235
- });
237
+ title: titleFromLogContent(head) ?? parsed.agent
238
+ };
239
+ });
240
+ return discovered.filter((reference) => reference !== undefined);
241
+ },
242
+ async *scan(options) {
243
+ const root = path.join(options.homeDir, ".poe-code", "spawn-logs");
244
+ yield { directory: root, files: await listJsonlFiles(options.fs, root) };
245
+ },
246
+ readHeadMetadata(head, filePath) {
247
+ const parsed = parseLogFileName(filePath);
248
+ if (!parsed) {
249
+ return undefined;
236
250
  }
237
- return references;
251
+ return {
252
+ id: parsed.sessionId,
253
+ title: titleFromLogContent(head) ?? parsed.agent,
254
+ ...(parsed.timestamp ? { updatedAt: parsed.timestamp } : {})
255
+ };
238
256
  },
239
257
  async read(reference, options) {
240
258
  if (!reference.path) {
@@ -89,14 +89,32 @@ export interface CollectHumanPromptsOptions {
89
89
  allWorkspaces?: boolean;
90
90
  fs?: AgentTraceFileSystem;
91
91
  sqlite?: SqliteTraceDatabaseFactory;
92
+ indexDir?: string;
92
93
  }
93
94
  export interface CollectHumanPromptsResult {
94
95
  records: HumanPromptRecord[];
95
96
  traceCount: number;
96
97
  }
98
+ export interface TraceScanOptions {
99
+ homeDir: string;
100
+ fs: AgentTraceFileSystem;
101
+ }
102
+ export interface TraceScanDirectory {
103
+ directory: string;
104
+ files: string[];
105
+ }
106
+ export interface TraceHeadMetadata {
107
+ id: string;
108
+ cwd?: string;
109
+ title?: string;
110
+ updatedAt?: Date;
111
+ metadata?: Record<string, unknown>;
112
+ }
97
113
  export interface TraceReader {
98
114
  id: AgentTraceSource;
99
115
  defaultRoots(homeDir: string): string[];
100
116
  discover(options: TraceDiscoverOptions): Promise<TraceReference[]>;
101
117
  read(reference: TraceReference, options: TraceReadOptions): Promise<NormalizedTrace>;
118
+ scan?(options: TraceScanOptions): AsyncIterable<TraceScanDirectory>;
119
+ readHeadMetadata?(head: string, filePath: string): TraceHeadMetadata | undefined;
102
120
  }