@linxiraos/pi-stats 1.1.13 → 1.1.15

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +10 -35
  2. package/dist/client/index.css +1 -1
  3. package/dist/client/index.js +56 -56
  4. package/dist/client/styles.css +46 -46
  5. package/dist/types/client/components/chart-shared.d.ts +21 -21
  6. package/dist/types/client/components/models-table-shared.d.ts +14 -14
  7. package/dist/types/client/data/formatters.d.ts +7 -2
  8. package/dist/types/client/traces/trace-colors.d.ts +1 -1
  9. package/dist/types/client/types.d.ts +2 -0
  10. package/dist/types/db.d.ts +24 -2
  11. package/dist/types/parser.d.ts +45 -2
  12. package/dist/types/shared-types.d.ts +4 -0
  13. package/dist/types/trace.d.ts +1 -1
  14. package/dist/types/types.d.ts +21 -0
  15. package/package.json +4 -4
  16. package/src/aggregator.ts +4 -0
  17. package/src/client/app/AppLayout.tsx +1 -1
  18. package/src/client/app/NavRail.tsx +1 -1
  19. package/src/client/components/AgentTokenShare.tsx +6 -6
  20. package/src/client/components/chart-shared.tsx +22 -22
  21. package/src/client/data/formatters.ts +15 -3
  22. package/src/client/routes/BehaviorRoute.tsx +10 -10
  23. package/src/client/routes/ModelsRoute.tsx +1 -1
  24. package/src/client/routes/OverviewRoute.tsx +4 -4
  25. package/src/client/routes/ProvidersRoute.tsx +3 -3
  26. package/src/client/routes/TracesRoute.tsx +10 -3
  27. package/src/client/styles.css +54 -52
  28. package/src/client/traces/SummaryStrip.tsx +2 -2
  29. package/src/client/traces/trace-colors.ts +13 -13
  30. package/src/client/types.ts +2 -0
  31. package/src/db.ts +242 -46
  32. package/src/parser.ts +131 -18
  33. package/src/server.ts +1 -1
  34. package/src/shared-types.ts +4 -0
  35. package/src/trace.ts +52 -8
  36. package/src/types.ts +20 -0
@@ -481,21 +481,21 @@
481
481
  }
