pi-mega-compact 0.7.5 → 0.7.7
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/README.md +131 -27
- package/dist/extensions/dashboard-server.js +137 -4
- package/dist/extensions/dashboard-server.test.js +59 -0
- package/dist/extensions/mega-pipeline.js +25 -1
- package/dist/extensions/mega-runtime.js +31 -1
- package/dist/src/store/sqlite.cachehit.test.js +55 -0
- package/dist/src/store/sqlite.js +23 -0
- package/extensions/dashboard-server.test.ts +69 -0
- package/extensions/dashboard-server.ts +141 -1
- package/extensions/mega-dashboard.ts +17 -0
- package/extensions/mega-pipeline.ts +18 -1
- package/extensions/mega-runtime.ts +40 -1
- package/package.json +1 -1
- package/src/store/sqlite.cachehit.test.ts +68 -0
- package/src/store/sqlite.ts +28 -0
- package/dist/extensions/openclaw-mega-compact.js +0 -291
- package/dist/src/minilm.js +0 -92
- package/dist/src/wordpiece.js +0 -129
package/README.md
CHANGED
|
@@ -6,19 +6,37 @@ sessions into a **local SQLite store** and offers **deduped inline recall** —
|
|
|
6
6
|
running **locally inside the extension**, with **no remote MCP server** and
|
|
7
7
|
**zero network calls at runtime** (PREVENT-PI-004).
|
|
8
8
|
|
|
9
|
-
> **
|
|
10
|
-
> (`DatabaseSync`,
|
|
11
|
-
>
|
|
12
|
-
>
|
|
13
|
-
> `.checkpoints.json.gz` snapshots are retained as disaster-recovery fallbacks
|
|
14
|
-
> and auto-imported on first run. The S24 line ties auto-compact, the tier
|
|
15
|
-
> label, trim depth, and durable-memory review to one **unified pressure
|
|
16
|
-
> signal**, adds a **cross-repo memory-RAG index**, and relieves context
|
|
17
|
-
> **during team runs** (not just at the end).
|
|
9
|
+
> **Status - v0.7.7.** Storage uses the built-in `node:sqlite` backend
|
|
10
|
+
> (`DatabaseSync`, Node >=22.13): zero native build step, fully local, and zero
|
|
11
|
+
> network at runtime. See [RELEASE_NOTES.md](RELEASE_NOTES.md) for the full
|
|
12
|
+
> changelog.
|
|
18
13
|
|
|
19
14
|
---
|
|
20
15
|
|
|
21
|
-
##
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Local & private** - everything stays on your disk. No telemetry, no API key, no MCP server, no cloud. The only network surface is an optional localhost dashboard you open yourself.
|
|
19
|
+
- **Two-layer compaction** - a non-destructive live summary every LLM call, plus durable checkpoints that relieve context mid-run (not just at the end).
|
|
20
|
+
- **Vector store + dedup** - each checkpoint is embedded and stored locally; an L0->L2 + RAPTOR cascade collapses duplicate work so storage and recall stay lean.
|
|
21
|
+
- **Automatic recall** - the most relevant checkpoints are re-inlined on resume or branch switch; cross-repo memory-RAG augments a thin store with decisions from other repos.
|
|
22
|
+
- **Live dashboard** - a localhost-only view of token usage, store stats, savings, per-repo activity, and an **Active Repos tab** that shows every currently-open session (last 30 minutes) side by side — so running multiple sessions at once is visible in one place.
|
|
23
|
+
- **Database maintenance** - `/mega-db-*` commands plus best-effort auto-maintenance on session start.
|
|
24
|
+
|
|
25
|
+
## Table of contents
|
|
26
|
+
|
|
27
|
+
- [Overview](#overview)
|
|
28
|
+
- [How it works](#how-it-works)
|
|
29
|
+
- [Installation](#installation)
|
|
30
|
+
- [Usage](#usage)
|
|
31
|
+
- [Configuration](#configuration)
|
|
32
|
+
- [Dashboard](#dashboard)
|
|
33
|
+
- [Architecture](#architecture)
|
|
34
|
+
- [Development](#development)
|
|
35
|
+
- [Testing & bug reports](#testing--bug-reports)
|
|
36
|
+
- [Acknowledgements](#acknowledgements)
|
|
37
|
+
- [License](#license)
|
|
38
|
+
|
|
39
|
+
## Overview
|
|
22
40
|
|
|
23
41
|
pi's context window is finite. When a session gets long — especially a team run
|
|
24
42
|
with sub-agents — pi-mega-compact keeps it going without overflowing:
|
|
@@ -214,7 +232,7 @@ rm -f ~/.pi/agent/extensions/pi-mega-compact
|
|
|
214
232
|
|
|
215
233
|
---
|
|
216
234
|
|
|
217
|
-
##
|
|
235
|
+
## Usage
|
|
218
236
|
|
|
219
237
|
Once installed and registered, pi-mega-compact runs **automatically** — you don't
|
|
220
238
|
have to drive it. Past the context threshold it compacts in the background and
|
|
@@ -222,6 +240,8 @@ drops a checkpoint; on resume it re-inlines the relevant ones silently.
|
|
|
222
240
|
|
|
223
241
|
The commands (slash commands inside pi):
|
|
224
242
|
|
|
243
|
+
### Commands
|
|
244
|
+
|
|
225
245
|
| Command | Description |
|
|
226
246
|
|---|---|
|
|
227
247
|
| `/mega-compact [summary...]` | Manually compact the current session. A summary arg is used verbatim; otherwise the COLLAPSE heuristics build one. Persists a `chkpt_xxx`. |
|
|
@@ -234,18 +254,26 @@ The commands (slash commands inside pi):
|
|
|
234
254
|
| `/mega-view <chkpt\|recent>` | Show a checkpoint's verbatim original region. |
|
|
235
255
|
| `/mega-help` | Explain the toolbar widget terms (live tier, gate, dedup, tokens saved). |
|
|
236
256
|
| `/mega-compat-check` | Detect extension conflicts (duplicate commands / overlapping handlers) across installed pi extensions. |
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
| `/mega-
|
|
240
|
-
| `/mega-
|
|
257
|
+
| `/mega-db-stats` | Show mega-compact SQLite DB stats: table row counts, disk footprint (db + WAL + SHM), page count, freelist %, WAL frames. Read-only; safe any time. |
|
|
258
|
+
| `/mega-db-prune [days]` | DELETE `raw_transcript` + `checkpoint_epochs` rows older than N days (default 30) + orphan `dedup_mirror` rows. Reports deleted counts + reclaimed bytes. |
|
|
259
|
+
| `/mega-db-vacuum` | `VACUUM` the DB (rebuild pages, reclaim freelist). Heavy: briefly doubles disk usage. |
|
|
260
|
+
| `/mega-db-check` | `PRAGMA integrity_check` + `wal_checkpoint(TRUNCATE)`. Fold the WAL into the main file and verify DB health. Use after a crash. |
|
|
261
|
+
| `/mega-db-reconcile` | Fix `dedup_mirror.ref_count` drift vs actual `raw_transcript` refs, delete orphan dedup rows, backfill missing `content_ref`. Run after `/mega-db-prune` or a crash. |
|
|
262
|
+
| `/mega-dashboard [open]` | Start the **localhost-only** live dashboard and open it in a browser (token gauge, store stats, live event stream, per-repo + All-repos/Summary views, cross-repo drift). |
|
|
263
|
+
| `/mega-dashboard-status` | Report dashboard server status (port / url / live). |
|
|
241
264
|
| `/mega-dashboard-stop` | Stop the dashboard server. |
|
|
242
265
|
|
|
266
|
+
### The tier system
|
|
267
|
+
|
|
268
|
+
The **tier** you see in the toolbar and dashboard is a *live pressure band* (`low` → `medium` → `high` → `ultra` → `mega`) that climbs automatically as your context window fills and falls back as it's relieved — it is driven by `currentTokens / effectiveThreshold`, not a manual setting. The base compaction *threshold* is set by `MEGACOMPACT_TIER` at startup as a **% of the model context window** (`low` 50% · `medium` 60% · `high` 70% · `ultra` 70% · `mega` 75%; default `low`) — the fire point is `tierPct × contextWindow`, so it always lands below pi's native ~80% auto-compaction (any model size). The old static token amounts (50k/100k/200k/1M/10M) are now only the boot fallback used before the first context event reports a window. `/mega-tier` was removed in v0.7.6. Higher pressure also deepens the live trim and reviews durable memory more often — the whole system reacts as one.
|
|
269
|
+
|
|
270
|
+
|
|
243
271
|
### Live stats widget
|
|
244
272
|
|
|
245
273
|
Above the pi editor the extension shows a compact widget:
|
|
246
274
|
|
|
247
275
|
```
|
|
248
|
-
⚡ high·low v0.
|
|
276
|
+
⚡ high·low v0.7.7 │ 142k/200k tokens (71%) │ 3 chkpts │ 🤖 2 agents │ turn 5
|
|
249
277
|
◐ armed │ dedup: 92% │ saved: 45k tok
|
|
250
278
|
```
|
|
251
279
|
|
|
@@ -259,6 +287,13 @@ Above the pi editor the extension shows a compact widget:
|
|
|
259
287
|
- **Dedup hit rate** — % of checkpoints collapsed as duplicates
|
|
260
288
|
- **Active agents / turn** — sub-agent count and conversation turn (when > 0)
|
|
261
289
|
|
|
290
|
+
> **DB housekeeping** — `/mega-db-stats` / `prune` / `vacuum` / `check` / `reconcile`
|
|
291
|
+
> give you manual control over the SQLite store. In addition, a best-effort
|
|
292
|
+
> **auto-maintenance** pass runs on `session_start`: it prunes rows older than
|
|
293
|
+
> 30d, checkpoints the WAL if it's over 10 MB, and VACUUMs if the DB is over
|
|
294
|
+
> 100 MB AND the freelist is >20% of pages. It never blocks session start and
|
|
295
|
+
> logs a one-line summary to the diagnostic log. (v0.7.6+)
|
|
296
|
+
|
|
262
297
|
---
|
|
263
298
|
|
|
264
299
|
## Configuration (env-backed)
|
|
@@ -266,6 +301,8 @@ Above the pi editor the extension shows a compact widget:
|
|
|
266
301
|
All defaults are in `src/config/dedup.ts` (single source of truth). Set env vars
|
|
267
302
|
before starting pi.
|
|
268
303
|
|
|
304
|
+
### Core settings
|
|
305
|
+
|
|
269
306
|
| Variable | Default | Meaning |
|
|
270
307
|
|---|---|---|
|
|
271
308
|
| `MEGACOMPACT_FAST_GATE_PCT` | `70` | Context-usage % that arms the auto-trigger. Defaults to the tier's % of window (`tierPct*100`): low 50 · med 60 · high 70 · ultra 70 · mega 75. Override raises the arming floor. |
|
|
@@ -279,7 +316,7 @@ before starting pi.
|
|
|
279
316
|
| `MEGACOMPACT_DEDUP_SIM` | `0.90` | Cosine threshold to collapse near-dupes. |
|
|
280
317
|
| `MEGACOMPACT_STATE_DIR` | _(none — per-repo default)_ | Override the store location. By default state is per-repo at `<repo>/.pi/mega-compact/`; this env var forces a single explicit dir (used as the fallback for non-git cwds). |
|
|
281
318
|
|
|
282
|
-
|
|
319
|
+
### Dedup pipeline flags
|
|
283
320
|
|
|
284
321
|
These gate the L0/L1/L2/RAPTOR dedup tiers. Defaults reproduce the all-active
|
|
285
322
|
behavior. `MARK_ONLY_*` tiers run + record their decision but never
|
|
@@ -309,7 +346,7 @@ See `docs/DEDUP_RUNBOOK.md` for incident response (SEV tiers, first-15-min
|
|
|
309
346
|
checklist, MARK_ONLY degrade) and `docs/RETENTION_POLICY.md` for TTL / soft-delete
|
|
310
347
|
/ VACUUM.
|
|
311
348
|
|
|
312
|
-
|
|
349
|
+
### Continuity & memory
|
|
313
350
|
|
|
314
351
|
| Variable | Default | Meaning |
|
|
315
352
|
|---|---|---|
|
|
@@ -320,17 +357,84 @@ checklist, MARK_ONLY degrade) and `docs/RETENTION_POLICY.md` for TTL / soft-dele
|
|
|
320
357
|
| `MEGACOMPACT_MEMORY_REVIEW_INTERVAL` | `10` | Turns between auto-review cycles. |
|
|
321
358
|
| `MEGACOMPACT_PGLITE_DISABLED` | _(unset — index on)_ | Kill-switch for the PGlite/HNSW cross-repo index; set `1`/`true` to disable (falls back to sync per-session scan). |
|
|
322
359
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
The localhost-only dashboard
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
360
|
+
## Dashboard
|
|
361
|
+
|
|
362
|
+
The localhost-only dashboard (started with `/mega-dashboard`) is a single-page
|
|
363
|
+
app served from a detached child process on `127.0.0.1` (random port in
|
|
364
|
+
9320–9329). Every API is read-only — the server never writes the index or your
|
|
365
|
+
store. It reads the machine-wide `repo_registry`
|
|
366
|
+
(`~/.mega-compact-index/index.sqlite`) plus the current repo's own `node:sqlite`
|
|
367
|
+
store.
|
|
368
|
+
|
|
369
|
+
### Tabs
|
|
370
|
+
|
|
371
|
+
- **Current repo** — the live single-session view: context-window gauge, trigger
|
|
372
|
+
status, the Vector Store (checkpoints / dropped / kept / freed / injected /
|
|
373
|
+
dedup / collapsed), the repo-wide aggregate, the Data Safety card, the live
|
|
374
|
+
Model & Cost Savings card, and crew/agent activity.
|
|
375
|
+
- **All repos** — a machine-wide table aggregated from `repo_registry`
|
|
376
|
+
(`GET /api/index`): one row per repo with checkpoints, tokens saved,
|
|
377
|
+
compressed-originals, last-compacted, and active model. Each row opens a
|
|
378
|
+
per-repo detail modal. Currently-open sessions are badged **active** (see
|
|
379
|
+
below).
|
|
380
|
+
- **Summary** — machine-wide header tiles plus a **savings-by-model** table
|
|
381
|
+
(tokens in/out/freed, context window, $ saved) grouped by the model you were
|
|
382
|
+
running.
|
|
383
|
+
|
|
384
|
+
### Active Repos tab
|
|
385
|
+
|
|
386
|
+
The dashboard has a dedicated **Active Repos** tab that lists every server /
|
|
387
|
+
session seen within the **last 30 minutes**, each with its live tier,
|
|
388
|
+
context %, and session state. It is backed by `GET /api/servers`, which walks
|
|
389
|
+
the machine-wide `repo_registry`, reads each repo's per-process
|
|
390
|
+
`dashboard.json` snapshot, and returns one row per currently-open session —
|
|
391
|
+
so 1–6 sessions running at once are visible together in a single table instead
|
|
392
|
+
of only the single current-repo view. Each row shows that repo's live
|
|
393
|
+
cache-hit / compaction / time-saved totals (see below).
|
|
394
|
+
|
|
395
|
+
The older **All repos** view and `GET /api/summary` still surface an
|
|
396
|
+
`activeRepos` count, and `GET /api/repos?active=Nh` filters to repos seen
|
|
397
|
+
within the last *N* hours (e.g. `?active=24h`, hour-granular) for the
|
|
398
|
+
longer-window cross-repo table.
|
|
399
|
+
|
|
400
|
+
### Metrics (DB-backed, durable)
|
|
401
|
+
|
|
402
|
+
The dashboard's cumulative **Cache hits**, **Compactions**, and **Estimated
|
|
403
|
+
time saved** cards are backed by **SQLite `meta` counters**
|
|
404
|
+
(`compact_count`, `recall_injected`, `cache_hit_tokens_saved`, plus the
|
|
405
|
+
existing `tokens_saved` / `deduped`), not the per-process `dashboard.json`
|
|
406
|
+
snapshot. Because they live in the repo's `node:sqlite` store, the totals are
|
|
407
|
+
**durable across session restarts** and travel with the repo's state dir —
|
|
408
|
+
`dashboard.json` is now just the live per-process view that feeds the Active
|
|
409
|
+
Repos rows. The cards show:
|
|
410
|
+
|
|
411
|
+
- **Cache hits** = dedup collapses (`deduped`) + recall re-injections
|
|
412
|
+
(`recall_injected`) — as **current session** and **repo-wide total**.
|
|
413
|
+
- **Compactions** = current session (`checkpointCount`) + repo-wide total
|
|
414
|
+
(`compact_count` from `meta`).
|
|
415
|
+
- **Estimated time saved** = compact time saved + cache-hit time saved, derived
|
|
416
|
+
from tokens ÷ ~2k tok/s and labeled `est.`, as current session + total.
|
|
417
|
+
|
|
418
|
+
### Localhost API
|
|
419
|
+
|
|
420
|
+
`GET /api/snapshot` (current-repo live state),
|
|
421
|
+
`/api/servers` (**Active Repos** — sessions active in the last 30 min, with
|
|
422
|
+
tier / context % / state / live cache-hit & compaction totals),
|
|
423
|
+
`/api/index` (all repos), `/api/repos` (with `?active=Nh` filter), `/api/summary`
|
|
424
|
+
(header tiles + `activeRepos`), `/api/drift` (cross-repo drift: stale /
|
|
425
|
+
compaction-lag / model-churn — read-only), `/api/events` (SSE live event stream),
|
|
426
|
+
and `/api/version`.
|
|
427
|
+
|
|
428
|
+
### Data safety
|
|
429
|
+
|
|
430
|
+
Every compacted region is kept verbatim (compressed);
|
|
431
|
+
the Data Safety card shows regions retained, compressed-originals bytes, dedup
|
|
432
|
+
duplicates, and permanently-deleted bytes (**always 0**). Nothing is permanently
|
|
433
|
+
deleted — any region is restorable.
|
|
330
434
|
|
|
331
435
|
---
|
|
332
436
|
|
|
333
|
-
## Architecture
|
|
437
|
+
## Architecture
|
|
334
438
|
|
|
335
439
|
```
|
|
336
440
|
extensions/mega-compact.ts pi extension entry; wires src/ into pi lifecycle
|
|
@@ -370,7 +474,7 @@ The extension entry adapts between the engine and pi's runtime types.
|
|
|
370
474
|
|
|
371
475
|
```bash
|
|
372
476
|
npm run build # tsc
|
|
373
|
-
npm test # build + node --test on dist/**/*.test.js (
|
|
477
|
+
npm test # build + node --test on dist/**/*.test.js (407 tests)
|
|
374
478
|
npm run lint # tsc --noEmit + guardrails-scan
|
|
375
479
|
npm run guardrails # regression_check + guardrails-scan
|
|
376
480
|
```
|
|
@@ -45,6 +45,7 @@ function log(...parts) {
|
|
|
45
45
|
// concurrent writer's WAL never blocks the request). All registry data lives in
|
|
46
46
|
// SQLite (the project's one-store invariant) — there is no JSON mirror. Same
|
|
47
47
|
// index-dir resolution as src/store/sqlite.ts getIndexDir().
|
|
48
|
+
const ACTIVE_WINDOW_SEC = 1800;
|
|
48
49
|
function getIndexDir() {
|
|
49
50
|
const override = process.env.MEGACOMPACT_INDEX_DIR;
|
|
50
51
|
if (override && override.trim() !== "")
|
|
@@ -169,9 +170,6 @@ function readIndex() {
|
|
|
169
170
|
// ---------------------------------------------------------------------------
|
|
170
171
|
/** Package version of this extension, surfaced in the dashboard header. */
|
|
171
172
|
let dashboardServerVersion = "0.0.0";
|
|
172
|
-
// ---------------------------------------------------------------------------
|
|
173
|
-
// Helpers
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
173
|
function readSnapshot(snapshotPath) {
|
|
176
174
|
try {
|
|
177
175
|
const raw = readFileSync(snapshotPath, "utf-8");
|
|
@@ -192,6 +190,9 @@ function readSnapshot(snapshotPath) {
|
|
|
192
190
|
crew: { activeAgents: 0, currentTurn: 0 },
|
|
193
191
|
repo: { checkpointCount: 0, totalTokenEstimate: 0, originalTokens: 0, tokensSaved: 0, sessionCount: 0, dedupAttempts: 0, dedupCollapsed: 0, storageDedupRate: 0 },
|
|
194
192
|
integrity: { regionsRetained: 0, compressedOriginalBytes: 0, duplicatesCollapsed: 0, bytesPermanentlyDeleted: 0 },
|
|
193
|
+
cacheHits: { session: 0, total: 0, sessionTokensSaved: 0, totalTokensSaved: 0 },
|
|
194
|
+
compacts: { session: 0, total: 0 },
|
|
195
|
+
timeSaved: { compact: { sessionSec: 0, totalSec: 0 }, cacheHit: { sessionSec: 0, totalSec: 0 } },
|
|
195
196
|
compression: { session: { tokensIn: 0, tokensOut: 0, tokensFreed: 0, compressionPct: 0, dedupPct: 0 }, repo: { tokensIn: 0, tokensOut: 0, tokensFreed: 0, compressionPct: 0, dedupPct: 0 } },
|
|
196
197
|
model: undefined,
|
|
197
198
|
};
|
|
@@ -315,6 +316,7 @@ function dashboardHtml(tierName) {
|
|
|
315
316
|
<nav class="tabs">
|
|
316
317
|
<button class="tab active" data-tab="current">Current repo</button>
|
|
317
318
|
<button class="tab" data-tab="all">All repos</button>
|
|
319
|
+
<button class="tab" data-tab="active">Active Repos</button>
|
|
318
320
|
<button class="tab" data-tab="summary">Summary</button>
|
|
319
321
|
</nav>
|
|
320
322
|
|
|
@@ -416,6 +418,26 @@ function dashboardHtml(tierName) {
|
|
|
416
418
|
</ul>
|
|
417
419
|
<p class="legend-note">Hover any label above for a quick explanation.</p>
|
|
418
420
|
</div>
|
|
421
|
+
<div class="card">
|
|
422
|
+
<h2>💾 Cache Hits & Compactions</h2>
|
|
423
|
+
<div class="stat-grid">
|
|
424
|
+
<span class="label">Cache Hits (session)</span><span class="value" id="ch-session">0</span>
|
|
425
|
+
<span class="label">Cache Hits (total)</span><span class="value" id="ch-total">0</span>
|
|
426
|
+
<span class="label">Tokens Saved (session)</span><span class="value" id="ch-tok-session">0</span>
|
|
427
|
+
<span class="label">Tokens Saved (total)</span><span class="value" id="ch-tok-total">0</span>
|
|
428
|
+
<span class="label">Compactions (session)</span><span class="value" id="cp-session">0</span>
|
|
429
|
+
<span class="label">Compactions (total)</span><span class="value" id="cp-total">0</span>
|
|
430
|
+
</div>
|
|
431
|
+
</div>
|
|
432
|
+
<div class="card">
|
|
433
|
+
<h2>⏱ Time Saved (est.)</h2>
|
|
434
|
+
<div class="stat-grid">
|
|
435
|
+
<span class="label">Compact (session)</span><span class="value" id="ts-compact-session">0</span>
|
|
436
|
+
<span class="label">Compact (total)</span><span class="value" id="ts-compact-total">0</span>
|
|
437
|
+
<span class="label">Cache Hit (session)</span><span class="value" id="ts-cache-session">0</span>
|
|
438
|
+
<span class="label">Cache Hit (total)</span><span class="value" id="ts-cache-total">0</span>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
419
441
|
</div>
|
|
420
442
|
|
|
421
443
|
<div class="events">
|
|
@@ -441,6 +463,28 @@ function dashboardHtml(tierName) {
|
|
|
441
463
|
<div class="updated" id="updated"></div>
|
|
442
464
|
</div><!-- /panel-current -->
|
|
443
465
|
|
|
466
|
+
<!-- Active repos (live cache-hit / compaction stats across machines) -->
|
|
467
|
+
<div class="tab-panel" id="panel-active">
|
|
468
|
+
<div class="card">
|
|
469
|
+
<h2>Active Repos — Live Cache Hits & Compactions</h2>
|
|
470
|
+
<p class="legend-note">Repos seen within the last 30 minutes, with their per-repo cache-hit, compaction, and time-saved (est.) totals pulled live from each repo's dashboard.json.</p>
|
|
471
|
+
<table class="repos">
|
|
472
|
+
<thead>
|
|
473
|
+
<tr>
|
|
474
|
+
<th>Repo</th><th>Model</th><th>Tier</th>
|
|
475
|
+
<th style="text-align:right">Context %</th><th>State</th>
|
|
476
|
+
<th style="text-align:right">Compactions (s/t)</th>
|
|
477
|
+
<th style="text-align:right">Cache Hits (s/t)</th>
|
|
478
|
+
<th style="text-align:right">Compact s/t (s)</th>
|
|
479
|
+
<th style="text-align:right">CacheHit s/t (s)</th>
|
|
480
|
+
</tr>
|
|
481
|
+
</thead>
|
|
482
|
+
<tbody id="active-rows"><tr><td colspan="9" class="repo-none">loading…</td></tr></tbody>
|
|
483
|
+
</table>
|
|
484
|
+
<div class="updated" id="active-updated"></div>
|
|
485
|
+
</div>
|
|
486
|
+
</div>
|
|
487
|
+
|
|
444
488
|
<!-- Per-repo detail modal -->
|
|
445
489
|
<div class="repo-detail" id="repo-detail">
|
|
446
490
|
<div class="repo-detail-box">
|
|
@@ -635,6 +679,21 @@ function dashboardHtml(tierName) {
|
|
|
635
679
|
document.getElementById('cost-windows').textContent = '0 context-windows extended';
|
|
636
680
|
}
|
|
637
681
|
|
|
682
|
+
// --- Cache hits & compactions (live counters) ---------------------------
|
|
683
|
+
var ch = d.cacheHits || { session: 0, total: 0, sessionTokensSaved: 0, totalTokensSaved: 0 };
|
|
684
|
+
var cp = d.compacts || { session: 0, total: 0 };
|
|
685
|
+
var ts = d.timeSaved || { compact: { sessionSec: 0, totalSec: 0 }, cacheHit: { sessionSec: 0, totalSec: 0 } };
|
|
686
|
+
document.getElementById('ch-session').textContent = (ch.session || 0).toLocaleString();
|
|
687
|
+
document.getElementById('ch-total').textContent = (ch.total || 0).toLocaleString();
|
|
688
|
+
document.getElementById('ch-tok-session').textContent = (ch.sessionTokensSaved || 0).toLocaleString();
|
|
689
|
+
document.getElementById('ch-tok-total').textContent = (ch.totalTokensSaved || 0).toLocaleString();
|
|
690
|
+
document.getElementById('cp-session').textContent = (cp.session || 0).toLocaleString();
|
|
691
|
+
document.getElementById('cp-total').textContent = (cp.total || 0).toLocaleString();
|
|
692
|
+
document.getElementById('ts-compact-session').textContent = fmtSec(ts.compact.sessionSec);
|
|
693
|
+
document.getElementById('ts-compact-total').textContent = fmtSec(ts.compact.totalSec);
|
|
694
|
+
document.getElementById('ts-cache-session').textContent = fmtSec(ts.cacheHit.sessionSec);
|
|
695
|
+
document.getElementById('ts-cache-total').textContent = fmtSec(ts.cacheHit.totalSec);
|
|
696
|
+
|
|
638
697
|
document.getElementById('updated').textContent = 'Updated ' + new Date(d.updatedAt).toLocaleTimeString();
|
|
639
698
|
}
|
|
640
699
|
|
|
@@ -840,9 +899,51 @@ function dashboardHtml(tierName) {
|
|
|
840
899
|
pollIndex();
|
|
841
900
|
setInterval(pollIndex, 5000);
|
|
842
901
|
|
|
902
|
+
// --- Active repos (live cache-hit / compaction stats) ---------------------
|
|
903
|
+
function fmtSec(s) {
|
|
904
|
+
s = s || 0;
|
|
905
|
+
if (s >= 3600) return (s / 3600).toFixed(1) + 'h';
|
|
906
|
+
if (s >= 60) return Math.round(s / 60) + 'm';
|
|
907
|
+
if (s >= 1) return s.toFixed(1) + 's';
|
|
908
|
+
return Math.round(s * 1000) + 'ms';
|
|
909
|
+
}
|
|
910
|
+
function renderActiveRepos(d) {
|
|
911
|
+
d = d || { updatedAt: null, servers: [] };
|
|
912
|
+
var servers = d.servers || [];
|
|
913
|
+
var rowsEl = document.getElementById('active-rows');
|
|
914
|
+
if (!rowsEl) return;
|
|
915
|
+
if (!servers.length) {
|
|
916
|
+
rowsEl.innerHTML = '<tr><td colspan="9" class="repo-none">No active repositories.</td></tr>';
|
|
917
|
+
} else {
|
|
918
|
+
rowsEl.innerHTML = servers.map(function(r) {
|
|
919
|
+
var ch = r.cacheHits || { session: 0, total: 0, sessionTokensSaved: 0, totalTokensSaved: 0 };
|
|
920
|
+
var cp = r.compacts || { session: 0, total: 0 };
|
|
921
|
+
var ts = r.timeSaved || { compact: { sessionSec: 0, totalSec: 0 }, cacheHit: { sessionSec: 0, totalSec: 0 } };
|
|
922
|
+
return '<tr>' +
|
|
923
|
+
'<td title="' + sanitize(r.repoRoot) + '">' + sanitize(r.displayName || r.repoRoot) + '</td>' +
|
|
924
|
+
'<td>' + sanitize(r.model || '—') + '</td>' +
|
|
925
|
+
'<td>' + sanitize(r.tier || '—') + '</td>' +
|
|
926
|
+
'<td class="num">' + (r.contextPct != null ? Math.round(r.contextPct * 100) + '%' : '—') + '</td>' +
|
|
927
|
+
'<td>' + sanitize(r.state || '—') + '</td>' +
|
|
928
|
+
'<td class="num">' + (cp.session || 0) + ' / ' + (cp.total || 0) + '</td>' +
|
|
929
|
+
'<td class="num">' + (ch.session || 0) + ' / ' + (ch.total || 0) + '</td>' +
|
|
930
|
+
'<td class="num">' + fmtSec(ts.compact.sessionSec) + ' / ' + fmtSec(ts.compact.totalSec) + '</td>' +
|
|
931
|
+
'<td class="num">' + fmtSec(ts.cacheHit.sessionSec) + ' / ' + fmtSec(ts.cacheHit.totalSec) + '</td>' +
|
|
932
|
+
'</tr>';
|
|
933
|
+
}).join('');
|
|
934
|
+
}
|
|
935
|
+
var upd = document.getElementById('active-updated');
|
|
936
|
+
if (upd) upd.textContent = d.updatedAt ? 'Updated ' + new Date(d.updatedAt).toLocaleTimeString() : '';
|
|
937
|
+
}
|
|
938
|
+
function pollServers() {
|
|
939
|
+
fetch('/api/servers').then(function(r) { return r.json(); }).then(renderActiveRepos).catch(function() {});
|
|
940
|
+
}
|
|
941
|
+
pollServers();
|
|
942
|
+
setInterval(pollServers, 5000);
|
|
943
|
+
|
|
843
944
|
// --- Tab switching ------------------------------------------------------
|
|
844
945
|
var tabs = document.querySelectorAll('.tab');
|
|
845
|
-
var panels = { current: 'panel-current', all: 'panel-all', summary: 'panel-summary' };
|
|
946
|
+
var panels = { current: 'panel-current', all: 'panel-all', active: 'panel-active', summary: 'panel-summary' };
|
|
846
947
|
for (var i = 0; i < tabs.length; i++) {
|
|
847
948
|
tabs[i].addEventListener('click', function() {
|
|
848
949
|
var name = this.getAttribute('data-tab');
|
|
@@ -855,6 +956,7 @@ function dashboardHtml(tierName) {
|
|
|
855
956
|
}
|
|
856
957
|
}
|
|
857
958
|
if (name === 'all' || name === 'summary') pollIndex();
|
|
959
|
+
if (name === 'active') pollServers();
|
|
858
960
|
});
|
|
859
961
|
}
|
|
860
962
|
})();
|
|
@@ -1063,6 +1165,37 @@ export async function launchDashboardServer(stateDir) {
|
|
|
1063
1165
|
res.end(JSON.stringify(report));
|
|
1064
1166
|
return;
|
|
1065
1167
|
}
|
|
1168
|
+
if (req.url === "/api/servers") {
|
|
1169
|
+
try {
|
|
1170
|
+
const idx = readIndex();
|
|
1171
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
1172
|
+
const servers = (idx?.repos ?? []).filter((r) => (r.lastSeen ?? 0) >= nowSec - ACTIVE_WINDOW_SEC).map((r) => {
|
|
1173
|
+
const out = { repoRoot: r.repoRoot, displayName: r.displayName, model: r.modelName, provider: r.providerName, lastSeen: r.lastSeen, lastCompactedAt: r.lastCompactedAt };
|
|
1174
|
+
try {
|
|
1175
|
+
const p = join(r.stateDir, "dashboard.json");
|
|
1176
|
+
if (existsSync(p)) {
|
|
1177
|
+
const snap = JSON.parse(readFileSync(p, "utf-8"));
|
|
1178
|
+
out.tier = snap.tier ?? null;
|
|
1179
|
+
out.contextPct = (snap.context && snap.context.percent != null) ? snap.context.percent : null;
|
|
1180
|
+
out.state = (snap.session && snap.session.state) || null;
|
|
1181
|
+
out.cacheHits = snap.cacheHits ?? null;
|
|
1182
|
+
out.compacts = snap.compacts ?? null;
|
|
1183
|
+
out.timeSaved = snap.timeSaved ?? null;
|
|
1184
|
+
out.updatedAt = snap.updatedAt ?? null;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
catch { /* best-effort */ }
|
|
1188
|
+
return out;
|
|
1189
|
+
}).sort((a, b) => b.lastSeen - a.lastSeen);
|
|
1190
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1191
|
+
res.end(JSON.stringify({ updatedAt: new Date().toISOString(), servers }));
|
|
1192
|
+
}
|
|
1193
|
+
catch {
|
|
1194
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1195
|
+
res.end(JSON.stringify({ error: "servers_unavailable" }));
|
|
1196
|
+
}
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1066
1199
|
if (req.url === "/api/events") {
|
|
1067
1200
|
res.writeHead(200, {
|
|
1068
1201
|
"Content-Type": "text/event-stream",
|
|
@@ -195,6 +195,65 @@ describe("multi-repo /api/index (S19)", () => {
|
|
|
195
195
|
}
|
|
196
196
|
});
|
|
197
197
|
});
|
|
198
|
+
describe("multi-repo /api/servers (active cache-hit stats)", () => {
|
|
199
|
+
test("returns active repos with live dashboard.json cache-hit/compaction stats", async () => {
|
|
200
|
+
const dir = mkdtempSync(join(tmpdir(), "dash-servers-"));
|
|
201
|
+
const indexDir = mkdtempSync(join(tmpdir(), "index-servers-"));
|
|
202
|
+
process.env.MEGACOMPACT_INDEX_DIR = indexDir;
|
|
203
|
+
process.env.MEGACOMPACT_DASHBOARD_PORT = "19323";
|
|
204
|
+
const { upsertRepoRegistry } = await import("../src/store/sqlite.js");
|
|
205
|
+
const activeState = mkdtempSync(join(tmpdir(), "srv-active-"));
|
|
206
|
+
writeFileSync(join(activeState, "dashboard.json"), JSON.stringify({
|
|
207
|
+
updatedAt: new Date().toISOString(),
|
|
208
|
+
tier: "high",
|
|
209
|
+
context: { tokens: 5000, percent: 0.42, contextWindow: 12000 },
|
|
210
|
+
session: { id: "s1", state: "idle" },
|
|
211
|
+
cacheHits: { session: 3, total: 7, sessionTokensSaved: 1200, totalTokensSaved: 9000 },
|
|
212
|
+
compacts: { session: 2, total: 5 },
|
|
213
|
+
timeSaved: { compact: { sessionSec: 1.5, totalSec: 4 }, cacheHit: { sessionSec: 0.6, totalSec: 4.5 } },
|
|
214
|
+
}, null, 2));
|
|
215
|
+
upsertRepoRegistry({ repoRoot: "/home/u/active", displayName: "active", stateDir: activeState, checkpointCount: 4, tokensSaved: 9000, compressedOriginalBytes: 0, lastSeen: Math.floor(Date.now() / 1000), modelName: "gpt-4o", providerName: "OpenAI" }, indexDir);
|
|
216
|
+
const staleState = mkdtempSync(join(tmpdir(), "srv-stale-"));
|
|
217
|
+
writeFileSync(join(staleState, "dashboard.json"), JSON.stringify({ updatedAt: new Date().toISOString(), tier: "low" }, null, 2));
|
|
218
|
+
const longAgo = Math.floor(Date.now() / 1000) - 7 * 86_400;
|
|
219
|
+
upsertRepoRegistry({ repoRoot: "/home/u/stale", displayName: "stale", stateDir: staleState, checkpointCount: 1, tokensSaved: 100, compressedOriginalBytes: 0, lastSeen: longAgo }, indexDir);
|
|
220
|
+
const child = spawn(process.execPath, [SERVER_ENTRY, dir], { stdio: "ignore" });
|
|
221
|
+
try {
|
|
222
|
+
await waitFor(async () => {
|
|
223
|
+
try {
|
|
224
|
+
const raw = JSON.parse(readFileSync(join(dir, "port.pid"), "utf-8"));
|
|
225
|
+
const res = await fetch(`http://localhost:${raw.port}/api/version`);
|
|
226
|
+
return res.ok;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
const raw = JSON.parse(readFileSync(join(dir, "port.pid"), "utf-8"));
|
|
233
|
+
const body = (await fetch(`http://localhost:${raw.port}/api/servers`).then((r) => r.json()));
|
|
234
|
+
assert.equal(body.servers.length, 1, "only the active repo is returned");
|
|
235
|
+
const s = body.servers[0];
|
|
236
|
+
assert.equal(s.displayName, "active");
|
|
237
|
+
assert.equal(s.tier, "high");
|
|
238
|
+
assert.equal(s.model, "gpt-4o");
|
|
239
|
+
assert.equal(s.provider, "OpenAI");
|
|
240
|
+
assert.equal(s.contextPct, 0.42);
|
|
241
|
+
assert.equal(s.state, "idle");
|
|
242
|
+
assert.deepEqual(s.cacheHits, { session: 3, total: 7, sessionTokensSaved: 1200, totalTokensSaved: 9000 });
|
|
243
|
+
assert.deepEqual(s.compacts, { session: 2, total: 5 });
|
|
244
|
+
const ts = s.timeSaved;
|
|
245
|
+
assert.equal(ts.compact.sessionSec, 1.5);
|
|
246
|
+
assert.equal(ts.cacheHit.sessionSec, 0.6);
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
child.kill("SIGTERM");
|
|
250
|
+
delete process.env.MEGACOMPACT_INDEX_DIR;
|
|
251
|
+
delete process.env.MEGACOMPACT_DASHBOARD_PORT;
|
|
252
|
+
rmSync(dir, { recursive: true, force: true });
|
|
253
|
+
rmSync(indexDir, { recursive: true, force: true });
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
});
|
|
198
257
|
// ---------------------------------------------------------------------------
|
|
199
258
|
// Lifecycle integration — launch the compiled server as a real subprocess
|
|
200
259
|
// (the same way the /dashboard command spawns it) and assert the two failure
|
|
@@ -11,7 +11,7 @@ import { compactSession } from "../src/engine.js";
|
|
|
11
11
|
import { recallAndInline, recallAndInlineAsync, formatRecallBlock } from "../src/recall.js";
|
|
12
12
|
import { normalizeSessionId } from "../src/store.js";
|
|
13
13
|
import { estimateBlockTokens } from "../src/tokens.js";
|
|
14
|
-
import { touchSession, logDaily } from "../src/store/sqlite.js";
|
|
14
|
+
import { touchSession, logDaily, incCompactCount, incRecallInjected, incCacheHitTokens } from "../src/store/sqlite.js";
|
|
15
15
|
import { consolidateMemories } from "../src/memory.js";
|
|
16
16
|
import { C, MARKER_TYPE, } from "./mega-runtime.js";
|
|
17
17
|
import { resolveRepoRoot, preserveRecentForPressure } from "./mega-config.js";
|
|
@@ -104,6 +104,12 @@ function doCompact(view, keepFrom, opts, sid, config, pi, ctx, runtime) {
|
|
|
104
104
|
? result.originalTokenEstimate
|
|
105
105
|
: Math.max(0, result.originalTokenEstimate - result.tokenEstimate);
|
|
106
106
|
runtime.rt.tokensSaved += saved;
|
|
107
|
+
runtime.rt.compactCount += 1;
|
|
108
|
+
incCompactCount(runtime.currentStateDir);
|
|
109
|
+
if (result.deduped) {
|
|
110
|
+
runtime.rt.cacheHitTokens += saved;
|
|
111
|
+
incCacheHitTokens(saved, runtime.currentStateDir);
|
|
112
|
+
}
|
|
107
113
|
runtime.rt.lastCompactAt = Date.now();
|
|
108
114
|
if (result.deduped)
|
|
109
115
|
runtime.rt.dedupSkips++;
|
|
@@ -376,6 +382,15 @@ export function doRecall(runtime, config, ctx, query, source) {
|
|
|
376
382
|
runtime.pushTicker(`${C.amber}↩${C.reset} recalled ${top.checkpoint.checkpointId} · ${scorePct}% · ${label}`);
|
|
377
383
|
runtime.lastWhy = `why: recalled@${scorePct}% (${result.toInject.length} chkpt)`;
|
|
378
384
|
}
|
|
385
|
+
if (result.toInject.length > 0) {
|
|
386
|
+
let sumTokens = 0;
|
|
387
|
+
for (const h of result.toInject)
|
|
388
|
+
sumTokens += h.checkpoint.tokenEstimate;
|
|
389
|
+
runtime.rt.recallInjections += result.toInject.length;
|
|
390
|
+
runtime.rt.cacheHitTokens += sumTokens;
|
|
391
|
+
incRecallInjected(result.toInject.length, runtime.currentStateDir);
|
|
392
|
+
incCacheHitTokens(sumTokens, runtime.currentStateDir);
|
|
393
|
+
}
|
|
379
394
|
return result;
|
|
380
395
|
}
|
|
381
396
|
/**
|
|
@@ -425,6 +440,15 @@ export async function doRecallAsync(runtime, config, ctx, query, source, opts =
|
|
|
425
440
|
}
|
|
426
441
|
}
|
|
427
442
|
const block = merged.length ? formatRecallBlock(merged) : "";
|
|
443
|
+
if (merged.length > 0) {
|
|
444
|
+
let sumTokens = 0;
|
|
445
|
+
for (const h of merged)
|
|
446
|
+
sumTokens += h.checkpoint.tokenEstimate;
|
|
447
|
+
runtime.rt.recallInjections += merged.length;
|
|
448
|
+
runtime.rt.cacheHitTokens += sumTokens;
|
|
449
|
+
incRecallInjected(merged.length, runtime.currentStateDir);
|
|
450
|
+
incCacheHitTokens(sumTokens, runtime.currentStateDir);
|
|
451
|
+
}
|
|
428
452
|
return {
|
|
429
453
|
toInject: merged,
|
|
430
454
|
report: merged.map((h) => ` • ${h.checkpoint.checkpointId}${h.repoId ? ` (from ${h.repoId.split("/").filter(Boolean).pop()})` : ""}`),
|
|
@@ -16,7 +16,7 @@ import { VectorStore } from "../src/vectorStore.js";
|
|
|
16
16
|
import { toEngineMessages } from "../src/adapt.js";
|
|
17
17
|
import { normalizeSessionId } from "../src/store.js";
|
|
18
18
|
import { Logger } from "../src/log.js";
|
|
19
|
-
import { recordModelSnapshot, latestModelSnapshot, upsertRepoRegistry, recordRepoModel, } from "../src/store/sqlite.js";
|
|
19
|
+
import { recordModelSnapshot, latestModelSnapshot, upsertRepoRegistry, recordRepoModel, getDedupStats, getCompactCount, getRecallInjected, getCacheHitTokensSaved, } from "../src/store/sqlite.js";
|
|
20
20
|
import { detectCrossRepoDrift } from "../src/driftDetection.js";
|
|
21
21
|
import { repoStateDir, resolveRepoRoot, pressureRatio, pressureFromPct, pressureBand, effectiveThresholdTokens, } from "./mega-config.js";
|
|
22
22
|
import { Dashboard } from "./mega-dashboard.js";
|
|
@@ -57,6 +57,10 @@ export const C = {
|
|
|
57
57
|
red: "\x1b[38;5;203m", // pressure / overflow
|
|
58
58
|
};
|
|
59
59
|
const PULSE = ["◐", "◓", "◑", "◒"];
|
|
60
|
+
// Rough tokens-processed-per-second heuristic for the dashboard's "time saved"
|
|
61
|
+
// estimate. Throughput varies by model/hardware; this is order-of-magnitude so
|
|
62
|
+
// the dashboard can show a human-readable figure, not a precise measurement.
|
|
63
|
+
const TOKENS_PER_SEC_ESTIMATE = 2000;
|
|
60
64
|
// ── Full-width widget panel helpers ────────────────────────────────────────
|
|
61
65
|
// pi's above-editor widget renderer (a Container of Text lines) does NOT pass
|
|
62
66
|
// a terminal width to setWidget(), so lines render left-aligned by default. To
|
|
@@ -192,6 +196,9 @@ export class MegaRuntime {
|
|
|
192
196
|
tokensSaved: 0,
|
|
193
197
|
lastCompactAt: null,
|
|
194
198
|
lastNativeCompactAt: null,
|
|
199
|
+
compactCount: 0,
|
|
200
|
+
recallInjections: 0,
|
|
201
|
+
cacheHitTokens: 0,
|
|
195
202
|
};
|
|
196
203
|
debounceUntil = 0;
|
|
197
204
|
// S16: debounce for the agent_end resume nudge (avoid busy-loops).
|
|
@@ -390,6 +397,12 @@ export class MegaRuntime {
|
|
|
390
397
|
const st = this.store.stats(this.rt.sessionId);
|
|
391
398
|
const repo = this.store.repoStats();
|
|
392
399
|
const di = this.store.dataInvariant();
|
|
400
|
+
// Live + store-wide cache-hit / compaction counters for the dashboard.
|
|
401
|
+
const ds = getDedupStats(this.currentStateDir);
|
|
402
|
+
const cacheHitsTotal = ds.deduped + getRecallInjected(this.currentStateDir);
|
|
403
|
+
const cacheHitsTotalTokens = getCacheHitTokensSaved(this.currentStateDir);
|
|
404
|
+
const cacheHitsSession = this.rt.dedupSkips + this.rt.recallInjections;
|
|
405
|
+
const sec = (tok) => (tok || 0) / TOKENS_PER_SEC_ESTIMATE;
|
|
393
406
|
// Active model/provider for the current-repo card + the multi-repo table.
|
|
394
407
|
const modelSnap = latestModelSnapshot(this.currentStateDir);
|
|
395
408
|
const model = modelSnap
|
|
@@ -505,6 +518,20 @@ export class MegaRuntime {
|
|
|
505
518
|
duplicatesCollapsed: di.duplicatesCollapsed,
|
|
506
519
|
bytesPermanentlyDeleted: di.bytesPermanentlyDeleted,
|
|
507
520
|
},
|
|
521
|
+
cacheHits: {
|
|
522
|
+
session: cacheHitsSession,
|
|
523
|
+
total: cacheHitsTotal,
|
|
524
|
+
sessionTokensSaved: this.rt.cacheHitTokens,
|
|
525
|
+
totalTokensSaved: cacheHitsTotalTokens,
|
|
526
|
+
},
|
|
527
|
+
compacts: {
|
|
528
|
+
session: this.rt.compactCount,
|
|
529
|
+
total: getCompactCount(this.currentStateDir),
|
|
530
|
+
},
|
|
531
|
+
timeSaved: {
|
|
532
|
+
compact: { sessionSec: sec(this.rt.tokensSaved), totalSec: sec(this.store.repoStats().tokensSaved) },
|
|
533
|
+
cacheHit: { sessionSec: sec(this.rt.cacheHitTokens), totalSec: sec(cacheHitsTotalTokens) },
|
|
534
|
+
},
|
|
508
535
|
model,
|
|
509
536
|
});
|
|
510
537
|
// Live stats widget above the editor
|
|
@@ -722,6 +749,9 @@ export class MegaRuntime {
|
|
|
722
749
|
tokensSaved: 0,
|
|
723
750
|
lastCompactAt: null,
|
|
724
751
|
lastNativeCompactAt: null,
|
|
752
|
+
compactCount: 0,
|
|
753
|
+
recallInjections: 0,
|
|
754
|
+
cacheHitTokens: 0,
|
|
725
755
|
};
|
|
726
756
|
this.statusKey = undefined;
|
|
727
757
|
this.activeAgents = 0;
|