pi-mega-compact 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -108
- package/dist/extensions/dashboard-server.js +12 -3
- package/dist/extensions/mega-commands.js +5 -23
- package/dist/extensions/mega-compact.test.js +6 -3
- package/dist/extensions/mega-config.js +12 -9
- package/dist/extensions/mega-events.js +25 -20
- package/dist/extensions/mega-pipeline.js +22 -0
- package/dist/extensions/mega-runtime.js +34 -4
- package/dist/src/config.js +48 -0
- package/dist/src/memoryOps.test.js +50 -1
- package/dist/src/store/compression.test.js +24 -0
- package/dist/src/store/sqlite.js +60 -3
- package/extensions/dashboard-server.ts +14 -3
- package/extensions/mega-commands.ts +6 -25
- package/extensions/mega-compact.test.ts +8 -5
- package/extensions/mega-config.ts +26 -11
- package/extensions/mega-dashboard.ts +5 -0
- package/extensions/mega-events.ts +24 -19
- package/extensions/mega-pipeline.ts +22 -0
- package/extensions/mega-runtime.ts +36 -4
- package/package.json +1 -1
- package/src/config.ts +61 -0
- package/src/memoryOps.test.ts +59 -1
- package/src/store/compression.test.ts +27 -0
- package/src/store/sqlite.ts +56 -3
package/README.md
CHANGED
|
@@ -6,10 +6,13 @@ 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
|
-
> **v0.
|
|
10
|
-
>
|
|
11
|
-
> JSON checkpoint files.
|
|
12
|
-
>
|
|
9
|
+
> **Current version:** `v0.6.0` — storage backend is **`node:sqlite`**
|
|
10
|
+
> (`DatabaseSync`, a Node ≥22.13 built-in), replacing the old `better-sqlite3`
|
|
11
|
+
> native addon and the per-session gzipped JSON checkpoint files. **Zero native
|
|
12
|
+
> build step, fully local, zero network at runtime.** Legacy
|
|
13
|
+
> `.checkpoints.json.gz` snapshots are retained as disaster-recovery fallbacks
|
|
14
|
+
> and auto-imported on first run. Cross-repo recall, durable memory, and a
|
|
15
|
+
> localhost dashboard round out the continuity story.
|
|
13
16
|
|
|
14
17
|
---
|
|
15
18
|
|
|
@@ -110,9 +113,9 @@ OpenAI-style contract and the `MEGACOMPACT_EMBEDDING_KEY` / `MEGACOMPACT_EMBEDDI
|
|
|
110
113
|
|
|
111
114
|
### Requirements
|
|
112
115
|
|
|
113
|
-
- **Node >=
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
- **Node >= 22.13** (the synchronous `node:sqlite` backend requires it; see
|
|
117
|
+
`engines.node`). No native module is compiled — the store is a Node built-in.
|
|
118
|
+
- No network call and no API key are needed at runtime (PREVENT-PI-004).
|
|
116
119
|
- A pi coding agent install with package support (`pi install` / `pi update
|
|
117
120
|
--extensions`). npm-installed packages are auto-discovered via the package's
|
|
118
121
|
`pi` manifest entry; local checkouts load from `~/.pi/agent/extensions/`.
|
|
@@ -135,8 +138,10 @@ source (which pi loads directly) and the compiled `dist/`, so nothing else needs
|
|
|
135
138
|
building.
|
|
136
139
|
|
|
137
140
|
> **Tip — keep the spec unpinned.** Use `npm:pi-mega-compact`, not
|
|
138
|
-
> `npm:pi-mega-compact@0.
|
|
139
|
-
> `pi update --extensions`, so a pin would freeze you on that release.
|
|
141
|
+
> `npm:pi-mega-compact@0.5.1`. Version-pinned specs are *skipped* by
|
|
142
|
+
> `pi update --extensions`, so a pin would freeze you on that release. The
|
|
143
|
+
> installed version is always visible in the toolbar widget (`⚡ <tier> vX.Y.Z`)
|
|
144
|
+
> and via `/mega-status`.
|
|
140
145
|
|
|
141
146
|
> **From a git checkout (development only).** To hack on the extension, clone and
|
|
142
147
|
> build locally, then symlink it into pi's extensions dir — but this bypasses the
|
|
@@ -158,7 +163,7 @@ building.
|
|
|
158
163
|
> `pi update --extensions` on the device. (`.gitignore` rejects `*.tgz` so one can't
|
|
159
164
|
> be committed by accident.)
|
|
160
165
|
|
|
161
|
-
### Storage
|
|
166
|
+
### Storage
|
|
162
167
|
|
|
163
168
|
pi-mega-compact uses a dual local backend — **zero network, no native build step**:
|
|
164
169
|
|
|
@@ -167,22 +172,14 @@ pi-mega-compact uses a dual local backend — **zero network, no native build st
|
|
|
167
172
|
|
|
168
173
|
Kill-switch: `MEGACOMPACT_PGLITE_DISABLED=1` fully disables the PGlite index (falls back to sync scan). Requires Node ≥22.13 (`engines.node`).
|
|
169
174
|
|
|
170
|
-
### Cross-repo recall
|
|
175
|
+
### Cross-repo recall
|
|
171
176
|
|
|
172
177
|
On resume, recall augments from other repos' checkpoints when this repo's store is thin; `/mega-recall --cross-repo` searches all repos via the HNSW index. Cross-repo hits use a stricter cosine floor (`MEGACOMPACT_CROSSREPO_COSINE`, default 0.90) and are labeled with their source repo. A machine-wide injected-set (`~/.mega-compact-index/index.sqlite`) prevents re-injecting the same foreign checkpoint.
|
|
173
178
|
|
|
174
|
-
### Memory
|
|
179
|
+
### Memory
|
|
175
180
|
|
|
176
181
|
pi-mega-compact auto-reviews the conversation every 10 turns and writes durable `decision`/`fact`/`preference` memories to SQLite (local, hallucination-guarded). Relevant memories are injected as RAG context on recall (capped, deduped). Manual: `/mega-memory save|list|forget`.
|
|
177
182
|
|
|
178
|
-
### Verify
|
|
179
|
-
|
|
180
|
-
```bash
|
|
181
|
-
npm test # all unit/integration tests pass (346 as of v0.5.0)
|
|
182
|
-
npm run lint # tsc --noEmit + guardrails scan clean
|
|
183
|
-
python3 scripts/regression_check.py --all # spec/plan regression gate
|
|
184
|
-
```
|
|
185
|
-
|
|
186
183
|
### Uninstall
|
|
187
184
|
|
|
188
185
|
```bash
|
|
@@ -211,11 +208,17 @@ The commands (slash commands inside pi):
|
|
|
211
208
|
|---|---|
|
|
212
209
|
| `/mega-compact [summary...]` | Manually compact the current session. A summary arg is used verbatim; otherwise the COLLAPSE heuristics build one. Persists a `chkpt_xxx`. |
|
|
213
210
|
| `/mega-compact off` | Disable auto-compaction for this session. |
|
|
214
|
-
| `/mega-status` | Show config + current context usage + store stats (checkpoint count, dedup rate, tokens saved)
|
|
211
|
+
| `/mega-status` | Show config + current context usage + store stats (checkpoint count, dedup rate, tokens saved) + the **installed version**. |
|
|
215
212
|
| `/mega-recall [query]` | Semantic-search the local store, dedupe against the current window, and inline the top-K relevant checkpoints. No query → uses your latest message. `--cross-repo` searches all repos. |
|
|
216
213
|
| `/mega-memory save <text>` / `save <category> <text>` / `list` / `search <query>` / `forget <text>` / `consolidate` | Manage durable memories (decisions, facts, preferences) written by auto-review and recalled as RAG context. Also `/m` shortform. |
|
|
217
|
-
| `/mega-
|
|
218
|
-
| `/mega-
|
|
214
|
+
| `/mega-restore <chkpt\|recent>` | Re-inject a checkpoint's verbatim original region into context. |
|
|
215
|
+
| `/mega-history` | List this session's checkpoints (id, date, files, tokens). |
|
|
216
|
+
| `/mega-view <chkpt\|recent>` | Show a checkpoint's verbatim original region. |
|
|
217
|
+
| `/mega-help` | Explain the toolbar widget terms (live tier, gate, dedup, tokens saved). |
|
|
218
|
+
| `/mega-compat-check` | Detect extension conflicts (duplicate commands / overlapping handlers) across installed pi extensions. |
|
|
219
|
+
|
|
220
|
+
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 / thresholdTokens`, not a manual setting. The base compaction *threshold* (token budget) is still chosen by the `MEGACOMPACT_TIER` env var at startup (`low`/`medium`/`high`/`ultra`/`mega`, default `low`); `/mega-tier` was removed in v0.6.0. Higher pressure also deepens the live trim and reviews durable memory more often — the whole system reacts as one.
|
|
221
|
+
| `/mega-dashboard` | Start the **localhost-only** live dashboard and open it in a browser (token gauge, store stats, live event stream, per-repo + cross-repo drift). |
|
|
219
222
|
| `/mega-dashboard-status` | Report dashboard server status. |
|
|
220
223
|
| `/mega-dashboard-stop` | Stop the dashboard server. |
|
|
221
224
|
|
|
@@ -224,10 +227,13 @@ The commands (slash commands inside pi):
|
|
|
224
227
|
Above the pi editor the extension shows a compact widget:
|
|
225
228
|
|
|
226
229
|
```
|
|
227
|
-
⚡
|
|
230
|
+
⚡ high·low v0.6.0 │ 142k/200k tokens (71%) │ 3 chkpts │ 🤖 2 agents │ turn 5
|
|
228
231
|
◐ armed │ dedup: 92% │ saved: 45k tok
|
|
229
232
|
```
|
|
230
233
|
|
|
234
|
+
- **Version** — the installed npm version (read from `package.json` at runtime),
|
|
235
|
+
so the widget always reflects what `pi update --extensions` last pulled. If
|
|
236
|
+
this looks stale after an update, restart the dashboard server / pi session.
|
|
231
237
|
- **Tier** — active compaction tier (low/medium/high/ultra/mega)
|
|
232
238
|
- **Token usage** — current / max context window and %
|
|
233
239
|
- **Checkpoints** — persisted checkpoints for the session
|
|
@@ -255,10 +261,10 @@ before starting pi.
|
|
|
255
261
|
| `MEGACOMPACT_DEDUP_SIM` | `0.90` | Cosine threshold to collapse near-dupes. |
|
|
256
262
|
| `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). |
|
|
257
263
|
|
|
258
|
-
#### Dedup pipeline flags (
|
|
264
|
+
#### Dedup pipeline flags (single source: `src/config/dedup.ts`)
|
|
259
265
|
|
|
260
266
|
These gate the L0/L1/L2/RAPTOR dedup tiers. Defaults reproduce the all-active
|
|
261
|
-
|
|
267
|
+
behavior. `MARK_ONLY_*` tiers run + record their decision but never
|
|
262
268
|
collapse (safe partial-rollout / auto-degrade state).
|
|
263
269
|
|
|
264
270
|
| Variable | Default | Meaning |
|
|
@@ -285,18 +291,18 @@ See `docs/DEDUP_RUNBOOK.md` for incident response (SEV tiers, first-15-min
|
|
|
285
291
|
checklist, MARK_ONLY degrade) and `docs/RETENTION_POLICY.md` for TTL / soft-delete
|
|
286
292
|
/ VACUUM.
|
|
287
293
|
|
|
288
|
-
#### Continuity + memory knobs
|
|
294
|
+
#### Continuity + memory knobs
|
|
289
295
|
|
|
290
296
|
| Variable | Default | Meaning |
|
|
291
297
|
|---|---|---|
|
|
292
|
-
| `MEGACOMPACT_LEGACY_DURABLE_TRIM` | `false` | Restore the
|
|
298
|
+
| `MEGACOMPACT_LEGACY_DURABLE_TRIM` | `false` | Restore the legacy auto-trigger (`ctx.compact()` stops the agent). One-release rollback; default uses live context-event trim + pi native auto-compaction (compact-and-continue). |
|
|
293
299
|
| `MEGACOMPACT_CROSSREPO_ENABLED` | `true` | Cross-repo recall on resume + `/mega-recall --cross-repo` (HNSW index over every repo). |
|
|
294
300
|
| `MEGACOMPACT_CROSSREPO_COSINE` | `0.90` | Stricter cosine floor for cross-repo hits (vs `0.85` same-repo). |
|
|
295
301
|
| `MEGACOMPACT_MEMORY_AUTO_REVIEW` | `true` | Auto-review the conversation every `MEGACOMPACT_MEMORY_REVIEW_INTERVAL` turns → durable memories. |
|
|
296
302
|
| `MEGACOMPACT_MEMORY_REVIEW_INTERVAL` | `10` | Turns between auto-review cycles. |
|
|
297
|
-
| `MEGACOMPACT_PGLITE_DISABLED` |
|
|
303
|
+
| `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). |
|
|
298
304
|
|
|
299
|
-
#### Dashboard
|
|
305
|
+
#### Dashboard
|
|
300
306
|
|
|
301
307
|
The localhost-only dashboard adds a **Summary** + **All-repos** view over the
|
|
302
308
|
machine-wide `repo_registry`, plus a **cross-repo drift** report (`GET /api/drift`)
|
|
@@ -306,78 +312,35 @@ All read-only — the report never writes the index.
|
|
|
306
312
|
|
|
307
313
|
---
|
|
308
314
|
|
|
309
|
-
## Reporting for testers (what to capture)
|
|
310
|
-
|
|
311
|
-
If you're testing pi-mega-compact, the maintainers need **local evidence**, not
|
|
312
|
-
guesswork. The store and logs are plain local files — never a network port.
|
|
313
|
-
|
|
314
|
-
1. **Install + run it** (see [Installation](#install)).
|
|
315
|
-
2. **Work a real session** until context fills past the gate (80%+) — you should
|
|
316
|
-
see the status chip flip to `● ready`, then `◐ armed`, a checkpoint persist,
|
|
317
|
-
and context visibly drop.
|
|
318
|
-
3. **Resume and confirm recall:** restart pi, ask about something you worked on
|
|
319
|
-
earlier; relevant checkpoints should auto-inline (or use
|
|
320
|
-
`/mega-recall <topic>`).
|
|
321
|
-
4. **Watch the live signal** while testing:
|
|
322
|
-
```bash
|
|
323
|
-
tail -f ~/.pi/agent/extensions/pi-mega-compact/events.log | jq .
|
|
324
|
-
```
|
|
325
|
-
Each line is `{ts, tier, result, latencyMs, falsePositive?}`.
|
|
326
|
-
5. **Run the dashboard** (`/mega-dashboard`) and check the token gauge, store
|
|
327
|
-
stats, and live event stream.
|
|
328
|
-
6. **Try `/mega-tier`** to see and switch compaction tiers.
|
|
329
|
-
|
|
330
|
-
### What to include in a bug report
|
|
331
|
-
|
|
332
|
-
- Output of `/mega-status` (config + store stats).
|
|
333
|
-
- Output of `/mega-dashboard-status`.
|
|
334
|
-
- Your pi version + OS + Node version (`node -v`).
|
|
335
|
-
- A slice of `events.log` around the problem (the `result`/`tier` lines).
|
|
336
|
-
- `dashboard.json` from the state dir (aggregate metrics: hit rate, FP rate,
|
|
337
|
-
per-tier p95, storage bytes).
|
|
338
|
-
- If you suspect data loss or duplication: the checkpoint count and the
|
|
339
|
-
`sqlite.db` size, plus the output of the DR drill (below).
|
|
340
|
-
|
|
341
|
-
**Disaster-recovery drill** (validates the store against its JSON snapshots and
|
|
342
|
-
rebuilds if corrupt — see `docs/RETENTION_POLICY.md` §5):
|
|
343
|
-
|
|
344
|
-
```bash
|
|
345
|
-
scripts/dedup-restore-drill.sh ~/.pi/agent/extensions/pi-mega-compact
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
**Benchmark** (dedup hit rate, compression ratio, per-tier p95, storage at
|
|
349
|
-
100 / 1K / 10K checkpoints):
|
|
350
|
-
|
|
351
|
-
```bash
|
|
352
|
-
npm run build
|
|
353
|
-
node scripts/dedup-benchmark.mjs 100 1000 10000
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
Open issues at: https://github.com/TheArchitectit/pi-mega-compact/issues
|
|
357
|
-
|
|
358
|
-
---
|
|
359
|
-
|
|
360
315
|
## Architecture & layout
|
|
361
316
|
|
|
362
317
|
```
|
|
363
318
|
extensions/mega-compact.ts pi extension entry; wires src/ into pi lifecycle
|
|
319
|
+
extensions/mega-trim.ts live context-event trim (compact-and-continue, no abort)
|
|
320
|
+
extensions/mega-conflict-cmds.ts extension-conflict detector (/mega-compat-check)
|
|
321
|
+
extensions/dashboard-server.ts localhost dashboard (HTML + snapshot/version/drift APIs)
|
|
364
322
|
src/adapt.ts the single pi↔engine message adapter (index-aligned)
|
|
365
323
|
src/engine.ts Layer 4: compactSession() Trident pipeline + recall()
|
|
366
324
|
src/vectorStore.ts Layer 3: local vector DB (add/search/dedupe + near-dup)
|
|
367
325
|
src/embedder.ts default TrigramEmbedder (deterministic, 512-dim)
|
|
368
326
|
src/httpEmbedder.ts BYO localhost embedder seam (MEGACOMPACT_EMBEDDING_URL)
|
|
369
|
-
src/store/sqlite.ts the "one store" —
|
|
327
|
+
src/store/sqlite.ts the "one store" — node:sqlite context_chunks + session_state (FTS5 trigram)
|
|
328
|
+
src/store/vectorIndex.ts async PGlite/HNSW cross-repo vector index (redundant, best-effort)
|
|
370
329
|
src/store/migrate.ts JSON → SQLite migration (legacy .checkpoints.json.gz retained)
|
|
371
330
|
src/store/backfill.ts resumable backfill orchestrator (L0/L1/L2/RAPTOR)
|
|
331
|
+
src/memory.ts durable memories (decision/fact/preference) + auto-review
|
|
332
|
+
src/memoryOps.ts memory apply/consolidate ops
|
|
333
|
+
src/memoryRecall.ts memory recall + auto-inline (RAG context)
|
|
334
|
+
src/driftDetection.ts cross-repo drift report (stale/idle/compaction-lag/model-churn)
|
|
372
335
|
src/monitoring.ts local events.log + dashboard.json metrics + FP alerts
|
|
373
336
|
src/canary.ts sequential L0→L1→L2→RAPTOR rollout, auto-disable on p95 breach
|
|
374
337
|
src/config/dedup.ts single source of truth for ALL dedup tier flags + thresholds
|
|
375
338
|
src/store.ts state dir + JSON DR helpers + compression re-exports
|
|
376
|
-
src/compact.ts
|
|
377
|
-
src/supersede.ts
|
|
378
|
-
src/boundary.ts
|
|
379
|
-
src/tokens.ts
|
|
380
|
-
src/types.ts
|
|
339
|
+
src/compact.ts Layer 2: summarize / merge / autoCompactCheck
|
|
340
|
+
src/supersede.ts Layer 1: obsolete file-read pruning
|
|
341
|
+
src/boundary.ts drop-boundary guards (anchor floor + tool-pair)
|
|
342
|
+
src/tokens.ts deterministic token estimator
|
|
343
|
+
src/types.ts engine-internal types
|
|
381
344
|
```
|
|
382
345
|
|
|
383
346
|
The `src/` directory is **pi-agnostic** and fully unit-tested (`node --test`).
|
|
@@ -389,34 +352,22 @@ The extension entry adapts between the engine and pi's runtime types.
|
|
|
389
352
|
|
|
390
353
|
```bash
|
|
391
354
|
npm run build # tsc
|
|
392
|
-
npm test # build + node --test on dist/**/*.test.js
|
|
355
|
+
npm test # build + node --test on dist/**/*.test.js (346 tests)
|
|
393
356
|
npm run lint # tsc --noEmit + guardrails-scan
|
|
394
357
|
npm run guardrails # regression_check + guardrails-scan
|
|
395
358
|
```
|
|
396
359
|
|
|
397
360
|
The agent-guardrails suite (Four Laws, scope, secrets, regression) gates every
|
|
398
|
-
|
|
361
|
+
change.
|
|
399
362
|
|
|
400
363
|
---
|
|
401
364
|
|
|
402
|
-
##
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
-
|
|
408
|
-
- ✅ Sprint 5 — commands / UX / config polish (status chip, store stats, debug log)
|
|
409
|
-
- ✅ Sprint 6 — hardening, docs, release (`install.sh`, CHANGELOG, `v0.1.0`)
|
|
410
|
-
- ✅ Sprint 8 — SQLite storage backbone (`better-sqlite3`, one store) + compression v2
|
|
411
|
-
- ✅ Sprints 9–11 — L0 exact-hash + L1 MinHash/LSH near-dup dedup tiers
|
|
412
|
-
- ✅ Sprint 12 — L2 semantic cosine + MMR; BYO localhost embedder (`HttpEmbedder`)
|
|
413
|
-
- ✅ Sprint 13 — RAPTOR hierarchical pre-compression (shadow mode)
|
|
414
|
-
- ✅ Sprint 14 — full pipeline: flags, backfill, monitoring, canary rollout
|
|
415
|
-
- ✅ Sprint 15 — benchmarks, DR drill, docs, `v0.2.0`
|
|
416
|
-
|
|
417
|
-
See `SPRINT_PLAN.md` for the full breakdown and `PLAN.md` for architecture,
|
|
418
|
-
`RESEARCH.md` for the pi-API constraints that shaped it, `CHANGELOG.md` for
|
|
419
|
-
release notes.
|
|
365
|
+
## Testing & bug reports
|
|
366
|
+
|
|
367
|
+
Full QA instructions — environment setup, the manual test checklist, what to
|
|
368
|
+
include in a bug report, and known limitations — live in
|
|
369
|
+
[`TESTER_GUIDE.md`](TESTER_GUIDE.md). Open issues at
|
|
370
|
+
[github.com/TheArchitectit/pi-mega-compact/issues](https://github.com/TheArchitectit/pi-mega-compact/issues).
|
|
420
371
|
|
|
421
372
|
---
|
|
422
373
|
|
|
@@ -428,4 +379,4 @@ neuralwatt-mcr (pi-extension mechanics). Attribution as design sources only.
|
|
|
428
379
|
|
|
429
380
|
## License
|
|
430
381
|
|
|
431
|
-
[
|
|
382
|
+
[BSD-2-Clause](./LICENSE)
|
|
@@ -136,6 +136,8 @@ function readSnapshot(snapshotPath) {
|
|
|
136
136
|
version: 1,
|
|
137
137
|
updatedAt: null,
|
|
138
138
|
tier: "unknown",
|
|
139
|
+
presetTier: "unknown",
|
|
140
|
+
pressure: 0,
|
|
139
141
|
config: { fastGatePct: 80, thresholdTokens: 100_000, anchorUserMessages: 1, preserveRecent: 2, auto: true, autoInlineK: 3 },
|
|
140
142
|
session: { id: null, state: null, persistedThisSession: false, lastCheckpointId: null, lastCompactedFrom: 0 },
|
|
141
143
|
context: { tokens: null, percent: null, contextWindow: 0 },
|
|
@@ -261,7 +263,7 @@ function dashboardHtml(tierName) {
|
|
|
261
263
|
|
|
262
264
|
<div class="offline-banner" id="offline-banner">Dashboard data unavailable — waiting for a pi session to write snapshot...</div>
|
|
263
265
|
|
|
264
|
-
<h1><span>mega-compact</span><span class="tier">${tierName}</span><span class="version-pill">v${dashboardServerVersion}</span><span class="model-pill" id="hdr-model">—</span></h1>
|
|
266
|
+
<h1><span>mega-compact</span><span class="tier" id="hdr-tier">${tierName}</span><span class="version-pill">v${dashboardServerVersion}</span><span class="model-pill" id="hdr-model">—</span></h1>
|
|
265
267
|
|
|
266
268
|
<nav class="tabs">
|
|
267
269
|
<button class="tab active" data-tab="current">Current repo</button>
|
|
@@ -323,7 +325,9 @@ function dashboardHtml(tierName) {
|
|
|
323
325
|
<div class="card">
|
|
324
326
|
<h2>Configuration</h2>
|
|
325
327
|
<div class="conf-grid">
|
|
326
|
-
<span class="label">Tier</span><span class="value" id="cf-tier">${tierName}</span>
|
|
328
|
+
<span class="label" title="Live pressure band — climbs low→mega as context fills the window.">Tier (live)</span><span class="value" id="cf-tier">${tierName}</span>
|
|
329
|
+
<span class="label" title="The env-resolved base compaction preset (low/medium/high/ultra/mega) that set the token threshold.">Preset</span><span class="value" id="cf-preset">—</span>
|
|
330
|
+
<span class="label" title="Live pressure = currentTokens / thresholdTokens (0–100%).">Pressure</span><span class="value" id="cf-pressure">—</span>
|
|
327
331
|
<span class="label">Threshold</span><span class="value" id="cf-threshold">—</span>
|
|
328
332
|
<span class="label">Fast Gate</span><span class="value" id="cf-gate">—</span>
|
|
329
333
|
<span class="label">Auto</span><span class="value" id="cf-auto">—</span>
|
|
@@ -502,7 +506,12 @@ function dashboardHtml(tierName) {
|
|
|
502
506
|
document.getElementById('cr-status').textContent = (crew.activeAgents > 0)
|
|
503
507
|
? ('▶ ' + crew.activeAgents + ' running') : 'idle';
|
|
504
508
|
|
|
505
|
-
|
|
509
|
+
// S24: headline tier is the LIVE pressure band; the config card shows the
|
|
510
|
+
// env preset + live pressure ratio so the user sees the system react.
|
|
511
|
+
document.getElementById('hdr-tier').textContent = d.tier;
|
|
512
|
+
document.getElementById('cf-tier').textContent = d.tier + ' (live)';
|
|
513
|
+
document.getElementById('cf-preset').textContent = d.presetTier;
|
|
514
|
+
document.getElementById('cf-pressure').textContent = Math.round((d.pressure || 0) * 100) + '%';
|
|
506
515
|
document.getElementById('cf-threshold').textContent = d.config.thresholdTokens.toLocaleString();
|
|
507
516
|
document.getElementById('cf-gate').textContent = d.config.fastGatePct + '%';
|
|
508
517
|
document.getElementById('cf-auto').textContent = d.config.auto ? 'enabled' : 'disabled';
|
|
@@ -12,7 +12,6 @@ import { decompressSmart } from "../src/store/compression.js";
|
|
|
12
12
|
import { loadMetrics, fpRate, p95 } from "../src/monitoring.js";
|
|
13
13
|
import { C, recentUserQuery } from "./mega-runtime.js";
|
|
14
14
|
import { runCompact, doRecall, doRecallAsync } from "./mega-pipeline.js";
|
|
15
|
-
import { setTier, COMPACT_TIERS } from "./mega-config.js";
|
|
16
15
|
/** Resolve a checkpoint by id (or "recent"/"last") from this session's store. */
|
|
17
16
|
export function findCheckpoint(runtime, sid, ref) {
|
|
18
17
|
const all = listCheckpoints(sid, runtime.currentStateDir);
|
|
@@ -117,7 +116,8 @@ export function registerCommands(pi, runtime, config) {
|
|
|
117
116
|
}
|
|
118
117
|
catch { /* non-fatal */ }
|
|
119
118
|
const crossRepoStr = `${crossRepoInjections} cross-repo injections recorded · ${repoCount} repos indexed`;
|
|
120
|
-
ctx.ui.notify(`[mega-compact] pct=${pct} tokens=${tokens} tier=${
|
|
119
|
+
ctx.ui.notify(`[mega-compact] pct=${pct} tokens=${tokens} tier=${runtime.pressureBand} (live) preset=${config.tier} ` +
|
|
120
|
+
`pressure=${Math.round(runtime.pressure * 100)}% fastGate=${config.fastGatePct}% ` +
|
|
121
121
|
`threshold=${config.thresholdTokens} auto=${config.auto} autoInline=${config.autoInline}\n` +
|
|
122
122
|
`[mega-compact] store: ${st.checkpointCount} chkpt · ` +
|
|
123
123
|
`${st.totalTokenEstimate} tok · last=${st.lastCheckpointId ?? "—"} · ` +
|
|
@@ -215,25 +215,7 @@ export function registerCommands(pi, runtime, config) {
|
|
|
215
215
|
`• data safety — every compressed region is kept verbatim; nothing is permanently deleted. /mega-restore brings any of it back.`);
|
|
216
216
|
},
|
|
217
217
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const arg = args.trim().toLowerCase();
|
|
222
|
-
if (!arg) {
|
|
223
|
-
// Show current tier and available options.
|
|
224
|
-
ctx.ui.notify(`[mega-compact] current tier: ${config.tier} (${config.thresholdTokens} tok)\n` +
|
|
225
|
-
`[mega-compact] available tiers: ${Object.entries(COMPACT_TIERS).map(([k, v]) => `${k}=${v}`).join(", ")}`);
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
if (!(arg in COMPACT_TIERS)) {
|
|
229
|
-
ctx.ui.notify(`[mega-compact] unknown tier "${arg}". Available: ${Object.keys(COMPACT_TIERS).join(", ")}`);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
const newTier = arg;
|
|
233
|
-
setTier(config, newTier);
|
|
234
|
-
runtime.setStatus(ctx, `mega-compact: tier → ${newTier} (${config.thresholdTokens} tok)`);
|
|
235
|
-
ctx.ui.notify(`[mega-compact] tier changed to ${newTier} (threshold: ${config.thresholdTokens} tokens)`);
|
|
236
|
-
runtime.snapshot(ctx);
|
|
237
|
-
},
|
|
238
|
-
});
|
|
218
|
+
// NOTE: /mega-tier was removed in S24. The tier the user sees is now the LIVE
|
|
219
|
+
// pressure band (low/medium/high/ultra/mega), which climbs automatically as
|
|
220
|
+
// context fills — there is no manual tier to set. See docs/specs/s24-unified-pressure.md.
|
|
239
221
|
}
|
|
@@ -384,16 +384,19 @@ const TIER_CASES = [
|
|
|
384
384
|
["mega", 10_000_000],
|
|
385
385
|
];
|
|
386
386
|
for (const [tier, threshold] of TIER_CASES) {
|
|
387
|
-
test(`tier "${tier}" resolves to a ${threshold}-token threshold`, async () => {
|
|
387
|
+
test(`tier "${tier}" resolves to a ${threshold}-token threshold (preset; live band shown separately)`, async () => {
|
|
388
388
|
// Keep tier + keep threshold UNSET so the tier (not an explicit number)
|
|
389
389
|
// drives the threshold. harness() would otherwise reset the threshold.
|
|
390
390
|
delete process.env.MEGACOMPACT_THRESHOLD_TOKENS;
|
|
391
391
|
process.env.MEGACOMPACT_TIER = tier;
|
|
392
392
|
const h = harness({ keepTier: true, keepThreshold: true });
|
|
393
|
+
// tokens=1 against a 2M window → near-zero pressure → live band "low".
|
|
393
394
|
const ctx = h.ctx({ getContextUsage: () => ({ tokens: 1, contextWindow: 2_000_000, percent: 0.01 }) });
|
|
394
395
|
await h.commands["mega-status"].handler("", ctx);
|
|
395
396
|
delete process.env.MEGACOMPACT_TIER;
|
|
396
|
-
assert.ok(h.notifies.some((n) => n.includes(`
|
|
397
|
+
assert.ok(h.notifies.some((n) => n.includes(`preset=${tier}`) && n.includes(`threshold=${threshold}`)), `status should report preset=${tier} threshold=${threshold}`);
|
|
398
|
+
// S24: the headline tier is the LIVE pressure band, shown as "tier=low (live)".
|
|
399
|
+
assert.ok(h.notifies.some((n) => n.includes("tier=low (live)")), "live band reported (low at near-zero pressure)");
|
|
397
400
|
});
|
|
398
401
|
}
|
|
399
402
|
test("explicit MEGACOMPACT_THRESHOLD_TOKENS overrides the tier", async () => {
|
|
@@ -403,7 +406,7 @@ test("explicit MEGACOMPACT_THRESHOLD_TOKENS overrides the tier", async () => {
|
|
|
403
406
|
const ctx = h.ctx({ getContextUsage: () => ({ tokens: 1, contextWindow: 2_000_000, percent: 0.01 }) });
|
|
404
407
|
await h.commands["mega-status"].handler("", ctx);
|
|
405
408
|
delete process.env.MEGACOMPACT_TIER;
|
|
406
|
-
assert.ok(h.notifies.some((n) => n.includes("
|
|
409
|
+
assert.ok(h.notifies.some((n) => n.includes("preset=custom") && n.includes("threshold=777")), "explicit threshold wins over tier (preset=custom)");
|
|
407
410
|
});
|
|
408
411
|
// ---- /dashboard commands ----------------------------------------------------
|
|
409
412
|
test("/dashboard-status reports no server when pid file missing", async () => {
|
|
@@ -47,11 +47,13 @@ function resolveThreshold() {
|
|
|
47
47
|
return { tier, thresholdTokens: COMPACT_TIERS[tier] };
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* Pressure helpers for adaptive compression
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* Pressure helpers for adaptive compression live in src/config.ts (pi-agnostic)
|
|
51
|
+
* so unit tests can import them without the pi runtime. Re-export here so the
|
|
52
|
+
* extension has one import surface. (S24 unified the previously percentage-only
|
|
53
|
+
* signal into pressureRatio/pressureBand, which the runtime uses as the single
|
|
54
|
+
* "how full" signal that drives the tier label, trim depth, and memory cadence.)
|
|
53
55
|
*/
|
|
54
|
-
export { pressureFromPct, preserveRecentForPressure } from "../src/config.js";
|
|
56
|
+
export { pressureFromPct, preserveRecentForPressure, pressureRatio, pressureBand, memoryReviewCadence, } from "../src/config.js";
|
|
55
57
|
/** Build the resolved config from env + defaults. */
|
|
56
58
|
export function loadConfig() {
|
|
57
59
|
const { tier, thresholdTokens } = resolveThreshold();
|
|
@@ -80,11 +82,12 @@ export function loadConfig() {
|
|
|
80
82
|
debug: envBool("MEGACOMPACT_DEBUG", false),
|
|
81
83
|
};
|
|
82
84
|
}
|
|
83
|
-
/**
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Remove a cached tier mutation helper here — the live tier the user sees is the
|
|
87
|
+
* pressure band (MegaRuntime.pressureBand), and the base preset is env-resolved
|
|
88
|
+
* at load (loadConfig). The /mega-tier command was removed in S24 so there is no
|
|
89
|
+
* runtime tier mutation; see the S24 spec (docs/specs/s24-unified-pressure.md).
|
|
90
|
+
*/
|
|
88
91
|
/**
|
|
89
92
|
* Resolve the current repo's git root from a cwd. Returns undefined for a
|
|
90
93
|
* non-git directory (caller falls back to a global state dir).
|
|
@@ -14,7 +14,7 @@ import { runCompact, doRecall, doRecallAsync, piCompactWouldNoop } from "./mega-
|
|
|
14
14
|
import { recallMemoriesAndInline } from "../src/recall.js";
|
|
15
15
|
import { driveNativeCompaction } from "./mega-compact-driver.js";
|
|
16
16
|
import { computeLiveTrimCut, liveTrimSummaryMessage } from "./mega-trim.js";
|
|
17
|
-
import { pressureFromPct } from "./mega-config.js";
|
|
17
|
+
import { pressureFromPct, memoryReviewCadence } from "./mega-config.js";
|
|
18
18
|
/** Register all pi lifecycle event handlers. */
|
|
19
19
|
export function registerEventHandlers(pi, runtime, config) {
|
|
20
20
|
// ---- Session lifecycle (state reset points) -------------------------------
|
|
@@ -158,26 +158,31 @@ export function registerEventHandlers(pi, runtime, config) {
|
|
|
158
158
|
pi.on("turn_end", async (event, ctx) => {
|
|
159
159
|
runtime.dashboard.event("turn_end", { turnIndex: event.turnIndex });
|
|
160
160
|
runtime.snapshot(ctx);
|
|
161
|
-
// S20: auto-review the conversation
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
161
|
+
// S20+S24: auto-review the conversation and persist durable memories. The
|
|
162
|
+
// review cadence scales with pressure (memoryReviewCadence): as context
|
|
163
|
+
// fills, the conversation is reviewed more often so memories keep pace with
|
|
164
|
+
// faster churn. Best-effort + non-fatal: a review failure must never break
|
|
165
|
+
// the agent loop. Debounced by the pressure-adjusted interval.
|
|
166
|
+
if (config.memoryAutoReview && runtime.currentTurn > 0) {
|
|
167
|
+
const cadence = memoryReviewCadence(runtime.pressureBand, config.memoryReviewInterval);
|
|
168
|
+
if (runtime.currentTurn % cadence === 0) {
|
|
169
|
+
try {
|
|
170
|
+
const { reviewConversation } = await import("../src/memory.js");
|
|
171
|
+
const { applyMemoryOps } = await import("../src/memoryOps.js");
|
|
172
|
+
const entries = ctx.sessionManager.getEntries();
|
|
173
|
+
const view = runtime.engineView(entries.flatMap((e) => (e.message ? [e.message] : [])));
|
|
174
|
+
const ops = reviewConversation(view, []);
|
|
175
|
+
if (ops.length) {
|
|
176
|
+
await applyMemoryOps(ops, runtime.currentStateDir);
|
|
177
|
+
// S21.2: a memory op landed in this turn window. The pipeline reads
|
|
178
|
+
// this counter after a successful compaction and fires
|
|
179
|
+
// `consolidateMemories` only when it's > 0.
|
|
180
|
+
runtime.memoriesTouchedThisCompaction += ops.length;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
/* non-fatal — auto-review must not break the turn loop */
|
|
177
185
|
}
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
/* non-fatal — auto-review must not break the turn loop */
|
|
181
186
|
}
|
|
182
187
|
}
|
|
183
188
|
});
|
|
@@ -133,6 +133,28 @@ function doCompact(view, keepFrom, opts, sid, config, pi, ctx, runtime) {
|
|
|
133
133
|
/* non-fatal */
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
+
// S24 review-on-compact: when pressure is high, the just-compacted region is
|
|
137
|
+
// exactly the context worth remembering, so review it immediately rather than
|
|
138
|
+
// waiting for the next turn-cadence tick. Fire-and-forget (doCompact is sync):
|
|
139
|
+
// best-effort + non-fatal, paralleling the consolidate pass above. Only fires
|
|
140
|
+
// above the `high` band so low-pressure compactions don't pay the review cost.
|
|
141
|
+
if (!result.deduped && config.memoryAutoReview && runtime.pressureBand !== "low" && runtime.pressureBand !== "medium") {
|
|
142
|
+
void (async () => {
|
|
143
|
+
try {
|
|
144
|
+
const { reviewConversation } = await import("../src/memory.js");
|
|
145
|
+
const { applyMemoryOps } = await import("../src/memoryOps.js");
|
|
146
|
+
const ops = reviewConversation(view, []);
|
|
147
|
+
if (ops.length) {
|
|
148
|
+
await applyMemoryOps(ops, runtime.currentStateDir);
|
|
149
|
+
runtime.memoriesTouchedThisCompaction += ops.length;
|
|
150
|
+
runtime.pushTicker(`${C.green}🧠${C.reset} reviewed ${ops.length} memory op${ops.length === 1 ? "" : "s"} (pressure)`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
/* non-fatal — review-on-compact must never break the compaction */
|
|
155
|
+
}
|
|
156
|
+
})();
|
|
157
|
+
}
|
|
136
158
|
// Sentinel marker: a non-LLM bookkeeping entry so subsequent triggers can
|
|
137
159
|
// skip re-vectorizing an already-compacted region (zero token cost).
|
|
138
160
|
pi.appendEntry(MARKER_TYPE, {
|
|
@@ -17,7 +17,7 @@ import { toEngineMessages } from "../src/adapt.js";
|
|
|
17
17
|
import { normalizeSessionId } from "../src/store.js";
|
|
18
18
|
import { Logger } from "../src/log.js";
|
|
19
19
|
import { recordModelSnapshot, latestModelSnapshot, upsertRepoRegistry, recordRepoModel } from "../src/store/sqlite.js";
|
|
20
|
-
import { repoStateDir, resolveRepoRoot } from "./mega-config.js";
|
|
20
|
+
import { repoStateDir, resolveRepoRoot, pressureRatio, pressureFromPct, pressureBand } from "./mega-config.js";
|
|
21
21
|
import { Dashboard } from "./mega-dashboard.js";
|
|
22
22
|
export const STATUS_KEY = "mega-compact";
|
|
23
23
|
export const WIDGET_KEY = "mega-compact-stats";
|
|
@@ -120,6 +120,24 @@ export class MegaRuntime {
|
|
|
120
120
|
lastCtxTokens = null;
|
|
121
121
|
lastCtxPercent = null;
|
|
122
122
|
lastCtxWindow = 0;
|
|
123
|
+
/**
|
|
124
|
+
* Live 0–1 pressure: how full the context window is relative to the compaction
|
|
125
|
+
* threshold. Computed from the most recent context event the runtime already
|
|
126
|
+
* tracks (token count when available — the direct signal — otherwise the usage
|
|
127
|
+
* percentage). This is the single "how full" number every subsystem reads; the
|
|
128
|
+
* toolbar/dashboard tier label is `pressureBand` over this, so it climbs
|
|
129
|
+
* low→mega as context rises (S24). Always finite + in [0,1].
|
|
130
|
+
*/
|
|
131
|
+
get pressure() {
|
|
132
|
+
if (this.lastCtxTokens != null && this.lastCtxTokens > 0 && this.config.thresholdTokens > 0) {
|
|
133
|
+
return pressureRatio(this.lastCtxTokens, this.config.thresholdTokens);
|
|
134
|
+
}
|
|
135
|
+
return pressureFromPct(this.lastCtxPercent);
|
|
136
|
+
}
|
|
137
|
+
/** Live discrete pressure band (low/medium/high/ultra/mega) over `pressure`. */
|
|
138
|
+
get pressureBand() {
|
|
139
|
+
return pressureBand(this.pressure);
|
|
140
|
+
}
|
|
123
141
|
constructor(config) {
|
|
124
142
|
this.config = config;
|
|
125
143
|
this.store = new VectorStore({ dedupSim: config.dedupSim, stateDir: config.stateDir });
|
|
@@ -190,7 +208,11 @@ export class MegaRuntime {
|
|
|
190
208
|
this.dashboard.snapshot({
|
|
191
209
|
version: 1,
|
|
192
210
|
updatedAt: new Date().toISOString(),
|
|
193
|
-
|
|
211
|
+
// S24: the headline tier is the LIVE pressure band; the env preset is kept
|
|
212
|
+
// alongside as presetTier so the dashboard can show both.
|
|
213
|
+
tier: this.pressureBand,
|
|
214
|
+
presetTier: this.config.tier,
|
|
215
|
+
pressure: this.pressure,
|
|
194
216
|
config: {
|
|
195
217
|
fastGatePct: this.config.fastGatePct,
|
|
196
218
|
thresholdTokens: this.config.thresholdTokens,
|
|
@@ -236,6 +258,11 @@ export class MegaRuntime {
|
|
|
236
258
|
const tokStr = this.lastCtxTokens != null ? `${Math.round(this.lastCtxTokens / 1000)}k` : "?";
|
|
237
259
|
const maxStr = this.lastCtxWindow > 0 ? `${Math.round(this.lastCtxWindow / 1000)}k` : "?";
|
|
238
260
|
const pctStr = this.lastCtxPercent != null ? `${Math.round(this.lastCtxPercent * 10) / 10}%` : "?%";
|
|
261
|
+
// S24: the tier label is the LIVE pressure band (low/medium/high/ultra/
|
|
262
|
+
// mega), not the static env preset. It climbs as context fills, so the
|
|
263
|
+
// user can see the system react. The base preset is shown as a dim suffix.
|
|
264
|
+
const liveBand = this.pressureBand;
|
|
265
|
+
const tierLabel = `${C.bold}${liveBand}${C.reset}${C.gray}·${this.config.tier}${C.reset}`;
|
|
239
266
|
const triggerLabel = ready ? `${C.green}● ready${C.reset}` : armed ? `${C.amber}◐ armed${C.reset}` : `${C.gray}○ idle${C.reset}`;
|
|
240
267
|
// Storage dedup rate is cumulative (store-wide, per-repo) and survives
|
|
241
268
|
// session resets. Always show a number: 0% before any compaction, a
|
|
@@ -258,7 +285,7 @@ export class MegaRuntime {
|
|
|
258
285
|
// Phase 3 — pulsing status glyph while a compaction is in flight.
|
|
259
286
|
const pulse = this.pulsing ? `${C.cyan}${PULSE[Math.floor(Date.now() / 250) % PULSE.length]}${C.reset} ` : "";
|
|
260
287
|
const lines = [
|
|
261
|
-
` ${C.amber}⚡ ${
|
|
288
|
+
` ${C.amber}⚡ ${tierLabel}${C.reset} v${C.bold}${ownVersion()}${C.reset} │ ${tokStr}/${maxStr} tokens (${C.bold}${pctStr}${C.reset}) │ ${st.checkpointCount} saved${agentStr}${turnStr}`,
|
|
262
289
|
` ${triggerLabel} │ ${C.magenta}repeat-skipped: ${dedupStr}${C.reset} │ ${C.gray}memory held:${C.reset} ${usedStr} │ ${C.gray}space freed:${C.reset} ${savedStr}`,
|
|
263
290
|
];
|
|
264
291
|
// Phase 3 — compact progress bar: session tokens saved toward the rolling goal.
|
|
@@ -267,7 +294,10 @@ export class MegaRuntime {
|
|
|
267
294
|
const pct = Math.min(100, Math.round((this.rt.tokensSaved / goal) * 100));
|
|
268
295
|
const filled = Math.round((pct / 100) * 10);
|
|
269
296
|
const bar = "▓".repeat(filled) + "░".repeat(10 - filled);
|
|
270
|
-
|
|
297
|
+
// Session tokens saved, with the repo-wide total held alongside so the
|
|
298
|
+
// bar reads "saved X of goal" and the right side shows saved vs total.
|
|
299
|
+
const totalHeld = st.totalTokenEstimate > 0 ? st.totalTokenEstimate : repo.totalTokenEstimate;
|
|
300
|
+
lines.push(` ${C.green}saved ${fmt(this.rt.tokensSaved)} ${bar}${C.reset} ${pct}% of ${fmt(goal)} ${C.gray}│${C.reset} ${C.blue}${fmt(this.rt.tokensSaved)}${C.reset}/${C.blue}${fmt(totalHeld)}${C.reset} tok held`);
|
|
271
301
|
}
|
|
272
302
|
// Live "now processing" line + why + recent deduped/compacted events,
|
|
273
303
|
// collapsed to ONE rotating line (fresh only). The ticker ring buffer
|