pi-mega-compact 0.4.5 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/extensions/dashboard-server.js +450 -0
  2. package/dist/extensions/dashboard-server.test.js +111 -0
  3. package/dist/extensions/error-patterns.js +115 -0
  4. package/dist/extensions/mega-compact.js +782 -0
  5. package/dist/extensions/mega-compact.test.js +328 -0
  6. package/dist/extensions/openclaw-mega-compact.js +291 -0
  7. package/dist/src/adapt.js +106 -0
  8. package/dist/src/boundary.js +88 -0
  9. package/dist/src/boundary.test.js +53 -0
  10. package/dist/src/canary.js +118 -0
  11. package/dist/src/compact.js +250 -0
  12. package/dist/src/compact.test.js +78 -0
  13. package/dist/src/config/dedup.js +81 -0
  14. package/dist/src/config.js +12 -0
  15. package/dist/src/dedup/dedup.test.js +41 -0
  16. package/dist/src/dedup/digest.js +30 -0
  17. package/dist/src/dedup/l1-lsh.js +52 -0
  18. package/dist/src/dedup/l1-minhash.js +91 -0
  19. package/dist/src/dedup/l1-verify.js +54 -0
  20. package/dist/src/dedup/l1.test.js +50 -0
  21. package/dist/src/dedup/mmr.js +45 -0
  22. package/dist/src/dedup/normalize.js +39 -0
  23. package/dist/src/dedup/raptor/guardrails.js +83 -0
  24. package/dist/src/dedup/raptor/index.js +94 -0
  25. package/dist/src/dedup/raptor/kmeans.js +152 -0
  26. package/dist/src/dedup/raptor/raptor.test.js +205 -0
  27. package/dist/src/dedup/raptor/retrieval.js +81 -0
  28. package/dist/src/dedup/raptor/summarizer.js +85 -0
  29. package/dist/src/dedup/raptor/tree.js +177 -0
  30. package/dist/src/dedup/sprint12.test.js +219 -0
  31. package/dist/src/dedup/topk.js +60 -0
  32. package/dist/src/dedup-engine.test.js +447 -0
  33. package/dist/src/e2e.test.js +698 -0
  34. package/dist/src/embedder.js +102 -0
  35. package/dist/src/engine.js +137 -0
  36. package/dist/src/engine.test.js +111 -0
  37. package/dist/src/extractive.js +209 -0
  38. package/dist/src/extractive.test.js +130 -0
  39. package/dist/src/httpEmbedder.js +143 -0
  40. package/dist/src/log.js +47 -0
  41. package/dist/src/log.test.js +42 -0
  42. package/dist/src/minilm.js +92 -0
  43. package/dist/src/monitoring.js +131 -0
  44. package/dist/src/ratio.bench.test.js +897 -0
  45. package/dist/src/recall.integration.test.js +77 -0
  46. package/dist/src/recall.js +60 -0
  47. package/dist/src/recall.test.js +50 -0
  48. package/dist/src/sprint14.test.js +219 -0
  49. package/dist/src/store/backfill.js +189 -0
  50. package/dist/src/store/bloom.js +114 -0
  51. package/dist/src/store/compression.js +177 -0
  52. package/dist/src/store/compression.test.js +67 -0
  53. package/dist/src/store/integrity.js +44 -0
  54. package/dist/src/store/migrate.js +79 -0
  55. package/dist/src/store/migrate.test.js +139 -0
  56. package/dist/src/store/sprint10.test.js +186 -0
  57. package/dist/src/store/sqlite.js +574 -0
  58. package/dist/src/store.js +115 -0
  59. package/dist/src/store.test.js +142 -0
  60. package/dist/src/supersede.js +68 -0
  61. package/dist/src/supersede.test.js +36 -0
  62. package/dist/src/tokens.js +31 -0
  63. package/dist/src/types.js +8 -0
  64. package/dist/src/types.test.js +9 -0
  65. package/dist/src/vectorStore.js +465 -0
  66. package/dist/src/vectorStore.test.js +479 -0
  67. package/dist/src/wordpiece.js +129 -0
  68. package/package.json +4 -2
