@oh-my-pi/omp-stats 18.3.5 → 18.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +13 -11
- package/build.ts +5 -49
- package/dist/client/index.css +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/index.js +105 -126
- package/dist/types/aggregator.d.ts +25 -22
- package/dist/types/client/api.d.ts +11 -7
- package/dist/types/client/app/LiveChip.d.ts +2 -0
- package/dist/types/client/app/Shell.d.ts +16 -0
- package/dist/types/client/app/ThemeToggle.d.ts +1 -0
- package/dist/types/client/app/nav.d.ts +18 -0
- package/dist/types/client/charts/BarList.d.ts +17 -0
- package/dist/types/client/charts/Chart.d.ts +49 -0
- package/dist/types/client/charts/Legend.d.ts +16 -0
- package/dist/types/client/charts/ShareBar.d.ts +11 -0
- package/dist/types/client/charts/Sparkline.d.ts +11 -0
- package/dist/types/client/charts/TimeChart.d.ts +9 -0
- package/dist/types/client/charts/index.d.ts +7 -0
- package/dist/types/client/charts/types.d.ts +25 -0
- package/dist/types/client/charts/useWidth.d.ts +3 -0
- package/dist/types/client/data/colors.d.ts +25 -0
- package/dist/types/client/data/formatters.d.ts +9 -1
- package/dist/types/client/data/live.d.ts +29 -0
- package/dist/types/client/data/query.d.ts +35 -0
- package/dist/types/client/data/range.d.ts +31 -0
- package/dist/types/client/data/series.d.ts +28 -0
- package/dist/types/client/data/useHashRoute.d.ts +18 -4
- package/dist/types/client/data/view-models.d.ts +111 -28
- package/dist/types/client/index.d.ts +0 -1
- package/dist/types/client/routes/CostsRoute.d.ts +2 -2
- package/dist/types/client/routes/ErrorsRoute.d.ts +2 -2
- package/dist/types/client/routes/FrustrationRoute.d.ts +7 -0
- package/dist/types/client/routes/GainRoute.d.ts +1 -2
- package/dist/types/client/routes/ModelsRoute.d.ts +2 -2
- package/dist/types/client/routes/OverviewRoute.d.ts +1 -2
- package/dist/types/client/routes/ProjectsRoute.d.ts +2 -2
- package/dist/types/client/routes/ProvidersRoute.d.ts +2 -2
- package/dist/types/client/routes/RequestsRoute.d.ts +1 -2
- package/dist/types/client/routes/ToolsRoute.d.ts +2 -2
- package/dist/types/client/routes/TracesRoute.d.ts +2 -2
- package/dist/types/client/routes/index.d.ts +1 -1
- package/dist/types/client/traces/AggregatesPanel.d.ts +4 -3
- package/dist/types/client/traces/SummaryStrip.d.ts +2 -2
- package/dist/types/client/traces/trace-colors.d.ts +18 -12
- package/dist/types/client/ui/Badge.d.ts +19 -0
- package/dist/types/client/ui/Card.d.ts +25 -0
- package/dist/types/client/ui/Drawer.d.ts +25 -0
- package/dist/types/client/ui/JsonBlock.d.ts +2 -2
- package/dist/types/client/ui/Modal.d.ts +23 -0
- package/dist/types/client/ui/RequestDrawer.d.ts +10 -1
- package/dist/types/client/ui/SearchInput.d.ts +8 -0
- package/dist/types/client/ui/Segmented.d.ts +15 -0
- package/dist/types/client/ui/Stat.d.ts +36 -0
- package/dist/types/client/ui/States.d.ts +41 -0
- package/dist/types/client/ui/Table.d.ts +48 -0
- package/dist/types/client/ui/index.d.ts +9 -10
- package/dist/types/db.d.ts +90 -106
- package/dist/types/frustration.d.ts +70 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/live.d.ts +30 -0
- package/dist/types/rollup.d.ts +115 -0
- package/dist/types/server.d.ts +6 -1
- package/dist/types/shared-types.d.ts +116 -56
- package/dist/types/types.d.ts +4 -0
- package/dist/types/user-metrics.d.ts +8 -0
- package/package.json +7 -13
- package/src/aggregator.ts +276 -211
- package/src/client/App.tsx +38 -80
- package/src/client/api.ts +65 -7
- package/src/client/app/LiveChip.tsx +97 -0
- package/src/client/app/Shell.tsx +181 -0
- package/src/client/app/ThemeToggle.tsx +5 -2
- package/src/client/app/nav.ts +78 -0
- package/src/client/charts/BarList.tsx +48 -0
- package/src/client/charts/Chart.tsx +388 -0
- package/src/client/charts/Legend.tsx +61 -0
- package/src/client/charts/ShareBar.tsx +26 -0
- package/src/client/charts/Sparkline.tsx +38 -0
- package/src/client/charts/TimeChart.tsx +22 -0
- package/src/client/charts/index.ts +7 -0
- package/src/client/charts/types.ts +27 -0
- package/src/client/charts/useWidth.ts +19 -0
- package/src/client/data/colors.ts +45 -0
- package/src/client/data/formatters.ts +30 -9
- package/src/client/data/live.tsx +85 -0
- package/src/client/data/query.ts +166 -0
- package/src/client/data/range.ts +82 -0
- package/src/client/data/series.ts +85 -0
- package/src/client/data/useHashRoute.ts +41 -83
- package/src/client/data/view-models.ts +347 -140
- package/src/client/index.tsx +3 -6
- package/src/client/routes/CostsRoute.tsx +394 -222
- package/src/client/routes/ErrorsRoute.tsx +401 -98
- package/src/client/routes/FrustrationRoute.tsx +924 -0
- package/src/client/routes/GainRoute.tsx +209 -206
- package/src/client/routes/ModelsRoute.tsx +540 -431
- package/src/client/routes/OverviewRoute.tsx +283 -311
- package/src/client/routes/ProjectsRoute.tsx +298 -156
- package/src/client/routes/ProvidersRoute.tsx +948 -416
- package/src/client/routes/RequestsRoute.tsx +295 -107
- package/src/client/routes/ToolsRoute.tsx +495 -398
- package/src/client/routes/TracesRoute.tsx +136 -117
- package/src/client/routes/costs.css +24 -0
- package/src/client/routes/errors.css +60 -0
- package/src/client/routes/frustration.css +123 -0
- package/src/client/routes/index.ts +1 -1
- package/src/client/routes/models.css +43 -0
- package/src/client/routes/projects.css +6 -0
- package/src/client/routes/providers.css +28 -0
- package/src/client/routes/tools.css +14 -0
- package/src/client/styles.css +1396 -1299
- package/src/client/traces/AggregatesPanel.tsx +55 -74
- package/src/client/traces/Minimap.tsx +6 -15
- package/src/client/traces/SpanDrawer.tsx +128 -194
- package/src/client/traces/SummaryStrip.tsx +21 -24
- package/src/client/traces/TimelineCanvas.tsx +27 -44
- package/src/client/traces/TraceView.tsx +175 -130
- package/src/client/traces/TranscriptList.tsx +13 -41
- package/src/client/traces/trace-colors.ts +60 -53
- package/src/client/traces/traces.css +279 -0
- package/src/client/ui/Badge.tsx +29 -0
- package/src/client/ui/Card.tsx +61 -0
- package/src/client/ui/Drawer.tsx +93 -0
- package/src/client/ui/JsonBlock.tsx +26 -44
- package/src/client/ui/Modal.tsx +106 -0
- package/src/client/ui/RequestDrawer.tsx +163 -188
- package/src/client/ui/SearchInput.tsx +25 -0
- package/src/client/ui/Segmented.tsx +61 -0
- package/src/client/ui/Stat.tsx +104 -0
- package/src/client/ui/States.tsx +81 -0
- package/src/client/ui/Table.tsx +229 -0
- package/src/client/ui/index.ts +9 -10
- package/src/client/ui/modal.css +54 -0
- package/src/client/ui/request-drawer.css +40 -0
- package/src/db.ts +379 -983
- package/src/frustration.ts +473 -0
- package/src/index.ts +22 -22
- package/src/live.ts +259 -0
- package/src/parser.ts +55 -66
- package/src/rollup.ts +951 -0
- package/src/server.ts +123 -40
- package/src/shared-types.ts +120 -56
- package/src/trace.ts +14 -7
- package/src/types.ts +4 -0
- package/src/user-metrics.ts +13 -0
- package/dist/client/styles.css +0 -1976
- package/dist/types/client/app/AppLayout.d.ts +0 -16
- package/dist/types/client/app/NavRail.d.ts +0 -7
- package/dist/types/client/app/RangeControl.d.ts +0 -7
- package/dist/types/client/app/SyncButton.d.ts +0 -14
- package/dist/types/client/app/TopBar.d.ts +0 -15
- package/dist/types/client/app/routes.d.ts +0 -12
- package/dist/types/client/components/AgentTokenShare.d.ts +0 -5
- package/dist/types/client/components/chart-shared.d.ts +0 -178
- package/dist/types/client/components/models-table-shared.d.ts +0 -175
- package/dist/types/client/components/range-meta.d.ts +0 -21
- package/dist/types/client/data/charts.d.ts +0 -1
- package/dist/types/client/data/useResource.d.ts +0 -13
- package/dist/types/client/routes/BehaviorRoute.d.ts +0 -7
- package/dist/types/client/ui/AsyncBoundary.d.ts +0 -12
- package/dist/types/client/ui/DataTable.d.ts +0 -17
- package/dist/types/client/ui/EmptyState.d.ts +0 -7
- package/dist/types/client/ui/ErrorState.d.ts +0 -6
- package/dist/types/client/ui/MetricCluster.d.ts +0 -5
- package/dist/types/client/ui/Panel.d.ts +0 -7
- package/dist/types/client/ui/SegmentedControl.d.ts +0 -12
- package/dist/types/client/ui/Skeleton.d.ts +0 -8
- package/dist/types/client/ui/StatusPill.d.ts +0 -7
- package/src/client/app/AppLayout.tsx +0 -93
- package/src/client/app/NavRail.tsx +0 -44
- package/src/client/app/RangeControl.tsx +0 -39
- package/src/client/app/SyncButton.tsx +0 -75
- package/src/client/app/TopBar.tsx +0 -73
- package/src/client/app/routes.ts +0 -93
- package/src/client/components/AgentTokenShare.tsx +0 -68
- package/src/client/components/chart-shared.tsx +0 -273
- package/src/client/components/models-table-shared.tsx +0 -255
- package/src/client/components/range-meta.ts +0 -73
- package/src/client/data/charts.ts +0 -14
- package/src/client/data/useResource.ts +0 -154
- package/src/client/routes/BehaviorRoute.tsx +0 -623
- package/src/client/ui/AsyncBoundary.tsx +0 -54
- package/src/client/ui/DataTable.tsx +0 -122
- package/src/client/ui/EmptyState.tsx +0 -16
- package/src/client/ui/ErrorState.tsx +0 -25
- package/src/client/ui/MetricCluster.tsx +0 -92
- package/src/client/ui/Panel.tsx +0 -24
- package/src/client/ui/SegmentedControl.tsx +0 -36
- package/src/client/ui/Skeleton.tsx +0 -17
- package/src/client/ui/StatusPill.tsx +0 -15
- package/tailwind.config.js +0 -40
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./ErrorState.js";
|
|
1
|
+
export * from "./Badge.js";
|
|
2
|
+
export * from "./Card.js";
|
|
3
|
+
export * from "./Drawer.js";
|
|
5
4
|
export * from "./JsonBlock.js";
|
|
6
|
-
export * from "./
|
|
7
|
-
export * from "./
|
|
8
|
-
export * from "./
|
|
9
|
-
export * from "./
|
|
10
|
-
export * from "./
|
|
11
|
-
export * from "./
|
|
5
|
+
export * from "./Modal.js";
|
|
6
|
+
export * from "./SearchInput.js";
|
|
7
|
+
export * from "./Segmented.js";
|
|
8
|
+
export * from "./Stat.js";
|
|
9
|
+
export * from "./States.js";
|
|
10
|
+
export * from "./Table.js";
|
package/dist/types/db.d.ts
CHANGED
|
@@ -1,26 +1,58 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import { type ParseSessionResult, type SessionParserState } from "./parser.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { DailyActivityPoint, FrustrationCounts, MessageStats, MessageStatsInput, ToolCallStats, ToolResultLink, UserMessageLink, UserMessageStats } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Predicate counting one stored request as "unpriced" — the public rate card
|
|
6
|
+
* has no charge for it, so its zero is unknown spend rather than free usage.
|
|
7
|
+
* Two shapes store that zero:
|
|
8
|
+
* - `xai-oauth` bills through the SuperGrok subscription, so ingestion
|
|
9
|
+
* deliberately records no per-request price;
|
|
10
|
+
* - `cost_unpriced = 1`, set by `insertMessageStats` when `resolveStoredCost`
|
|
11
|
+
* refuses to price a scheduled (time-based) card whose entry carried no
|
|
12
|
+
* recoverable request timestamp — the epoch would silently become the
|
|
13
|
+
* tariff. Such a row keeps its real tokens and its zero until a re-parse
|
|
14
|
+
* recovers the time.
|
|
15
|
+
*
|
|
16
|
+
* Nothing else sets the marker: an explicit recorded zero, a free flat card,
|
|
17
|
+
* and a model with no catalog card at all keep `cost_unpriced = 0` even at the
|
|
18
|
+
* parser's timestamp sentinel, because their zero is a real price.
|
|
19
|
+
* `prefix` qualifies the columns for queries that alias `messages`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function unpricedRequestSql(prefix?: string): string;
|
|
22
|
+
/** The open stats database, or null before {@link initDb}. Used by the rollup query layer. */
|
|
23
|
+
export declare function currentDb(): Database | null;
|
|
4
24
|
/**
|
|
5
25
|
* Initialize the database and create tables.
|
|
6
26
|
*/
|
|
7
27
|
export declare function initDb(): Promise<Database>;
|
|
8
28
|
/** Whether the catalog prices this model on a time-based (scheduled) card. */
|
|
9
29
|
export declare function isScheduledCatalogModel(provider: string, modelId: string): boolean;
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
*/
|
|
13
|
-
export declare function getFileOffset(sessionFile: string): {
|
|
30
|
+
/** Persisted transcript identity and parser cursor used to resume stats ingestion. */
|
|
31
|
+
export interface FileOffset {
|
|
14
32
|
offset: number;
|
|
15
33
|
lastModified: number;
|
|
16
34
|
parserState?: SessionParserState;
|
|
17
|
-
}
|
|
35
|
+
}
|
|
36
|
+
/** Read one persisted cursor for callers inspecting an individual transcript. */
|
|
37
|
+
export declare function getFileOffset(sessionFile: string): FileOffset | null;
|
|
38
|
+
/** Read a bounded sync work set's cursors with one SQLite snapshot instead of one lookup per file. */
|
|
39
|
+
export declare function getFileOffsets(sessionFiles: string[]): Map<string, FileOffset>;
|
|
18
40
|
/**
|
|
19
41
|
* Update the stored offset for a session file.
|
|
20
42
|
*/
|
|
21
43
|
export declare function setFileOffset(sessionFile: string, offset: number, lastModified: number, parserState?: SessionParserState): void;
|
|
22
|
-
|
|
44
|
+
/** Parsed transcript queued by the sync loop for an atomic database batch. */
|
|
45
|
+
export interface ParsedSession {
|
|
46
|
+
sessionFile: string;
|
|
47
|
+
result: ParseSessionResult;
|
|
48
|
+
rebuild: boolean;
|
|
49
|
+
/** Recover missing rows from a full-history scan without rewriting unchanged records. */
|
|
50
|
+
replay: boolean;
|
|
51
|
+
}
|
|
52
|
+
/** Commit distinct transcripts' rows, reconciliation state, and cursors together; failure rolls back the batch. */
|
|
53
|
+
export declare function applySessionParseResults(sessions: ParsedSession[]): {
|
|
23
54
|
processed: number;
|
|
55
|
+
files: number;
|
|
24
56
|
reconcile: boolean;
|
|
25
57
|
};
|
|
26
58
|
export declare function prepareSessionSync(): boolean;
|
|
@@ -41,54 +73,7 @@ export declare function completeSessionSync(reconcile: boolean): void;
|
|
|
41
73
|
* stored cost (orchestration-aware) and keeps `premium_requests` monotonic, so
|
|
42
74
|
* a forced re-parse repairs historical `premium_requests` and cost fix-ups.
|
|
43
75
|
*/
|
|
44
|
-
export declare function insertMessageStats(stats: MessageStatsInput
|
|
45
|
-
/**
|
|
46
|
-
* Get overall aggregated stats.
|
|
47
|
-
*/
|
|
48
|
-
export declare function getOverallStats(cutoff?: number): AggregatedStats;
|
|
49
|
-
/**
|
|
50
|
-
* Get stats grouped by model.
|
|
51
|
-
*/
|
|
52
|
-
export declare function getStatsByModel(cutoff?: number): ModelStats[];
|
|
53
|
-
/**
|
|
54
|
-
* Get stats grouped by folder.
|
|
55
|
-
*/
|
|
56
|
-
export declare function getStatsByFolder(cutoff?: number): FolderStats[];
|
|
57
|
-
/**
|
|
58
|
-
* Get token usage grouped by agent type (main agent, task subagents, advisor).
|
|
59
|
-
* Token columns are explicit so the dashboard's share denominator matches the
|
|
60
|
-
* counts it renders. Rows missing `agent_type` (defensive) fall back to "main".
|
|
61
|
-
*/
|
|
62
|
-
export declare function getStatsByAgentType(cutoff?: number): AgentTypeStats[];
|
|
63
|
-
/**
|
|
64
|
-
* Get time series data.
|
|
65
|
-
*/
|
|
66
|
-
export declare function getTimeSeries(hours?: number, cutoff?: number | null, bucketMs?: number): TimeSeriesPoint[];
|
|
67
|
-
/**
|
|
68
|
-
* Get daily performance time series data for the last N days.
|
|
69
|
-
*/
|
|
70
|
-
/**
|
|
71
|
-
* Get daily model usage time series data for the last N days.
|
|
72
|
-
*/
|
|
73
|
-
export declare function getModelTimeSeries(days?: number, cutoff?: number | null, bucketMs?: number): ModelTimeSeriesPoint[];
|
|
74
|
-
/**
|
|
75
|
-
* Get request/token/cost totals grouped by provider.
|
|
76
|
-
*/
|
|
77
|
-
export declare function getStatsByProvider(cutoff?: number | null): ProviderAggregate[];
|
|
78
|
-
/**
|
|
79
|
-
* Get token burn grouped by provider and local hour of day (0-23).
|
|
80
|
-
* Hours use the server's timezone — the dashboard is a localhost tool, so
|
|
81
|
-
* server-local and viewer-local time coincide.
|
|
82
|
-
*/
|
|
83
|
-
export declare function getProviderHourlyBurn(cutoff?: number | null): ProviderHourlyPoint[];
|
|
84
|
-
/**
|
|
85
|
-
* Get token/cost time series grouped by provider (bucketed like the model series).
|
|
86
|
-
*/
|
|
87
|
-
export declare function getProviderTimeSeries(days?: number, cutoff?: number | null, bucketMs?: number): ProviderTimeSeriesPoint[];
|
|
88
|
-
/**
|
|
89
|
-
* Get daily model performance time series data for the last N days.
|
|
90
|
-
*/
|
|
91
|
-
export declare function getModelPerformanceSeries(days?: number, cutoff?: number | null, bucketMs?: number): ModelPerformancePoint[];
|
|
76
|
+
export declare function insertMessageStats(stats: Iterable<MessageStatsInput>): number;
|
|
92
77
|
/**
|
|
93
78
|
* Get total message count.
|
|
94
79
|
*/
|
|
@@ -100,26 +85,6 @@ export declare function closeDb(): void;
|
|
|
100
85
|
export declare function getRecentRequests(limit?: number): MessageStats[];
|
|
101
86
|
export declare function getRecentErrors(limit?: number, cutoff?: number | null): MessageStats[];
|
|
102
87
|
export declare function getMessageById(id: number): MessageStats | null;
|
|
103
|
-
/** Per-transcript-file rollup for the Traces session list. */
|
|
104
|
-
export interface SessionRollupRow {
|
|
105
|
-
sessionFile: string;
|
|
106
|
-
requests: number;
|
|
107
|
-
startedAt: number;
|
|
108
|
-
endedAt: number;
|
|
109
|
-
totalTokens: number;
|
|
110
|
-
costTotal: number;
|
|
111
|
-
unpricedRequests: number;
|
|
112
|
-
/** Comma-joined DISTINCT models. */
|
|
113
|
-
models: string;
|
|
114
|
-
}
|
|
115
|
-
/** Aggregate every synced transcript file into one row (subagents unfolded). */
|
|
116
|
-
export declare function getSessionRollups(): SessionRollupRow[];
|
|
117
|
-
/** Tool-call counts keyed by transcript file, for the Traces session list. */
|
|
118
|
-
export declare function getToolCallCountsBySession(): Map<string, number>;
|
|
119
|
-
/**
|
|
120
|
-
* Get daily cost time series data for the last N days, broken down by model.
|
|
121
|
-
*/
|
|
122
|
-
export declare function getCostTimeSeries(days?: number, cutoff?: number | null): CostTimeSeriesPoint[];
|
|
123
88
|
/**
|
|
124
89
|
* Per-local-day activity aggregates for the last `days` days, oldest first.
|
|
125
90
|
* Self-initializing (opens the stats DB on first use) so the coding-agent TUI
|
|
@@ -138,30 +103,61 @@ export declare function markSessionBackfillsComplete(): void;
|
|
|
138
103
|
* copy user entries verbatim into the child JSONL, so the same
|
|
139
104
|
* `(entry_id, timestamp)` must not land twice across different session files.
|
|
140
105
|
*/
|
|
141
|
-
export declare function insertUserMessageStats(stats: UserMessageStats
|
|
106
|
+
export declare function insertUserMessageStats(stats: Iterable<UserMessageStats>): number;
|
|
142
107
|
/**
|
|
143
108
|
* Backfill the responding `model`/`provider` on user-message rows that were
|
|
144
|
-
* persisted before their assistant reply was parsed
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* once because the `model IS NULL` guard short-circuits subsequent passes.
|
|
109
|
+
* persisted before their assistant reply was parsed by an incremental tail
|
|
110
|
+
* read. Each row is updated at most once because the `model IS NULL` guard
|
|
111
|
+
* short-circuits subsequent passes.
|
|
148
112
|
*
|
|
149
113
|
* Returns the number of rows actually updated.
|
|
150
114
|
*/
|
|
151
|
-
export declare function updateUserMessageLinks(links: UserMessageLink
|
|
152
|
-
/**
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
115
|
+
export declare function updateUserMessageLinks(links: Iterable<UserMessageLink>): number;
|
|
116
|
+
/** Frustration tallies of one responding (model, provider) pair, before identity merging. */
|
|
117
|
+
export interface FrustrationModelRow extends FrustrationCounts {
|
|
118
|
+
model: string;
|
|
119
|
+
provider: string | null;
|
|
120
|
+
/** Earliest message timestamp (ms) in range. */
|
|
121
|
+
firstSeen: number;
|
|
122
|
+
}
|
|
123
|
+
/** One unique unjudged prose text: identical messages share a hash and one verdict. */
|
|
124
|
+
export interface PendingProse {
|
|
125
|
+
hash: string;
|
|
126
|
+
prose: string;
|
|
127
|
+
}
|
|
128
|
+
/** Cached judge verdict for one prose hash. */
|
|
129
|
+
export interface FrustrationVerdict {
|
|
130
|
+
proseHash: string;
|
|
131
|
+
/** P(clearly annoyed) + P(angry) on the `annoyed` score question. */
|
|
132
|
+
pAnnoyed: number;
|
|
133
|
+
/** P(angry, hostile, or swearing). */
|
|
134
|
+
pAngry: number;
|
|
135
|
+
/** Most likely `target` choice: `assistant`, `other`, or `none`. */
|
|
136
|
+
target: string;
|
|
137
|
+
/** `provider/model` that produced the verdict. */
|
|
138
|
+
judge: string;
|
|
139
|
+
judgedAt: number;
|
|
140
|
+
}
|
|
141
|
+
/** Frustration tallies over every user message with prose in range, linked to a model or not. */
|
|
142
|
+
export declare function getFrustrationOverall(cutoff?: number | null): FrustrationCounts;
|
|
143
|
+
/**
|
|
144
|
+
* Frustration tallies per responding (model, provider) over the range. User
|
|
145
|
+
* messages that never got a reply (null model) are only in the overall tally.
|
|
146
|
+
*/
|
|
147
|
+
export declare function getFrustrationByModel(cutoff?: number | null): FrustrationModelRow[];
|
|
148
|
+
/** Unique prose texts in range that have no cached verdict yet. */
|
|
149
|
+
export declare function getPendingFrustrationProse(cutoff?: number | null): PendingProse[];
|
|
150
|
+
/** Count and total characters of {@link getPendingFrustrationProse} without loading the text. */
|
|
151
|
+
export declare function getPendingFrustrationTotals(cutoff?: number | null): {
|
|
152
|
+
messages: number;
|
|
153
|
+
chars: number;
|
|
154
|
+
};
|
|
160
155
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
156
|
+
* Cache (or replace) verdicts, all in one transaction: a judge run lands
|
|
157
|
+
* dozens per second, and a commit per verdict would contend with ingest for
|
|
158
|
+
* the write lock every time.
|
|
163
159
|
*/
|
|
164
|
-
export declare function
|
|
160
|
+
export declare function upsertFrustrationVerdicts(verdicts: readonly FrustrationVerdict[]): void;
|
|
165
161
|
/**
|
|
166
162
|
* Insert tool-call rows. Idempotent via UNIQUE(session_file, tool_call_id);
|
|
167
163
|
* the `WHERE NOT EXISTS` guard mirrors {@link insertMessageStats}: forked
|
|
@@ -171,7 +167,7 @@ export declare function getBehaviorByModel(cutoff?: number | null): BehaviorMode
|
|
|
171
167
|
* identity, not the call id alone — provider call ids are not a global
|
|
172
168
|
* namespace across unrelated sessions.
|
|
173
169
|
*/
|
|
174
|
-
export declare function insertToolCalls(calls: ToolCallStats
|
|
170
|
+
export declare function insertToolCalls(calls: Iterable<ToolCallStats>): number;
|
|
175
171
|
/**
|
|
176
172
|
* Attach result size / error flag to persisted tool-call rows. Results can
|
|
177
173
|
* land in a later incremental sync pass than the call that produced them, so
|
|
@@ -179,16 +175,4 @@ export declare function insertToolCalls(calls: ToolCallStats[]): number;
|
|
|
179
175
|
* guard makes re-syncs idempotent; rows skipped by the fork guard simply
|
|
180
176
|
* never match.
|
|
181
177
|
*/
|
|
182
|
-
export declare function updateToolResults(links: ToolResultLink
|
|
183
|
-
/**
|
|
184
|
-
* Get tool usage aggregated by tool name.
|
|
185
|
-
*/
|
|
186
|
-
export declare function getToolStats(cutoff?: number): ToolUsageStats[];
|
|
187
|
-
/**
|
|
188
|
-
* Get tool usage aggregated by (tool, model, provider).
|
|
189
|
-
*/
|
|
190
|
-
export declare function getToolStatsByModel(cutoff?: number): ToolModelStats[];
|
|
191
|
-
/**
|
|
192
|
-
* Get tool-call time series (one point per bucket per tool).
|
|
193
|
-
*/
|
|
194
|
-
export declare function getToolTimeSeries(days?: number, cutoff?: number | null, bucketMs?: number): ToolTimeSeriesPoint[];
|
|
178
|
+
export declare function updateToolResults(links: Iterable<ToolResultLink>): number;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frustration dashboard: per-model share of user messages that sound annoyed,
|
|
3
|
+
* annoyed at the assistant, and angry at the assistant.
|
|
4
|
+
*
|
|
5
|
+
* Each message is classified by its cached judge verdict when one exists:
|
|
6
|
+
* annoyed = P(level 2) + P(level 3) ≥ 0.5 on {@link FRUSTRATION_QUESTIONS}.annoyed
|
|
7
|
+
* atAssistant = annoyed && target = `assistant`
|
|
8
|
+
* angry = atAssistant && P(level 3) ≥ 0.5
|
|
9
|
+
* and otherwise by the regex signals stored at ingest (`user-metrics.ts`):
|
|
10
|
+
* annoyed = yelling + profanity + anguish + negation + repetition + blame > 0
|
|
11
|
+
* atAssistant = negation + repetition + blame > 0
|
|
12
|
+
* angry = atAssistant && (profanity > 0 || yelling > 0)
|
|
13
|
+
* The SQL implementing both lives in `db.ts` (`FRUSTRATION_COUNTS_SQL`).
|
|
14
|
+
*
|
|
15
|
+
* Verdicts come from a single process-wide judge run over the unjudged prose,
|
|
16
|
+
* using the judge the omp host registered through `startServer`. Standalone
|
|
17
|
+
* `omp-stats` has none and only shows the regex fallback.
|
|
18
|
+
*/
|
|
19
|
+
import type { ChoiceQuestion, Judge, Model, ScoreQuestion } from "@oh-my-pi/pi-ai";
|
|
20
|
+
import { type FrustrationModelRow } from "./db.js";
|
|
21
|
+
import type { FrustrationDashboardStats, FrustrationEstimate, FrustrationJobStatus, FrustrationModelStats } from "./shared-types.js";
|
|
22
|
+
/** Judge supplied by the omp host process; `primaryModel` prices the pre-run estimate. */
|
|
23
|
+
export interface StatsJudge extends Judge {
|
|
24
|
+
primaryModel(): Model | undefined;
|
|
25
|
+
}
|
|
26
|
+
/** Lazily resolves the host judge on first estimate/run so `omp stats` startup stays fast. */
|
|
27
|
+
export type StatsJudgeProvider = () => Promise<StatsJudge>;
|
|
28
|
+
/** Where the user's annoyance is aimed. */
|
|
29
|
+
export type FrustrationTarget = "assistant" | "other" | "none";
|
|
30
|
+
/**
|
|
31
|
+
* The two questions asked about every prose text, in ONE judge request.
|
|
32
|
+
* The cost constants below were measured with exactly these strings; changing
|
|
33
|
+
* them invalidates both the estimate and every cached verdict's meaning.
|
|
34
|
+
*/
|
|
35
|
+
export declare const FRUSTRATION_QUESTIONS: {
|
|
36
|
+
annoyed: ScoreQuestion;
|
|
37
|
+
target: ChoiceQuestion<FrustrationTarget>;
|
|
38
|
+
};
|
|
39
|
+
/** Register (or replace) the host judge. Called by `startServer`. */
|
|
40
|
+
export declare function setStatsJudgeProvider(provider: StatsJudgeProvider | undefined): void;
|
|
41
|
+
/** Snapshot of the current run's progress. */
|
|
42
|
+
export declare function getFrustrationJobStatus(): FrustrationJobStatus;
|
|
43
|
+
/**
|
|
44
|
+
* Merge per-(model, provider) rows into catalog identities (`class/family/revision`)
|
|
45
|
+
* so provider and spelling variants share a row. Classified rows sort by class,
|
|
46
|
+
* then revision ascending, then family; unclassified rows follow by first use.
|
|
47
|
+
*/
|
|
48
|
+
export declare function mergeFrustrationRows(rows: readonly FrustrationModelRow[]): FrustrationModelStats[];
|
|
49
|
+
/** Payload of `GET /api/stats/frustration`. */
|
|
50
|
+
export declare function getFrustrationDashboardStats(range?: string | null): Promise<FrustrationDashboardStats>;
|
|
51
|
+
/** Pre-run cost quote for judging every unjudged prose text in range. */
|
|
52
|
+
export declare function estimateFrustrationRun(range?: string | null): Promise<FrustrationEstimate>;
|
|
53
|
+
/** Outcome of {@link startFrustrationRun}: the new job, or the HTTP status and reason it was refused. */
|
|
54
|
+
export type StartFrustrationRunResult = {
|
|
55
|
+
started: true;
|
|
56
|
+
job: FrustrationJobStatus;
|
|
57
|
+
/** Settles once every worker has stopped (done, cancelled, or failed); never rejects. */
|
|
58
|
+
finished: Promise<void>;
|
|
59
|
+
} | {
|
|
60
|
+
started: false;
|
|
61
|
+
status: 409 | 503;
|
|
62
|
+
error: string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Start the process-wide judge run over the unique unjudged prose in range.
|
|
66
|
+
* Returns immediately after queueing; progress is read via {@link getFrustrationJobStatus}.
|
|
67
|
+
*/
|
|
68
|
+
export declare function startFrustrationRun(range?: string | null): Promise<StartFrustrationRunResult>;
|
|
69
|
+
/** Abort the active run; in-flight judge requests are cancelled and no new text is taken. */
|
|
70
|
+
export declare function cancelFrustrationRun(): FrustrationJobStatus;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
export { getDashboardStats, getToolDashboardStats, getTotalMessageCount, type SyncOptions, type SyncProgress, smokeTestSyncWorker, syncAllSessions, } from "./aggregator.js";
|
|
3
3
|
export { closeDb } from "./db.js";
|
|
4
|
+
export { refreshRollups } from "./rollup.js";
|
|
5
|
+
export type { StatsJudge, StatsJudgeProvider } from "./frustration.js";
|
|
4
6
|
export { getGainDashboardStats } from "./gain-aggregator.js";
|
|
5
|
-
export { formatStatsDashboardUrl, startServer } from "./server.js";
|
|
7
|
+
export { formatStatsDashboardUrl, type StartServerOptions, startServer } from "./server.js";
|
|
6
8
|
export type { GainDashboardStats, GainSource, GainSourceTotals, GainTimeSeriesPoint } from "./shared-types.js";
|
|
7
9
|
export type { AggregatedStats, DashboardStats, FolderStats, MessageStats, ModelPerformancePoint, ModelStats, ModelTimeSeriesPoint, TimeSeriesPoint, ToolDashboardStats, ToolModelStats, ToolTimeSeriesPoint, ToolUsageStats, } from "./types.js";
|
|
8
10
|
/** Parsed arguments for the standalone `omp-stats` entry point. */
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live ingest for a running dashboard: background session sync, rollup
|
|
3
|
+
* refresh, and a status stream the client subscribes to (`/api/events`).
|
|
4
|
+
*
|
|
5
|
+
* The dashboard never waits for ingest. {@link StatsLive.start} kicks a full
|
|
6
|
+
* sync in the background and watches the sessions directory; changed
|
|
7
|
+
* transcripts are re-synced (only those files) shortly after they are written.
|
|
8
|
+
* Every committed batch bumps {@link LiveStatus.version} (throttled), so open
|
|
9
|
+
* pages refetch and fill in while parsing is still running.
|
|
10
|
+
*/
|
|
11
|
+
import type { LiveStatus } from "./shared-types.js";
|
|
12
|
+
type Listener = (status: LiveStatus) => void;
|
|
13
|
+
/** One per process; owned by the dashboard server (see `startServer`). */
|
|
14
|
+
export declare class StatsLive {
|
|
15
|
+
#private;
|
|
16
|
+
status(): LiveStatus;
|
|
17
|
+
/** Receive every status change; returns the unsubscribe function. */
|
|
18
|
+
subscribe(listener: Listener): () => void;
|
|
19
|
+
/** Begin background ingest: an immediate full sync, the transcript watcher, and periodic resyncs. Idempotent. */
|
|
20
|
+
start(): void;
|
|
21
|
+
stop(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Queue a sync of `files`, or of every session when omitted. Requests made
|
|
24
|
+
* while a sync runs coalesce into one follow-up run.
|
|
25
|
+
*/
|
|
26
|
+
requestSync(files?: readonly string[]): void;
|
|
27
|
+
}
|
|
28
|
+
/** The process-wide live hub. */
|
|
29
|
+
export declare function statsLive(): StatsLive;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hourly rollups of `messages` and `tool_calls`, and every range query the
|
|
3
|
+
* dashboard runs over them.
|
|
4
|
+
*
|
|
5
|
+
* The raw tables hold millions of rows; grouping them per request made every
|
|
6
|
+
* range switch cost seconds. Instead, `message_rollup` / `tool_rollup` keep one
|
|
7
|
+
* row per (hour, dimensions) with additive sums, so a 90-day query reads a few
|
|
8
|
+
* thousand rows.
|
|
9
|
+
*
|
|
10
|
+
* Freshness is trigger-driven: any INSERT/UPDATE/DELETE on the raw tables — from
|
|
11
|
+
* this process, a sync worker, or another omp process sharing the database —
|
|
12
|
+
* records the touched hour in `rollup_dirty`. {@link refreshRollups} recomputes
|
|
13
|
+
* dirty hours newest-first in short immediate transactions.
|
|
14
|
+
*
|
|
15
|
+
* Reads never wait for a refresh and are exact: each query unions
|
|
16
|
+
* - clean rollup hours inside the range,
|
|
17
|
+
* - raw rows for the partial hour at the range start, and
|
|
18
|
+
* - raw rows for dirty hours (while few are dirty),
|
|
19
|
+
* so live ingest shows up before its hour is re-rolled. While the initial build
|
|
20
|
+
* (or a mass re-ingest) leaves many hours dirty, reads use the rollup rows as
|
|
21
|
+
* they stand (stale-but-present; not-yet-built hours are missing) and
|
|
22
|
+
* {@link getRollupStatus} reports the backlog so the UI can show progress.
|
|
23
|
+
*
|
|
24
|
+
* The 1h range needs 5-minute buckets, so it aggregates raw rows directly
|
|
25
|
+
* (an hour of data is small and indexed by timestamp).
|
|
26
|
+
*/
|
|
27
|
+
import type { Database } from "bun:sqlite";
|
|
28
|
+
import type { AgentTypeStats, AggregatedStats, CostTimeSeriesPoint, FolderStats, ModelPerformancePoint, ModelStats, ModelTimeSeriesPoint, ProviderAggregate, ProviderHourlyPoint, ProviderTimeSeriesPoint, TimeSeriesPoint, ToolModelStats, ToolTimeSeriesPoint, ToolUsageStats } from "./types.js";
|
|
29
|
+
/**
|
|
30
|
+
* Create (or rebuild after a version bump) the rollup tables and the triggers
|
|
31
|
+
* that mark hours dirty. Called from `initDb` after the raw tables exist.
|
|
32
|
+
*/
|
|
33
|
+
export declare function ensureRollupSchema(database: Database): void;
|
|
34
|
+
/** Backlog of hours and transcripts whose rollups are stale. */
|
|
35
|
+
export interface RollupStatus {
|
|
36
|
+
dirtyHours: number;
|
|
37
|
+
dirtySessions: number;
|
|
38
|
+
}
|
|
39
|
+
export declare function getRollupStatus(): RollupStatus;
|
|
40
|
+
/**
|
|
41
|
+
* Recompute up to `limit` dirty hours, newest first, in one immediate
|
|
42
|
+
* transaction (so no writer can dirty an hour between its read and its
|
|
43
|
+
* dirty-mark removal). Returns the number of hours refreshed.
|
|
44
|
+
*/
|
|
45
|
+
export declare function refreshRollupBatch(limit: number): number;
|
|
46
|
+
export interface RefreshOptions {
|
|
47
|
+
/** Called after each committed batch with the remaining backlog. */
|
|
48
|
+
onProgress?: (remaining: number) => void;
|
|
49
|
+
/** Stop early (between batches). */
|
|
50
|
+
signal?: AbortSignal;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Drain the dirty-hour backlog in small transactions, yielding to the event
|
|
54
|
+
* loop between batches so a host process (TUI, dashboard server) stays
|
|
55
|
+
* responsive during the initial build. Batch size adapts so each transaction
|
|
56
|
+
* takes about {@link REFRESH_TARGET_MS}; busy hours shrink it to one.
|
|
57
|
+
*/
|
|
58
|
+
export declare function refreshRollups(opts?: RefreshOptions): Promise<void>;
|
|
59
|
+
/** A range query's time window. `cutoff` null = all time. */
|
|
60
|
+
export interface RangeWindow {
|
|
61
|
+
cutoff: number | null;
|
|
62
|
+
/** Bucket size for time series (5 minutes, an hour, or a day). */
|
|
63
|
+
bucketMs: number;
|
|
64
|
+
}
|
|
65
|
+
/** Overall request/token/cost aggregate since `cutoff` (all time when null). */
|
|
66
|
+
export declare function getOverallStats(cutoff?: number | null): AggregatedStats;
|
|
67
|
+
/** Aggregates per (model, provider), busiest first. */
|
|
68
|
+
export declare function getStatsByModel(cutoff?: number | null): ModelStats[];
|
|
69
|
+
/** Aggregates per project folder, busiest first; at most `limit` folders. */
|
|
70
|
+
export declare function getStatsByFolder(cutoff?: number | null, limit?: number): FolderStats[];
|
|
71
|
+
/** Token usage per agent type (main, subagent, advisor). */
|
|
72
|
+
export declare function getStatsByAgentType(cutoff?: number | null): AgentTypeStats[];
|
|
73
|
+
/** Requests, errors, tokens and cost per bucket. */
|
|
74
|
+
export declare function getTimeSeries({ cutoff, bucketMs }: RangeWindow): TimeSeriesPoint[];
|
|
75
|
+
/** Requests per bucket per (model, provider). */
|
|
76
|
+
export declare function getModelTimeSeries({ cutoff, bucketMs }: RangeWindow): ModelTimeSeriesPoint[];
|
|
77
|
+
/** Average TTFT and output tokens/s per bucket per (model, provider). */
|
|
78
|
+
export declare function getModelPerformanceSeries({ cutoff, bucketMs }: RangeWindow): ModelPerformancePoint[];
|
|
79
|
+
/** Daily cost per (model, provider) with the per-component split. */
|
|
80
|
+
export declare function getCostTimeSeries(cutoff?: number | null): CostTimeSeriesPoint[];
|
|
81
|
+
/** Request/token/cost totals per provider, biggest token burn first. */
|
|
82
|
+
export declare function getStatsByProvider(cutoff?: number | null): ProviderAggregate[];
|
|
83
|
+
/**
|
|
84
|
+
* Token burn per provider per local hour of day (0-23). Hours use the
|
|
85
|
+
* server's timezone — a localhost tool, so server and viewer coincide. Rolled
|
|
86
|
+
* hours are attributed by their start, so half-hour UTC offsets shift burn by
|
|
87
|
+
* up to 30 minutes.
|
|
88
|
+
*/
|
|
89
|
+
export declare function getProviderHourlyBurn(cutoff?: number | null): ProviderHourlyPoint[];
|
|
90
|
+
/** Tokens/cost per bucket per provider. */
|
|
91
|
+
export declare function getProviderTimeSeries({ cutoff, bucketMs }: RangeWindow): ProviderTimeSeriesPoint[];
|
|
92
|
+
/** Per-transcript-file rollup for the Traces session list. */
|
|
93
|
+
export interface SessionRollupRow {
|
|
94
|
+
sessionFile: string;
|
|
95
|
+
requests: number;
|
|
96
|
+
startedAt: number;
|
|
97
|
+
endedAt: number;
|
|
98
|
+
totalTokens: number;
|
|
99
|
+
costTotal: number;
|
|
100
|
+
unpricedRequests: number;
|
|
101
|
+
/** Comma-joined DISTINCT models. */
|
|
102
|
+
models: string | null;
|
|
103
|
+
toolCalls: number;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* One row per synced transcript file (subagents unfolded). Same exactness
|
|
107
|
+
* rules as the range queries: dirty transcripts are aggregated raw while few.
|
|
108
|
+
*/
|
|
109
|
+
export declare function getSessionRollups(): SessionRollupRow[];
|
|
110
|
+
/** Tool usage per tool name, most-called first. */
|
|
111
|
+
export declare function getToolStats(cutoff?: number | null): ToolUsageStats[];
|
|
112
|
+
/** Tool usage per (tool, model, provider), most-called first. */
|
|
113
|
+
export declare function getToolStatsByModel(cutoff?: number | null): ToolModelStats[];
|
|
114
|
+
/** Calls and errors per bucket per tool. */
|
|
115
|
+
export declare function getToolTimeSeries({ cutoff, bucketMs }: RangeWindow): ToolTimeSeriesPoint[];
|
package/dist/types/server.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type StatsJudgeProvider } from "./frustration.js";
|
|
1
2
|
/**
|
|
2
3
|
* Handle API requests.
|
|
3
4
|
*/
|
|
@@ -12,4 +13,8 @@ export interface StatsServerHandle {
|
|
|
12
13
|
port: number;
|
|
13
14
|
stop: () => void;
|
|
14
15
|
}
|
|
15
|
-
export
|
|
16
|
+
export interface StartServerOptions {
|
|
17
|
+
/** Host judge for the Frustration dashboard's judge runs; replaces any previously registered one. */
|
|
18
|
+
judge?: StatsJudgeProvider;
|
|
19
|
+
}
|
|
20
|
+
export declare function startServer(port?: number, hostname?: string, options?: StartServerOptions): Promise<StatsServerHandle>;
|