diff --git a/server/src/modules/ai-runtime/ai-runtime.routes.ts b/server/src/modules/ai-runtime/ai-runtime.routes.ts index e65567a0..106362f2 100644 --- a/server/src/modules/ai-runtime/ai-runtime.routes.ts +++ b/server/src/modules/ai-runtime/ai-runtime.routes.ts @@ -492,6 +492,18 @@ router.get( }), ); +/** + * Returns one DB-indexed session metadata row. + */ +router.get( + '/sessions/:sessionId', + asyncHandler(async (req: Request, res: Response) => { + const sessionId = readPathParam(req.params.sessionId, 'sessionId'); + const session = llmSessionsService.getIndexedSession(sessionId); + res.json(createApiSuccessResponse({ session })); + }), +); + /** * Triggers provider disk scans and refreshes the shared sessions table. */ diff --git a/server/src/modules/ai-runtime/services/sessions.service.ts b/server/src/modules/ai-runtime/services/sessions.service.ts index 53c7992e..f1b2c9e0 100644 --- a/server/src/modules/ai-runtime/services/sessions.service.ts +++ b/server/src/modules/ai-runtime/services/sessions.service.ts @@ -106,6 +106,21 @@ export const llmSessionsService = { return allSessions.filter((session) => session.provider === provider); }, + /** + * Reads one indexed session metadata row. + */ + getIndexedSession(sessionId: string) { + const session = sessionsDb.getSessionById(sessionId); + if (!session) { + throw new AppError(`Session "${sessionId}" was not found.`, { + code: 'SESSION_NOT_FOUND', + statusCode: 404, + }); + } + + return session; + }, + /** * Runs all provider indexers and updates `scan_state.last_scanned_at`. */ diff --git a/server/src/modules/ai-runtime/tests/sessions.test.ts b/server/src/modules/ai-runtime/tests/sessions.test.ts index 08a97e07..5568a489 100644 --- a/server/src/modules/ai-runtime/tests/sessions.test.ts +++ b/server/src/modules/ai-runtime/tests/sessions.test.ts @@ -131,6 +131,44 @@ test('llmSessionsService.updateSessionCustomName validates existence before upda } }); +// This test covers fetching one indexed DB session metadata row from getSessionById. +test('llmSessionsService.getIndexedSession returns DB session metadata', { concurrency: false }, () => { + const restoreGetById = patchMethod(sessionsDb, 'getSessionById', (sessionId: string) => ( + sessionId === 'known-session' + ? { + session_id: 'known-session', + provider: 'claude', + workspace_path: '/tmp/workspace', + jsonl_path: '/tmp/workspace/session.jsonl', + created_at: '2026-04-01T00:00:00.000Z', + updated_at: '2026-04-02T00:00:00.000Z', + } + : null + )); + + try { + const session = llmSessionsService.getIndexedSession('known-session'); + assert.deepEqual(session, { + session_id: 'known-session', + provider: 'claude', + workspace_path: '/tmp/workspace', + jsonl_path: '/tmp/workspace/session.jsonl', + created_at: '2026-04-01T00:00:00.000Z', + updated_at: '2026-04-02T00:00:00.000Z', + }); + + assert.throws( + () => llmSessionsService.getIndexedSession('missing-session'), + (error: unknown) => + error instanceof AppError && + error.code === 'SESSION_NOT_FOUND' && + error.statusCode === 404, + ); + } finally { + restoreGetById(); + } +}); + // This test covers delete behavior using only DB jsonl_path, including invalid id validation. test('llmSessionsService.deleteSessionArtifacts validates ids and deletes disk/db artifacts', { concurrency: false }, async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-delete-session-'));