✨ feat(vscode): 会话管理支持导入已 /rename 的命名会话

This commit is contained in:
cruldra committed 2026-07-24 00:12:28 +08:00
1 parent 7c67d220db
commit dc522064ce
10 files changed
+438 -9

No files matched your search

@@ -0,0 +1,96 @@
/**
* 从 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
}