Files
superwork/vscode/src/sessions/importableNamedSessions.ts
T

97 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 从 Claude projects 目录扫描已 /rename(jsonl 含 custom-title)且尚未登记到
* managed store 的会话,供「导入」候选列表使用。
*/
import { promises as fsp } from 'node:fs'
import * as path from 'node:path'
import { projectsDirFor } from '../cc/sessionWatcher'
import { readManagedSessions } from './managedStore'
export interface ImportableNamedSession {
id: string
name: string
mtimeMs: number
}
/** 从 jsonl 文本提取最后一条 custom-title(trim 后非空),否则 null */
export function extractLastCustomTitle(jsonlText: string): string | null {
let last: string | null = null
const lines = jsonlText.split(/\r?\n/)
for (const line of lines) {
const trimmed = line.trim()
if (!trimmed)
continue
let parsed: unknown
try {
parsed = JSON.parse(trimmed)
}
catch {
continue
}
if (!parsed || typeof parsed !== 'object')
continue
const rec = parsed as { type?: unknown, customTitle?: unknown }
if (rec.type !== 'custom-title')
continue
if (typeof rec.customTitle !== 'string')
continue
const title = rec.customTitle.trim()
if (!title)
continue
last = title
}
return last
}
/**
* 列出可导入命名会话:扫 projectsDir 顶层 *.jsonl,过滤已在 managed 的 id,mtime 降序。
* `projectsDir` 可选,便于测试注入;默认 `projectsDirFor(workspaceRoot)`。
*/
export async function listImportableNamedSessions(
workspaceRoot: string,
projectsDir?: string,
): Promise<ImportableNamedSession[]> {
const dir = projectsDir ?? projectsDirFor(workspaceRoot)
let entries: import('node:fs').Dirent[]
try {
entries = await fsp.readdir(dir, { withFileTypes: true })
}
catch {
return []
}
const managed = await readManagedSessions(workspaceRoot)
const managedIds = new Set(managed.sessions.map(s => s.id))
const results: ImportableNamedSession[] = []
for (const ent of entries) {
if (!ent.isFile() || !ent.name.endsWith('.jsonl'))
continue
const id = ent.name.slice(0, -'.jsonl'.length)
if (!id || managedIds.has(id))
continue
const filePath = path.join(dir, ent.name)
let text: string
let mtimeMs: number
try {
const [raw, st] = await Promise.all([
fsp.readFile(filePath, 'utf8'),
fsp.stat(filePath),
])
text = raw
mtimeMs = st.mtimeMs
}
catch {
continue
}
const name = extractLastCustomTitle(text)
if (!name)
continue
results.push({ id, name, mtimeMs })
}
results.sort((a, b) => b.mtimeMs - a.mtimeMs)
return results
}