Spaces:
Sleeping
Sleeping
| import { NextRequest, NextResponse } from 'next/server' | |
| import { existsSync, readdirSync, statSync } from 'fs' | |
| import path from 'path' | |
| import Database from 'better-sqlite3' | |
| import { config } from '@/lib/config' | |
| import { requireRole } from '@/lib/auth' | |
| import { readLimiter } from '@/lib/rate-limit' | |
| import { logger } from '@/lib/logger' | |
| interface AgentFileInfo { | |
| path: string | |
| chunks: number | |
| textSize: number | |
| } | |
| interface AgentGraphData { | |
| name: string | |
| dbSize: number | |
| totalChunks: number | |
| totalFiles: number | |
| files: AgentFileInfo[] | |
| } | |
| const memoryDbDir = config.openclawStateDir | |
| ? path.join(config.openclawStateDir, 'memory') | |
| : '' | |
| function getAgentData(dbPath: string, agentName: string): AgentGraphData | null { | |
| try { | |
| const dbStat = statSync(dbPath) | |
| const db = new Database(dbPath, { readonly: true, fileMustExist: true }) | |
| let files: AgentFileInfo[] = [] | |
| let totalChunks = 0 | |
| let totalFiles = 0 | |
| try { | |
| // Check if chunks table exists | |
| const tableCheck = db | |
| .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='chunks'") | |
| .get() as { name: string } | undefined | |
| if (tableCheck) { | |
| // Use COUNT only — skip SUM(LENGTH(text)) which forces a full data scan | |
| const rows = db | |
| .prepare( | |
| 'SELECT path, COUNT(*) as chunks FROM chunks GROUP BY path ORDER BY chunks DESC' | |
| ) | |
| .all() as Array<{ path: string; chunks: number }> | |
| files = rows.map((r) => ({ | |
| path: r.path || '(unknown)', | |
| chunks: r.chunks, | |
| textSize: 0, | |
| })) | |
| totalChunks = files.reduce((sum, f) => sum + f.chunks, 0) | |
| totalFiles = files.length | |
| } | |
| } finally { | |
| db.close() | |
| } | |
| return { | |
| name: agentName, | |
| dbSize: dbStat.size, | |
| totalChunks, | |
| totalFiles, | |
| files, | |
| } | |
| } catch (err) { | |
| logger.warn(`Failed to read memory DB for agent "${agentName}": ${err}`) | |
| return null | |
| } | |
| } | |
| export async function GET(request: NextRequest) { | |
| const auth = requireRole(request, 'viewer') | |
| if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) | |
| const limited = readLimiter(request) | |
| if (limited) return limited | |
| if (!memoryDbDir || !existsSync(memoryDbDir)) { | |
| return NextResponse.json( | |
| { error: 'Memory directory not available', agents: [] }, | |
| { status: 404 } | |
| ) | |
| } | |
| const agentFilter = request.nextUrl.searchParams.get('agent') || 'all' | |
| try { | |
| const entries = readdirSync(memoryDbDir).filter((f) => f.endsWith('.sqlite')) | |
| const agents: AgentGraphData[] = [] | |
| for (const entry of entries) { | |
| const agentName = entry.replace('.sqlite', '') | |
| if (agentFilter !== 'all' && agentName !== agentFilter) continue | |
| const dbPath = path.join(memoryDbDir, entry) | |
| const data = getAgentData(dbPath, agentName) | |
| if (data) agents.push(data) | |
| } | |
| // Sort by total chunks descending | |
| agents.sort((a, b) => b.totalChunks - a.totalChunks) | |
| return NextResponse.json({ agents }) | |
| } catch (err) { | |
| logger.error(`Failed to build memory graph data: ${err}`) | |
| return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) | |
| } | |
| } | |