@@ -0,0 +1,142 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { gzipSync } from "node:zlib";
7
+ import { compressSmart, decompressSmart, readGzJson, writeGzJson, normalizeSessionId, nextCheckpointId, } from "./store.js";
8
+ const baseTmp = mkdtempSync(join(tmpdir(), "mc-store-"));
9
+ let counter = 0;
10
+ function tmpDir() {
11
+ return join(baseTmp, `d-${counter++}`);
12
+ }
13
+ // ---------------------------------------------------------------------------
14
+ // compressSmart / decompressSmart
15
+ // ---------------------------------------------------------------------------
16
+ test("compressSmart: tiny payload (<512B) uses RAW tier", () => {
17
+ const data = Buffer.from(JSON.stringify({ hello: "world" }));
18
+ assert.ok(data.length < 512);
19
+ const compressed = compressSmart(data);
20
+ // Versioned header: 0xEC 0x01 [version=1] [tier=0x00 RAW]; payload after byte 4.
21
+ assert.equal(compressed[0], 0xec, "magic hi");
22
+ assert.equal(compressed[1], 0x01, "magic lo / version marker");
23
+ assert.equal(compressed[2], 0x01, "format version 1");
24
+ assert.equal(compressed[3], 0x00, "tag RAW");
25
+ // Payload after the 4-byte header should be identical to original
26
+ assert.deepEqual(compressed.subarray(4), data);
27
+ });
28
+ test("compressSmart: medium payload (4KB–32KB) uses GZIP-6 tier", () => {
29
+ // ~8KB of repetitive text
30
+ const data = Buffer.from("the quick brown fox jumps over the lazy dog. ".repeat(180));
31
+ assert.ok(data.length >= 4096 && data.length < 32768);
32
+ const compressed = compressSmart(data);
33
+ // Versioned header + tier tag 0x02 (GZIP-6) at byte 3.
34
+ assert.equal(compressed[0], 0xec);
35
+ assert.equal(compressed[3], 0x02, "tag GZIP-6");
36
+ // Compressed should be smaller
37
+ assert.ok(compressed.length < data.length, "compressed smaller than raw");
38
+ });
39
+ test("compressSmart: large payload (>32KB) uses BROTLI tier", () => {
40
+ // ~40KB of repetitive text
41
+ const data = Buffer.from("this is a long summary of a coding session. ".repeat(900));
42
+ assert.ok(data.length >= 32768);
43
+ const compressed = compressSmart(data);
44
+ // Versioned header + tier tag 0x05 (BROTLI_4) at byte 3.
45
+ assert.equal(compressed[0], 0xec);
46
+ assert.equal(compressed[3], 0x05, "tag BROTLI_4");
47
+ // Compressed should be smaller
48
+ assert.ok(compressed.length < data.length, "brotli compressed smaller than raw");
49
+ });
50
+ test("compressSmart: small payload (512B–4KB) uses GZIP-1 tier", () => {
51
+ // ~1.5KB
52
+ const data = Buffer.from("a moderately sized summary with some repetition. ".repeat(30));
53
+ assert.ok(data.length >= 512 && data.length < 4096);
54
+ const compressed = compressSmart(data);
55
+ assert.equal(compressed[0], 0xec);
56
+ assert.equal(compressed[3], 0x01, "tag GZIP-1");
57
+ });
58
+ test("decompressSmart roundtrips all tiers", () => {
59
+ const sizes = [
60
+ { label: "tiny", gen: () => Buffer.from("small") },
61
+ { label: "small", gen: () => Buffer.from("x".repeat(600)) },
62
+ { label: "medium", gen: () => Buffer.from("y".repeat(8000)) },
63
+ { label: "large", gen: () => Buffer.from("z".repeat(40000)) },
64
+ ];
65
+ for (const { label, gen } of sizes) {
66
+ const original = gen();
67
+ const compressed = compressSmart(original);
68
+ const decompressed = decompressSmart(compressed);
69
+ assert.deepEqual(decompressed, original, `roundtrip failed for ${label} (${original.length}B)`);
70
+ }
71
+ });
72
+ test("decompressSmart handles legacy untagged gzip files (backward compat)", () => {
73
+ const data = Buffer.from(JSON.stringify({ legacy: true }));
74
+ const legacyGzip = gzipSync(data); // no tag byte, starts with 0x1f
75
+ assert.equal(legacyGzip[0], 0x1f, "gzip magic byte present");
76
+ const result = decompressSmart(legacyGzip);
77
+ assert.deepEqual(JSON.parse(result.toString()), { legacy: true });
78
+ });
79
+ test("readGzJson / writeGzJson roundtrip with smart compression", () => {
80
+ const dir = tmpDir();
81
+ const path = join(dir, "test.json.gz");
82
+ const data = [{ id: "a", value: 42 }, { id: "b", value: 99 }];
83
+ writeGzJson(path, data);
84
+ const loaded = readGzJson(path, []);
85
+ assert.deepEqual(loaded, data);
86
+ });
87
+ test("readGzJson reads legacy gzip files written by old code", () => {
88
+ const dir = tmpDir();
89
+ const path = join(dir, "legacy.json.gz");
90
+ const data = { old: true };
91
+ // Simulate old writeGzJson (plain gzip, no tag)
92
+ mkdirSync(join(path, ".."), { recursive: true });
93
+ const buf = gzipSync(Buffer.from(JSON.stringify(data), "utf-8"));
94
+ writeFileSync(path, buf);
95
+ const loaded = readGzJson(path, { old: false });
96
+ assert.deepEqual(loaded, { old: true });
97
+ });
98
+ test("compression tier: GZIP-1 and GZIP-6 tiers produce valid, smaller output", () => {
99
+ // GZIP-1 tier (~600B input, 512B–4KB band)
100
+ const small = compressSmart(Buffer.from("compress me ".repeat(200)));
101
+ assert.equal(small[0], 0xec, "versioned magic");
102
+ assert.equal(small[3], 0x01, "GZIP-1 tag for small input");
103
+ assert.ok(small.length < 600, "GZIP-1 output smaller than input");
104
+ assert.deepEqual(decompressSmart(small), Buffer.from("compress me ".repeat(200)));
105
+ // GZIP-6 tier (~8KB input, 4KB–32KB band)
106
+ const big = compressSmart(Buffer.from("compress me ".repeat(1800)));
107
+ assert.equal(big[0], 0xec, "versioned magic");
108
+ assert.equal(big[3], 0x02, "GZIP-6 tag for medium input");
109
+ assert.ok(big.length < Buffer.from("compress me ".repeat(1800)).length, "GZIP-6 output smaller than input");
110
+ assert.deepEqual(decompressSmart(big), Buffer.from("compress me ".repeat(1800)));
111
+ });
112
+ // ---------------------------------------------------------------------------
113
+ // normalizeSessionId
114
+ // ---------------------------------------------------------------------------
115
+ test("normalizeSessionId adds sess_ prefix when missing", () => {
116
+ assert.equal(normalizeSessionId("abc"), "sess_abc");
117
+ assert.equal(normalizeSessionId("sess_abc"), "sess_abc");
118
+ });
119
+ // ---------------------------------------------------------------------------
120
+ // nextCheckpointId
121
+ // ---------------------------------------------------------------------------
122
+ test("nextCheckpointId returns chkpt_001 for new session", () => {
123
+ const dir = tmpDir();
124
+ assert.equal(nextCheckpointId("sess_nci1", dir), "chkpt_001");
125
+ });
126
+ test("nextCheckpointId increments highest existing id", () => {
127
+ const dir = tmpDir();
128
+ const sid = "sess_nci2";
129
+ // Write a checkpoints file with two entries to simulate existing state
130
+ const fakeCheckpoints = [
131
+ { checkpointId: "chkpt_001", sessionId: "sess_nci2", summary: "a", keyDecisions: [], nextSteps: [], filesModified: [], tokenEstimate: 100, regionHash: "h1", embedding: [], timestamp: 1 },
132
+ { checkpointId: "chkpt_003", sessionId: "sess_nci2", summary: "b", keyDecisions: [], nextSteps: [], filesModified: [], tokenEstimate: 100, regionHash: "h2", embedding: [], timestamp: 2 },
133
+ ];
134
+ writeGzJson(join(dir, "sess_nci2.checkpoints.json.gz"), fakeCheckpoints);
135
+ assert.equal(nextCheckpointId(sid, dir), "chkpt_004");
136
+ });
137
+ // ---------------------------------------------------------------------------
138
+ // cleanup
139
+ // ---------------------------------------------------------------------------
140
+ test("cleanup", () => {
141
+ rmSync(baseTmp, { recursive: true, force: true });
142
+ });
@@ -0,0 +1,68 @@
1
+ /**
2
+ * supersede.ts — Layer 1 (SUPERSEDE): zero-cost factual pruning.
3
+ *
4
+ * If you read server.py, the previous read of server.py is factually obsolete
5
+ * once you write it. We detect file-read turns that are superseded by a later
6
+ * write (or a later read) to the same path and mark them for pruning — no
7
+ * summarization, no token cost. Mirrors memory-mcp MemoryCompactor Stage 1.
8
+ */
9
+ import { extractFileCandidates } from "./compact.js";
10
+ /** Classify a message's relationship to a file path. */
11
+ function fileOps(msg) {
12
+ const paths = extractFileCandidates(msg.text);
13
+ if (paths.length === 0)
14
+ return [];
15
+ const low = msg.text.toLowerCase();
16
+ const isWrite = /\b(write|edit|create|save|append|overwrite|update|patch|modify)\b/.test(low);
17
+ return paths.map((p) => ({ path: p, op: isWrite ? "write" : "read" }));
18
+ }
19
+ /**
20
+ * Return the indexes (into `messages`) of file-read turns superseded by a later
21
+ * operation on the same path. A read is obsolete once a write touches the same
22
+ * path, or once a newer read of the same path exists (keep only the latest).
23
+ */
24
+ export function findSuperseded(messages) {
25
+ // Build, per path, the latest operation index and whether a write occurred.
26
+ const lastWriteAt = new Map();
27
+ const lastReadAt = new Map();
28
+ messages.forEach((m, i) => {
29
+ for (const { path, op } of fileOps(m)) {
30
+ if (op === "write")
31
+ lastWriteAt.set(path, i);
32
+ else
33
+ lastReadAt.set(path, i);
34
+ }
35
+ });
36
+ const superseded = new Set();
37
+ // Reads before a write to the same path are obsolete.
38
+ messages.forEach((m, i) => {
39
+ for (const { path, op } of fileOps(m)) {
40
+ if (op !== "read")
41
+ continue;
42
+ const writeAt = lastWriteAt.get(path);
43
+ if (writeAt !== undefined && i < writeAt)
44
+ superseded.add(i);
45
+ }
46
+ });
47
+ // For paths with multiple reads and no write, keep only the latest read.
48
+ const readsByPath = new Map();
49
+ messages.forEach((m, i) => {
50
+ for (const { path, op } of fileOps(m)) {
51
+ if (op !== "read")
52
+ continue;
53
+ if (!readsByPath.has(path))
54
+ readsByPath.set(path, []);
55
+ readsByPath.get(path).push(i);
56
+ }
57
+ });
58
+ for (const [, idxs] of readsByPath) {
59
+ if (idxs.length > 1)
60
+ idxs.slice(0, -1).forEach((i) => superseded.add(i));
61
+ }
62
+ return [...superseded].sort((a, b) => a - b);
63
+ }
64
+ /** Convenience: drop superseded messages, preserving order. */
65
+ export function supersede(messages) {
66
+ const drop = new Set(findSuperseded(messages));
67
+ return messages.filter((_m, i) => !drop.has(i));
68
+ }
@@ -0,0 +1,36 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { findSuperseded, supersede } from "./supersede.js";
4
+ function msg(role, text) { return { role, text }; }
5
+ test("read superseded by later write to same path is pruned", () => {
6
+ const messages = [
7
+ msg("assistant", "read src/server.ts"),
8
+ msg("user", "now change it"),
9
+ msg("assistant", "write src/server.ts with the fix"),
10
+ ];
11
+ assert.deepEqual(findSuperseded(messages), [0]);
12
+ });
13
+ test("older read superseded by newer read of same path (keep latest)", () => {
14
+ const messages = [
15
+ msg("assistant", "read src/a.ts"),
16
+ msg("assistant", "read src/a.ts again"),
17
+ ];
18
+ assert.deepEqual(findSuperseded(messages), [0]);
19
+ });
20
+ test("unrelated reads are kept", () => {
21
+ const messages = [
22
+ msg("assistant", "read src/a.ts"),
23
+ msg("assistant", "read src/b.ts"),
24
+ ];
25
+ assert.deepEqual(findSuperseded(messages), []);
26
+ });
27
+ test("supersede() drops the obsolete read and preserves order", () => {
28
+ const messages = [
29
+ msg("assistant", "read src/server.ts"),
30
+ msg("user", "change it"),
31
+ msg("assistant", "write src/server.ts done"),
32
+ ];
33
+ const out = supersede(messages);
34
+ assert.equal(out.length, 2);
35
+ assert.equal(out[0].text, "change it");
36
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Token estimation — rough, deterministic, no LLM.
3
+ *
4
+ * Ported from claw-code rusty-claude-cli compact.rs estimate_message_tokens
5
+ * (len/4 + 1 per content block). Good enough to gate compaction and to report
6
+ * tokens-saved; NOT a substitute for a real tokenizer.
7
+ */
8
+ /** Estimate tokens for a single text/tool block already as a string. */
9
+ export function estimateBlockTokens(text) {
10
+ return Math.floor(text.length / 4) + 1;
11
+ }
12
+ /**
13
+ * Estimate tokens for an EngineMessage. Tool-use/result blocks carry name +
14
+ * input/output strings, mirrored from the claw-code block accounting.
15
+ */
16
+ export function estimateMessageTokens(msg) {
17
+ let t = 0;
18
+ if (msg.text)
19
+ t += estimateBlockTokens(msg.text);
20
+ if (msg.toolName)
21
+ t += estimateBlockTokens(msg.toolName);
22
+ if (msg.input)
23
+ t += estimateBlockTokens(msg.input);
24
+ if (msg.output)
25
+ t += estimateBlockTokens(msg.output);
26
+ return t;
27
+ }
28
+ /** Sum tokens over a list of messages. */
29
+ export function estimateSessionTokens(messages) {
30
+ return messages.reduce((acc, m) => acc + estimateMessageTokens(m), 0);
31
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared internal types for the pi-mega-compact engine.
3
+ *
4
+ * Kept independent of pi's runtime types (src/ is pi-agnostic; the extension
5
+ * entry in extensions/ adapts between the two). See RESEARCH.md for the pi
6
+ * AgentMessage contract this must eventually satisfy.
7
+ */
8
+ export {};
@@ -0,0 +1,9 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ test("EngineMessage roles are constrained to the pi message contract (no system role)", () => {
4
+ const roles = ["user", "assistant", "tool", "custom"];
5
+ // pi Message = user|assistant|tool (+ custom for markers). There is no system role.
6
+ const probe = "system";
7
+ assert.equal(roles.includes(probe), false);
8
+ assert.equal(roles.length, 4);
9
+ });