import { sessionStore } from '@electron/gateway/session-store'; import { getTranscriptFilePath } from '@electron/utils/token-usage-writer'; import { buildAgentSessionKey, normalizeAgentSessionKey, parseSessionKey } from '@runtime/lib/agents'; import type { HostApiContext } from '../context'; import type { NormalizedHostApiRequest } from '../route-utils'; import { fail, ok, parseJsonBody } from '../route-utils'; function parseSessionIdentity(request: NormalizedHostApiRequest): { agentId: string; sessionId: string; sessionKey: string } | null { const sessionKey = normalizeAgentSessionKey(request.url.searchParams.get('sessionKey')?.trim() || ''); const parsed = parseSessionKey(sessionKey); if (sessionKey && parsed.isAgentSession) { return { agentId: parsed.agentId, sessionId: parsed.sessionId, sessionKey: parsed.sessionKey, }; } const agentId = request.url.searchParams.get('agentId')?.trim() || ''; const sessionId = request.url.searchParams.get('sessionId')?.trim() || ''; if (!agentId || !sessionId) return null; return { agentId: parseSessionKey(buildAgentSessionKey(agentId, sessionId)).agentId, sessionId: parseSessionKey(buildAgentSessionKey(agentId, sessionId)).sessionId, sessionKey: buildAgentSessionKey(agentId, sessionId), }; } export async function handleSessionRoutes( request: NormalizedHostApiRequest, _ctx: HostApiContext, ) { const { pathname, method } = request; if (pathname === '/api/sessions/transcript' && method === 'GET') { const identity = parseSessionIdentity(request); if (!identity) { return fail(400, 'sessionKey or agentId/sessionId is required'); } try { const fsP = await import('node:fs/promises'); const transcriptPath = getTranscriptFilePath(identity.sessionKey); let raw: string; try { raw = await fsP.readFile(transcriptPath, 'utf8'); } catch (error: any) { const requestedSessionKey = request.url.searchParams.get('sessionKey')?.trim() || ''; if (error?.code === 'ENOENT' && requestedSessionKey && requestedSessionKey !== identity.sessionKey) { raw = await fsP.readFile(getTranscriptFilePath(requestedSessionKey), 'utf8'); } else { throw error; } } const lines = raw.split(/\r?\n/).filter(Boolean); const messages = lines.flatMap((line) => { try { const entry = JSON.parse(line) as { type?: string; message?: unknown; timestamp?: string }; return entry.type === 'message' && entry.message ? [{ ...entry.message, timestamp: entry.timestamp }] : []; } catch { return []; } }); return ok({ agentId: identity.agentId, sessionId: identity.sessionId, sessionKey: identity.sessionKey, transcriptPath, messages, }); } catch (error: any) { if (error?.code === 'ENOENT') { return fail(404, 'Transcript not found'); } return fail(500, error instanceof Error ? error.message : String(error)); } } if (pathname === '/api/sessions/delete' && method === 'POST') { const body = parseJsonBody<{ sessionKey: string }>(request.body); const rawSessionKey = String(body?.sessionKey || '').trim(); const sessionKey = normalizeAgentSessionKey(rawSessionKey); if (!sessionKey) { return fail(400, 'sessionKey is required'); } sessionStore.deleteSession(sessionKey); const transcriptPath = getTranscriptFilePath(sessionKey); try { const fsP = await import('node:fs/promises'); await fsP.rename(transcriptPath, transcriptPath.replace(/\.jsonl$/, '.deleted.jsonl')); } catch { if (rawSessionKey && rawSessionKey !== sessionKey) { try { const fsP = await import('node:fs/promises'); const legacyTranscriptPath = getTranscriptFilePath(rawSessionKey); await fsP.rename(legacyTranscriptPath, legacyTranscriptPath.replace(/\.jsonl$/, '.deleted.jsonl')); } catch { // Best effort: transcript may not exist yet. } } } return ok({ success: true }); } return null; }