@oh-my-pi/omp-stats 18.1.22 → 18.2.1

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/src/trace.ts CHANGED
@@ -854,22 +854,54 @@ export async function getTraceEntry(fileParam: string, entryId: string): Promise
854
854
  // ---------------------------------------------------------------------------
855
855
  // Session list
856
856
 
857
- const titleCache = new Map<string, { mtimeMs: number; title: string | null }>();
857
+ interface SessionMetadata {
858
+ title: string | null;
859
+ cwd: string | null;
860
+ }
861
+
862
+ const metadataCache = new Map<string, SessionMetadata & { mtimeMs: number }>();
858
863
 
859
- /** Read the session title from the fixed title slot or the header line. */
860
- async function readSessionTitle(file: string): Promise<string | null> {
864
+ /** Read enough decompressed transcript bytes to cover the title slot and session header. */
865
+ async function readSessionMetadataHead(file: string): Promise<string> {
866
+ if (!file.endsWith(".gz")) return Bun.file(file).slice(0, TITLE_SCAN_BYTES).text();
867
+
868
+ const reader = Bun.file(file).stream().pipeThrough(new DecompressionStream("gzip")).getReader();
869
+ const decoder = new TextDecoder();
870
+ let remaining = TITLE_SCAN_BYTES;
871
+ let head = "";
872
+ try {
873
+ while (remaining > 0) {
874
+ const { done, value } = await reader.read();
875
+ if (done) {
876
+ head += decoder.decode();
877
+ break;
878
+ }
879
+ const chunk = value.subarray(0, remaining);
880
+ remaining -= chunk.byteLength;
881
+ head += decoder.decode(chunk, { stream: remaining > 0 && chunk.byteLength === value.byteLength });
882
+ if (chunk.byteLength < value.byteLength) break;
883
+ }
884
+ } finally {
885
+ await reader.cancel();
886
+ }
887
+ return head;
888
+ }
889
+
890
+ /** Read list metadata from the fixed title slot and session header. */
891
+ async function readSessionMetadata(file: string): Promise<SessionMetadata> {
861
892
  let mtimeMs: number;
862
893
  try {
863
894
  mtimeMs = (await fs.stat(file)).mtimeMs;
864
895
  } catch {
865
- return null;
896
+ return { title: null, cwd: null };
866
897
  }
867
- const cached = titleCache.get(file);
868
- if (cached && cached.mtimeMs === mtimeMs) return cached.title;
898
+ const cached = metadataCache.get(file);
899
+ if (cached && cached.mtimeMs === mtimeMs) return cached;
869
900
 
870
901
  let title: string | null = null;
902
+ let cwd: string | null = null;
871
903
  try {
872
- const head = await Bun.file(file).slice(0, TITLE_SCAN_BYTES).text();
904
+ const head = await readSessionMetadataHead(file);
873
905
  for (const line of head.split("\n")) {
874
906
  if (!line.trim()) continue;
875
907
  let parsed: EntryView;
@@ -881,18 +913,20 @@ async function readSessionTitle(file: string): Promise<string | null> {
881
913
  }
882
914
  if (parsed.type === "title" && typeof parsed.title === "string") {
883
915
  title = parsed.title;
884
- break;
916
+ continue;
885
917
  }
886
918
  if (parsed.type === "session") {
887
- if (typeof parsed.title === "string") title = parsed.title;
919
+ if (title === null && typeof parsed.title === "string") title = parsed.title;
920
+ if (typeof parsed.cwd === "string") cwd = parsed.cwd;
888
921
  break;
889
922
  }
890
923
  }
891
924
  } catch {
892
- // Unreadable head (gc'd, gz, permission) → keep the row, title null.
925
+ // Unreadable head (gc'd, gz, permission) → keep the row with path-derived metadata.
893
926
  }
894
- titleCache.set(file, { mtimeMs, title });
895
- return title;
927
+ const metadata = { mtimeMs, title, cwd };
928
+ metadataCache.set(file, metadata);
929
+ return metadata;
896
930
  }
897
931
 
898
932
  interface SummaryFold extends SessionSummary {
@@ -1027,7 +1061,9 @@ export async function listSessionSummaries(limit = 100, q?: string): Promise<Ses
1027
1061
  const folds = [...byRoot.values()].sort((a, b) => b.endedAt - a.endedAt).slice(0, LIST_FOLD_LIMIT);
1028
1062
  await Promise.all(
1029
1063
  folds.map(async fold => {
1030
- fold.title = await readSessionTitle(fold.file);
1064
+ const metadata = await readSessionMetadata(fold.file);
1065
+ fold.title = metadata.title;
1066
+ fold.folder = metadata.cwd ?? extractFolderFromPath(fold.file);
1031
1067
  fold.models = [...fold.modelSet].sort();
1032
1068
  }),
1033
1069
  );