import path from "node:path"; import { parseUsageCountedSessionIdFromFileName } from "../config/sessions/artifacts.js"; import type { SessionEntry } from "../config/sessions/types.js"; import { normalizeAgentId } from "../routing/session-key.js"; import { normalizeOptionalString } from "../shared/string-coerce.js"; export { loadCombinedSessionStoreForGateway } from "../config/sessions/combined-store-gateway.js"; const QMD_ARCHIVE_STEM_RE = /^(.+)-jsonl-(reset|deleted)-(.+)$/; const QMD_ARCHIVE_TIMESTAMP_RE = /^(\d{4}-\d{2}-\d{2})[tT](\d{2}-\d{2}-\d{2})(?:(?:\.|-)(\d{3}))?[zZ]$/; function restoreQmdNormalizedArchiveTimestamp(timestamp: string): string | null { const match = QMD_ARCHIVE_TIMESTAMP_RE.exec(timestamp); if (!match) { return null; } const [, date, time, milliseconds] = match; return `${date}T${time}${milliseconds ? `.${milliseconds}` : ""}Z`; } function restoreQmdNormalizedArchiveName(mdStem: string): string | null { const match = QMD_ARCHIVE_STEM_RE.exec(mdStem); if (!match) { return null; } const [, sessionId, reason, timestamp] = match; const restoredTimestamp = restoreQmdNormalizedArchiveTimestamp(timestamp); return restoredTimestamp ? `${sessionId}.jsonl.${reason}.${restoredTimestamp}` : null; } function normalizeQmdSessionStem(stem: string): string { return stem .normalize("NFKD") .toLowerCase() .replace(/[^\p{Letter}\p{Number}]+/gu, "-") .replace(/-{2,}/g, "-") .replace(/^-+|-+$/g, ""); } export type SessionTranscriptHitIdentity = { stem: string; liveStem?: string; ownerAgentId?: string; archived: boolean; }; function parseSessionsPath(hitPath: string): { base: string; ownerAgentId?: string } { const normalized = hitPath.replace(/\\/g, "/"); const fromSessionsRoot = normalized.startsWith("sessions/") ? normalized.slice("sessions/".length) : normalized; const parts = fromSessionsRoot.split("/").filter(Boolean); const base = path.posix.basename(fromSessionsRoot); const ownerAgentId = normalized.startsWith("sessions/") && parts.length === 2 ? normalizeAgentId(parts[0]) : undefined; return { base, ownerAgentId }; } /** * Derive transcript stem `S` from a memory search hit path for `source === "sessions"`. * Builtin index uses `sessions/.jsonl`; QMD exports use `.md`. * Archived transcripts (`.jsonl.reset.` / `.jsonl.deleted.`) resolve * to the same stem as the live `.jsonl` they were rotated from. */ export function extractTranscriptStemFromSessionsMemoryHit(hitPath: string): string | null { return extractTranscriptIdentityFromSessionsMemoryHit(hitPath)?.stem ?? null; } export function extractTranscriptIdentityFromSessionsMemoryHit( hitPath: string, ): SessionTranscriptHitIdentity | null { const isQmdPath = hitPath.replace(/\\/g, "/").startsWith("qmd/"); const { base, ownerAgentId } = parseSessionsPath(hitPath); const archivedStem = parseUsageCountedSessionIdFromFileName(base); if (archivedStem && base !== `${archivedStem}.jsonl`) { return { stem: archivedStem, ownerAgentId, archived: true }; } if (base.endsWith(".jsonl")) { const stem = base.slice(0, -".jsonl".length); return stem ? { stem, ownerAgentId, archived: false } : null; } if (base.endsWith(".md")) { const mdStem = base.slice(0, -".md".length); if (!mdStem) { return null; } if (isQmdPath) { const exportedArchiveStem = parseUsageCountedSessionIdFromFileName(mdStem); if (exportedArchiveStem && mdStem !== `${exportedArchiveStem}.jsonl`) { return { stem: exportedArchiveStem, liveStem: mdStem, ownerAgentId, archived: true }; } const restoredArchiveName = restoreQmdNormalizedArchiveName(mdStem); if (restoredArchiveName) { const archivedStem = parseUsageCountedSessionIdFromFileName(restoredArchiveName); if (archivedStem && restoredArchiveName !== `${archivedStem}.jsonl`) { return { stem: archivedStem, liveStem: mdStem, ownerAgentId, archived: true }; } } } return { stem: mdStem, ownerAgentId, archived: false }; } return null; } /** * Map transcript stem to canonical session store keys (all agents in the combined store). * Session tools visibility and agent-to-agent policy are enforced by the caller (e.g. * `createSessionVisibilityGuard`), including cross-agent cases. */ export function resolveTranscriptStemToSessionKeys(params: { store: Record; stem: string; archivedOwnerAgentId?: string; allowQmdSlugFallback?: boolean; }): string[] { const { store } = params; const matches: string[] = []; const stemAsFile = params.stem.endsWith(".jsonl") ? params.stem : `${params.stem}.jsonl`; const parsedStemId = parseUsageCountedSessionIdFromFileName(stemAsFile); for (const [sessionKey, entry] of Object.entries(store)) { const sessionFile = normalizeOptionalString(entry.sessionFile); if (sessionFile) { const base = path.basename(sessionFile); const fileStem = base.endsWith(".jsonl") ? base.slice(0, -".jsonl".length) : base; if (fileStem === params.stem) { matches.push(sessionKey); continue; } } if (entry.sessionId === params.stem || (parsedStemId && entry.sessionId === parsedStemId)) { matches.push(sessionKey); } } const deduped = [...new Set(matches)]; if (deduped.length > 0) { return deduped; } const normalizedStem = normalizeQmdSessionStem(params.stem); if (params.allowQmdSlugFallback === true && normalizedStem) { for (const [sessionKey, entry] of Object.entries(store)) { const sessionFile = normalizeOptionalString(entry.sessionFile); if (sessionFile) { const base = path.basename(sessionFile); const fileStem = base.endsWith(".jsonl") ? base.slice(0, -".jsonl".length) : base; if (normalizeQmdSessionStem(fileStem) === normalizedStem) { matches.push(sessionKey); continue; } } if (normalizeQmdSessionStem(entry.sessionId) === normalizedStem) { matches.push(sessionKey); } } } const normalizedDeduped = [...new Set(matches)]; if (normalizedDeduped.length > 0) { return normalizedDeduped.length === 1 ? normalizedDeduped : []; } const archivedOwnerAgentId = normalizeOptionalString(params.archivedOwnerAgentId); return archivedOwnerAgentId ? [`agent:${normalizeAgentId(archivedOwnerAgentId)}:${params.stem}`] : []; }