pi-mega-compact 0.8.24 → 0.8.26
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 +26 -0
- package/dist/extensions/mega-compact-s38.test.js +263 -14
- package/dist/extensions/mega-compact.js +15 -0
- package/dist/extensions/mega-config.js +3 -0
- package/dist/extensions/mega-events/agent-handlers.js +211 -26
- package/dist/extensions/mega-events/context-handler.js +45 -7
- package/dist/extensions/mega-events/error-classifier.js +125 -18
- package/dist/extensions/mega-pipeline/compact.js +24 -13
- package/dist/extensions/mega-pipeline/recall.js +31 -2
- package/dist/extensions/mega-runtime/dashboard-snapshot.js +4 -0
- package/dist/extensions/mega-runtime/runtime-snapshot.js +4 -0
- package/dist/extensions/mega-runtime/runtime.js +58 -5
- package/dist/src/boundary.js +79 -43
- package/dist/src/boundary.test.js +119 -2
- package/dist/src/canary.js +10 -0
- package/dist/src/config/dedup.js +14 -0
- package/dist/src/config.js +3 -1
- package/dist/src/dedup/raptor/buildHistory.js +164 -0
- package/dist/src/dedup/raptor/buildHistory.test.js +292 -0
- package/dist/src/dedup/raptor/index.js +38 -0
- package/dist/src/dedup/raptor/multilevel-serve.test.js +229 -0
- package/dist/src/dedup/raptor/multilevel.js +17 -5
- package/dist/src/dedup/raptor/multilevel.test.js +36 -1
- package/dist/src/dedup/raptor/raptor.test.js +43 -0
- package/dist/src/dedup/raptor/retrieval.js +14 -2
- package/dist/src/dedup/raptor/retrieval.test.js +95 -0
- package/dist/src/dedup/raptor/serve-gate.test.js +298 -0
- package/dist/src/dedup/raptor/summarizer.js +1 -0
- package/dist/src/dedup/raptor/tree.js +16 -2
- package/dist/src/engine.js +18 -2
- package/dist/src/httpEmbedder.js +96 -6
- package/dist/src/httpEmbedder.test.js +277 -0
- package/dist/src/mechanical-fix.test.js +65 -0
- package/dist/src/raptor-inject-summaries.test.js +162 -0
- package/dist/src/recall.js +153 -24
- package/dist/src/recall.test.js +179 -4
- package/dist/src/store/sqlite/dedup-mirror.js +32 -15
- package/dist/src/store/sqlite/maintenance.js +2 -2
- package/dist/src/store/sqlite/mechanical-fix.test.js +146 -0
- package/dist/src/store/sqlite/memories.js +5 -5
- package/dist/src/store/sqlite/meta.js +1 -1
- package/dist/src/store/sqlite/raptor.js +56 -17
- package/dist/src/store/sqlite/raptor.test.js +106 -0
- package/dist/src/store/sqlite/schema.js +90 -1
- package/dist/src/store/sqlite/session-state.js +9 -3
- package/dist/src/store/sqlite/stats.js +9 -5
- package/dist/src/store/sqlite/turns.js +179 -0
- package/dist/src/store/sqlite/turns.test.js +183 -0
- package/dist/src/store/sqlite/utils.js +15 -4
- package/dist/src/store/sqlite.js +1 -0
- package/dist/src/store.js +2 -2
- package/dist/src/vector-search-cache.test.js +157 -0
- package/dist/src/vector-search.js +107 -15
- package/dist/src/vectorStore.js +36 -8
- package/extensions/mega-compact-s38.test.ts +259 -14
- package/extensions/mega-compact.ts +15 -0
- package/extensions/mega-config.ts +18 -0
- package/extensions/mega-dashboard.ts +10 -1
- package/extensions/mega-events/agent-handlers.ts +211 -26
- package/extensions/mega-events/context-handler.ts +43 -7
- package/extensions/mega-events/error-classifier.ts +125 -17
- package/extensions/mega-pipeline/compact.ts +28 -16
- package/extensions/mega-pipeline/recall.ts +34 -2
- package/extensions/mega-runtime/dashboard-snapshot.ts +8 -0
- package/extensions/mega-runtime/helpers.ts +25 -1
- package/extensions/mega-runtime/runtime-snapshot.ts +4 -0
- package/extensions/mega-runtime/runtime.ts +69 -23
- package/package.json +1 -1
- package/src/boundary.test.ts +128 -2
- package/src/boundary.ts +75 -39
- package/src/canary.ts +10 -0
- package/src/config/dedup.ts +25 -0
- package/src/config.ts +3 -1
- package/src/dedup/raptor/buildHistory.test.ts +353 -0
- package/src/dedup/raptor/buildHistory.ts +259 -0
- package/src/dedup/raptor/index.ts +38 -0
- package/src/dedup/raptor/multilevel-serve.test.ts +273 -0
- package/src/dedup/raptor/multilevel.test.ts +47 -0
- package/src/dedup/raptor/multilevel.ts +18 -8
- package/src/dedup/raptor/raptor.test.ts +59 -0
- package/src/dedup/raptor/retrieval.test.ts +118 -0
- package/src/dedup/raptor/retrieval.ts +14 -2
- package/src/dedup/raptor/serve-gate.test.ts +348 -0
- package/src/dedup/raptor/summarizer.ts +1 -0
- package/src/dedup/raptor/tree.ts +17 -2
- package/src/engine.ts +32 -3
- package/src/httpEmbedder.test.ts +286 -0
- package/src/httpEmbedder.ts +98 -8
- package/src/mechanical-fix.test.ts +70 -0
- package/src/raptor-inject-summaries.test.ts +228 -0
- package/src/recall.test.ts +220 -4
- package/src/recall.ts +462 -265
- package/src/store/sqlite/dedup-mirror.ts +35 -18
- package/src/store/sqlite/maintenance.ts +2 -2
- package/src/store/sqlite/mechanical-fix.test.ts +162 -0
- package/src/store/sqlite/memories.ts +5 -5
- package/src/store/sqlite/meta.ts +1 -1
- package/src/store/sqlite/raptor.test.ts +139 -0
- package/src/store/sqlite/raptor.ts +135 -81
- package/src/store/sqlite/schema.ts +90 -1
- package/src/store/sqlite/session-state.ts +9 -3
- package/src/store/sqlite/stats.ts +10 -8
- package/src/store/sqlite/turns.test.ts +218 -0
- package/src/store/sqlite/turns.ts +302 -0
- package/src/store/sqlite/utils.ts +14 -4
- package/src/store/sqlite.ts +1 -0
- package/src/store.ts +9 -2
- package/src/vector-search-cache.test.ts +190 -0
- package/src/vector-search.ts +273 -156
- package/src/vectorStore.ts +443 -382
- package/extensions/mega-runtime/reset-runtime.ts +0 -80
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* raptor-inject-summaries.test.ts — S25 Phase-2 acceptance tests.
|
|
3
|
+
*
|
|
4
|
+
* Verifies the RAPTOR_INJECT_SUMMARIES optional phase (default ON): when
|
|
5
|
+
* enabled, recallAndInline prepends a hierarchical overview header built from
|
|
6
|
+
* the tree's top-level summary nodes (root + top level-1 clusters); when
|
|
7
|
+
* disabled, the recall block is unchanged (detail-only). Also covers the
|
|
8
|
+
* formatRaptorBlock formatter directly.
|
|
9
|
+
*
|
|
10
|
+
* No network. Real stores, temp state dirs.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { test, beforeEach, afterEach } from "node:test";
|
|
14
|
+
import assert from "node:assert/strict";
|
|
15
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { VectorStore, vectorList } from "./vectorStore.js";
|
|
19
|
+
import { runRaptor } from "./dedup/raptor/index.js";
|
|
20
|
+
import { compactSession } from "./engine.js";
|
|
21
|
+
import { loadDedupConfig } from "./config/dedup.js";
|
|
22
|
+
import { normalizeSessionId } from "./store.js";
|
|
23
|
+
import { recallAndInline, formatRaptorBlock } from "./recall.js";
|
|
24
|
+
import type { EngineMessage } from "./types.js";
|
|
25
|
+
import type { DedupConfigShape } from "./config/dedup.js";
|
|
26
|
+
|
|
27
|
+
let tmpDir: string;
|
|
28
|
+
let counter = 0;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
tmpDir = mkdtempSync(join(tmpdir(), "mc-ris-"));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function stateDir(): string {
|
|
39
|
+
return join(tmpDir, `run-${counter++}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function cfg(overrides?: Record<string, unknown>): DedupConfigShape {
|
|
43
|
+
return {
|
|
44
|
+
...loadDedupConfig(),
|
|
45
|
+
RAPTOR_ENABLED: true,
|
|
46
|
+
RAPTOR_MULTILEVEL_ENABLED: true,
|
|
47
|
+
L0_ENABLED: false,
|
|
48
|
+
L1_ENABLED: false,
|
|
49
|
+
L2_ENABLED: true,
|
|
50
|
+
RAPTOR_INJECT_SUMMARIES: true,
|
|
51
|
+
...overrides,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function msg(text: string, toolName?: string): EngineMessage {
|
|
56
|
+
return toolName
|
|
57
|
+
? { role: "assistant", text, toolName, input: text, output: text }
|
|
58
|
+
: { role: "user", text };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function seedTwoTopics(store: VectorStore, sid: string, per: number) {
|
|
62
|
+
const topics = [
|
|
63
|
+
"database connection pool postgres",
|
|
64
|
+
"user interface button react",
|
|
65
|
+
];
|
|
66
|
+
for (let i = 1; i <= per * 2; i++) {
|
|
67
|
+
compactSession(
|
|
68
|
+
{
|
|
69
|
+
sessionId: sid,
|
|
70
|
+
messages: [
|
|
71
|
+
msg(`${topics[i % 2]} checkpoint ${i} alpha beta`),
|
|
72
|
+
msg(`ack ${i}`, "Edit"),
|
|
73
|
+
],
|
|
74
|
+
keepFrom: 2,
|
|
75
|
+
timestamp: i,
|
|
76
|
+
},
|
|
77
|
+
store,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildTree(store: VectorStore, sid: string) {
|
|
83
|
+
const nsid = normalizeSessionId(sid);
|
|
84
|
+
runRaptor(
|
|
85
|
+
vectorList(store, nsid).map((cp) => ({
|
|
86
|
+
id: cp.checkpointId,
|
|
87
|
+
messages: [],
|
|
88
|
+
sourceText: cp.normalizedText ?? cp.summary ?? cp.regionHash,
|
|
89
|
+
embedding: cp.embedding,
|
|
90
|
+
})),
|
|
91
|
+
{ stateDir: store.stateDir, sessionId: nsid },
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── 1. Flag ON (default) → overview header prepended ───────────────────────
|
|
96
|
+
|
|
97
|
+
test("S25-P2: RAPTOR_INJECT_SUMMARIES=true prepends a hierarchical overview header", () => {
|
|
98
|
+
const sd = stateDir();
|
|
99
|
+
const s = new VectorStore({ dedupSim: 0.9, stateDir: sd, config: cfg() });
|
|
100
|
+
const sid = "on";
|
|
101
|
+
seedTwoTopics(s, sid, 8);
|
|
102
|
+
buildTree(s, sid);
|
|
103
|
+
process.env.RAPTOR_SHADOW_MODE = "false";
|
|
104
|
+
const r = recallAndInline(
|
|
105
|
+
{ sessionId: sid, query: "database pool", source: "resume", limit: 3 },
|
|
106
|
+
s,
|
|
107
|
+
);
|
|
108
|
+
assert.ok(r.block.length > 0, "recall block is non-empty");
|
|
109
|
+
assert.ok(
|
|
110
|
+
r.block.includes("hierarchical overview"),
|
|
111
|
+
"overview header present when flag ON",
|
|
112
|
+
);
|
|
113
|
+
assert.ok(
|
|
114
|
+
r.block.includes("Recalled context"),
|
|
115
|
+
"detailed recall block still present after overview",
|
|
116
|
+
);
|
|
117
|
+
assert.ok(
|
|
118
|
+
r.block.indexOf("hierarchical overview") <
|
|
119
|
+
r.block.indexOf("Recalled context"),
|
|
120
|
+
"overview precedes detail",
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// ─── 2. Flag OFF → no overview (detail-only, unchanged behavior) ────────────
|
|
125
|
+
|
|
126
|
+
test("S25-P2: RAPTOR_INJECT_SUMMARIES=false → no overview (detail-only)", () => {
|
|
127
|
+
const sd = stateDir();
|
|
128
|
+
const s = new VectorStore({
|
|
129
|
+
dedupSim: 0.9,
|
|
130
|
+
stateDir: sd,
|
|
131
|
+
config: cfg({ RAPTOR_INJECT_SUMMARIES: false }),
|
|
132
|
+
});
|
|
133
|
+
const sid = "off";
|
|
134
|
+
seedTwoTopics(s, sid, 8);
|
|
135
|
+
buildTree(s, sid);
|
|
136
|
+
process.env.RAPTOR_SHADOW_MODE = "false";
|
|
137
|
+
const r = recallAndInline(
|
|
138
|
+
{ sessionId: sid, query: "database pool", source: "resume", limit: 3 },
|
|
139
|
+
s,
|
|
140
|
+
);
|
|
141
|
+
if (r.block.length > 0) {
|
|
142
|
+
assert.ok(
|
|
143
|
+
!r.block.includes("hierarchical overview"),
|
|
144
|
+
"no overview header when flag OFF",
|
|
145
|
+
);
|
|
146
|
+
assert.ok(
|
|
147
|
+
r.block.includes("Recalled context") || r.block.length > 0,
|
|
148
|
+
"detail block present",
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ─── 3. No tree → no overview (graceful, detail-only) ────────────────────────
|
|
154
|
+
|
|
155
|
+
test("S25-P2: no RAPTOR tree → no overview (graceful degradation)", () => {
|
|
156
|
+
const sd = stateDir();
|
|
157
|
+
const s = new VectorStore({ dedupSim: 0.9, stateDir: sd, config: cfg() });
|
|
158
|
+
const sid = "notree";
|
|
159
|
+
seedTwoTopics(s, sid, 4); // checkpoints exist but no tree built
|
|
160
|
+
process.env.RAPTOR_SHADOW_MODE = "false";
|
|
161
|
+
const r = recallAndInline(
|
|
162
|
+
{ sessionId: sid, query: "database pool", source: "resume", limit: 3 },
|
|
163
|
+
s,
|
|
164
|
+
);
|
|
165
|
+
assert.ok(
|
|
166
|
+
!r.block.includes("hierarchical overview"),
|
|
167
|
+
"no overview when no tree exists",
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// ─── 4. formatRaptorBlock formatter contract ────────────────────────────────
|
|
172
|
+
|
|
173
|
+
test("S25-P2: formatRaptorBlock labels root as 'Session overview' + clusters as 'Cluster summary'", () => {
|
|
174
|
+
const block = formatRaptorBlock([
|
|
175
|
+
{ summary: "root overview text", level: 0, score: 0.9 },
|
|
176
|
+
{ summary: "cluster A text", level: 1, score: 0.7 },
|
|
177
|
+
]);
|
|
178
|
+
assert.ok(
|
|
179
|
+
/The following hierarchical overview/.test(block),
|
|
180
|
+
"preamble present",
|
|
181
|
+
);
|
|
182
|
+
assert.ok(
|
|
183
|
+
/Session overview \[1\] \(relevance 90%\)/.test(block),
|
|
184
|
+
"root labeled",
|
|
185
|
+
);
|
|
186
|
+
assert.ok(
|
|
187
|
+
/Cluster summary \[2\] \(level 1\) \(relevance 70%\)/.test(block),
|
|
188
|
+
"cluster labeled",
|
|
189
|
+
);
|
|
190
|
+
assert.ok(block.includes("root overview text"), "root body present");
|
|
191
|
+
assert.ok(block.includes("cluster A text"), "cluster body present");
|
|
192
|
+
assert.equal(formatRaptorBlock([]), "", "empty input → empty string");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// ─── 5. overview is skipped when shadows mode is on (tree not served) ──────
|
|
196
|
+
//
|
|
197
|
+
// Actually the overview reads rehydrateRaptorTree directly (not raptorSearchHits),
|
|
198
|
+
// so shadow mode does NOT suppress it via the serve gate. Verify the behavior:
|
|
199
|
+
// the overview IS emitted in shadow mode (the tree exists and is rehydrated).
|
|
200
|
+
// This is acceptable — the overview is a session map, not a live-serve decision.
|
|
201
|
+
// QA finding #1 (v0.8.25): the overview previously bypassed the shadow gate —
|
|
202
|
+
// the tree was injected into recall while RAPTOR_SERVE was logging-only. The
|
|
203
|
+
// gate now applies to the inject path too: shadow mode = no overview.
|
|
204
|
+
|
|
205
|
+
test("S25-P2: overview is suppressed in shadow mode (QA fix)", () => {
|
|
206
|
+
const sd = stateDir();
|
|
207
|
+
const s = new VectorStore({ dedupSim: 0.9, stateDir: sd, config: cfg() });
|
|
208
|
+
const sid = "shadow";
|
|
209
|
+
seedTwoTopics(s, sid, 8);
|
|
210
|
+
buildTree(s, sid);
|
|
211
|
+
const orig = process.env.RAPTOR_SHADOW_MODE;
|
|
212
|
+
process.env.RAPTOR_SHADOW_MODE = "true";
|
|
213
|
+
try {
|
|
214
|
+
const r = recallAndInline(
|
|
215
|
+
{ sessionId: sid, query: "database pool", source: "resume", limit: 3 },
|
|
216
|
+
s,
|
|
217
|
+
);
|
|
218
|
+
// Shadow mode = build + persist + log only; nothing RAPTOR-derived is
|
|
219
|
+
// injected into recall, including the overview header.
|
|
220
|
+
assert.ok(
|
|
221
|
+
!r.block.includes("hierarchical overview"),
|
|
222
|
+
"overview suppressed in shadow mode",
|
|
223
|
+
);
|
|
224
|
+
} finally {
|
|
225
|
+
if (orig === undefined) delete process.env.RAPTOR_SHADOW_MODE;
|
|
226
|
+
else process.env.RAPTOR_SHADOW_MODE = orig;
|
|
227
|
+
}
|
|
228
|
+
});
|
package/src/recall.test.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
import { VectorStore } from "./vectorStore.js";
|
|
7
7
|
import { compactSession } from "./engine.js";
|
|
8
8
|
import { recallAndInline, recallAndInlineAsync, formatRecallBlock } from "./recall.js";
|
|
9
|
-
import { vectorList } from "./vectorStore.js";
|
|
9
|
+
import { vectorList, vectorWasInjected } from "./vectorStore.js";
|
|
10
10
|
import { markInjectedGlobal, wasInjectedGlobal, closeIndexStore } from "./store/sqlite.js";
|
|
11
11
|
import {
|
|
12
12
|
closeVectorIndex,
|
|
@@ -88,10 +88,12 @@ test("Fix C: recallMaxTokens caps the injected block", () => {
|
|
|
88
88
|
compactSession({ sessionId: SESS, messages: [msg("user", "beta module config and env resolution"), msg("assistant", "ok", "Edit")], keepFrom: 2, timestamp: 2 }, s);
|
|
89
89
|
compactSession({ sessionId: SESS, messages: [msg("user", "gamma module shutdown and cleanup hooks"), msg("assistant", "ok", "Edit")], keepFrom: 2, timestamp: 3 }, s);
|
|
90
90
|
|
|
91
|
-
// A ceiling of
|
|
92
|
-
// second (~
|
|
91
|
+
// A ceiling of 50 tokens fits the first checkpoint (~33 body tokens) but
|
|
92
|
+
// stops before the second (~65 cumulative) — proving the cap bites mid-stream.
|
|
93
|
+
// (F3: the cap now counts body tokens only — one preamble at format time, not
|
|
94
|
+
// N — matching the async path. The old per-hit preamble counting needed ~100.)
|
|
93
95
|
const r = recallAndInline(
|
|
94
|
-
{ sessionId: SESS, query: "module wiring config shutdown", limit: 5, source: "command", recallMaxTokens:
|
|
96
|
+
{ sessionId: SESS, query: "module wiring config shutdown", limit: 5, source: "command", recallMaxTokens: 50, skipInjected: false },
|
|
95
97
|
s as any,
|
|
96
98
|
);
|
|
97
99
|
assert.ok(r.toInject.length >= 1, "at least one injected under the cap");
|
|
@@ -246,6 +248,220 @@ test("S18: a fresh foreign checkpoint is injected AND recorded globally", async
|
|
|
246
248
|
}
|
|
247
249
|
});
|
|
248
250
|
|
|
251
|
+
// ---- F3: format-once — one preamble, [1..n] numbering, cap respected ----
|
|
252
|
+
//
|
|
253
|
+
// The fix (landed async, extended to sync here) accumulates the hit list and
|
|
254
|
+
// calls formatRecallBlock ONCE at the end, so a multi-hit block carries exactly
|
|
255
|
+
// one preamble and contiguous [1..n] labels instead of one preamble per hit.
|
|
256
|
+
|
|
257
|
+
test("F3 (sync): multi-hit injection has exactly one preamble and [1..n] numbering", () => {
|
|
258
|
+
const s = store();
|
|
259
|
+
compactSession({ sessionId: SESS, messages: [msg("user", "alpha module wiring and bootstrap sequence"), msg("assistant", "ok", "Edit")], keepFrom: 2, timestamp: 1 }, s);
|
|
260
|
+
compactSession({ sessionId: SESS, messages: [msg("user", "beta module config and env resolution"), msg("assistant", "ok", "Edit")], keepFrom: 2, timestamp: 2 }, s);
|
|
261
|
+
compactSession({ sessionId: SESS, messages: [msg("user", "gamma module shutdown and cleanup hooks"), msg("assistant", "ok", "Edit")], keepFrom: 2, timestamp: 3 }, s);
|
|
262
|
+
|
|
263
|
+
const r = recallAndInline(
|
|
264
|
+
{ sessionId: SESS, query: "module wiring config shutdown", limit: 5, source: "command", skipInjected: false },
|
|
265
|
+
s as any,
|
|
266
|
+
);
|
|
267
|
+
assert.ok(r.toInject.length >= 2, "at least two hits injected (got " + r.toInject.length + ")");
|
|
268
|
+
// Exactly one preamble (the old per-hit format produced one per hit).
|
|
269
|
+
const preamble = "The following compacted context was recalled";
|
|
270
|
+
assert.equal(r.block.split(preamble).length - 1, 1, "exactly one preamble for a multi-hit block");
|
|
271
|
+
// Contiguous [1..n] numbering.
|
|
272
|
+
for (let i = 1; i <= r.toInject.length; i++) {
|
|
273
|
+
assert.ok(r.block.includes(`[${i}]`), `block includes [${i}]`);
|
|
274
|
+
}
|
|
275
|
+
// No out-of-range label (e.g. [n+1]) leaks in.
|
|
276
|
+
assert.ok(!r.block.includes(`[${r.toInject.length + 1}]`), "no extra-numbered label");
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("F3 (sync): recallMaxTokens caps mid-stream with exactly one preamble", () => {
|
|
280
|
+
const s = store();
|
|
281
|
+
compactSession({ sessionId: SESS, messages: [msg("user", "alpha module wiring and bootstrap sequence"), msg("assistant", "ok", "Edit")], keepFrom: 2, timestamp: 1 }, s);
|
|
282
|
+
compactSession({ sessionId: SESS, messages: [msg("user", "beta module config and env resolution"), msg("assistant", "ok", "Edit")], keepFrom: 2, timestamp: 2 }, s);
|
|
283
|
+
compactSession({ sessionId: SESS, messages: [msg("user", "gamma module shutdown and cleanup hooks"), msg("assistant", "ok", "Edit")], keepFrom: 2, timestamp: 3 }, s);
|
|
284
|
+
|
|
285
|
+
// A tight ceiling that fits the first checkpoint (~33 body tokens) but stops
|
|
286
|
+
// before the second (~65 cumulative). F3: body-only counting (one preamble at
|
|
287
|
+
// format time, not per hit), matching the async path.
|
|
288
|
+
const r = recallAndInline(
|
|
289
|
+
{ sessionId: SESS, query: "module wiring config shutdown", limit: 5, source: "command", skipInjected: false, recallMaxTokens: 50 },
|
|
290
|
+
s as any,
|
|
291
|
+
);
|
|
292
|
+
assert.ok(r.toInject.length < 3, "cap stopped before all three injected");
|
|
293
|
+
assert.ok(r.toInject.length >= 1, "at least one injected under the cap");
|
|
294
|
+
assert.equal(
|
|
295
|
+
r.block.split("The following compacted context was recalled").length - 1,
|
|
296
|
+
1,
|
|
297
|
+
"still exactly one preamble under the cap",
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("F3 (async): multi-hit cross-repo injection has exactly one preamble and [1..n] numbering", async () => {
|
|
302
|
+
if (process.env.MEGACOMPACT_PGLITE_DISABLED === "true") { return; } // skip when WASM index is off
|
|
303
|
+
const indexDir = mkdtempSync(join(tmpdir(), "mc-f3a-"));
|
|
304
|
+
const foreignStateDir = mkdtempSync(join(tmpdir(), "mc-f3a-foreign-"));
|
|
305
|
+
const selfStateDir = mkdtempSync(join(tmpdir(), "mc-f3a-self-"));
|
|
306
|
+
process.env.MEGACOMPACT_VECTOR_INDEX_DIR = mkdtempSync(join(tmpdir(), "mc-f3a-vidx-"));
|
|
307
|
+
try {
|
|
308
|
+
await closeVectorIndex();
|
|
309
|
+
// Seed multiple distinct foreign checkpoints so the cross-repo query yields
|
|
310
|
+
// more than one hit (needed to assert [1..n] numbering).
|
|
311
|
+
const foreign = new VectorStore({ stateDir: foreignStateDir, dedupSim: 0.9 });
|
|
312
|
+
const fsess = "sess_foreign_f3";
|
|
313
|
+
const summaries = [
|
|
314
|
+
"foreign repo authentication jwt token validation",
|
|
315
|
+
"foreign repo database connection pooling and retry",
|
|
316
|
+
"foreign repo logging telemetry and tracing spans",
|
|
317
|
+
];
|
|
318
|
+
const cids: string[] = [];
|
|
319
|
+
for (let i = 0; i < summaries.length; i++) {
|
|
320
|
+
const res = compactSession(
|
|
321
|
+
{ sessionId: fsess, messages: [msg("user", summaries[i]), msg("assistant", "ok", "Edit")], keepFrom: 2, timestamp: i + 1 },
|
|
322
|
+
foreign,
|
|
323
|
+
);
|
|
324
|
+
if (res.checkpointId) cids.push(res.checkpointId);
|
|
325
|
+
}
|
|
326
|
+
await rebuildFromSqlite(
|
|
327
|
+
() => [{ repoId: foreignStateDir, stateDir: foreignStateDir }],
|
|
328
|
+
(sd) => {
|
|
329
|
+
const st = new VectorStore({ stateDir: sd, dedupSim: 0.9 });
|
|
330
|
+
return vectorList(st, fsess).map((cp) => ({
|
|
331
|
+
sessionId: fsess, checkpointId: cp.checkpointId, embedding: cp.embedding,
|
|
332
|
+
}));
|
|
333
|
+
},
|
|
334
|
+
);
|
|
335
|
+
const pg = await initVectorIndex();
|
|
336
|
+
assert.ok(pg, "PGlite index should initialize");
|
|
337
|
+
|
|
338
|
+
const selfStore = new VectorStore({ stateDir: selfStateDir, dedupSim: 0.9 });
|
|
339
|
+
const r = await recallAndInlineAsync(
|
|
340
|
+
{ sessionId: "sess_f3", query: "foreign repo", limit: 5, source: "command", crossRepo: true, skipInjected: false, globalIndexDir: indexDir },
|
|
341
|
+
selfStore,
|
|
342
|
+
);
|
|
343
|
+
assert.ok(r.toInject.length >= 2, "at least two cross-repo hits (got " + r.toInject.length + ")");
|
|
344
|
+
assert.equal(
|
|
345
|
+
r.block.split("The following compacted context was recalled").length - 1,
|
|
346
|
+
1,
|
|
347
|
+
"exactly one preamble for a multi-hit async block",
|
|
348
|
+
);
|
|
349
|
+
for (let i = 1; i <= r.toInject.length; i++) {
|
|
350
|
+
assert.ok(r.block.includes(`[${i}]`), `async block includes [${i}]`);
|
|
351
|
+
}
|
|
352
|
+
assert.ok(!r.block.includes(`[${r.toInject.length + 1}]`), "no extra-numbered label (async)");
|
|
353
|
+
} finally {
|
|
354
|
+
await closeVectorIndex();
|
|
355
|
+
closeIndexStore();
|
|
356
|
+
delete process.env.MEGACOMPACT_VECTOR_INDEX_DIR;
|
|
357
|
+
rmSync(indexDir, { recursive: true, force: true });
|
|
358
|
+
rmSync(foreignStateDir, { recursive: true, force: true });
|
|
359
|
+
rmSync(selfStateDir, { recursive: true, force: true });
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// ---- F2: cross-repo injected-set bypass ----------------------------------
|
|
364
|
+
//
|
|
365
|
+
// When the same session resumes in a DIFFERENT repo, the per-repo session_state
|
|
366
|
+
// has no injection marker for the foreign checkpoint (different stateDir), so
|
|
367
|
+
// only the machine-wide injected-set (shared globalIndexDir) can block a
|
|
368
|
+
// re-inject. The F2 fix ensures globalIndexDir is always resolved (via
|
|
369
|
+
// getIndexDir in the extension), so wasInjectedGlobal is consulted; without
|
|
370
|
+
// the fix, globalIndexDir was undefined and foreign checkpoints re-injected
|
|
371
|
+
// every resume. This test passes globalIndexDir explicitly (simulating the
|
|
372
|
+
// resolver default) and does NOT set MEGACOMPACT_INDEX_DIR.
|
|
373
|
+
|
|
374
|
+
test("F2: cross-repo hit is not re-injected when resuming the same session in a different repo (shared index dir)", async () => {
|
|
375
|
+
if (process.env.MEGACOMPACT_PGLITE_DISABLED === "true") { return; } // skip when WASM index is off
|
|
376
|
+
const indexDir = mkdtempSync(join(tmpdir(), "mc-f2-"));
|
|
377
|
+
const foreignStateDir = mkdtempSync(join(tmpdir(), "mc-f2-foreign-"));
|
|
378
|
+
const selfStateDir1 = mkdtempSync(join(tmpdir(), "mc-f2-self1-"));
|
|
379
|
+
const selfStateDir2 = mkdtempSync(join(tmpdir(), "mc-f2-self2-"));
|
|
380
|
+
process.env.MEGACOMPACT_VECTOR_INDEX_DIR = mkdtempSync(join(tmpdir(), "mc-f2-vidx-"));
|
|
381
|
+
try {
|
|
382
|
+
await closeVectorIndex();
|
|
383
|
+
const seed = await seedForeignRepo(foreignStateDir);
|
|
384
|
+
await rebuildFromSqlite(
|
|
385
|
+
() => [{ repoId: foreignStateDir, stateDir: foreignStateDir }],
|
|
386
|
+
(sd) => {
|
|
387
|
+
const st = new VectorStore({ stateDir: sd, dedupSim: 0.9 });
|
|
388
|
+
return vectorList(st, seed.sessionId).map((cp) => ({
|
|
389
|
+
sessionId: seed.sessionId, checkpointId: cp.checkpointId, embedding: cp.embedding,
|
|
390
|
+
}));
|
|
391
|
+
},
|
|
392
|
+
);
|
|
393
|
+
const pg = await initVectorIndex();
|
|
394
|
+
assert.ok(pg, "PGlite index should initialize");
|
|
395
|
+
|
|
396
|
+
const sess = "sess_resume";
|
|
397
|
+
// Repo B: first recall — fresh foreign checkpoint is injected AND recorded
|
|
398
|
+
// in the shared machine-wide index.
|
|
399
|
+
const storeB = new VectorStore({ stateDir: selfStateDir1, dedupSim: 0.9 });
|
|
400
|
+
const r1 = await recallAndInlineAsync(
|
|
401
|
+
{ sessionId: sess, query: "foreign repo authentication jwt", limit: 3, source: "command", crossRepo: true, globalIndexDir: indexDir },
|
|
402
|
+
storeB,
|
|
403
|
+
);
|
|
404
|
+
assert.equal(r1.toInject.length, 1, "first recall (repo B) injects the foreign checkpoint");
|
|
405
|
+
assert.equal(wasInjectedGlobal(seed.checkpointId, sess, indexDir), true, "recorded in the shared index");
|
|
406
|
+
|
|
407
|
+
// Repo C: SAME session resumed in a DIFFERENT repo. The per-session injected
|
|
408
|
+
// set in repo C's store is empty (different stateDir), so only the shared
|
|
409
|
+
// global injected-set can block re-injection.
|
|
410
|
+
const storeC = new VectorStore({ stateDir: selfStateDir2, dedupSim: 0.9 });
|
|
411
|
+
assert.equal(vectorWasInjected(storeC, sess, seed.checkpointId), false, "repo C per-session set is empty (different stateDir)");
|
|
412
|
+
const r2 = await recallAndInlineAsync(
|
|
413
|
+
{ sessionId: sess, query: "foreign repo authentication jwt", limit: 3, source: "command", crossRepo: true, globalIndexDir: indexDir },
|
|
414
|
+
storeC,
|
|
415
|
+
);
|
|
416
|
+
assert.equal(r2.toInject.length, 0, "second recall (repo C) does NOT re-inject (machine-wide global dedup)");
|
|
417
|
+
} finally {
|
|
418
|
+
await closeVectorIndex();
|
|
419
|
+
closeIndexStore();
|
|
420
|
+
delete process.env.MEGACOMPACT_VECTOR_INDEX_DIR;
|
|
421
|
+
rmSync(indexDir, { recursive: true, force: true });
|
|
422
|
+
rmSync(foreignStateDir, { recursive: true, force: true });
|
|
423
|
+
rmSync(selfStateDir1, { recursive: true, force: true });
|
|
424
|
+
rmSync(selfStateDir2, { recursive: true, force: true });
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("F2: without globalIndexDir, cross-repo hits are skipped (not injected undeduped)", async () => {
|
|
429
|
+
if (process.env.MEGACOMPACT_PGLITE_DISABLED === "true") { return; } // skip when WASM index is off
|
|
430
|
+
const foreignStateDir = mkdtempSync(join(tmpdir(), "mc-f2b-foreign-"));
|
|
431
|
+
const selfStateDir = mkdtempSync(join(tmpdir(), "mc-f2b-self-"));
|
|
432
|
+
process.env.MEGACOMPACT_VECTOR_INDEX_DIR = mkdtempSync(join(tmpdir(), "mc-f2b-vidx-"));
|
|
433
|
+
try {
|
|
434
|
+
await closeVectorIndex();
|
|
435
|
+
const seed = await seedForeignRepo(foreignStateDir);
|
|
436
|
+
await rebuildFromSqlite(
|
|
437
|
+
() => [{ repoId: foreignStateDir, stateDir: foreignStateDir }],
|
|
438
|
+
(sd) => {
|
|
439
|
+
const st = new VectorStore({ stateDir: sd, dedupSim: 0.9 });
|
|
440
|
+
return vectorList(st, seed.sessionId).map((cp) => ({
|
|
441
|
+
sessionId: seed.sessionId, checkpointId: cp.checkpointId, embedding: cp.embedding,
|
|
442
|
+
}));
|
|
443
|
+
},
|
|
444
|
+
);
|
|
445
|
+
const pg = await initVectorIndex();
|
|
446
|
+
assert.ok(pg, "PGlite index should initialize");
|
|
447
|
+
|
|
448
|
+
// No globalIndexDir → the F2 guard skips foreign hits rather than injecting
|
|
449
|
+
// them undeduped. Same-repo hits (none here) would still pass.
|
|
450
|
+
const selfStore = new VectorStore({ stateDir: selfStateDir, dedupSim: 0.9 });
|
|
451
|
+
const r = await recallAndInlineAsync(
|
|
452
|
+
{ sessionId: "sess_nodir", query: "foreign repo authentication jwt", limit: 3, source: "command", crossRepo: true },
|
|
453
|
+
selfStore,
|
|
454
|
+
);
|
|
455
|
+
assert.equal(r.toInject.length, 0, "foreign hit skipped when globalIndexDir is unset (no undeduped injection)");
|
|
456
|
+
} finally {
|
|
457
|
+
await closeVectorIndex();
|
|
458
|
+
closeIndexStore();
|
|
459
|
+
delete process.env.MEGACOMPACT_VECTOR_INDEX_DIR;
|
|
460
|
+
rmSync(foreignStateDir, { recursive: true, force: true });
|
|
461
|
+
rmSync(selfStateDir, { recursive: true, force: true });
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
249
465
|
test("cleanup", () => {
|
|
250
466
|
rmSync(baseTmp, { recursive: true, force: true });
|
|
251
467
|
});
|