(res: http.ServerResponse, url?: URL)
| 872 | } |
| 873 | |
| 874 | private serveStats(res: http.ServerResponse, url?: URL): void { |
| 875 | const emptyStats = { |
| 876 | totalMemories: 0, totalSessions: 0, totalEmbeddings: 0, totalSkills: 0, totalTasks: 0, |
| 877 | embeddingProvider: this.embedder?.provider ?? "none", |
| 878 | dedupBreakdown: {}, |
| 879 | timeRange: { earliest: null, latest: null }, |
| 880 | sessions: [], |
| 881 | }; |
| 882 | |
| 883 | if (!this.store || !(this.store as any).db) { |
| 884 | this.jsonResponse(res, emptyStats); |
| 885 | return; |
| 886 | } |
| 887 | |
| 888 | const ownerFilter = url?.searchParams.get("owner") ?? ""; |
| 889 | |
| 890 | try { |
| 891 | const db = (this.store as any).db; |
| 892 | const total = db.prepare("SELECT COUNT(*) as count FROM chunks").get() as any; |
| 893 | const sessions = db.prepare("SELECT COUNT(DISTINCT session_key) as count FROM chunks").get() as any; |
| 894 | const timeRange = db.prepare("SELECT MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks WHERE dedup_status = 'active'").get() as any; |
| 895 | const MIN_VALID_TS = 1704067200000; // 2024-01-01 |
| 896 | if (timeRange.earliest != null && timeRange.earliest < MIN_VALID_TS) { |
| 897 | timeRange.earliest = db.prepare("SELECT MIN(created_at) as v FROM chunks WHERE dedup_status = 'active' AND created_at >= ?").get(MIN_VALID_TS) as any; |
| 898 | timeRange.earliest = timeRange.earliest?.v ?? null; |
| 899 | } |
| 900 | if (timeRange.latest != null && timeRange.latest < MIN_VALID_TS) { |
| 901 | timeRange.latest = null; |
| 902 | } |
| 903 | let embCount = 0; |
| 904 | try { embCount = (db.prepare("SELECT COUNT(*) as count FROM embeddings").get() as any).count; } catch { /* table may not exist */ } |
| 905 | let sessionQuery: string; |
| 906 | let sessionParams: any[]; |
| 907 | if (ownerFilter && ownerFilter.startsWith("agent:")) { |
| 908 | sessionQuery = "SELECT session_key, COUNT(*) as count, MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks WHERE (owner = ? OR owner = 'public') GROUP BY session_key ORDER BY latest DESC"; |
| 909 | sessionParams = [ownerFilter]; |
| 910 | } else if (ownerFilter) { |
| 911 | sessionQuery = "SELECT session_key, COUNT(*) as count, MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks WHERE owner = ? GROUP BY session_key ORDER BY latest DESC"; |
| 912 | sessionParams = [ownerFilter]; |
| 913 | } else { |
| 914 | sessionQuery = "SELECT session_key, COUNT(*) as count, MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks GROUP BY session_key ORDER BY latest DESC"; |
| 915 | sessionParams = []; |
| 916 | } |
| 917 | const sessionList = db.prepare(sessionQuery).all(...sessionParams) as any[]; |
| 918 | |
| 919 | let taskSessionList: Array<{ session_key: string; count: number; earliest: number | null; latest: number | null }> = []; |
| 920 | try { |
| 921 | taskSessionList = db.prepare( |
| 922 | "SELECT session_key, COUNT(*) as count, MIN(started_at) as earliest, MAX(COALESCE(updated_at, started_at)) as latest FROM tasks GROUP BY session_key ORDER BY latest DESC", |
| 923 | ).all() as any[]; |
| 924 | } catch { /* tasks table may not exist yet */ } |
| 925 | |
| 926 | let skillSessionList: Array<{ session_key: string; count: number; earliest: number | null; latest: number | null }> = []; |
| 927 | try { |
| 928 | skillSessionList = db.prepare( |
| 929 | `SELECT t.session_key as session_key, COUNT(DISTINCT ts.skill_id) as count, |
| 930 | MIN(t.started_at) as earliest, MAX(COALESCE(t.updated_at, t.started_at)) as latest |
| 931 | FROM task_skills ts JOIN tasks t ON t.id = ts.task_id |
no test coverage detected