feat: implement manual SDK session sync and fix UI crash
Some checks failed
Release Binaries / release (push) Has been cancelled
Some checks failed
Release Binaries / release (push) Has been cancelled
This commit is contained in:
@@ -9,10 +9,9 @@
|
||||
*/
|
||||
|
||||
import { FastifyInstance } from "fastify"
|
||||
import { readdir, readFile } from "fs/promises"
|
||||
import { readdir, readFile, appendFile } from "fs/promises"
|
||||
import { existsSync } from "fs"
|
||||
import { join } from "path"
|
||||
import { createHash } from "crypto"
|
||||
import { homedir } from "os"
|
||||
import { Logger } from "../../logger"
|
||||
import { getSessionManager } from "../../storage/session-store"
|
||||
@@ -59,54 +58,92 @@ function getOpenCodeStorageDir(): string {
|
||||
return join(homeDir, ".local", "share", "opencode", "storage")
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the project ID hash that OpenCode uses
|
||||
* OpenCode uses a SHA1 hash of the folder path
|
||||
*/
|
||||
function generateProjectId(folderPath: string): string {
|
||||
return createHash("sha1").update(folderPath).digest("hex")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read all sessions for a project from OpenCode's storage
|
||||
*/
|
||||
async function readOpenCodeSessions(folderPath: string, logger: Logger): Promise<OpenCodeSession[]> {
|
||||
const storageDir = getOpenCodeStorageDir()
|
||||
const projectId = generateProjectId(folderPath)
|
||||
const sessionDir = join(storageDir, "session", projectId)
|
||||
const sessionBaseDir = join(storageDir, "session")
|
||||
const debugLogPath = join(process.cwd(), "sdk-sync-debug.log")
|
||||
|
||||
logger.info({ folderPath, projectId, sessionDir }, "Looking for OpenCode sessions")
|
||||
const logDebug = async (msg: string, obj?: any) => {
|
||||
const line = `[${new Date().toISOString()}] ${msg}${obj ? ' ' + JSON.stringify(obj) : ''}\n`
|
||||
await appendFile(debugLogPath, line).catch(() => { })
|
||||
logger.info(obj || {}, msg)
|
||||
}
|
||||
|
||||
if (!existsSync(sessionDir)) {
|
||||
logger.info({ sessionDir }, "OpenCode session directory not found")
|
||||
// Normalize target folder path for comparison
|
||||
const targetPath = folderPath.replace(/\\/g, '/').toLowerCase().trim()
|
||||
|
||||
await logDebug("Starting SDK session search", { folderPath, targetPath, sessionBaseDir })
|
||||
|
||||
if (!existsSync(sessionBaseDir)) {
|
||||
await logDebug("OpenCode session base directory not found", { sessionBaseDir })
|
||||
return []
|
||||
}
|
||||
|
||||
const sessions: OpenCodeSession[] = []
|
||||
|
||||
try {
|
||||
const files = await readdir(sessionDir)
|
||||
const sessionFiles = files.filter(f => f.startsWith("ses_") && f.endsWith(".json"))
|
||||
const projectDirs = await readdir(sessionBaseDir, { withFileTypes: true })
|
||||
const dirs = projectDirs.filter(d => d.isDirectory()).map(d => d.name)
|
||||
|
||||
logger.info({ count: sessionFiles.length }, "Found OpenCode session files")
|
||||
await logDebug("Scanning project directories", { count: dirs.length })
|
||||
|
||||
for (const projectId of dirs) {
|
||||
const sessionDir = join(sessionBaseDir, projectId)
|
||||
|
||||
for (const file of sessionFiles) {
|
||||
try {
|
||||
const filePath = join(sessionDir, file)
|
||||
const content = await readFile(filePath, "utf-8")
|
||||
const session = JSON.parse(content) as OpenCodeSession
|
||||
sessions.push(session)
|
||||
} catch (error) {
|
||||
logger.warn({ file, error }, "Failed to read session file")
|
||||
const files = await readdir(sessionDir)
|
||||
const firstSessionFile = files.find(f => f.startsWith("ses_") && f.endsWith(".json"))
|
||||
|
||||
if (firstSessionFile) {
|
||||
const content = await readFile(join(sessionDir, firstSessionFile), "utf-8")
|
||||
const sessionData = JSON.parse(content) as OpenCodeSession
|
||||
|
||||
if (!sessionData.directory) {
|
||||
await logDebug("Session file missing directory field", { projectId, firstSessionFile })
|
||||
continue
|
||||
}
|
||||
|
||||
const sessionPath = sessionData.directory.replace(/\\/g, '/').toLowerCase().trim()
|
||||
|
||||
if (sessionPath === targetPath) {
|
||||
await logDebug("MATCH FOUND!", { projectId, sessionPath })
|
||||
|
||||
// This is the correct directory, read all sessions
|
||||
const sessions: OpenCodeSession[] = [sessionData]
|
||||
const otherFiles = files.filter(f => f !== firstSessionFile && f.startsWith("ses_") && f.endsWith(".json"))
|
||||
|
||||
for (const file of otherFiles) {
|
||||
try {
|
||||
const fileContent = await readFile(join(sessionDir, file), "utf-8")
|
||||
sessions.push(JSON.parse(fileContent) as OpenCodeSession)
|
||||
} catch (e) {
|
||||
logger.warn({ file, error: e }, "Failed to read session file")
|
||||
}
|
||||
}
|
||||
|
||||
await logDebug("Read sessions count", { count: sessions.length })
|
||||
return sessions
|
||||
} else {
|
||||
// Just log a few mismatches to avoid bloating
|
||||
// await logDebug("Mismatch", { sessionPath, targetPath })
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
await logDebug("Error scanning project directory", { projectId, error: String(e) })
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error({ error }, "Failed to read OpenCode sessions directory")
|
||||
await logDebug("Failed to scan OpenCode sessions directory", { error: String(error) })
|
||||
}
|
||||
|
||||
return sessions
|
||||
await logDebug("No sessions found after scan")
|
||||
return []
|
||||
}
|
||||
|
||||
|
||||
export function registerSdkSyncRoutes(app: FastifyInstance, deps: SdkSyncRouteDeps) {
|
||||
const logger = deps.logger.child({ component: "sdk-sync" })
|
||||
const sessionManager = getSessionManager(deps.dataDir)
|
||||
|
||||
Reference in New Issue
Block a user