482
482
  :root {
483
483
  color-scheme: dark;
484
- --page: oklch(0.16 0.02 307);
485
- --surface: oklch(0.19 0.022 307);
486
- --surface-2: oklch(0.235 0.026 307);
487
- --border: oklch(1 0 0 / 9%);
488
- --border-strong: oklch(1 0 0 / 13%);
489
- --text: oklch(0.92 0.01 307);
490
- --muted: oklch(0.71 0.016 307);
491
- --dim: oklch(0.53 0.018 307);
492
- --accent: oklch(0.674 0.23 341);
493
- --accent-fg: oklch(0.2 0.07 341);
494
- --accent-muted: oklch(0.674 0.23 341 / 18%);
495
- --link: oklch(0.817 0.112 205);
496
- --success: oklch(0.76 0.14 150);
497
- --danger: oklch(0.66 0.19 25);
498
- --warning: oklch(0.79 0.14 85);
484
+ --page: #161616;
485
+ --surface: #1e1e1e;
486
+ --surface-2: #232323;
487
+ --border: #2e2e2e;
488
+ --border-strong: #3c3c3c;
489
+ --text: #f5f5f5;
490
+ --muted: #9d9d9d;
491
+ --dim: #6e6e6e;
492
+ --accent: #eaeaea;
493
+ --accent-fg: #161616;
494
+ --accent-muted: rgb(234 234 234 / 0.18);
495
+ --link: #61afef;
496
+ --success: #98c379;
497
+ --danger: #f07178;
498
+ --warning: #e5c07b;
499
499
  --text-primary: var(--text);
500
500
  --text-secondary: var(--muted);
501
501
  --text-muted: var(--dim);
@@ -513,42 +513,42 @@
513
513
  @media (prefers-color-scheme: light) {
514
514
  :root:not([data-theme]) {
515
515
  color-scheme: light;
516
- --page: oklch(0.985 0.004 307);
517
- --surface: oklch(1 0 0);
518
- --surface-2: oklch(0.965 0.006 307);
519
- --border: oklch(0 0 0 / 10%);
520
- --border-strong: oklch(0 0 0 / 15%);
521
- --text: oklch(0.26 0.03 307);
522
- --muted: oklch(0.46 0.03 307);
523
- --dim: oklch(0.58 0.025 307);
524
- --accent: oklch(0.62 0.23 341);
525
- --accent-fg: oklch(0.99 0.01 341);
526
- --accent-muted: oklch(0.62 0.23 341 / 14%);
527
- --link: oklch(0.58 0.13 230);
528
- --success: oklch(0.55 0.13 150);
529
- --danger: oklch(0.55 0.19 25);
516
+ --page: #fafafa;
517
+ --surface: #ffffff;
518
+ --surface-2: #f0f0f0;
519
+ --border: #e0e0e0;
520
+ --border-strong: #c8c8c8;
521
+ --text: #1f1f1f;
522
+ --muted: #616161;
523
+ --dim: #9e9e9e;
524
+ --accent: #1a1a1a;
525
+ --accent-fg: #fafafa;
526
+ --accent-muted: rgb(26 26 26 / 0.14);
527
+ --link: #4078c0;
528
+ --success: #50a14f;
529
+ --danger: #e45649;
530
530
  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23555' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
531
- --warning: oklch(0.6 0.13 85);
531
+ --warning: #986801;
532
532
  }
533
533
  }
534
534
  [data-theme="light"] {
535
535
  color-scheme: light;
536
- --page: oklch(0.985 0.004 307);
537
- --surface: oklch(1 0 0);
538
- --surface-2: oklch(0.965 0.006 307);
539
- --border: oklch(0 0 0 / 10%);
540
- --border-strong: oklch(0 0 0 / 15%);
541
- --text: oklch(0.26 0.03 307);
542
- --muted: oklch(0.46 0.03 307);
543
- --dim: oklch(0.58 0.025 307);
544
- --accent: oklch(0.62 0.23 341);
545
- --accent-fg: oklch(0.99 0.01 341);
546
- --accent-muted: oklch(0.62 0.23 341 / 14%);
547
- --link: oklch(0.58 0.13 230);
548
- --success: oklch(0.55 0.13 150);
549
- --danger: oklch(0.55 0.19 25);
536
+ --page: #fafafa;
537
+ --surface: #ffffff;
538
+ --surface-2: #f0f0f0;
539
+ --border: #e0e0e0;
540
+ --border-strong: #c8c8c8;
541
+ --text: #1f1f1f;
542
+ --muted: #616161;
543
+ --dim: #9e9e9e;
544
+ --accent: #1a1a1a;
545
+ --accent-fg: #fafafa;
546
+ --accent-muted: rgb(26 26 26 / 0.14);
547
+ --link: #4078c0;
548
+ --success: #50a14f;
549
+ --danger: #e45649;
550
550
  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23555' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
551
- --warning: oklch(0.6 0.13 85);
551
+ --warning: #986801;
552
552
  }
553
553
  @layer base {
554
554
  * {
@@ -1570,7 +1570,7 @@
1570
1570
  width: 16px;
1571
1571
  height: 16px;
1572
1572
  border-radius: 4px;
1573
- background: linear-gradient(135deg, #ed4abf 0%, #9b4dff 50%, #5ad8e6 100%);
1573
+ background: linear-gradient(135deg, #eaeaea 0%, #9d9d9d 50%, #3c3c3c 100%);
1574
1574
  }
1575
1575
  .stats-logo-text, .stats-logo-subtext {
1576
1576
  grid-column: 2;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Shared chart primitives for the dashboard timeline charts: the OMP color
2
+ * Shared chart primitives for the dashboard timeline charts: the Zeta color
3
3
  * palette, light/dark chart chrome, legend/tooltip + scale plumbing, dataset
4
4
  * styling, and the top-N-by-model / aggregate bucketing used by the cost and
5
5
  * behavior series.
@@ -12,22 +12,22 @@ export declare function buildModelColorLookup(records: readonly {
12
12
  }[]): Map<string, string>;
13
13
  export declare const CHART_THEMES: {
14
14
  readonly dark: {
15
- readonly legendLabel: "#a89fb3";
16
- readonly tooltipBackground: "#241a2e";
17
- readonly tooltipTitle: "#eae5ef";
18
- readonly tooltipBody: "#a89fb3";
15
+ readonly legendLabel: "#9d9d9d";
16
+ readonly tooltipBackground: "#232323";
17
+ readonly tooltipTitle: "#f5f5f5";
18
+ readonly tooltipBody: "#9d9d9d";
19
19
  readonly tooltipBorder: "rgba(255, 255, 255, 0.12)";
20
20
  readonly grid: "rgba(255, 255, 255, 0.06)";
21
- readonly tick: "#867a93";
21
+ readonly tick: "#7a7a7a";
22
22
  };
23
23
  readonly light: {
24
- readonly legendLabel: "#5a5462";
24
+ readonly legendLabel: "#616161";
25
25
  readonly tooltipBackground: "#ffffff";
26
- readonly tooltipTitle: "#241a2e";
27
- readonly tooltipBody: "#5a5462";
28
- readonly tooltipBorder: "rgba(20, 12, 28, 0.15)";
29
- readonly grid: "rgba(20, 12, 28, 0.08)";
30
- readonly tick: "#6a6275";
26
+ readonly tooltipTitle: "#1f1f1f";
27
+ readonly tooltipBody: "#616161";
28
+ readonly tooltipBorder: "rgba(0, 0, 0, 0.15)";
29
+ readonly grid: "rgba(0, 0, 0, 0.08)";
30
+ readonly tick: "#8c8c8c";
31
31
  };
32
32
  };
33
33
  export type ChartTheme = (typeof CHART_THEMES)[keyof typeof CHART_THEMES];
@@ -56,7 +56,7 @@ export declare function buildSharedPlugins(opts: {
56
56
  position: "top";
57
57
  align: "start";
58
58
  labels: {
59
- color: "#5a5462" | "#a89fb3";
59
+ color: "#616161" | "#9d9d9d";
60
60
  usePointStyle: boolean;
61
61
  padding: number;
62
62
  font: {
@@ -66,10 +66,10 @@ export declare function buildSharedPlugins(opts: {
66
66
  };
67
67
  };
68
68
  tooltip: {
69
- backgroundColor: "#241a2e" | "#ffffff";
70
- titleColor: "#241a2e" | "#eae5ef";
71
- bodyColor: "#5a5462" | "#a89fb3";
72
- borderColor: "rgba(20, 12, 28, 0.15)" | "rgba(255, 255, 255, 0.12)";
69
+ backgroundColor: "#232323" | "#ffffff";
70
+ titleColor: "#1f1f1f" | "#f5f5f5";
71
+ bodyColor: "#616161" | "#9d9d9d";
72
+ borderColor: "rgba(0, 0, 0, 0.15)" | "rgba(255, 255, 255, 0.12)";
73
73
  borderWidth: number;
74
74
  padding: number;
75
75
  cornerRadius: number;
@@ -93,11 +93,11 @@ export declare function buildSharedScales(opts: {
93
93
  }): {
94
94
  sharedScaleBase: {
95
95
  grid: {
96
- color: "rgba(20, 12, 28, 0.08)" | "rgba(255, 255, 255, 0.06)";
96
+ color: "rgba(0, 0, 0, 0.08)" | "rgba(255, 255, 255, 0.06)";
97
97
  drawBorder: boolean;
98
98
  };
99
99
  ticks: {
100
- color: "#6a6275" | "#867a93";
100
+ color: "#7a7a7a" | "#8c8c8c";
101
101
  font: {
102
102
  size: number;
103
103
  };
@@ -105,11 +105,11 @@ export declare function buildSharedScales(opts: {
105
105
  };
106
106
  yScale: {
107
107
  grid: {
108
- color: "rgba(20, 12, 28, 0.08)" | "rgba(255, 255, 255, 0.06)";
108
+ color: "rgba(0, 0, 0, 0.08)" | "rgba(255, 255, 255, 0.06)";
109
109
  drawBorder: boolean;
110
110
  };
111
111
  ticks: {
112
- color: "#6a6275" | "#867a93";
112
+ color: "#7a7a7a" | "#8c8c8c";
113
113
  font: {
114
114
  size: number;
115
115
  };
@@ -34,7 +34,7 @@ export declare function detailChartPlugins(chartTheme: TableChartTheme): {
34
34
  display: boolean;
35
35
  position: "top";
36
36
  labels: {
37
- color: "#5a5462" | "#a89fb3";
37
+ color: "#616161" | "#9d9d9d";
38
38
  usePointStyle: boolean;
39
39
  padding: number;
40
40
  font: {
@@ -43,10 +43,10 @@ export declare function detailChartPlugins(chartTheme: TableChartTheme): {
43
43
  };
44
44
  };
45
45
  tooltip: {
46
- backgroundColor: "#241a2e" | "#ffffff";
47
- titleColor: "#241a2e" | "#eae5ef";
48
- bodyColor: "#5a5462" | "#a89fb3";
49
- borderColor: "rgba(20, 12, 28, 0.15)" | "rgba(255, 255, 255, 0.12)";
46
+ backgroundColor: "#232323" | "#ffffff";
47
+ titleColor: "#1f1f1f" | "#f5f5f5";
48
+ bodyColor: "#616161" | "#9d9d9d";
49
+ borderColor: "rgba(0, 0, 0, 0.15)" | "rgba(255, 255, 255, 0.12)";
50
50
  borderWidth: number;
51
51
  cornerRadius: number;
52
52
  };
@@ -58,10 +58,10 @@ export declare function detailChartPlugins(chartTheme: TableChartTheme): {
58
58
  export declare function detailChartScalesSingleAxis(chartTheme: TableChartTheme): {
59
59
  x: {
60
60
  grid: {
61
- color: "rgba(20, 12, 28, 0.08)" | "rgba(255, 255, 255, 0.06)";
61
+ color: "rgba(0, 0, 0, 0.08)" | "rgba(255, 255, 255, 0.06)";
62
62
  };
63
63
  ticks: {
64
- color: "#6a6275" | "#867a93";
64
+ color: "#7a7a7a" | "#8c8c8c";
65
65
  font: {
66
66
  size: number;
67
67
  };
@@ -69,10 +69,10 @@ export declare function detailChartScalesSingleAxis(chartTheme: TableChartTheme)
69
69
  };
70
70
  y: {
71
71
  grid: {
72
- color: "rgba(20, 12, 28, 0.08)" | "rgba(255, 255, 255, 0.06)";
72
+ color: "rgba(0, 0, 0, 0.08)" | "rgba(255, 255, 255, 0.06)";
73
73
  };
74
74
  ticks: {
75
- color: "#6a6275" | "#867a93";
75
+ color: "#7a7a7a" | "#8c8c8c";
76
76
  font: {
77
77
  size: number;
78
78
  };
@@ -88,10 +88,10 @@ export declare function detailChartScalesSingleAxis(chartTheme: TableChartTheme)
88
88
  export declare function detailChartScalesDualAxis(chartTheme: TableChartTheme): {
89
89
  x: {
90
90
  grid: {
91
- color: "rgba(20, 12, 28, 0.08)" | "rgba(255, 255, 255, 0.06)";
91
+ color: "rgba(0, 0, 0, 0.08)" | "rgba(255, 255, 255, 0.06)";
92
92
  };
93
93
  ticks: {
94
- color: "#6a6275" | "#867a93";
94
+ color: "#7a7a7a" | "#8c8c8c";
95
95
  font: {
96
96
  size: number;
97
97
  };
@@ -102,10 +102,10 @@ export declare function detailChartScalesDualAxis(chartTheme: TableChartTheme):
102
102
  display: boolean;
103
103
  position: "left";
104
104
  grid: {
105
- color: "rgba(20, 12, 28, 0.08)" | "rgba(255, 255, 255, 0.06)";
105
+ color: "rgba(0, 0, 0, 0.08)" | "rgba(255, 255, 255, 0.06)";
106
106
  };
107
107
  ticks: {
108
- color: "#6a6275" | "#867a93";
108
+ color: "#7a7a7a" | "#8c8c8c";
109
109
  font: {
110
110
  size: number;
111
111
  };
@@ -119,7 +119,7 @@ export declare function detailChartScalesDualAxis(chartTheme: TableChartTheme):
119
119
  drawOnChartArea: boolean;
120
120
  };
121
121
  ticks: {
122
- color: "#6a6275" | "#867a93";
122
+ color: "#7a7a7a" | "#8c8c8c";
123
123
  font: {
124
124
  size: number;
125
125
  };
@@ -4,8 +4,13 @@ export declare function formatCompact(value: number): string;
4
4
  export declare function formatCost(value: number, digits?: number): string;
5
5
  /** Format an API-equivalent estimate, using N/A when all usage is unpriced. */
6
6
  export declare function formatEstimatedCost(value: number, unpricedRequests: number, digits?: number): string;
7
- /** Format one request's cost, distinguishing unpriced SuperGrok usage from free usage. */
8
- export declare function formatMessageCost(message: Pick<MessageStats, "provider" | "usage">, digits?: number): string;
7
+ /**
8
+ * Format one request's cost, distinguishing unpriced usage from free usage.
9
+ * Mirrors the server's `unpricedRequestSql`: `xai-oauth` is billed through a
10
+ * subscription, and `costUnpriced` marks a row the ingest path could not price
11
+ * (a scheduled card with no recoverable request timestamp).
12
+ */
13
+ export declare function formatMessageCost(message: Pick<MessageStats, "provider" | "usage" | "costUnpriced">, digits?: number): string;
9
14
  export declare function formatPercent(value: number, digits?: number): string;
10
15
  export declare function formatDurationMs(value: number | null, digits?: number): string;
11
16
  export declare function formatTokensPerSecond(value: number | null): string;
@@ -21,7 +21,7 @@ export interface TraceTheme {
21
21
  tick: string;
22
22
  /** Span label text drawn inside blocks. */
23
23
  spanText: string;
24
- /** Selection outline (brand cyan focus ring). */
24
+ /** Selection outline (theme focus accent). */
25
25
  selection: string;
26
26
  tooltipBackground: string;
27
27
  tooltipTitle: string;
@@ -42,6 +42,8 @@ export interface MessageStats {
42
42
  stopReason: string;
43
43
  errorMessage: string | null;
44
44
  usage: Usage;
45
+ /** Ingest found no tariff to price this request from; its zero is unknown spend. */
46
+ costUnpriced?: boolean;
45
47
  }
46
48
  export interface RequestDetails extends MessageStats {
47
49
  messages: unknown[];
@@ -1,9 +1,11 @@
1
1
  import { Database } from "bun:sqlite";
2
- import type { AgentTypeStats, AggregatedStats, BehaviorModelStats, BehaviorOverallStats, BehaviorTimeSeriesPoint, CostTimeSeriesPoint, DailyActivityPoint, FolderStats, MessageStats, ModelPerformancePoint, ModelStats, ModelTimeSeriesPoint, ProviderAggregate, ProviderHourlyPoint, ProviderTimeSeriesPoint, TimeSeriesPoint, ToolCallStats, ToolModelStats, ToolResultLink, ToolTimeSeriesPoint, ToolUsageStats, UserMessageLink, UserMessageStats } from "./types.js";
2
+ import type { AgentTypeStats, AggregatedStats, BehaviorModelStats, BehaviorOverallStats, BehaviorTimeSeriesPoint, CostTimeSeriesPoint, DailyActivityPoint, FolderStats, MessageStats, MessageStatsInput, ModelPerformancePoint, ModelStats, ModelTimeSeriesPoint, ProviderAggregate, ProviderHourlyPoint, ProviderTimeSeriesPoint, TimeSeriesPoint, ToolCallStats, ToolModelStats, ToolResultLink, ToolTimeSeriesPoint, ToolUsageStats, UserMessageLink, UserMessageStats } from "./types.js";
3
3
  /**
4
4
  * Initialize the database and create tables.
5
5
  */
6
6
  export declare function initDb(): Promise<Database>;
7
+ /** Whether the catalog prices this model on a time-based (scheduled) card. */
8
+ export declare function isScheduledCatalogModel(provider: string, modelId: string): boolean;
7
9
  /**
8
10
  * Get the stored offset for a session file.
9
11
  */
@@ -31,7 +33,7 @@ export declare function setFileOffset(sessionFile: string, offset: number, lastM
31
33
  * stored cost (orchestration-aware) and keeps `premium_requests` monotonic, so
32
34
  * a forced re-parse repairs historical `premium_requests` and cost fix-ups.
33
35
  */
34
- export declare function insertMessageStats(stats: MessageStats[]): number;
36
+ export declare function insertMessageStats(stats: MessageStatsInput[]): number;
35
37
  /**
36
38
  * Get overall aggregated stats.
37
39
  */
@@ -98,6 +100,7 @@ export interface SessionRollupRow {
98
100
  endedAt: number;
99
101
  totalTokens: number;
100
102
  costTotal: number;
103
+ unpricedRequests: number;
101
104
  /** Comma-joined DISTINCT models. */
102
105
  models: string;
103
106
  }
@@ -121,6 +124,25 @@ export declare function getDailyActivity(days?: number): Promise<DailyActivityPo
121
124
  * Settle every full-session backfill after a successful sync pass.
122
125
  */
123
126
  export declare function markSessionBackfillsComplete(): void;
127
+ export interface PruneOrphanResult {
128
+ /** Distinct session files whose rows were removed. */
129
+ prunedFiles: number;
130
+ /** Total rows removed across all session-keyed tables. */
131
+ deletedRows: number;
132
+ }
133
+ /**
134
+ * Delete every row whose `session_file` no longer exists on disk. Deleted
135
+ * projects (tmp test suites, wiped transcripts) otherwise keep surfacing in
136
+ * the Operational Feed and models list forever. Runs in one transaction; the
137
+ * existing-file set is collected by the caller before any DELETE so the
138
+ * comparison is a single in-memory set probe per stored path.
139
+ */
140
+ export declare function pruneOrphanSessionsInDb(database: Database, existingFiles: readonly string[]): PruneOrphanResult;
141
+ /**
142
+ * {@link pruneOrphanSessionsInDb} against the module DB handle. No-op when
143
+ * {@link initDb} has not run.
144
+ */
145
+ export declare function pruneOrphanSessions(existingFiles: readonly string[]): PruneOrphanResult;
124
146
  /**
125
147
  * Insert user-message stats. Idempotent via UNIQUE(session_file, entry_id).
126
148
  * The `WHERE NOT EXISTS` clause matches {@link insertMessageStats}: forks
@@ -1,4 +1,4 @@
1
- import type { AgentType, MessageStats, SessionEntry, ToolCallStats, ToolResultLink, UserMessageLink, UserMessageStats } from "./types.js";
1
+ import type { AgentType, MessageStatsInput, SessionEntry, ToolCallStats, ToolResultLink, UserMessageLink, UserMessageStats } from "./types.js";
2
2
  /**
3
3
  * Classify which agent produced a transcript from its path within the sessions
4
4
  * directory. Layout: `<sessionsDir>/<project>/<file>.jsonl` is the `main`
@@ -16,6 +16,49 @@ export declare function classifyAgentType(sessionPath: string): AgentType;
16
16
  * The folder part uses -- as path separator.
17
17
  */
18
18
  export declare function extractFolderFromPath(sessionPath: string): string;
19
+ /**
20
+ * Decode a session project folder name back to its source cwd, mirroring the
21
+ * encoder in the coding agent's session-paths module: home-relative cwds
22
+ * encode as `-<segments>`, temp-relative ones as `-tmp<segments>`, and legacy
23
+ * absolute ones as `--<absolute>--`, with `/`, `\`, and `:` collapsed to `-`.
24
+ * Returns null when the name carries no decodable cwd.
25
+ */
26
+ export declare function decodeProjectFolderCwd(projectDir: string): string | null;
27
+ /**
28
+ * Whether a session project folder decodes to a cwd inside the OS temp dir.
29
+ * Test suites spawn agents under `os.tmpdir()` and re-create their transcripts
30
+ * on every run; those sessions are throwaway usage that must not reach the
31
+ * stats DB. The `-` collapse in the encoder is lossy (a literal `-` inside a
32
+ * path segment is indistinguishable from a separator), which can only produce
33
+ * false positives here — a skipped ingestion, never a misattributed row.
34
+ */
35
+ export declare function isTempProjectFolder(projectDir: string): boolean;
36
+ /**
37
+ * Token-bucket view for total derivation. Persisted session payloads are
38
+ * outside-controlled (old versions, foreign producers), so every counter is
39
+ * `unknown` and validated at read time.
40
+ */
41
+ export interface UsageBucketView {
42
+ totalTokens?: unknown;
43
+ input?: unknown;
44
+ output?: unknown;
45
+ cacheRead?: unknown;
46
+ cacheWrite?: unknown;
47
+ orchestration?: {
48
+ input?: unknown;
49
+ output?: unknown;
50
+ cacheRead?: unknown;
51
+ } | null;
52
+ }
53
+ /**
54
+ * Total tokens for one usage payload, per the documented contract: the
55
+ * conversation buckets plus provider-reported orchestration tokens. A present
56
+ * finite provider total stays authoritative; a missing or malformed one
57
+ * (absent, string, NaN) is derived from the buckets, which would otherwise
58
+ * persist a zero total next to real token counts. Shared by ingest and the
59
+ * trace builder so stored and displayed totals cannot disagree.
60
+ */
61
+ export declare function resolveUsageTotal(usage: UsageBucketView | null | undefined): number;
19
62
  /** Parse every well-formed entry in a transcript buffer (malformed lines skipped). */
20
63
  export declare function parseAllSessionEntries(bytes: Uint8Array): SessionEntry[];
21
64
  /**
@@ -35,7 +78,7 @@ export declare function parseAllSessionEntries(bytes: Uint8Array): SessionEntry[
35
78
  * entries, preserving offset-based memory behavior for large sessions.
36
79
  */
37
80
  export interface ParseSessionResult {
38
- stats: MessageStats[];
81
+ stats: MessageStatsInput[];
39
82
  userStats: UserMessageStats[];
40
83
  userLinks: UserMessageLink[];
41
84
  toolCalls: ToolCallStats[];
@@ -454,6 +454,8 @@ export interface SessionSummary {
454
454
  subagents: number;
455
455
  totalTokens: number;
456
456
  costTotal: number;
457
+ /** Requests whose zero cost is unknown spend (scheduled card, no timestamp). */
458
+ unpricedRequests: number;
457
459
  models: string[];
458
460
  }
459
461
  export type TraceSpanKind = "turn" | "model" | "tool" | "subagent" | "background";
@@ -533,6 +535,8 @@ export interface TraceSummary {
533
535
  subagents: number;
534
536
  totalTokens: number;
535
537
  costTotal: number;
538
+ /** Model requests whose zero cost is unknown spend, not free usage. */
539
+ unpricedRequests: number;
536
540
  /** Sorted totalMs desc. */
537
541
  toolStats: TraceToolStat[];
538
542
  }
@@ -17,7 +17,7 @@ export declare class TracePathError extends Error {
17
17
  * span assembly changes so browsers don't revalidate stale cached bodies
18
18
  * against an unchanged transcript mtime.
19
19
  */
20
- export declare const TRACE_ETAG_VERSION = 2;
20
+ export declare const TRACE_ETAG_VERSION = 3;
21
21
  /**
22
22
  * Assemble the full span tree for one root session transcript.
23
23
  * Throws {@link TracePathError} for paths outside the sessions root; ENOENT
@@ -33,6 +33,27 @@ export interface MessageStats {
33
33
  usage: Usage;
34
34
  /** Which agent produced this message (main agent, task subagent, advisor) */
35
35
  agentType: AgentType;
36
+ /**
37
+ * Ingest refused to price this request: a scheduled (time-based) card with no
38
+ * recoverable request timestamp, so `usage.cost.total` of 0 is unknown spend
39
+ * rather than a free request. Always written by `rowToMessageStats`; optional
40
+ * only because session fixtures that stand in for ingest input are typed as
41
+ * `MessageStats` too.
42
+ */
43
+ costUnpriced?: boolean;
44
+ }
45
+ /**
46
+ * Session-recorded token usage before pricing: counters are coerced to numbers
47
+ * because their columns are NOT NULL, but `cost` is only present when the
48
+ * session entry actually recorded one. Absence is meaningful — `resolveStoredCost`
49
+ * estimates a request with no recorded price, whereas a recorded zero is a real
50
+ * charge that scheduled cards freeze. `costUnpriced` is omitted because it is a
51
+ * decision of the ingest path, not something the parser or a caller supplies.
52
+ */
53
+ export interface MessageStatsInput extends Omit<MessageStats, "usage" | "costUnpriced"> {
54
+ usage: Omit<Usage, "cost"> & {
55
+ cost?: Partial<Usage["cost"]>;
56
+ };
36
57
  }
37
58
  /**
38
59
  * Full details of a request, including content.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@linxiraos/pi-stats",
4
- "version": "1.1.13",
4
+ "version": "1.1.15",
5
5
  "description": "Local observability dashboard for pi AI usage statistics",
6
6
  "homepage": "https://linxira-os.github.io/zeta/",
7
7
  "author": "Can Boluk",
@@ -39,9 +39,9 @@
39
39
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
40
40
  },
41
41
  "dependencies": {
42
- "@linxiraos/pi-ai": "1.1.13",
43
- "@linxiraos/pi-catalog": "1.1.13",
44
- "@linxiraos/pi-utils": "1.1.13",
42
+ "@linxiraos/pi-ai": "1.1.15",
43
+ "@linxiraos/pi-catalog": "1.1.15",
44
+ "@linxiraos/pi-utils": "1.1.15",
45
45
  "@tailwindcss/node": "^4.3.2",
46
46
  "chart.js": "^4.5.1",
47
47
  "lucide-react": "^1.24.0",
package/src/aggregator.ts CHANGED
@@ -29,6 +29,7 @@ import {
29
29
  insertToolCalls,
30
30
  insertUserMessageStats,
31
31
  markSessionBackfillsComplete,
32
+ pruneOrphanSessions,
32
33
  setFileOffset,
33
34
  updateToolResults,
34
35
  updateUserMessageLinks,
@@ -223,6 +224,9 @@ export async function syncAllSessions(opts?: SyncOptions): Promise<{ processed:
223
224
  await initDb();
224
225
 
225
226
  const files = await listAllSessionFiles();
227
+ // Drop rows for transcripts that no longer exist on disk (deleted temp/test
228
+ // projects) before parsing, so every aggregate reflects live files only.
229
+ pruneOrphanSessions(files);
226
230
  let totalProcessed = 0;
227
231
  let filesProcessed = 0;
228
232
  let completed = 0;
@@ -51,7 +51,7 @@ export function AppLayout({
51
51
  >
52
52
  <div className="stats-mobile-drawer-header">
53
53
  <div className="stats-logo-container">
54
- <span className="stats-logo-text">OH MY PI</span>
54
+ <span className="stats-logo-text">ZETA</span>
55
55
  <span className="stats-logo-subtext">Observability</span>
56
56
  </div>
57
57
  <button
@@ -11,7 +11,7 @@ export function NavRail({ activeSection, onSectionChange, className = "" }: NavR
11
11
  <aside className={`stats-nav-rail ${className}`}>
12
12
  <div className="stats-nav-rail-header">
13
13
  <div className="stats-logo-container">
14
- <span className="stats-logo-text">OH MY PI</span>
14
+ <span className="stats-logo-text">ZETA</span>
15
15
  <span className="stats-logo-subtext">Observability</span>
16
16
  </div>
17
17
  </div>
@@ -4,14 +4,14 @@ import { buildAgentTokenShare } from "../data/view-models";
4
4
  import type { AgentType, AgentTypeStats } from "../types";
5
5
 
6
6
  /**
7
- * Per-agent-type display chrome. Colors follow the OMP brand palette
8
- * (pink -> violet -> cyan) used by the dashboard charts so the bar reads on
9
- * both themes without per-theme overrides.
7
+ * Per-agent-type display chrome. Colors come from the Zeta chart palette
8
+ * (chart-shared.tsx) so the bar reads on both themes without per-theme
9
+ * overrides.
10
10
  */
11
11
  const AGENT_META: Record<AgentType, { label: string; color: string }> = {
12
- main: { label: "Main agent", color: "#ed4abf" },
13
- subagent: { label: "Subagents", color: "#9b4dff" },
14
- advisor: { label: "Advisor", color: "#5ad8e6" },
12
+ main: { label: "Main agent", color: "#9d9d9d" },
13
+ subagent: { label: "Subagents", color: "#61afef" },
14
+ advisor: { label: "Advisor", color: "#56b6c2" },
15
15
  };
16
16
 
17
17
  export interface AgentTokenShareProps {