pi-mega-compact 0.8.17 → 0.8.19
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/dist/extensions/dashboard-server/api-contracts/endpoints.js +12 -0
- package/dist/extensions/dashboard-server/api-contracts.test.js +19 -6
- package/dist/extensions/dashboard-server/helpers.js +37 -0
- package/dist/extensions/dashboard-server/html/all-repos-tab.js +26 -0
- package/dist/extensions/dashboard-server/html/body-open.js +23 -0
- package/dist/extensions/dashboard-server/html/current-repo-tab.js +130 -0
- package/dist/extensions/dashboard-server/html/head-open.js +16 -0
- package/dist/extensions/dashboard-server/html/high-score-tab.js +25 -0
- package/dist/extensions/dashboard-server/html/repo-detail-modal.js +26 -0
- package/dist/extensions/dashboard-server/html/script.js +259 -0
- package/dist/extensions/dashboard-server/html/styles.js +103 -0
- package/dist/extensions/dashboard-server/html/summary-tab.js +19 -0
- package/dist/extensions/dashboard-server/html-template.js +41 -0
- package/dist/extensions/dashboard-server/server.js +83 -0
- package/dist/extensions/mega-events/context-handler.js +22 -8
- package/dist/extensions/mega-runtime/state.js +17 -1
- package/dist/src/store/sqlite/connection.js +35 -0
- package/dist/src/store/sqlite/global-index.js +199 -1
- package/dist/src/store/sqlite/global-index.test.js +136 -0
- package/dist/src/store/sqlite/index-store.js +167 -0
- package/dist/src/store/sqlite/memory.js +54 -0
- package/dist/src/store/sqlite/minhash-lsh.js +47 -0
- package/dist/src/store/sqlite/sessions.js +39 -0
- package/dist/src/store/sqlite/transaction.js +19 -0
- package/dist/src/vectorStore/add.js +260 -0
- package/dist/src/vectorStore/dedup.js +52 -0
- package/dist/src/vectorStore/index.js +10 -0
- package/dist/src/vectorStore/queries.js +83 -0
- package/dist/src/vectorStore/search.js +95 -0
- package/dist/src/vectorStore/session.js +19 -0
- package/dist/src/vectorStore/store.js +105 -0
- package/dist/src/vectorStore/types.js +6 -0
- package/dist/src/vectorStore/utils.js +23 -0
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-BozP_h3c.js → AchievementTiles-DVgFowc-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-BozP_h3c.js.map → AchievementTiles-DVgFowc-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/AchievementsTab-Bg86TWPU.js +2 -0
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-DKgqx1RQ.js.map → AchievementsTab-Bg86TWPU.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-CPvso4-0.js → CacheTab-DBzoH_q_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-CPvso4-0.js.map → CacheTab-DBzoH_q_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-DXD7zbqy.js → ConfigTab-Bj12LgTh.js} +2 -2
- package/extensions/dashboard-client/dist/assets/ConfigTab-Bj12LgTh.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/EventsTab-EC0wMsp_.js +2 -0
- package/extensions/dashboard-client/dist/assets/EventsTab-EC0wMsp_.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{GameTab-Qj19W1Y-.js → GameTab-CISQitbu.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-Qj19W1Y-.js.map → GameTab-CISQitbu.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DA27vA95.js → MetricsTab-us4vVG5D.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DA27vA95.js.map → MetricsTab-us4vVG5D.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/OverviewTab-eJ7vFgBE.js +2 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-eJ7vFgBE.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{ReposTab-_HNR3qYj.js → ReposTab-CiU9AJkb.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-_HNR3qYj.js.map → ReposTab-CiU9AJkb.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/SessionsTab-DaCUvTC7.js +70 -0
- package/extensions/dashboard-client/dist/assets/SessionsTab-DaCUvTC7.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B7aPcBMs.js → TimeSavedCard-BAUSCzq5.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B7aPcBMs.js.map → TimeSavedCard-BAUSCzq5.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-B2JJExE9.js → index-5MPZyayK.js} +10 -10
- package/extensions/dashboard-client/dist/assets/{index-B2JJExE9.js.map → index-5MPZyayK.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-Cfxniu2R.css → index-CDkmYP9l.css} +1 -1
- package/extensions/dashboard-client/dist/assets/useSSE-BSpmWZZi.js +2 -0
- package/extensions/dashboard-client/dist/assets/useSSE-BSpmWZZi.js.map +1 -0
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/package-lock.json +374 -2
- package/extensions/dashboard-client/package.json +2 -1
- package/extensions/dashboard-client/src/App.tsx +5 -1
- package/extensions/dashboard-client/src/api/client.ts +12 -0
- package/extensions/dashboard-client/src/components/ActiveSessionsTable.tsx +185 -0
- package/extensions/dashboard-client/src/components/EventStream.tsx +3 -0
- package/extensions/dashboard-client/src/components/RepoContextStack.tsx +238 -0
- package/extensions/dashboard-client/src/components/SessionsMemoryChart.tsx +199 -0
- package/extensions/dashboard-client/src/main.tsx +2 -0
- package/extensions/dashboard-client/src/styles/repostack.css +87 -0
- package/extensions/dashboard-client/src/styles/sessions.css +193 -0
- package/extensions/dashboard-client/src/tabs/ConfigTab.tsx +6 -3
- package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +49 -1
- package/extensions/dashboard-client/src/tabs/SessionsTab.tsx +193 -0
- package/extensions/dashboard-server/api-contracts/core.ts +25 -0
- package/extensions/dashboard-server/api-contracts/endpoints.ts +102 -0
- package/extensions/dashboard-server/api-contracts/index.ts +10 -1
- package/extensions/dashboard-server/api-contracts.test.ts +20 -7
- package/extensions/dashboard-server/server.ts +100 -0
- package/extensions/mega-events/context-handler.ts +40 -18
- package/extensions/mega-runtime/state.ts +31 -0
- package/package.json +1 -1
- package/src/store/sqlite/global-index.test.ts +222 -0
- package/src/store/sqlite/global-index.ts +316 -1
- package/dist/extensions/dashboard-client/src/hooks/useApi.js +0 -51
- package/dist/extensions/dashboard-client/src/hooks/useSSE.js +0 -63
- package/extensions/dashboard-client/dist/assets/AchievementsTab-DKgqx1RQ.js +0 -2
- package/extensions/dashboard-client/dist/assets/ConfigTab-DXD7zbqy.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/EventsTab-BGeuYvM3.js +0 -2
- package/extensions/dashboard-client/dist/assets/EventsTab-BGeuYvM3.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/OverviewTab-lVGR1Jl_.js +0 -2
- package/extensions/dashboard-client/dist/assets/OverviewTab-lVGR1Jl_.js.map +0 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/styles/repostack.css — Stacked per-repo context bar (S40).
|
|
3
|
+
*
|
|
4
|
+
* Horizontal stacked bar: one colored segment per active repo (aggregating
|
|
5
|
+
* token counts across that repo's active sessions). Depends on base.css
|
|
6
|
+
* tokens (--panel, --border, --text, --muted, --accent, --ok, --warn, --err).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
.repo-stack-card {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: 10px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.repo-stack-card h3 {
|
|
16
|
+
margin: 0;
|
|
17
|
+
font-size: 14px;
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
color: var(--text);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.repo-stack-bar {
|
|
23
|
+
display: flex;
|
|
24
|
+
width: 100%;
|
|
25
|
+
height: 22px;
|
|
26
|
+
border-radius: 6px;
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
border: 1px solid var(--border);
|
|
29
|
+
background: var(--bg);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.repo-stack-segment {
|
|
33
|
+
height: 100%;
|
|
34
|
+
min-width: 2px;
|
|
35
|
+
transition: opacity 0.15s, filter 0.15s;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.repo-stack-segment:hover {
|
|
39
|
+
opacity: 0.85;
|
|
40
|
+
filter: brightness(1.15);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.repo-stack-total {
|
|
44
|
+
font-size: 13px;
|
|
45
|
+
color: var(--text);
|
|
46
|
+
font-variant-numeric: tabular-nums;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.repo-stack-total .pct {
|
|
50
|
+
color: var(--muted);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.repo-stack-legend {
|
|
54
|
+
display: grid;
|
|
55
|
+
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
|
56
|
+
gap: 6px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.repo-stack-legend-item {
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: 6px;
|
|
63
|
+
font-size: 12px;
|
|
64
|
+
color: var(--muted);
|
|
65
|
+
font-variant-numeric: tabular-nums;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.repo-stack-legend-swatch {
|
|
69
|
+
flex-shrink: 0;
|
|
70
|
+
width: 12px;
|
|
71
|
+
height: 12px;
|
|
72
|
+
border-radius: 3px;
|
|
73
|
+
border: 1px solid var(--border);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.repo-stack-legend-name {
|
|
77
|
+
color: var(--text);
|
|
78
|
+
overflow: hidden;
|
|
79
|
+
text-overflow: ellipsis;
|
|
80
|
+
white-space: nowrap;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@media (max-width: 640px) {
|
|
84
|
+
.repo-stack-legend {
|
|
85
|
+
grid-template-columns: 1fr;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/styles/sessions.css — Sessions tab (S39).
|
|
3
|
+
*
|
|
4
|
+
* Stacked memory graph + active sessions table. Depends on base.css
|
|
5
|
+
* tokens (--panel, --border, --text, --muted, --accent, --ok, --warn,
|
|
6
|
+
* --err). Mirrors the conventions in repos-metrics.css + metrics-extra.css.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
.sessions-tab {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: 14px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.sessions-toolbar {
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-wrap: wrap;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 8px;
|
|
20
|
+
padding: 8px 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.sessions-toolbar h2 {
|
|
24
|
+
margin: 0;
|
|
25
|
+
font-size: 14px;
|
|
26
|
+
font-weight: 600;
|
|
27
|
+
color: var(--text);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.sessions-window-select {
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
gap: 2px;
|
|
33
|
+
padding: 2px;
|
|
34
|
+
background: var(--panel);
|
|
35
|
+
border: 1px solid var(--border);
|
|
36
|
+
border-radius: 6px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.sessions-window-btn {
|
|
40
|
+
padding: 4px 10px;
|
|
41
|
+
background: transparent;
|
|
42
|
+
border: none;
|
|
43
|
+
border-radius: 4px;
|
|
44
|
+
color: var(--muted);
|
|
45
|
+
font-size: 12px;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
transition: background 0.15s, color 0.15s;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.sessions-window-btn:hover {
|
|
51
|
+
color: var(--text);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.sessions-window-btn.active {
|
|
55
|
+
background: var(--accent);
|
|
56
|
+
color: #0d1117;
|
|
57
|
+
font-weight: 600;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.sessions-summary-tiles {
|
|
61
|
+
display: grid;
|
|
62
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
63
|
+
gap: 10px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.sessions-chart {
|
|
67
|
+
background: var(--panel);
|
|
68
|
+
border: 1px solid var(--border);
|
|
69
|
+
border-radius: 8px;
|
|
70
|
+
padding: 12px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.sessions-chart-empty,
|
|
74
|
+
.sessions-empty {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
justify-content: center;
|
|
78
|
+
min-height: 160px;
|
|
79
|
+
color: var(--muted);
|
|
80
|
+
font-size: 14px;
|
|
81
|
+
background: var(--panel);
|
|
82
|
+
border: 1px solid var(--border);
|
|
83
|
+
border-radius: 8px;
|
|
84
|
+
padding: 32px;
|
|
85
|
+
text-align: center;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.sessions-table-scroll {
|
|
89
|
+
overflow-x: auto;
|
|
90
|
+
background: var(--panel);
|
|
91
|
+
border: 1px solid var(--border);
|
|
92
|
+
border-radius: 8px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.sessions-table {
|
|
96
|
+
width: 100%;
|
|
97
|
+
border-collapse: collapse;
|
|
98
|
+
font-size: 13px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.sessions-table th,
|
|
102
|
+
.sessions-table td {
|
|
103
|
+
padding: 8px 10px;
|
|
104
|
+
text-align: left;
|
|
105
|
+
border-bottom: 1px solid var(--border);
|
|
106
|
+
white-space: nowrap;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.sessions-table th {
|
|
110
|
+
font-size: 11px;
|
|
111
|
+
font-weight: 600;
|
|
112
|
+
text-transform: uppercase;
|
|
113
|
+
letter-spacing: 0.5px;
|
|
114
|
+
color: var(--muted);
|
|
115
|
+
background: rgba(255, 255, 255, 0.02);
|
|
116
|
+
position: sticky;
|
|
117
|
+
top: 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.sessions-table td.num {
|
|
121
|
+
text-align: right;
|
|
122
|
+
font-variant-numeric: tabular-nums;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.sessions-table tr:last-child td {
|
|
126
|
+
border-bottom: none;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.sessions-table tr:hover td {
|
|
130
|
+
background: rgba(88, 166, 255, 0.06);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.sessions-empty-row td {
|
|
134
|
+
color: var(--muted);
|
|
135
|
+
font-style: italic;
|
|
136
|
+
text-align: center;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.sessions-pct-pill {
|
|
140
|
+
display: inline-block;
|
|
141
|
+
padding: 2px 8px;
|
|
142
|
+
border-radius: 10px;
|
|
143
|
+
font-size: 11px;
|
|
144
|
+
font-weight: 600;
|
|
145
|
+
font-variant-numeric: tabular-nums;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.sessions-pct-ok {
|
|
149
|
+
background: rgba(63, 185, 80, 0.15);
|
|
150
|
+
color: var(--ok);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.sessions-pct-warn {
|
|
154
|
+
background: rgba(210, 153, 34, 0.15);
|
|
155
|
+
color: var(--warn);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.sessions-pct-err {
|
|
159
|
+
background: rgba(248, 81, 73, 0.15);
|
|
160
|
+
color: var(--err);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.sessions-sparkline {
|
|
164
|
+
display: inline-block;
|
|
165
|
+
vertical-align: middle;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.sessions-sparkline-empty {
|
|
169
|
+
color: var(--muted);
|
|
170
|
+
font-size: 11px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.sessions-meta {
|
|
174
|
+
font-size: 11px;
|
|
175
|
+
color: var(--muted);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.sessions-footer {
|
|
179
|
+
font-size: 11px;
|
|
180
|
+
color: var(--muted);
|
|
181
|
+
margin-top: 6px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@media (max-width: 640px) {
|
|
185
|
+
.sessions-summary-tiles {
|
|
186
|
+
grid-template-columns: 1fr;
|
|
187
|
+
}
|
|
188
|
+
.sessions-table th,
|
|
189
|
+
.sessions-table td {
|
|
190
|
+
font-size: 12px;
|
|
191
|
+
padding: 6px 8px;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -29,10 +29,13 @@ interface FlatGameState {
|
|
|
29
29
|
}
|
|
30
30
|
type FlatGameStatePatch = Partial<FlatGameState>;
|
|
31
31
|
|
|
32
|
-
/** Extended snapshot config — the runtime includes tierPct (not in contract).
|
|
33
|
-
interface
|
|
32
|
+
/** Extended snapshot config — the runtime includes tierPct (not in contract).
|
|
33
|
+
* Implemented as a type intersection rather than `interface extends` because
|
|
34
|
+
* TypeScript (TS2499) forbids `interface extends T["k"]` indexed-access
|
|
35
|
+
* parents; the resulting type is structurally identical. */
|
|
36
|
+
type ConfigExt = SnapshotResponse["config"] & {
|
|
34
37
|
tierPct?: number | null;
|
|
35
|
-
}
|
|
38
|
+
};
|
|
36
39
|
|
|
37
40
|
/* Tooltips copied verbatim from extensions/dashboard-server/html.ts */
|
|
38
41
|
const TOOLTIPS = {
|
|
@@ -18,9 +18,19 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import type React from "react";
|
|
21
|
-
import
|
|
21
|
+
import { useCallback, useEffect, useMemo } from "react";
|
|
22
|
+
import { useApi } from "../hooks/useApi";
|
|
23
|
+
import { useSSE } from "../hooks/useSSE";
|
|
24
|
+
import { fetchSessions } from "../api/client";
|
|
25
|
+
import type {
|
|
26
|
+
SnapshotResponse,
|
|
27
|
+
SseEvent,
|
|
28
|
+
SseSessionSample,
|
|
29
|
+
SessionsResponse,
|
|
30
|
+
} from "@contracts";
|
|
22
31
|
import type { RuntimeSnapshot } from "../utils/types";
|
|
23
32
|
import { ContextGauge } from "../components/ContextGauge";
|
|
33
|
+
import { RepoContextStack } from "../components/RepoContextStack";
|
|
24
34
|
import { TriggerStatus } from "../components/TriggerStatus";
|
|
25
35
|
import { VectorStoreCard } from "../components/VectorStoreCard";
|
|
26
36
|
import { RepoAllSessionsCard } from "../components/RepoAllSessionsCard";
|
|
@@ -32,6 +42,11 @@ import { CacheHitsCard } from "../components/CacheHitsCard";
|
|
|
32
42
|
import { TimeSavedCard } from "../components/TimeSavedCard";
|
|
33
43
|
import { LegendCard } from "../components/LegendCard";
|
|
34
44
|
|
|
45
|
+
/** Type guard: narrows an SSE event to a session_sample event. */
|
|
46
|
+
function isSessionSample(e: SseEvent): e is SseSessionSample {
|
|
47
|
+
return e.type === "session_sample";
|
|
48
|
+
}
|
|
49
|
+
|
|
35
50
|
export interface OverviewTabProps {
|
|
36
51
|
snapshot: SnapshotResponse | null;
|
|
37
52
|
loading: boolean;
|
|
@@ -52,6 +67,34 @@ export default function OverviewTab({
|
|
|
52
67
|
loading,
|
|
53
68
|
error,
|
|
54
69
|
}: OverviewTabProps): React.ReactElement {
|
|
70
|
+
// S40: real-time per-repo context via /api/sessions + SSE. Mirrors the
|
|
71
|
+
// SessionsTab pattern: 2s poll + session_sample SSE refetch.
|
|
72
|
+
const {
|
|
73
|
+
data: sessionsData,
|
|
74
|
+
error: sessionsErr,
|
|
75
|
+
loading: sessionsLoading,
|
|
76
|
+
refetch: refetchSessions,
|
|
77
|
+
} = useApi<SessionsResponse>(
|
|
78
|
+
useCallback(() => fetchSessions(), []),
|
|
79
|
+
{ pollInterval: 2_000 },
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const { events } = useSSE();
|
|
83
|
+
|
|
84
|
+
const sampleEvents = useMemo(
|
|
85
|
+
() => events.filter(isSessionSample),
|
|
86
|
+
[events],
|
|
87
|
+
);
|
|
88
|
+
const lastSampleTs = useMemo(() => {
|
|
89
|
+
if (sampleEvents.length === 0) return null;
|
|
90
|
+
return sampleEvents[sampleEvents.length - 1].ts;
|
|
91
|
+
}, [sampleEvents]);
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (lastSampleTs == null) return;
|
|
95
|
+
refetchSessions();
|
|
96
|
+
}, [lastSampleTs, refetchSessions]);
|
|
97
|
+
|
|
55
98
|
if (loading && !snapshot)
|
|
56
99
|
return <div className="tab-stub">Loading snapshot…</div>;
|
|
57
100
|
if (error && !snapshot)
|
|
@@ -83,6 +126,11 @@ export default function OverviewTab({
|
|
|
83
126
|
percent={context.percent}
|
|
84
127
|
contextWindow={context.contextWindow}
|
|
85
128
|
/>
|
|
129
|
+
<RepoContextStack
|
|
130
|
+
sessions={sessionsData}
|
|
131
|
+
loading={sessionsLoading}
|
|
132
|
+
error={sessionsErr}
|
|
133
|
+
/>
|
|
86
134
|
<TriggerStatus
|
|
87
135
|
armed={trigger.armed}
|
|
88
136
|
ready={trigger.ready}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/tabs/SessionsTab.tsx — Sessions tab (S39).
|
|
3
|
+
*
|
|
4
|
+
* Renders: window selector (5/15/30/60 min), sessions summary tiles,
|
|
5
|
+
* the stacked-memory graph (SessionsMemoryChart), and the active sessions
|
|
6
|
+
* table (ActiveSessionsTable).
|
|
7
|
+
*
|
|
8
|
+
* Data sources: fetchSessions() + fetchSessionTimeseries(minutes). Both poll
|
|
9
|
+
* every 2s for near-real-time updates. useSSE() listens for session_sample
|
|
10
|
+
* events emitted by appendTokenSample (Step 5) so the chart refetches
|
|
11
|
+
* instantly between polls when a new token sample is appended to events.log.
|
|
12
|
+
*
|
|
13
|
+
* Empty state: shows "No active sessions." when the /api/sessions response
|
|
14
|
+
* returns an empty array (e.g. no pi processes are running yet).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type React from "react";
|
|
18
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
19
|
+
import { useApi } from "../hooks/useApi";
|
|
20
|
+
import { useSSE } from "../hooks/useSSE";
|
|
21
|
+
import { fetchSessions, fetchSessionTimeseries } from "../api/client";
|
|
22
|
+
import type { SseEvent, SseSessionSample, SessionsResponse, SessionTimeseriesResponse } from "@contracts";
|
|
23
|
+
import { SessionsMemoryChart } from "../components/SessionsMemoryChart";
|
|
24
|
+
import { ActiveSessionsTable } from "../components/ActiveSessionsTable";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Shape of the {@link SseSessionSample} SSE event appended to events.log by
|
|
28
|
+
* `appendTokenSample` (src/store/sqlite/global-index.ts). The contract now
|
|
29
|
+
* defines this variant as part of the {@link SseEvent} union (Step 5); the
|
|
30
|
+
* local alias keeps the surface terser in this file.
|
|
31
|
+
*/
|
|
32
|
+
type SessionSampleEvent = SseSessionSample;
|
|
33
|
+
|
|
34
|
+
/** Type guard: narrows an SSE event to a session_sample event. */
|
|
35
|
+
function isSessionSample(
|
|
36
|
+
e: SseEvent,
|
|
37
|
+
): e is SessionSampleEvent {
|
|
38
|
+
return e.type === "session_sample";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const WINDOWS: ReadonlyArray<{ value: number; label: string }> = [
|
|
42
|
+
{ value: 5, label: "5m" },
|
|
43
|
+
{ value: 15, label: "15m" },
|
|
44
|
+
{ value: 30, label: "30m" },
|
|
45
|
+
{ value: 60, label: "60m" },
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
interface SummaryTilesProps {
|
|
49
|
+
sessions: SessionsResponse;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function SummaryTiles({ sessions }: SummaryTilesProps): React.ReactElement {
|
|
53
|
+
const active = sessions.sessions.length;
|
|
54
|
+
const combinedTokens = sessions.sessions.reduce(
|
|
55
|
+
(sum, s) => sum + (s.tokens ?? 0),
|
|
56
|
+
0,
|
|
57
|
+
);
|
|
58
|
+
const maxWindow = sessions.sessions.reduce(
|
|
59
|
+
(max, s) => Math.max(max, s.ctxWindow),
|
|
60
|
+
0,
|
|
61
|
+
);
|
|
62
|
+
const combinedPct = maxWindow > 0 ? (combinedTokens / maxWindow) * 100 : 0;
|
|
63
|
+
return (
|
|
64
|
+
<div className="sessions-summary-tiles">
|
|
65
|
+
<div className="tile">
|
|
66
|
+
<span className="tile-label">Active sessions</span>
|
|
67
|
+
<span className="tile-value">{active}</span>
|
|
68
|
+
</div>
|
|
69
|
+
<div className="tile">
|
|
70
|
+
<span className="tile-label">Combined tokens</span>
|
|
71
|
+
<span className="tile-value">
|
|
72
|
+
{combinedTokens >= 100_000
|
|
73
|
+
? `${(combinedTokens / 1000).toFixed(0)}k`
|
|
74
|
+
: combinedTokens.toLocaleString()}
|
|
75
|
+
</span>
|
|
76
|
+
</div>
|
|
77
|
+
<div className="tile">
|
|
78
|
+
<span className="tile-label">Combined % of max window</span>
|
|
79
|
+
<span className="tile-value">{Math.round(combinedPct)}%</span>
|
|
80
|
+
</div>
|
|
81
|
+
<div className="tile">
|
|
82
|
+
<span className="tile-label">Pruned (stale)</span>
|
|
83
|
+
<span className="tile-value">{sessions.pruned}</span>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default function SessionsTab(): React.ReactElement {
|
|
90
|
+
const [minutes, setMinutes] = useState(30);
|
|
91
|
+
|
|
92
|
+
const {
|
|
93
|
+
data: sessionsData,
|
|
94
|
+
error: sessionsErr,
|
|
95
|
+
refetch: refetchSessions,
|
|
96
|
+
} = useApi<SessionsResponse>(
|
|
97
|
+
useCallback(() => fetchSessions(), []),
|
|
98
|
+
{ pollInterval: 2_000 },
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const {
|
|
102
|
+
data: timeseriesData,
|
|
103
|
+
error: timeseriesErr,
|
|
104
|
+
refetch: refetchTimeseries,
|
|
105
|
+
} = useApi<SessionTimeseriesResponse>(
|
|
106
|
+
useCallback(() => fetchSessionTimeseries(minutes), [minutes]),
|
|
107
|
+
{ pollInterval: 2_000 },
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const { events } = useSSE();
|
|
111
|
+
|
|
112
|
+
// Filter SSE events for session_sample pushes (Step 5) and trigger an
|
|
113
|
+
// immediate refetch when a new sample arrives, so the chart updates
|
|
114
|
+
// between the 2s polls. The events array grows as new samples arrive; the
|
|
115
|
+
// length-based dependency means the effect re-runs when a new sample event
|
|
116
|
+
// lands (not on every poll). We debounce via a microtask guard.
|
|
117
|
+
const sampleEvents = useMemo(
|
|
118
|
+
() => events.filter(isSessionSample),
|
|
119
|
+
[events],
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const lastSampleTs = useMemo(() => {
|
|
123
|
+
if (sampleEvents.length === 0) return null;
|
|
124
|
+
return sampleEvents[sampleEvents.length - 1].ts;
|
|
125
|
+
}, [sampleEvents]);
|
|
126
|
+
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (lastSampleTs == null) return;
|
|
129
|
+
refetchSessions();
|
|
130
|
+
refetchTimeseries();
|
|
131
|
+
}, [lastSampleTs, refetchSessions, refetchTimeseries]);
|
|
132
|
+
|
|
133
|
+
if (sessionsErr && !sessionsData) {
|
|
134
|
+
return (
|
|
135
|
+
<div className="tab-stub">
|
|
136
|
+
Error loading sessions: {sessionsErr.message}
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (timeseriesErr && !timeseriesData) {
|
|
141
|
+
return (
|
|
142
|
+
<div className="tab-stub">
|
|
143
|
+
Error loading timeseries: {timeseriesErr.message}
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (!sessionsData) {
|
|
148
|
+
return <div className="tab-stub">Loading sessions…</div>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const sessions = sessionsData.sessions;
|
|
152
|
+
const series = timeseriesData?.series ?? [];
|
|
153
|
+
const totals = timeseriesData?.totals ?? [];
|
|
154
|
+
const windowLabel =
|
|
155
|
+
WINDOWS.find((w) => w.value === minutes)?.label ?? `${minutes}m`;
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div className="sessions-tab">
|
|
159
|
+
<div className="sessions-toolbar">
|
|
160
|
+
<h2>Session Memory Graph</h2>
|
|
161
|
+
<div className="sessions-window-select">
|
|
162
|
+
{WINDOWS.map((w) => (
|
|
163
|
+
<button
|
|
164
|
+
key={w.value}
|
|
165
|
+
type="button"
|
|
166
|
+
className={`sessions-window-btn ${minutes === w.value ? "active" : ""}`}
|
|
167
|
+
onClick={() => setMinutes(w.value)}
|
|
168
|
+
aria-pressed={minutes === w.value}
|
|
169
|
+
>
|
|
170
|
+
{w.label}
|
|
171
|
+
</button>
|
|
172
|
+
))}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
{sessions.length === 0 ? (
|
|
177
|
+
<div className="sessions-empty">No active sessions.</div>
|
|
178
|
+
) : (
|
|
179
|
+
<>
|
|
180
|
+
<SummaryTiles sessions={sessionsData} />
|
|
181
|
+
<SessionsMemoryChart series={series} totals={totals} />
|
|
182
|
+
<h2 className="section-header">Active Sessions</h2>
|
|
183
|
+
<ActiveSessionsTable sessions={sessions} series={series} />
|
|
184
|
+
<div className="sessions-footer">
|
|
185
|
+
{series.length} series · {totals.length} timestamps ·{" "}
|
|
186
|
+
{windowLabel} window · updated{" "}
|
|
187
|
+
{timeseriesData?.updatedAt ?? sessionsData.updatedAt}
|
|
188
|
+
</div>
|
|
189
|
+
</>
|
|
190
|
+
)}
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
@@ -300,3 +300,28 @@ export interface SseCrewBanditChosen {
|
|
|
300
300
|
/** Cumulative regret of the bandit policy (dimensionless). */
|
|
301
301
|
regret: number;
|
|
302
302
|
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* SSE event appened to events.log by `appendTokenSample`
|
|
306
|
+
* (src/store/sqlite/global-index.ts) on every material snapshot. The existing
|
|
307
|
+
* `GET /api/events` SSE handler tails events.log line-by-line, so these
|
|
308
|
+
* `session_sample` lines stream to the dashboard client for free
|
|
309
|
+
* (real-time chart updates between the 2s polls in the Sessions tab).
|
|
310
|
+
*
|
|
311
|
+
* Wire shape mirrors {@link SseCompactStart} etc. with `ts` (ISO 8601 string)
|
|
312
|
+
* plus `type` (`'session_sample'`) and the per-session payload.
|
|
313
|
+
*
|
|
314
|
+
* Served via `GET /api/events` (Server-Sent Events stream).
|
|
315
|
+
*/
|
|
316
|
+
export interface SseSessionSample {
|
|
317
|
+
/** Discriminator. Always `'session_sample'`. */
|
|
318
|
+
type: 'session_sample';
|
|
319
|
+
/** ISO 8601 timestamp of the sample (coincides with the token_samples row `ts`). */
|
|
320
|
+
ts: string;
|
|
321
|
+
/** Session identifier the sample belongs to. */
|
|
322
|
+
sessionId: string;
|
|
323
|
+
/** Context window token count at this sample (tokens). */
|
|
324
|
+
tokens: number;
|
|
325
|
+
/** Context pressure percentage (0–100). */
|
|
326
|
+
percent: number;
|
|
327
|
+
}
|