diff --git a/src/server/routes/session.ts b/src/server/routes/session.ts index 463aca009..d76e328a4 100644 --- a/src/server/routes/session.ts +++ b/src/server/routes/session.ts @@ -11,6 +11,16 @@ import { } from '../errors.js' import { listSessionsImpl } from '../../utils/listSessionsImpl.js' +/** 从磁盘历史记录查找 session 的原始 cwd,用于 resume 时恢复正确的工作目录 */ +async function getHistoryCwd(sessionId: string): Promise { + try { + const list = await listSessionsImpl({ limit: 1000 }) + return list.find(s => s.sessionId === sessionId)?.cwd + } catch { + return undefined + } +} + function ssePrompt( handle: import('../sessionHandle.js').SessionHandle, id: string, @@ -354,8 +364,22 @@ export function createSessionRoutes( }) .post('/session/:sessionID/prompt', async c => { const id = c.req.param('sessionID') - const handle = sessionManager.getSession(id) - if (!handle) throw notFound('session not found') + let handle = sessionManager.getSession(id) + if (!handle) { + const cwd = await getHistoryCwd(id) + try { + handle = await sessionManager.createSession({ + cwd, + sessionId: id, + resumeSessionId: id, + execPath: process.execPath, + scriptArgs: getScriptArgsForChild(), + }) + await handle.waitReady(30000) + } catch (err) { + throw sessionError(err instanceof Error ? err.message : 'Failed to resume session') + } + } const body = await c.req.json<{ content?: string @@ -381,9 +405,23 @@ export function createSessionRoutes( .post('/session/:sessionID/prompt_async', async c => { const id = c.req.param('sessionID') - const handle = sessionManager.getSession(id) + let handle = sessionManager.getSession(id) + + // 历史 session 不在内存中,自动恢复 if (!handle) { - throw notFound('session not found') + const cwd = await getHistoryCwd(id) + try { + handle = await sessionManager.createSession({ + cwd, + sessionId: id, + resumeSessionId: id, + execPath: process.execPath, + scriptArgs: getScriptArgsForChild(), + }) + await handle.waitReady(30000) + } catch (err) { + throw sessionError(err instanceof Error ? err.message : 'Failed to resume session') + } } const body = await c.req.json<{ diff --git a/src/server/sessionHandle.ts b/src/server/sessionHandle.ts index 315b36d2a..96c24d1cf 100644 --- a/src/server/sessionHandle.ts +++ b/src/server/sessionHandle.ts @@ -245,8 +245,8 @@ export class SessionHandle { 'stream-json', '--output-format', 'stream-json', - '--session-id', - this.sessionId, + // resume 时不传 --session-id,csc 会继承历史 session 的 id + ...(this.opts.resumeSessionId ? [] : ['--session-id', this.sessionId]), ...(this.opts.model ? ['--model', this.opts.model] : []), '--permission-mode', this._permissionMode, @@ -299,6 +299,7 @@ export class SessionHandle { const reject = this.initReject this.initResolve = null this.initReject = null + process.stderr.write(`[serve:${this.sessionId}] exit_code=${code} cwd=${this.cwd} stderr:\n${this.lastStderr.slice(-5).join('\n')}\n`) reject(new Error(`Process exited with code ${code}`)) } if (this.promptReject) { diff --git a/src/server/sessionManager.ts b/src/server/sessionManager.ts index bc2d227cb..76618f464 100644 --- a/src/server/sessionManager.ts +++ b/src/server/sessionManager.ts @@ -155,6 +155,7 @@ export class SessionManager { execPath: string scriptArgs: string[] silent?: boolean + sessionId?: string }): Promise { if (this.maxSessions > 0 && this.sessions.size >= this.maxSessions) { throw new Error( @@ -162,7 +163,7 @@ export class SessionManager { ) } - const sessionId = crypto.randomUUID() + const sessionId = opts.sessionId ?? crypto.randomUUID() let cwd = opts.cwd || this.defaultWorkspace || process.cwd() if (!isAbsolute(cwd)) { cwd = resolve(process.cwd(), cwd)