pi-mega-compact 0.4.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/LICENSE +24 -0
- package/README.md +375 -0
- package/extensions/DASHBOARD.md +160 -0
- package/extensions/dashboard-server.test.ts +124 -0
- package/extensions/dashboard-server.ts +459 -0
- package/extensions/error-patterns.ts +175 -0
- package/extensions/mega-compact.test.ts +351 -0
- package/extensions/mega-compact.ts +846 -0
- package/extensions/openclaw-mega-compact.ts +370 -0
- package/package.json +61 -0
- package/src/adapt.ts +120 -0
- package/src/boundary.test.ts +61 -0
- package/src/boundary.ts +94 -0
- package/src/canary.ts +126 -0
- package/src/compact.test.ts +99 -0
- package/src/compact.ts +262 -0
- package/src/config/dedup.ts +120 -0
- package/src/config.ts +15 -0
- package/src/dedup/dedup.test.ts +46 -0
- package/src/dedup/digest.ts +40 -0
- package/src/dedup/l1-lsh.ts +67 -0
- package/src/dedup/l1-minhash.ts +90 -0
- package/src/dedup/l1-verify.ts +55 -0
- package/src/dedup/l1.test.ts +57 -0
- package/src/dedup/mmr.ts +54 -0
- package/src/dedup/normalize.ts +41 -0
- package/src/dedup/raptor/guardrails.ts +112 -0
- package/src/dedup/raptor/index.ts +118 -0
- package/src/dedup/raptor/kmeans.ts +156 -0
- package/src/dedup/raptor/raptor.test.ts +238 -0
- package/src/dedup/raptor/retrieval.ts +102 -0
- package/src/dedup/raptor/summarizer.ts +91 -0
- package/src/dedup/raptor/tree.ts +254 -0
- package/src/dedup/sprint12.test.ts +242 -0
- package/src/dedup/topk.ts +61 -0
- package/src/dedup-engine.test.ts +609 -0
- package/src/e2e.test.ts +843 -0
- package/src/embedder.ts +111 -0
- package/src/engine.test.ts +123 -0
- package/src/engine.ts +192 -0
- package/src/extractive.test.ts +156 -0
- package/src/extractive.ts +265 -0
- package/src/httpEmbedder.ts +154 -0
- package/src/log.test.ts +47 -0
- package/src/log.ts +60 -0
- package/src/monitoring.ts +171 -0
- package/src/ratio.bench.test.ts +1316 -0
- package/src/recall.integration.test.ts +96 -0
- package/src/recall.test.ts +59 -0
- package/src/recall.ts +100 -0
- package/src/sprint14.test.ts +245 -0
- package/src/store/backfill.ts +263 -0
- package/src/store/bloom.ts +122 -0
- package/src/store/compression.test.ts +83 -0
- package/src/store/compression.ts +203 -0
- package/src/store/integrity.ts +65 -0
- package/src/store/migrate.test.ts +158 -0
- package/src/store/migrate.ts +108 -0
- package/src/store/sprint10.test.ts +182 -0
- package/src/store/sqlite.ts +519 -0
- package/src/store.test.ts +169 -0
- package/src/store.ts +192 -0
- package/src/supersede.test.ts +42 -0
- package/src/supersede.ts +67 -0
- package/src/tokens.ts +35 -0
- package/src/types.test.ts +10 -0
- package/src/types.ts +49 -0
- package/src/vectorStore.test.ts +480 -0
- package/src/vectorStore.ts +544 -0
package/src/store.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* store.ts — persistence primitives for checkpoints + session state.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors memory-mcp session_context.py: sessions normalize to `sess_xxx`,
|
|
5
|
+
* checkpoints are sequential `chkpt_001` per session. State lives under
|
|
6
|
+
* ~/.pi/agent/extensions/mega-compact/ as gzipped JSON.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { randomBytes } from "node:crypto";
|
|
10
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { compressSmart, decompressSmart } from "./store/compression.js";
|
|
14
|
+
|
|
15
|
+
// Re-export the compression primitives from their extracted home so existing
|
|
16
|
+
// imports (`import { compressSmart } from "./store.js"`) keep working.
|
|
17
|
+
export {
|
|
18
|
+
compressSmart,
|
|
19
|
+
decompressSmart,
|
|
20
|
+
compressZstd,
|
|
21
|
+
compressZstdMax,
|
|
22
|
+
decompressZstd,
|
|
23
|
+
isVersioned,
|
|
24
|
+
isZstd,
|
|
25
|
+
detectFormat,
|
|
26
|
+
decompressSyncAuto,
|
|
27
|
+
} from "./store/compression.js";
|
|
28
|
+
export type { CompressedFormat } from "./store/compression.js";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* State directory. Read lazily (per call) so tests can redirect it via
|
|
32
|
+
* MEGACOMPACT_STATE_DIR without re-importing the module. Defaults to
|
|
33
|
+
* ~/.pi/agent/extensions/mega-compact/.
|
|
34
|
+
*/
|
|
35
|
+
export function getStateDir(): string {
|
|
36
|
+
return process.env.MEGACOMPACT_STATE_DIR ?? join(homedir(), ".pi", "agent", "extensions", "mega-compact");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Normalize an arbitrary session id to the `sess_xxx` form (port of memory-mcp). */
|
|
40
|
+
export function normalizeSessionId(sessionId: string | undefined | null): string {
|
|
41
|
+
if (!sessionId) return `sess_${randomBytes(8).toString("hex")}`;
|
|
42
|
+
if (sessionId.startsWith("sess_")) return sessionId;
|
|
43
|
+
if (sessionId.length >= 32 && sessionId.includes("-")) {
|
|
44
|
+
return `sess_${sessionId.replace(/-/g, "").slice(0, 16)}`;
|
|
45
|
+
}
|
|
46
|
+
return `sess_${sessionId}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface StoredCheckpoint {
|
|
50
|
+
checkpointId: string;
|
|
51
|
+
sessionId: string;
|
|
52
|
+
summary: string;
|
|
53
|
+
/** Compressed topic summary (extractive, ~2K tokens vs ~70K raw). */
|
|
54
|
+
topicSummary?: string;
|
|
55
|
+
/** SHA-256 of topicSummary — summary-content dedup key. */
|
|
56
|
+
summaryHash?: string;
|
|
57
|
+
keyDecisions: string[];
|
|
58
|
+
nextSteps: string[];
|
|
59
|
+
filesModified: string[];
|
|
60
|
+
tokenEstimate: number;
|
|
61
|
+
regionHash: string;
|
|
62
|
+
/** Primary content-addressable hash (full 64-hex SHA-256 of normalized text). */
|
|
63
|
+
contentHash?: string;
|
|
64
|
+
/** Secondary independent hash (reversed-text view) — guards single-hash collisions. */
|
|
65
|
+
contentHash2?: string;
|
|
66
|
+
contentHashVersion?: number;
|
|
67
|
+
/** Whitespace/ANSI-normalized text the content hashes were computed over. */
|
|
68
|
+
normalizedText?: string;
|
|
69
|
+
/** Reconstructible raw region (sync-compressed) for audit/replay/re-summarize. */
|
|
70
|
+
compressedOriginal?: Buffer;
|
|
71
|
+
embedding: number[];
|
|
72
|
+
timestamp: number;
|
|
73
|
+
/** Dedup lifecycle: 'active' | 'removed' (SemDeDup) | 'dup-resolved' (backfill). */
|
|
74
|
+
dedupStatus?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// JSON persistence (DR snapshots + migration source).
|
|
79
|
+
// Compression lives in ./store/compression.ts; compressSmart/decompressSmart
|
|
80
|
+
// are imported above and re-exported for backward-compatible call sites.
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Read smart-compressed JSON, returning `fallback` on missing/corrupt file.
|
|
85
|
+
*
|
|
86
|
+
* Backward-compatible: detects legacy gzip files (magic byte 0x1f) and
|
|
87
|
+
* decompresses them correctly alongside new tagged files.
|
|
88
|
+
*/
|
|
89
|
+
export function readGzJson<T>(path: string, fallback: T): T {
|
|
90
|
+
try {
|
|
91
|
+
if (!existsSync(path)) return fallback;
|
|
92
|
+
const buf = readFileSync(path);
|
|
93
|
+
const out = decompressSmart(buf);
|
|
94
|
+
return JSON.parse(out.toString("utf-8")) as T;
|
|
95
|
+
} catch {
|
|
96
|
+
return fallback;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Write JSON with dynamic compression; creates parent dirs. */
|
|
101
|
+
export function writeGzJson(path: string, data: unknown): void {
|
|
102
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
103
|
+
const jsonBuf = Buffer.from(JSON.stringify(data), "utf-8");
|
|
104
|
+
const compressed = compressSmart(jsonBuf);
|
|
105
|
+
writeFileSync(path, compressed);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Append a checkpoint to the per-session checkpoint file (gzipped). */
|
|
109
|
+
export function appendCheckpoint(cp: StoredCheckpoint, stateDir: string = getStateDir()): void {
|
|
110
|
+
const file = join(stateDir, `${cp.sessionId}.checkpoints.json.gz`);
|
|
111
|
+
const existing = readGzJson<StoredCheckpoint[]>(file, []);
|
|
112
|
+
existing.push(cp);
|
|
113
|
+
writeGzJson(file, existing);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** All checkpoints for a session (across branches). */
|
|
117
|
+
export function listCheckpoints(sessionId: string, stateDir: string = getStateDir()): StoredCheckpoint[] {
|
|
118
|
+
const file = join(stateDir, `${normalizeSessionId(sessionId)}.checkpoints.json.gz`);
|
|
119
|
+
return readGzJson<StoredCheckpoint[]>(file, []);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Rewrite ALL checkpoints for a session (in-place update).
|
|
124
|
+
*
|
|
125
|
+
* Used by VectorStore when summaryHash or contentSimilarity dedup updates an
|
|
126
|
+
* existing checkpoint's timestamp/metadata instead of creating a new one.
|
|
127
|
+
*/
|
|
128
|
+
export function rewriteCheckpoints(
|
|
129
|
+
sessionId: string,
|
|
130
|
+
checkpoints: StoredCheckpoint[],
|
|
131
|
+
stateDir: string = getStateDir(),
|
|
132
|
+
): void {
|
|
133
|
+
const file = join(stateDir, `${normalizeSessionId(sessionId)}.checkpoints.json.gz`);
|
|
134
|
+
writeGzJson(file, checkpoints);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Generate the next sequential checkpoint id for a session (chkpt_001 ...).
|
|
139
|
+
*/
|
|
140
|
+
export function nextCheckpointId(sessionId: string, stateDir: string = getStateDir()): string {
|
|
141
|
+
const list = listCheckpoints(sessionId, stateDir);
|
|
142
|
+
const max = list.reduce((m, c) => {
|
|
143
|
+
const n = parseInt(c.checkpointId.replace("chkpt_", ""), 10);
|
|
144
|
+
return Number.isFinite(n) && n > m ? n : m;
|
|
145
|
+
}, 0);
|
|
146
|
+
return `chkpt_${String(max + 1).padStart(3, "0")}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface SessionState {
|
|
150
|
+
/** checkpointIds already injected into the current window this session. */
|
|
151
|
+
injectedCheckpointIds: string[];
|
|
152
|
+
/** regionHashes already represented (for sentinel dedup). */
|
|
153
|
+
storedRegionHashes: string[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Load mutable session state (created on demand). */
|
|
157
|
+
export function loadSessionState(sessionId: string, stateDir: string = getStateDir()): SessionState {
|
|
158
|
+
const file = join(stateDir, `${normalizeSessionId(sessionId)}.state.json.gz`);
|
|
159
|
+
return readGzJson<SessionState>(file, {
|
|
160
|
+
injectedCheckpointIds: [],
|
|
161
|
+
storedRegionHashes: [],
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function saveSessionState(sessionId: string, state: SessionState, stateDir: string = getStateDir()): void {
|
|
166
|
+
const file = join(stateDir, `${normalizeSessionId(sessionId)}.state.json.gz`);
|
|
167
|
+
writeGzJson(file, state);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Cumulative store-wide dedup accounting. Lives outside the per-session
|
|
172
|
+
* SessionState (which resets on every session instance) so the dedup rate
|
|
173
|
+
* stays stable across session restarts. Persisted as plain JSON in the
|
|
174
|
+
* state dir.
|
|
175
|
+
*/
|
|
176
|
+
export interface DedupStats {
|
|
177
|
+
/** Total add() calls (new checkpoints + deduped collapses). */
|
|
178
|
+
attempts: number;
|
|
179
|
+
/** add() calls that collapsed onto an existing checkpoint. */
|
|
180
|
+
deduped: number;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const DEDUP_STATS_FILE = "dedup-stats.json";
|
|
184
|
+
|
|
185
|
+
export function loadDedupStats(stateDir: string = getStateDir()): DedupStats {
|
|
186
|
+
const file = join(stateDir, DEDUP_STATS_FILE);
|
|
187
|
+
return readGzJson<DedupStats>(file, { attempts: 0, deduped: 0 });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function saveDedupStats(stats: DedupStats, stateDir: string = getStateDir()): void {
|
|
191
|
+
writeGzJson(join(stateDir, DEDUP_STATS_FILE), stats);
|
|
192
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import type { EngineMessage } from "./types.js";
|
|
4
|
+
import { findSuperseded, supersede } from "./supersede.js";
|
|
5
|
+
|
|
6
|
+
function msg(role: EngineMessage["role"], text: string): EngineMessage { return { role, text }; }
|
|
7
|
+
|
|
8
|
+
test("read superseded by later write to same path is pruned", () => {
|
|
9
|
+
const messages = [
|
|
10
|
+
msg("assistant", "read src/server.ts"),
|
|
11
|
+
msg("user", "now change it"),
|
|
12
|
+
msg("assistant", "write src/server.ts with the fix"),
|
|
13
|
+
];
|
|
14
|
+
assert.deepEqual(findSuperseded(messages), [0]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("older read superseded by newer read of same path (keep latest)", () => {
|
|
18
|
+
const messages = [
|
|
19
|
+
msg("assistant", "read src/a.ts"),
|
|
20
|
+
msg("assistant", "read src/a.ts again"),
|
|
21
|
+
];
|
|
22
|
+
assert.deepEqual(findSuperseded(messages), [0]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("unrelated reads are kept", () => {
|
|
26
|
+
const messages = [
|
|
27
|
+
msg("assistant", "read src/a.ts"),
|
|
28
|
+
msg("assistant", "read src/b.ts"),
|
|
29
|
+
];
|
|
30
|
+
assert.deepEqual(findSuperseded(messages), []);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("supersede() drops the obsolete read and preserves order", () => {
|
|
34
|
+
const messages = [
|
|
35
|
+
msg("assistant", "read src/server.ts"),
|
|
36
|
+
msg("user", "change it"),
|
|
37
|
+
msg("assistant", "write src/server.ts done"),
|
|
38
|
+
];
|
|
39
|
+
const out = supersede(messages);
|
|
40
|
+
assert.equal(out.length, 2);
|
|
41
|
+
assert.equal(out[0].text, "change it");
|
|
42
|
+
});
|
package/src/supersede.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
|
|
10
|
+
import type { EngineMessage } from "./types.js";
|
|
11
|
+
import { extractFileCandidates } from "./compact.js";
|
|
12
|
+
|
|
13
|
+
/** Classify a message's relationship to a file path. */
|
|
14
|
+
function fileOps(msg: EngineMessage): { path: string; op: "read" | "write" }[] {
|
|
15
|
+
const paths = extractFileCandidates(msg.text);
|
|
16
|
+
if (paths.length === 0) return [];
|
|
17
|
+
const low = msg.text.toLowerCase();
|
|
18
|
+
const isWrite = /\b(write|edit|create|save|append|overwrite|update|patch|modify)\b/.test(low);
|
|
19
|
+
return paths.map((p) => ({ path: p, op: isWrite ? "write" : "read" }));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Return the indexes (into `messages`) of file-read turns superseded by a later
|
|
24
|
+
* operation on the same path. A read is obsolete once a write touches the same
|
|
25
|
+
* path, or once a newer read of the same path exists (keep only the latest).
|
|
26
|
+
*/
|
|
27
|
+
export function findSuperseded(messages: EngineMessage[]): number[] {
|
|
28
|
+
// Build, per path, the latest operation index and whether a write occurred.
|
|
29
|
+
const lastWriteAt = new Map<string, number>();
|
|
30
|
+
const lastReadAt = new Map<string, number>();
|
|
31
|
+
messages.forEach((m, i) => {
|
|
32
|
+
for (const { path, op } of fileOps(m)) {
|
|
33
|
+
if (op === "write") lastWriteAt.set(path, i);
|
|
34
|
+
else lastReadAt.set(path, i);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const superseded = new Set<number>();
|
|
39
|
+
// Reads before a write to the same path are obsolete.
|
|
40
|
+
messages.forEach((m, i) => {
|
|
41
|
+
for (const { path, op } of fileOps(m)) {
|
|
42
|
+
if (op !== "read") continue;
|
|
43
|
+
const writeAt = lastWriteAt.get(path);
|
|
44
|
+
if (writeAt !== undefined && i < writeAt) superseded.add(i);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
// For paths with multiple reads and no write, keep only the latest read.
|
|
48
|
+
const readsByPath = new Map<string, number[]>();
|
|
49
|
+
messages.forEach((m, i) => {
|
|
50
|
+
for (const { path, op } of fileOps(m)) {
|
|
51
|
+
if (op !== "read") continue;
|
|
52
|
+
if (!readsByPath.has(path)) readsByPath.set(path, []);
|
|
53
|
+
readsByPath.get(path)!.push(i);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
for (const [, idxs] of readsByPath) {
|
|
57
|
+
if (idxs.length > 1) idxs.slice(0, -1).forEach((i) => superseded.add(i));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return [...superseded].sort((a, b) => a - b);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Convenience: drop superseded messages, preserving order. */
|
|
64
|
+
export function supersede(messages: EngineMessage[]): EngineMessage[] {
|
|
65
|
+
const drop = new Set(findSuperseded(messages));
|
|
66
|
+
return messages.filter((_m, i) => !drop.has(i));
|
|
67
|
+
}
|
package/src/tokens.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
|
|
9
|
+
/** Estimate tokens for a single text/tool block already as a string. */
|
|
10
|
+
export function estimateBlockTokens(text: string): number {
|
|
11
|
+
return Math.floor(text.length / 4) + 1;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Estimate tokens for an EngineMessage. Tool-use/result blocks carry name +
|
|
16
|
+
* input/output strings, mirrored from the claw-code block accounting.
|
|
17
|
+
*/
|
|
18
|
+
export function estimateMessageTokens(msg: {
|
|
19
|
+
text?: string;
|
|
20
|
+
toolName?: string;
|
|
21
|
+
input?: string;
|
|
22
|
+
output?: string;
|
|
23
|
+
}): number {
|
|
24
|
+
let t = 0;
|
|
25
|
+
if (msg.text) t += estimateBlockTokens(msg.text);
|
|
26
|
+
if (msg.toolName) t += estimateBlockTokens(msg.toolName);
|
|
27
|
+
if (msg.input) t += estimateBlockTokens(msg.input);
|
|
28
|
+
if (msg.output) t += estimateBlockTokens(msg.output);
|
|
29
|
+
return t;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Sum tokens over a list of messages. */
|
|
33
|
+
export function estimateSessionTokens(messages: Array<{ text?: string; toolName?: string; input?: string; output?: string }>): number {
|
|
34
|
+
return messages.reduce((acc, m) => acc + estimateMessageTokens(m), 0);
|
|
35
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
test("EngineMessage roles are constrained to the pi message contract (no system role)", () => {
|
|
5
|
+
const roles = ["user", "assistant", "tool", "custom"] as const;
|
|
6
|
+
// pi Message = user|assistant|tool (+ custom for markers). There is no system role.
|
|
7
|
+
const probe: string = "system";
|
|
8
|
+
assert.equal((roles as readonly string[]).includes(probe), false);
|
|
9
|
+
assert.equal(roles.length, 4);
|
|
10
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
|
|
9
|
+
/** A lightweight message shape the engine reasons about (role only matters). */
|
|
10
|
+
export interface EngineMessage {
|
|
11
|
+
role: "user" | "assistant" | "tool" | "custom";
|
|
12
|
+
text: string;
|
|
13
|
+
toolName?: string;
|
|
14
|
+
/** Tool input/output payload (for tool-use / tool-result roles). */
|
|
15
|
+
input?: string;
|
|
16
|
+
output?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A persisted compaction checkpoint (Layer 4 → vector store). */
|
|
20
|
+
export interface Checkpoint {
|
|
21
|
+
checkpointId: string; // chkpt_001
|
|
22
|
+
sessionId: string; // sess_xxx (normalized)
|
|
23
|
+
summary: string;
|
|
24
|
+
keyDecisions: string[];
|
|
25
|
+
nextSteps: string[];
|
|
26
|
+
filesModified: string[];
|
|
27
|
+
tokenEstimate: number;
|
|
28
|
+
regionHash: string; // dedup sentinel key
|
|
29
|
+
timestamp: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Result of a Trident run over a set of messages. */
|
|
33
|
+
export interface TridentResult {
|
|
34
|
+
superseded: string[];
|
|
35
|
+
collapsed: string;
|
|
36
|
+
checkpoints: Checkpoint[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Compaction config (mirrors claw-code CompactionConfig + memory-mcp knobs). */
|
|
40
|
+
export interface CompactionConfig {
|
|
41
|
+
/** Preserve the most recent N messages verbatim. */
|
|
42
|
+
preserveRecent: number;
|
|
43
|
+
/** Token budget that triggers compaction. */
|
|
44
|
+
maxEstimatedTokens: number;
|
|
45
|
+
/** Number of recent user messages to never drop (anchor floor). */
|
|
46
|
+
anchorUserMessages: number;
|
|
47
|
+
/** Min chatty messages before COLLAPSE summarizes them. */
|
|
48
|
+
collapseThreshold: number;
|
|
49
|
+
}
|