Skip to content
This repository was archived by the owner on Jun 8, 2026. It is now read-only.

Commit 17630c0

Browse files
committed
FEA-1550: Restore PGlite dashboard parity
- Use bigint token arithmetic in PGlite analytics and session totals, and carry hook identity fields into sync payloads. - Align dashboard pages, tables, controls, and visualizations on design-system primitives. - Port deleted SQLite/sidecar coverage that still maps to first-party PGlite behavior, including token reconciliation, pagination, lifecycle/import, parser, catchup-cache, and sync edge cases. Testing: Desktop typecheck, lint, build, focused PGlite dashboard tests, and expanded dashboard/PGlite/sync/parser/reconciliation/boundary tests passed. Risks: Full desktop test suite was not rerun; generated sidecar/vendor-only tests remain intentionally deleted because the sidecar path was removed.
1 parent 292d589 commit 17630c0

13 files changed

Lines changed: 1295 additions & 396 deletions

File tree

apps/desktop/src/main/database/pglite.ts

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ export interface OpenPgliteAgentDatabaseOptions {
242242
detectBillingMode: (harness: string) => string;
243243
emit?: (sessionId: string) => void;
244244
extractTranscript?: (path: string) => TranscriptExtract | null;
245+
getUserIdentity?: () => { userId: string | null; organizationId: string | null } | null;
245246
log?: (message: string) => void;
246247
now?: () => string;
247248
staleMinutes?: number;
@@ -285,6 +286,7 @@ export async function openPgliteAgentDatabase(
285286
detectBillingMode: options.detectBillingMode,
286287
emit: options.emit,
287288
extractTranscript: options.extractTranscript,
289+
getUserIdentity: options.getUserIdentity,
288290
log,
289291
now: nowFn,
290292
staleMinutes: options.staleMinutes,
@@ -335,7 +337,7 @@ function createPgliteSessionStore(db: PgliteClient) {
335337
s.*,
336338
COALESCE(ac.agent_count, 0)::int as agent_count,
337339
COALESCE(ec.event_count, 0)::int as event_count,
338-
COALESCE(tt.total_tokens, 0)::int as total_tokens
340+
COALESCE(tt.total_tokens, 0) as total_tokens
339341
FROM sessions s
340342
LEFT JOIN agent_counts ac ON ac.session_id = s.id
341343
LEFT JOIN event_counts ec ON ec.session_id = s.id
@@ -350,7 +352,7 @@ function createPgliteSessionStore(db: PgliteClient) {
350352
s.*,
351353
COALESCE(ac.agent_count, 0)::int as agent_count,
352354
COALESCE(ec.event_count, 0)::int as event_count,
353-
COALESCE(tt.total_tokens, 0)::int as total_tokens
355+
COALESCE(tt.total_tokens, 0) as total_tokens
354356
FROM sessions s
355357
LEFT JOIN agent_counts ac ON ac.session_id = s.id
356358
LEFT JOIN event_counts ec ON ec.session_id = s.id
@@ -369,7 +371,7 @@ function createPgliteSessionStore(db: PgliteClient) {
369371
s.*,
370372
COALESCE(ac.agent_count, 0)::int as agent_count,
371373
COALESCE(ec.event_count, 0)::int as event_count,
372-
COALESCE(tt.total_tokens, 0)::int as total_tokens
374+
COALESCE(tt.total_tokens, 0) as total_tokens
373375
FROM sessions s
374376
LEFT JOIN agent_counts ac ON ac.session_id = s.id
375377
LEFT JOIN event_counts ec ON ec.session_id = s.id
@@ -398,7 +400,7 @@ function createPgliteSessionStore(db: PgliteClient) {
398400
s.*,
399401
COALESCE(ac.agent_count, 0)::int as agent_count,
400402
COALESCE(ec.event_count, 0)::int as event_count,
401-
COALESCE(tt.total_tokens, 0)::int as total_tokens
403+
COALESCE(tt.total_tokens, 0) as total_tokens
402404
FROM sessions s
403405
LEFT JOIN agent_counts ac ON ac.session_id = s.id
404406
LEFT JOIN event_counts ec ON ec.session_id = s.id
@@ -670,7 +672,7 @@ function createPgliteDashboardQueries(db: PgliteClient) {
670672
count(db, "SELECT COUNT(*)::int as count FROM agents"),
671673
count(db, "SELECT COUNT(*)::int as count FROM events"),
672674
count(db, "SELECT COUNT(DISTINCT event_type)::int as count FROM events"),
673-
scalarNumber(db, "SELECT COALESCE(SUM(input_tokens + output_tokens), 0)::int as total FROM token_usage", "total"),
675+
scalarNumber(db, "SELECT COALESCE(SUM(input_tokens::bigint + output_tokens::bigint), 0) as total FROM token_usage", "total"),
674676
db.query<{
675677
id: string;
676678
name: string | null;
@@ -704,10 +706,10 @@ function createPgliteDashboardQueries(db: PgliteClient) {
704706
total_cache_read: number;
705707
total_cache_write: number;
706708
}>(`
707-
SELECT COALESCE(SUM(input_tokens), 0)::int as total_input,
708-
COALESCE(SUM(output_tokens), 0)::int as total_output,
709-
COALESCE(SUM(cache_read_tokens), 0)::int as total_cache_read,
710-
COALESCE(SUM(cache_write_tokens), 0)::int as total_cache_write
709+
SELECT COALESCE(SUM(input_tokens), 0) as total_input,
710+
COALESCE(SUM(output_tokens), 0) as total_output,
711+
COALESCE(SUM(cache_read_tokens), 0) as total_cache_read,
712+
COALESCE(SUM(cache_write_tokens), 0) as total_cache_write
711713
FROM token_usage
712714
`);
713715
const byModel = await db.query<{
@@ -717,22 +719,22 @@ function createPgliteDashboardQueries(db: PgliteClient) {
717719
sessions: number;
718720
}>(`
719721
SELECT model,
720-
SUM(input_tokens)::int as input_tokens,
721-
SUM(output_tokens)::int as output_tokens,
722+
SUM(input_tokens) as input_tokens,
723+
SUM(output_tokens) as output_tokens,
722724
COUNT(DISTINCT session_id)::int as sessions
723725
FROM token_usage
724726
WHERE model IS NOT NULL
725727
GROUP BY model
726-
ORDER BY SUM(input_tokens + output_tokens) DESC
728+
ORDER BY SUM(input_tokens::bigint + output_tokens::bigint) DESC
727729
`);
728730
const byDay = await db.query<{
729731
day: string;
730732
input_tokens: number;
731733
output_tokens: number;
732734
}>(`
733735
SELECT (created_at::timestamp::date)::text as day,
734-
SUM(input_tokens)::int as input_tokens,
735-
SUM(output_tokens)::int as output_tokens
736+
SUM(input_tokens) as input_tokens,
737+
SUM(output_tokens) as output_tokens
736738
FROM token_usage
737739
WHERE created_at IS NOT NULL
738740
GROUP BY created_at::timestamp::date
@@ -1159,6 +1161,7 @@ function createPgliteLifecycle(
11591161
detectBillingMode: (harness: string) => string;
11601162
emit?: (sessionId: string) => void;
11611163
extractTranscript?: (path: string) => TranscriptExtract | null;
1164+
getUserIdentity?: () => { userId: string | null; organizationId: string | null } | null;
11621165
log: (message: string) => void;
11631166
now: () => string;
11641167
staleMinutes?: number;
@@ -1195,6 +1198,7 @@ function createPgliteLifecycle(
11951198
tokenUsage,
11961199
transcript,
11971200
detectBillingMode: deps.detectBillingMode,
1201+
getUserIdentity: deps.getUserIdentity,
11981202
});
11991203
});
12001204
return true;
@@ -1229,11 +1233,12 @@ async function handleHook(
12291233
tokenUsage: ReturnType<typeof createPgliteTokenUsageStore>;
12301234
transcript: TranscriptExtract | null;
12311235
detectBillingMode: (harness: string) => string;
1236+
getUserIdentity?: () => { userId: string | null; organizationId: string | null } | null;
12321237
},
12331238
): Promise<void> {
12341239
const { data, hookType, harness, now, sessionId } = options;
12351240
const main = mainAgentId(sessionId);
1236-
await ensureSession(tx, sessionId, data, harness, now, options.detectBillingMode);
1241+
await ensureSession(tx, sessionId, data, harness, now, options.detectBillingMode, options.getUserIdentity);
12371242
const session = await getSession(tx, sessionId);
12381243
if (!session) {
12391244
return;
@@ -1571,6 +1576,8 @@ async function loadPgliteSyncedSessions(
15711576
metadata: string | null;
15721577
harness: string | null;
15731578
billing_mode: string | null;
1579+
user_id: string | null;
1580+
organization_id: string | null;
15741581
}>(
15751582
db,
15761583
`
@@ -1586,7 +1593,9 @@ async function loadPgliteSyncedSessions(
15861593
awaiting_input_since,
15871594
metadata,
15881595
harness,
1589-
billing_mode
1596+
billing_mode,
1597+
user_id,
1598+
organization_id
15901599
FROM sessions
15911600
WHERE id IN (__IDS__)
15921601
`,
@@ -1721,6 +1730,8 @@ async function loadPgliteSyncedSessions(
17211730
endedAt: row.ended_at,
17221731
awaitingInputSince: row.awaiting_input_since,
17231732
metadata: parseJsonObjectText(row.metadata),
1733+
...(row.user_id ? { userId: row.user_id } : {}),
1734+
...(row.organization_id ? { organizationId: row.organization_id } : {}),
17241735
...(attribution ? { attribution } : {}),
17251736
agents: (agentsBySessionId.get(id) ?? []).map((agentRow) => ({
17261737
externalAgentId: agentRow.id,
@@ -1848,7 +1859,7 @@ function sessionDetailsCtes(): string {
18481859
token_totals AS (
18491860
SELECT
18501861
session_id,
1851-
COALESCE(SUM(COALESCE(input_tokens, 0) + COALESCE(output_tokens, 0)), 0)::int as total_tokens
1862+
COALESCE(SUM(COALESCE(input_tokens, 0)::bigint + COALESCE(output_tokens, 0)::bigint), 0) as total_tokens
18521863
FROM token_usage
18531864
GROUP BY session_id
18541865
)
@@ -1987,14 +1998,19 @@ async function ensureSession(
19871998
harness: string,
19881999
now: string,
19892000
detectBillingMode: (harness: string) => string,
2001+
getUserIdentity?: () => { userId: string | null; organizationId: string | null } | null,
19902002
): Promise<void> {
19912003
if (await getSession(tx, sessionId)) {
19922004
return;
19932005
}
19942006
const billingMode = safe(() => detectBillingMode(harness)) ?? "unknown";
2007+
const identity = safe(() => getUserIdentity?.()) ?? null;
19952008
await tx.query(
1996-
`INSERT INTO sessions (id, name, status, cwd, model, started_at, updated_at, harness, billing_mode)
1997-
VALUES ($1, $2, 'active', $3, $4, $5, $5, $6, $7)`,
2009+
`INSERT INTO sessions (
2010+
id, name, status, cwd, model, started_at, updated_at, harness,
2011+
billing_mode, user_id, organization_id
2012+
)
2013+
VALUES ($1, $2, 'active', $3, $4, $5, $5, $6, $7, $8, $9)`,
19982014
[
19992015
sessionId,
20002016
data.session_name ?? null,
@@ -2003,6 +2019,8 @@ async function ensureSession(
20032019
now,
20042020
harness,
20052021
billingMode,
2022+
identity?.userId ?? null,
2023+
identity?.organizationId ?? null,
20062024
],
20072025
);
20082026
await tx.query(

apps/desktop/src/renderer/components/analytics/AnalyticsDetails.tsx

Lines changed: 27 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { Card, CardContent, CardHeader, CardTitle } from "@closedloop-ai/design-system/components/ui/card";
21
import { LineChart } from "@closedloop-ai/design-system/components/ui/primitives/line-chart";
32
import { DonutChart } from "@closedloop-ai/design-system/components/ui/primitives/donut-chart";
43
import { RankedBar } from "@closedloop-ai/design-system/components/ui/primitives/ranked-bar";
54
import { ActivityHeatmap } from "@closedloop-ai/design-system/components/ui/primitives/activity-heatmap";
65
import { SegmentedBar } from "@closedloop-ai/design-system/components/ui/primitives/segmented-bar";
76
import type { AnalyticsData } from "../../../shared/agent-db-contract";
7+
import { DashboardCard } from "../layout/page-shell";
88

99
const PALETTE = [
1010
"#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6",
@@ -78,72 +78,52 @@ export function AnalyticsDetails({ data }: { data: AnalyticsData }) {
7878
return (
7979
<>
8080
{dailyEvents.length > 0 && (
81-
<Card>
82-
<CardHeader><CardTitle>Activity Heatmap</CardTitle></CardHeader>
83-
<CardContent>
84-
<ActivityHeatmap weeks={heatmapWeeks} />
85-
</CardContent>
86-
</Card>
81+
<DashboardCard title="Activity Heatmap">
82+
<ActivityHeatmap weeks={heatmapWeeks} />
83+
</DashboardCard>
8784
)}
8885

89-
<div className="grid grid-cols-2 gap-6">
90-
<Card>
91-
<CardHeader><CardTitle>Token Distribution</CardTitle></CardHeader>
92-
<CardContent>
86+
<div className="grid grid-cols-1 gap-3 xl:grid-cols-2">
87+
<DashboardCard title="Token Distribution">
9388
{tokenSegments.length > 0 ? (
9489
<DonutChart segments={tokenSegments} formatTotal={(t) => t.toLocaleString()} centerLabel="Tokens" />
9590
) : (
9691
<p className="py-8 text-sm text-center text-[var(--muted-foreground)]">No token data</p>
9792
)}
98-
</CardContent>
99-
</Card>
93+
</DashboardCard>
10094

101-
<Card>
102-
<CardHeader><CardTitle>Sessions by Status</CardTitle></CardHeader>
103-
<CardContent>
95+
<DashboardCard title="Sessions by Status">
10496
{sessionSegments.length > 0 ? (
10597
<DonutChart segments={sessionSegments} formatTotal={(t) => `${t}`} centerLabel="Sessions" />
10698
) : (
10799
<p className="py-8 text-sm text-center text-[var(--muted-foreground)]">No session data</p>
108100
)}
109-
</CardContent>
110-
</Card>
101+
</DashboardCard>
111102

112-
<Card>
113-
<CardHeader><CardTitle>Agents by Status</CardTitle></CardHeader>
114-
<CardContent>
103+
<DashboardCard title="Agents by Status">
115104
{agentStatusSegments.length > 0 ? (
116105
<DonutChart segments={agentStatusSegments} formatTotal={(t) => `${t}`} centerLabel="Agents" />
117106
) : (
118107
<p className="py-8 text-sm text-center text-[var(--muted-foreground)]">No agent data</p>
119108
)}
120-
</CardContent>
121-
</Card>
109+
</DashboardCard>
122110

123-
<Card>
124-
<CardHeader><CardTitle>Events by Type</CardTitle></CardHeader>
125-
<CardContent>
111+
<DashboardCard title="Events by Type">
126112
{eventSegments.length > 0 ? (
127113
<DonutChart segments={eventSegments} formatTotal={(t) => t.toLocaleString()} centerLabel="Events" />
128114
) : (
129115
<p className="py-8 text-sm text-center text-[var(--muted-foreground)]">No event data</p>
130116
)}
131-
</CardContent>
132-
</Card>
117+
</DashboardCard>
133118
</div>
134119

135120
{agentTypeSegments.length > 0 && (
136-
<Card>
137-
<CardHeader><CardTitle>Agent Type Distribution</CardTitle></CardHeader>
138-
<CardContent>
139-
<SegmentedBar segments={agentTypeSegments} total={agentTypeTotal} />
140-
</CardContent>
141-
</Card>
121+
<DashboardCard title="Agent Type Distribution">
122+
<SegmentedBar segments={agentTypeSegments} total={agentTypeTotal} />
123+
</DashboardCard>
142124
)}
143125

144-
<Card>
145-
<CardHeader><CardTitle>Tool Usage</CardTitle></CardHeader>
146-
<CardContent>
126+
<DashboardCard title="Tool Usage">
147127
{toolUsage.length > 0 ? (
148128
<div className="space-y-2">
149129
{toolUsage.slice(0, 15).map((t) => (
@@ -158,12 +138,9 @@ export function AnalyticsDetails({ data }: { data: AnalyticsData }) {
158138
) : (
159139
<p className="py-8 text-sm text-center text-[var(--muted-foreground)]">No tool usage data</p>
160140
)}
161-
</CardContent>
162-
</Card>
141+
</DashboardCard>
163142

164-
<Card>
165-
<CardHeader><CardTitle>Token Usage by Model</CardTitle></CardHeader>
166-
<CardContent>
143+
<DashboardCard title="Token Usage by Model">
167144
{tokens.byModel.length > 0 ? (
168145
<div className="space-y-2">
169146
{tokens.byModel.map((m) => {
@@ -183,28 +160,21 @@ export function AnalyticsDetails({ data }: { data: AnalyticsData }) {
183160
) : (
184161
<p className="py-8 text-sm text-center text-[var(--muted-foreground)]">No model data</p>
185162
)}
186-
</CardContent>
187-
</Card>
163+
</DashboardCard>
188164

189165
{dayPoints.length > 0 && (
190-
<Card>
191-
<CardHeader><CardTitle>Daily Token Usage (Last 30 Days)</CardTitle></CardHeader>
192-
<CardContent>
193-
<div className="h-48">
194-
<LineChart points={dayPoints} color={PALETTE[0]} valueFormatter={(v) => v.toLocaleString()} />
195-
</div>
196-
</CardContent>
197-
</Card>
166+
<DashboardCard title="Daily Token Usage (Last 30 Days)">
167+
<div className="h-48">
168+
<LineChart points={dayPoints} color={PALETTE[0]} valueFormatter={(v) => v.toLocaleString()} />
169+
</div>
170+
</DashboardCard>
198171
)}
199172

200-
<Card>
201-
<CardHeader><CardTitle>At a Glance</CardTitle></CardHeader>
202-
<CardContent className="text-sm text-[var(--muted-foreground)]">
173+
<DashboardCard title="At a Glance" contentClassName="text-sm text-[var(--muted-foreground)]">
203174
{cacheTokens > 0
204175
? `${Math.round((cacheTokens / (totalTokens + cacheTokens)) * 100)}% of total token traffic was cache-related.`
205176
: "No cache token activity recorded yet."}
206-
</CardContent>
207-
</Card>
177+
</DashboardCard>
208178
</>
209179
);
210180
}

0 commit comments

Comments
 (0)