优化会话中最近项目列表加载慢

优化会话中最近项目列表加载慢
This commit is contained in:
zhb 2026-06-26 10:05:35 +08:00 committed by GitHub
parent eddaec547e
commit 4e59c2feb5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -41,6 +41,7 @@ import {
SessionBranchingError,
} from '../../utils/sessionBranching.js'
import { registerChangedFileAccessRoot, registerFilesystemAccessRoot } from '../services/filesystemAccessRoots.js'
import { findGitRoot } from '../../utils/git.js'
import { traceCaptureService, trimTraceCallPreviews } from '../services/traceCaptureService.js'
const workspaceService = new WorkspaceService(
@ -949,14 +950,14 @@ async function getRecentProjects(url: URL): Promise<Response> {
const validSessions = sessions.filter((session) => session.workDirExists && session.workDir)
// First pass: group by logical project root so worktrees stay under the same project.
// Optimization: prefer s.projectRoot (already resolved by listSessions) and only fall back
// to the expensive getSessionWorkDir (reads the full transcript) when projectRoot is absent.
const realPathMap = new Map<string, { projectPath: string; modifiedAt: string; sessionCount: number; sessionId: string }>()
const fallbackSessionIds: string[] = []
for (const s of validSessions) {
let realPath: string
try {
const workDir = await sessionService.getSessionWorkDir(s.id)
realPath = s.projectRoot || workDir || sessionService.desanitizePath(s.projectPath)
} catch {
realPath = s.projectRoot || sessionService.desanitizePath(s.projectPath)
const realPath = s.projectRoot || sessionService.desanitizePath(s.projectPath)
if (!s.projectRoot && s.id) {
fallbackSessionIds.push(s.id)
}
const existing = realPathMap.get(realPath)
@ -972,7 +973,46 @@ async function getRecentProjects(url: URL): Promise<Response> {
}
}
// Resolve fallback sessions in parallel (only those missing projectRoot)
if (fallbackSessionIds.length > 0) {
const resolvedPaths = await Promise.all(
fallbackSessionIds.map(async (sessionId) => {
try {
const workDir = await sessionService.getSessionWorkDir(sessionId)
return { sessionId, workDir }
} catch {
return { sessionId, workDir: null as string | null }
}
}),
)
for (const { sessionId, workDir } of resolvedPaths) {
if (!workDir) continue
// Find the entry we already inserted with the desanitized projectPath
const session = validSessions.find((s) => s.id === sessionId)
const oldKey = session?.projectRoot || sessionService.desanitizePath(session?.projectPath || '')
const oldEntry = oldKey ? realPathMap.get(oldKey) : undefined
const newRealPath = workDir
if (oldKey && oldEntry && oldKey !== newRealPath) {
// Migrate entry to the resolved real path
realPathMap.delete(oldKey)
const existingNew = realPathMap.get(newRealPath)
if (!existingNew || oldEntry.modifiedAt > (existingNew?.modifiedAt ?? '')) {
realPathMap.set(newRealPath, {
projectPath: newRealPath,
modifiedAt: oldEntry.modifiedAt,
sessionCount: oldEntry.sessionCount + (existingNew?.sessionCount ?? 0),
sessionId: oldEntry.sessionId,
})
} else {
existingNew.sessionCount += oldEntry.sessionCount
}
}
}
}
// Build project list with git info — parallelize git operations
// Optimization: use findGitRoot (fs.stat traversal + LRU cache, <5ms) to skip git spawns
// on non-git directories, avoiding slow git rev-parse on each (seconds per non-git dir).
const entries = Array.from(realPathMap.entries())
const projects = await Promise.all(
entries.map(async ([realPath, info]) => {
@ -981,15 +1021,11 @@ async function getRecentProjects(url: URL): Promise<Response> {
let isGit = false
let repoName: string | null = null
let branch: string | null = null
try {
const proc = Bun.spawn(['git', 'rev-parse', '--is-inside-work-tree'], {
cwd: realPath, stdout: 'pipe', stderr: 'pipe',
})
const out = await new Response(proc.stdout).text()
isGit = out.trim() === 'true'
if (isGit) {
// Run branch + remote in parallel
const gitRoot = findGitRoot(realPath)
if (gitRoot) {
isGit = true
// Run branch + remote in parallel
try {
const [branchResult, remoteResult] = await Promise.all([
(async () => {
const branchProc = Bun.spawn(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], {
@ -1010,8 +1046,9 @@ async function getRecentProjects(url: URL): Promise<Response> {
])
branch = isDesktopWorktreeBranchName(branchResult) ? null : branchResult
repoName = remoteResult
}
} catch { /* not a git repo or dir doesn't exist */ }
} catch { /* git command failed */ }
}
return {
projectPath: info.projectPath, realPath, projectName, isGit, repoName, branch,