From 2052a3bf4e5312f2af00a28ef8be0e1596a72f9b Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 18 Jun 2026 12:09:37 +0800 Subject: [PATCH] refactor(sessions): dedupe generated transcript parsing --- .../generated-transcript-session-id.ts | 29 +++++++++++++++++++ src/config/sessions/session-accessor.ts | 29 +------------------ src/gateway/session-transcript-files.fs.ts | 29 ++----------------- 3 files changed, 32 insertions(+), 55 deletions(-) create mode 100644 src/config/sessions/generated-transcript-session-id.ts diff --git a/src/config/sessions/generated-transcript-session-id.ts b/src/config/sessions/generated-transcript-session-id.ts new file mode 100644 index 00000000000..c15cff5815c --- /dev/null +++ b/src/config/sessions/generated-transcript-session-id.ts @@ -0,0 +1,29 @@ +import path from "node:path"; + +export function extractGeneratedTranscriptSessionId(sessionFile?: string): string | undefined { + const trimmed = sessionFile?.trim(); + if (!trimmed) { + return undefined; + } + const base = path.basename(trimmed); + if (!base.endsWith(".jsonl")) { + return undefined; + } + const withoutExt = base.slice(0, -".jsonl".length); + const topicIndex = withoutExt.indexOf("-topic-"); + if (topicIndex > 0) { + const topicSessionId = withoutExt.slice(0, topicIndex); + return looksLikeGeneratedSessionId(topicSessionId) ? topicSessionId : undefined; + } + const forkMatch = withoutExt.match( + /^(\d{4}-\d{2}-\d{2}T[\w-]+(?:Z|[+-]\d{2}(?:-\d{2})?)?)_(.+)$/, + ); + if (forkMatch?.[2]) { + return looksLikeGeneratedSessionId(forkMatch[2]) ? forkMatch[2] : undefined; + } + return looksLikeGeneratedSessionId(withoutExt) ? withoutExt : undefined; +} + +function looksLikeGeneratedSessionId(value: string): boolean { + return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value); +} diff --git a/src/config/sessions/session-accessor.ts b/src/config/sessions/session-accessor.ts index b540a9f2708..7422c2d5155 100644 --- a/src/config/sessions/session-accessor.ts +++ b/src/config/sessions/session-accessor.ts @@ -14,6 +14,7 @@ import type { SessionTranscriptUpdate } from "../../sessions/transcript-events.j import { getRuntimeConfig } from "../io.js"; import type { OpenClawConfig } from "../types.openclaw.js"; import { formatSessionArchiveTimestamp } from "./artifacts.js"; +import { extractGeneratedTranscriptSessionId } from "./generated-transcript-session-id.js"; import { resolveSessionFilePath, resolveSessionFilePathOptions, @@ -915,34 +916,6 @@ function classifyGeneratedTranscriptCandidate( return transcriptSessionId === sessionId ? "current" : "stale"; } -function extractGeneratedTranscriptSessionId(sessionFile?: string): string | undefined { - const trimmed = sessionFile?.trim(); - if (!trimmed) { - return undefined; - } - const base = path.basename(trimmed); - if (!base.endsWith(".jsonl")) { - return undefined; - } - const withoutExt = base.slice(0, -".jsonl".length); - const topicIndex = withoutExt.indexOf("-topic-"); - if (topicIndex > 0) { - const topicSessionId = withoutExt.slice(0, topicIndex); - return looksLikeGeneratedSessionId(topicSessionId) ? topicSessionId : undefined; - } - const forkMatch = withoutExt.match( - /^(\d{4}-\d{2}-\d{2}T[\w-]+(?:Z|[+-]\d{2}(?:-\d{2})?)?)_(.+)$/, - ); - if (forkMatch?.[2]) { - return looksLikeGeneratedSessionId(forkMatch[2]) ? forkMatch[2] : undefined; - } - return looksLikeGeneratedSessionId(withoutExt) ? withoutExt : undefined; -} - -function looksLikeGeneratedSessionId(value: string): boolean { - return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value); -} - /** * Persists one logical transcript turn through the current file-backed writer. * The file implementation resolves/rebinds the transcript file, holds one diff --git a/src/gateway/session-transcript-files.fs.ts b/src/gateway/session-transcript-files.fs.ts index 4aff2470470..ef1ff3bc09e 100644 --- a/src/gateway/session-transcript-files.fs.ts +++ b/src/gateway/session-transcript-files.fs.ts @@ -9,6 +9,7 @@ import { parseSessionArchiveTimestamp, type SessionArchiveReason, } from "../config/sessions/artifacts.js"; +import { extractGeneratedTranscriptSessionId } from "../config/sessions/generated-transcript-session-id.js"; import { resolveSessionFilePath, resolveSessionTranscriptPath, @@ -106,33 +107,7 @@ function classifySessionTranscriptCandidate( return transcriptSessionId === sessionId ? "current" : "stale"; } -export function extractGeneratedTranscriptSessionId(sessionFile?: string): string | undefined { - const trimmed = sessionFile?.trim(); - if (!trimmed) { - return undefined; - } - const base = path.basename(trimmed); - if (!base.endsWith(".jsonl")) { - return undefined; - } - const withoutExt = base.slice(0, -".jsonl".length); - const topicIndex = withoutExt.indexOf("-topic-"); - if (topicIndex > 0) { - const topicSessionId = withoutExt.slice(0, topicIndex); - return looksLikeGeneratedSessionId(topicSessionId) ? topicSessionId : undefined; - } - const forkMatch = withoutExt.match( - /^(\d{4}-\d{2}-\d{2}T[\w-]+(?:Z|[+-]\d{2}(?:-\d{2})?)?)_(.+)$/, - ); - if (forkMatch?.[2]) { - return looksLikeGeneratedSessionId(forkMatch[2]) ? forkMatch[2] : undefined; - } - return looksLikeGeneratedSessionId(withoutExt) ? withoutExt : undefined; -} - -function looksLikeGeneratedSessionId(value: string): boolean { - return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value); -} +export { extractGeneratedTranscriptSessionId }; function canonicalizePathForComparison(filePath: string): string { const resolved = path.resolve(filePath);