@animalabs/context-manager 0.3.0 → 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/dist/src/context-manager.d.ts +69 -2
- package/dist/src/context-manager.d.ts.map +1 -1
- package/dist/src/context-manager.js +128 -10
- package/dist/src/context-manager.js.map +1 -1
- package/dist/src/strategies/autobiographical.d.ts +205 -3
- package/dist/src/strategies/autobiographical.d.ts.map +1 -1
- package/dist/src/strategies/autobiographical.js +767 -59
- package/dist/src/strategies/autobiographical.js.map +1 -1
- package/dist/src/types/index.d.ts +2 -2
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +1 -1
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/strategy.d.ts +208 -0
- package/dist/src/types/strategy.d.ts.map +1 -1
- package/dist/src/types/strategy.js +21 -0
- package/dist/src/types/strategy.js.map +1 -1
- package/dist/test/non-blocking-compile.test.d.ts +22 -0
- package/dist/test/non-blocking-compile.test.d.ts.map +1 -0
- package/dist/test/non-blocking-compile.test.js +114 -0
- package/dist/test/non-blocking-compile.test.js.map +1 -0
- package/dist/test/pins.test.d.ts +10 -0
- package/dist/test/pins.test.d.ts.map +1 -0
- package/dist/test/pins.test.js +164 -0
- package/dist/test/pins.test.js.map +1 -0
- package/dist/test/recall-positioning.test.d.ts +16 -0
- package/dist/test/recall-positioning.test.d.ts.map +1 -0
- package/dist/test/recall-positioning.test.js +181 -0
- package/dist/test/recall-positioning.test.js.map +1 -0
- package/dist/test/search.test.d.ts +10 -0
- package/dist/test/search.test.d.ts.map +1 -0
- package/dist/test/search.test.js +141 -0
- package/dist/test/search.test.js.map +1 -0
- package/dist/test/speculation-cap.test.d.ts +17 -0
- package/dist/test/speculation-cap.test.d.ts.map +1 -0
- package/dist/test/speculation-cap.test.js +157 -0
- package/dist/test/speculation-cap.test.js.map +1 -0
- package/dist/test/strategy-persistence.test.d.ts +15 -0
- package/dist/test/strategy-persistence.test.d.ts.map +1 -0
- package/dist/test/strategy-persistence.test.js +244 -0
- package/dist/test/strategy-persistence.test.js.map +1 -0
- package/dist/test/tool-pruning.test.d.ts +10 -0
- package/dist/test/tool-pruning.test.d.ts.map +1 -0
- package/dist/test/tool-pruning.test.js +140 -0
- package/dist/test/tool-pruning.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/context-manager.ts +142 -10
- package/src/strategies/autobiographical.ts +815 -61
- package/src/types/index.ts +14 -1
- package/src/types/strategy.ts +223 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the search API (audit gap #7).
|
|
3
|
+
*
|
|
4
|
+
* Substring + regex search over summary content, plus single-summary
|
|
5
|
+
* lookup by id. Exposed through ContextManager passthrough so callers
|
|
6
|
+
* (e.g. agent-framework MCPL host) can build `memory_search` /
|
|
7
|
+
* `memory_read` agent tools on top.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
10
|
+
import assert from 'node:assert';
|
|
11
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
12
|
+
import { ContextManager, AutobiographicalStrategy } from '../src/index.js';
|
|
13
|
+
const TEST_STORE_PATH = './test-search';
|
|
14
|
+
function cleanup() {
|
|
15
|
+
if (existsSync(TEST_STORE_PATH)) {
|
|
16
|
+
rmSync(TEST_STORE_PATH, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function textBlock(text) {
|
|
20
|
+
return [{ type: 'text', text }];
|
|
21
|
+
}
|
|
22
|
+
class SeedableStrategy extends AutobiographicalStrategy {
|
|
23
|
+
seedSummary(content, level = 1, mergedInto) {
|
|
24
|
+
const entry = {
|
|
25
|
+
id: `L${level}-${this.nextSummaryIdCounter()}`,
|
|
26
|
+
level,
|
|
27
|
+
content,
|
|
28
|
+
tokens: Math.ceil(content.length / 4),
|
|
29
|
+
sourceLevel: 0,
|
|
30
|
+
sourceIds: ['x'],
|
|
31
|
+
sourceRange: { first: 'x', last: 'x' },
|
|
32
|
+
created: Date.now(),
|
|
33
|
+
...(mergedInto ? { mergedInto } : {}),
|
|
34
|
+
};
|
|
35
|
+
this.pushSummary(entry);
|
|
36
|
+
return entry;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
describe('AutobiographicalStrategy — search (gap #7)', () => {
|
|
40
|
+
before(cleanup);
|
|
41
|
+
after(cleanup);
|
|
42
|
+
beforeEach(cleanup);
|
|
43
|
+
it('returns empty when no summaries match the substring', async () => {
|
|
44
|
+
const strategy = new SeedableStrategy();
|
|
45
|
+
const manager = await ContextManager.open({
|
|
46
|
+
path: TEST_STORE_PATH,
|
|
47
|
+
strategy,
|
|
48
|
+
});
|
|
49
|
+
strategy.seedSummary('first memory about cats');
|
|
50
|
+
strategy.seedSummary('second memory about dogs');
|
|
51
|
+
const results = manager.searchSummaries({ text: 'unicorns' });
|
|
52
|
+
assert.equal(results.length, 0);
|
|
53
|
+
manager.close();
|
|
54
|
+
});
|
|
55
|
+
it('case-insensitive substring search returns matches sorted by hit count', async () => {
|
|
56
|
+
const strategy = new SeedableStrategy();
|
|
57
|
+
const manager = await ContextManager.open({
|
|
58
|
+
path: TEST_STORE_PATH,
|
|
59
|
+
strategy,
|
|
60
|
+
});
|
|
61
|
+
strategy.seedSummary('a meditation on cats and CATS and cats'); // 3 hits
|
|
62
|
+
strategy.seedSummary('one note about Cats'); // 1 hit
|
|
63
|
+
strategy.seedSummary('something about dogs'); // 0 hits
|
|
64
|
+
const results = manager.searchSummaries({ text: 'cats' });
|
|
65
|
+
assert.equal(results.length, 2, 'two summaries should match');
|
|
66
|
+
assert.equal(results[0].matches, 3, 'highest hit count first');
|
|
67
|
+
assert.equal(results[1].matches, 1);
|
|
68
|
+
manager.close();
|
|
69
|
+
});
|
|
70
|
+
it('regex search supports patterns and counts global matches', async () => {
|
|
71
|
+
const strategy = new SeedableStrategy();
|
|
72
|
+
const manager = await ContextManager.open({
|
|
73
|
+
path: TEST_STORE_PATH,
|
|
74
|
+
strategy,
|
|
75
|
+
});
|
|
76
|
+
strategy.seedSummary('found errors at 14:23 and 16:45 and 18:00');
|
|
77
|
+
strategy.seedSummary('no times here');
|
|
78
|
+
const results = manager.searchSummaries({ regex: /\d{2}:\d{2}/ });
|
|
79
|
+
assert.equal(results.length, 1);
|
|
80
|
+
assert.equal(results[0].matches, 3, 'should count all 3 timestamps');
|
|
81
|
+
manager.close();
|
|
82
|
+
});
|
|
83
|
+
it('level filter restricts to chosen levels', async () => {
|
|
84
|
+
const strategy = new SeedableStrategy();
|
|
85
|
+
const manager = await ContextManager.open({
|
|
86
|
+
path: TEST_STORE_PATH,
|
|
87
|
+
strategy,
|
|
88
|
+
});
|
|
89
|
+
strategy.seedSummary('shared word here', 1);
|
|
90
|
+
strategy.seedSummary('shared word also', 2);
|
|
91
|
+
strategy.seedSummary('shared word too', 3);
|
|
92
|
+
const l2only = manager.searchSummaries({ text: 'shared', levels: [2] });
|
|
93
|
+
assert.equal(l2only.length, 1);
|
|
94
|
+
assert.equal(l2only[0].summary.level, 2);
|
|
95
|
+
const l1andL3 = manager.searchSummaries({ text: 'shared', levels: [1, 3] });
|
|
96
|
+
assert.equal(l1andL3.length, 2);
|
|
97
|
+
assert.ok(l1andL3.every(r => r.summary.level === 1 || r.summary.level === 3));
|
|
98
|
+
manager.close();
|
|
99
|
+
});
|
|
100
|
+
it('excludes merged summaries by default; includeMerged opts in', async () => {
|
|
101
|
+
const strategy = new SeedableStrategy();
|
|
102
|
+
const manager = await ContextManager.open({
|
|
103
|
+
path: TEST_STORE_PATH,
|
|
104
|
+
strategy,
|
|
105
|
+
});
|
|
106
|
+
strategy.seedSummary('alive content', 1);
|
|
107
|
+
strategy.seedSummary('folded content', 1, 'L2-99'); // already merged
|
|
108
|
+
const live = manager.searchSummaries({ text: 'content' });
|
|
109
|
+
assert.equal(live.length, 1, 'merged summaries hidden by default');
|
|
110
|
+
assert.equal(live[0].summary.content, 'alive content');
|
|
111
|
+
const all = manager.searchSummaries({ text: 'content', includeMerged: true });
|
|
112
|
+
assert.equal(all.length, 2, 'includeMerged should reveal both');
|
|
113
|
+
manager.close();
|
|
114
|
+
});
|
|
115
|
+
it('limit caps the result count', async () => {
|
|
116
|
+
const strategy = new SeedableStrategy();
|
|
117
|
+
const manager = await ContextManager.open({
|
|
118
|
+
path: TEST_STORE_PATH,
|
|
119
|
+
strategy,
|
|
120
|
+
});
|
|
121
|
+
for (let i = 0; i < 10; i++) {
|
|
122
|
+
strategy.seedSummary(`hit ${i} hit hit`);
|
|
123
|
+
}
|
|
124
|
+
const results = manager.searchSummaries({ text: 'hit', limit: 3 });
|
|
125
|
+
assert.equal(results.length, 3);
|
|
126
|
+
});
|
|
127
|
+
it('getSummary returns an entry by id', async () => {
|
|
128
|
+
const strategy = new SeedableStrategy();
|
|
129
|
+
const manager = await ContextManager.open({
|
|
130
|
+
path: TEST_STORE_PATH,
|
|
131
|
+
strategy,
|
|
132
|
+
});
|
|
133
|
+
const seeded = strategy.seedSummary('lookup me');
|
|
134
|
+
const got = manager.getSummary(seeded.id);
|
|
135
|
+
assert.ok(got, 'should find the seeded summary');
|
|
136
|
+
assert.equal(got?.content, 'lookup me');
|
|
137
|
+
assert.equal(manager.getSummary('nonexistent'), null);
|
|
138
|
+
manager.close();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
//# sourceMappingURL=search.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.test.js","sourceRoot":"","sources":["../../test/search.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAI3E,MAAM,eAAe,GAAG,eAAe,CAAC;AAExC,SAAS,OAAO;IACd,IAAI,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QAChC,MAAM,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AACD,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,gBAAiB,SAAQ,wBAAwB;IACrD,WAAW,CAAC,OAAe,EAAE,QAAsB,CAAC,EAAE,UAAmB;QACvE,MAAM,KAAK,GAAiB;YAC1B,EAAE,EAAE,IAAI,KAAK,IAAI,IAAI,CAAC,oBAAoB,EAAE,EAAE;YAC9C,KAAK;YACL,OAAO;YACP,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;YACrC,WAAW,EAAE,CAAC;YACd,SAAS,EAAE,CAAC,GAAG,CAAC;YAChB,WAAW,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE;YACtC,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE;YACnB,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACtC,CAAC;QACF,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAED,QAAQ,CAAC,4CAA4C,EAAE,GAAG,EAAE;IAC1D,MAAM,CAAC,OAAO,CAAC,CAAC;IAChB,KAAK,CAAC,OAAO,CAAC,CAAC;IACf,UAAU,CAAC,OAAO,CAAC,CAAC;IAEpB,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC;YACxC,IAAI,EAAE,eAAe;YACrB,QAAQ;SACT,CAAC,CAAC;QACH,QAAQ,CAAC,WAAW,CAAC,yBAAyB,CAAC,CAAC;QAChD,QAAQ,CAAC,WAAW,CAAC,0BAA0B,CAAC,CAAC;QAEjD,MAAM,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QAC9D,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAChC,OAAO,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;QACrF,MAAM,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC;YACxC,IAAI,EAAE,eAAe;YACrB,QAAQ;SACT,CAAC,CAAC;QACH,QAAQ,CAAC,WAAW,CAAC,wCAAwC,CAAC,CAAC,CAAE,SAAS;QAC1E,QAAQ,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC,CAAuB,QAAQ;QAC3E,QAAQ,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAsB,SAAS;QAE5E,MAAM,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAC1D,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,4BAA4B,CAAC,CAAC;QAC9D,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,yBAAyB,CAAC,CAAC;QAC/D,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACpC,OAAO,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;QACxE,MAAM,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC;YACxC,IAAI,EAAE,eAAe;YACrB,QAAQ;SACT,CAAC,CAAC;QACH,QAAQ,CAAC,WAAW,CAAC,2CAA2C,CAAC,CAAC;QAClE,QAAQ,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;QAEtC,MAAM,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC;QAClE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,+BAA+B,CAAC,CAAC;QACrE,OAAO,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC;YACxC,IAAI,EAAE,eAAe;YACrB,QAAQ;SACT,CAAC,CAAC;QACH,QAAQ,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC;QAC5C,QAAQ,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC;QAC5C,QAAQ,CAAC,WAAW,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC;QAE3C,MAAM,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAEzC,MAAM,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5E,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC;QAC9E,OAAO,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC;YACxC,IAAI,EAAE,eAAe;YACrB,QAAQ;SACT,CAAC,CAAC;QACH,QAAQ,CAAC,WAAW,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;QACzC,QAAQ,CAAC,WAAW,CAAC,gBAAgB,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,iBAAiB;QAErE,MAAM,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAC1D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,oCAAoC,CAAC,CAAC;QACnE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;QAEvD,MAAM,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9E,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,kCAAkC,CAAC,CAAC;QAChE,OAAO,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC3C,MAAM,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC;YACxC,IAAI,EAAE,eAAe;YACrB,QAAQ;SACT,CAAC,CAAC;QACH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC3C,CAAC;QACD,MAAM,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACnE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC;YACxC,IAAI,EAAE,eAAe;YACrB,QAAQ;SACT,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QAEjD,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1C,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,gCAAgC,CAAC,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC,CAAC;QACtD,OAAO,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for speculation cap + preflight hook (audit gap #8).
|
|
3
|
+
*
|
|
4
|
+
* Pre-fix: every new message triggered an auto-tick that compressed any
|
|
5
|
+
* pending chunk, regardless of how many speculative L1s the strategy was
|
|
6
|
+
* already holding. Lena's archive on Hermes ended up with dozens of
|
|
7
|
+
* speculatively-built L1s before there was any real budget pressure.
|
|
8
|
+
*
|
|
9
|
+
* Post-fix:
|
|
10
|
+
* - `maxSpeculativeL1s` caps the count of (unmerged L1s + queued chunks).
|
|
11
|
+
* When at cap, `onNewMessage` defers auto-tick — chunks still queue,
|
|
12
|
+
* but compression doesn't fire until an explicit tick() / compile().
|
|
13
|
+
* - `shouldCompressPreflight()` is overridable so subclasses can add
|
|
14
|
+
* custom predictive scheduling.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=speculation-cap.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speculation-cap.test.d.ts","sourceRoot":"","sources":["../../test/speculation-cap.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for speculation cap + preflight hook (audit gap #8).
|
|
3
|
+
*
|
|
4
|
+
* Pre-fix: every new message triggered an auto-tick that compressed any
|
|
5
|
+
* pending chunk, regardless of how many speculative L1s the strategy was
|
|
6
|
+
* already holding. Lena's archive on Hermes ended up with dozens of
|
|
7
|
+
* speculatively-built L1s before there was any real budget pressure.
|
|
8
|
+
*
|
|
9
|
+
* Post-fix:
|
|
10
|
+
* - `maxSpeculativeL1s` caps the count of (unmerged L1s + queued chunks).
|
|
11
|
+
* When at cap, `onNewMessage` defers auto-tick — chunks still queue,
|
|
12
|
+
* but compression doesn't fire until an explicit tick() / compile().
|
|
13
|
+
* - `shouldCompressPreflight()` is overridable so subclasses can add
|
|
14
|
+
* custom predictive scheduling.
|
|
15
|
+
*/
|
|
16
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
17
|
+
import assert from 'node:assert';
|
|
18
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
19
|
+
import { ContextManager, AutobiographicalStrategy } from '../src/index.js';
|
|
20
|
+
const TEST_STORE_PATH = './test-speculation-cap';
|
|
21
|
+
function cleanup() {
|
|
22
|
+
if (existsSync(TEST_STORE_PATH)) {
|
|
23
|
+
rmSync(TEST_STORE_PATH, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function textBlock(text) {
|
|
27
|
+
return [{ type: 'text', text }];
|
|
28
|
+
}
|
|
29
|
+
class TickCountingStrategy extends AutobiographicalStrategy {
|
|
30
|
+
tickCalls = 0;
|
|
31
|
+
async tick(ctx) {
|
|
32
|
+
this.tickCalls++;
|
|
33
|
+
// Do NOT call super.tick — we don't have a real membrane in tests
|
|
34
|
+
// and we just want to count whether onNewMessage decided to fire.
|
|
35
|
+
}
|
|
36
|
+
seedL1(content) {
|
|
37
|
+
const entry = {
|
|
38
|
+
id: `L1-${this.nextSummaryIdCounter()}`,
|
|
39
|
+
level: 1,
|
|
40
|
+
content,
|
|
41
|
+
tokens: Math.ceil(content.length / 4),
|
|
42
|
+
sourceLevel: 0,
|
|
43
|
+
sourceIds: ['x'],
|
|
44
|
+
sourceRange: { first: 'x', last: 'x' },
|
|
45
|
+
created: Date.now(),
|
|
46
|
+
};
|
|
47
|
+
this.pushSummary(entry);
|
|
48
|
+
return entry;
|
|
49
|
+
}
|
|
50
|
+
/** True if there's an unprocessed chunk eligible for compression. */
|
|
51
|
+
hasQueuedChunk() {
|
|
52
|
+
return this.compressionQueue.length > 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Add enough small messages to force ≥1 chunk to form in the
|
|
57
|
+
* compressible region. With targetChunkTokens=20 and chunkOnMessageBoundary,
|
|
58
|
+
* 5 messages of ~6 tokens each will close one chunk after 4 msgs.
|
|
59
|
+
*/
|
|
60
|
+
async function setupWithQueuedChunk(strategy) {
|
|
61
|
+
const manager = await ContextManager.open({
|
|
62
|
+
path: TEST_STORE_PATH,
|
|
63
|
+
strategy,
|
|
64
|
+
});
|
|
65
|
+
// recentWindowTokens=5 (small) means only the latest message stays in recent;
|
|
66
|
+
// the rest are compressible and will form chunks once we hit targetChunkTokens.
|
|
67
|
+
// 5 padding messages of ~6 tokens, plus one trailing recent message.
|
|
68
|
+
for (let i = 0; i < 5; i++) {
|
|
69
|
+
manager.addMessage('user', textBlock(`m-${i} hello world`));
|
|
70
|
+
}
|
|
71
|
+
manager.addMessage('user', textBlock('latest one ' + 'X'.repeat(20)));
|
|
72
|
+
// Wait for any onNewMessage handlers to settle
|
|
73
|
+
await new Promise(r => setTimeout(r, 30));
|
|
74
|
+
return manager;
|
|
75
|
+
}
|
|
76
|
+
describe('AutobiographicalStrategy — speculation cap (gap #8)', () => {
|
|
77
|
+
before(cleanup);
|
|
78
|
+
after(cleanup);
|
|
79
|
+
beforeEach(cleanup);
|
|
80
|
+
it('default: auto-tick fires whenever a chunk is queued', async () => {
|
|
81
|
+
const strategy = new TickCountingStrategy({
|
|
82
|
+
headWindowTokens: 0,
|
|
83
|
+
recentWindowTokens: 5,
|
|
84
|
+
targetChunkTokens: 20,
|
|
85
|
+
autoTickOnNewMessage: true,
|
|
86
|
+
});
|
|
87
|
+
const manager = await setupWithQueuedChunk(strategy);
|
|
88
|
+
assert.ok(strategy.hasQueuedChunk(), 'sanity: chunk should have formed in compressible region');
|
|
89
|
+
assert.ok(strategy.tickCalls >= 1, `default behavior must auto-tick (got ${strategy.tickCalls})`);
|
|
90
|
+
manager.close();
|
|
91
|
+
});
|
|
92
|
+
it('with maxSpeculativeL1s, auto-tick is deferred when at cap', async () => {
|
|
93
|
+
const strategy = new TickCountingStrategy({
|
|
94
|
+
headWindowTokens: 0,
|
|
95
|
+
recentWindowTokens: 5,
|
|
96
|
+
targetChunkTokens: 20,
|
|
97
|
+
autoTickOnNewMessage: true,
|
|
98
|
+
maxSpeculativeL1s: 2,
|
|
99
|
+
});
|
|
100
|
+
const manager = await ContextManager.open({
|
|
101
|
+
path: TEST_STORE_PATH,
|
|
102
|
+
strategy,
|
|
103
|
+
});
|
|
104
|
+
// Seed AFTER open so initialize() doesn't wipe the seeds.
|
|
105
|
+
strategy.seedL1('summary 1');
|
|
106
|
+
strategy.seedL1('summary 2');
|
|
107
|
+
strategy.seedL1('summary 3');
|
|
108
|
+
const ticksBefore = strategy.tickCalls;
|
|
109
|
+
for (let i = 0; i < 5; i++) {
|
|
110
|
+
manager.addMessage('user', textBlock(`m-${i} hello world`));
|
|
111
|
+
}
|
|
112
|
+
manager.addMessage('user', textBlock('latest one ' + 'X'.repeat(20)));
|
|
113
|
+
await new Promise(r => setTimeout(r, 30));
|
|
114
|
+
assert.ok(strategy.hasQueuedChunk(), 'sanity: chunk formed');
|
|
115
|
+
assert.equal(strategy.tickCalls, ticksBefore, 'auto-tick must NOT fire when unmerged L1s already exceed maxSpeculativeL1s');
|
|
116
|
+
manager.close();
|
|
117
|
+
});
|
|
118
|
+
it('cap allows auto-tick when count is under the limit', async () => {
|
|
119
|
+
const strategy = new TickCountingStrategy({
|
|
120
|
+
headWindowTokens: 0,
|
|
121
|
+
recentWindowTokens: 5,
|
|
122
|
+
targetChunkTokens: 20,
|
|
123
|
+
autoTickOnNewMessage: true,
|
|
124
|
+
maxSpeculativeL1s: 5,
|
|
125
|
+
});
|
|
126
|
+
const manager = await ContextManager.open({
|
|
127
|
+
path: TEST_STORE_PATH,
|
|
128
|
+
strategy,
|
|
129
|
+
});
|
|
130
|
+
strategy.seedL1('summary 1');
|
|
131
|
+
const ticksBefore = strategy.tickCalls;
|
|
132
|
+
for (let i = 0; i < 5; i++) {
|
|
133
|
+
manager.addMessage('user', textBlock(`m-${i} hello world`));
|
|
134
|
+
}
|
|
135
|
+
manager.addMessage('user', textBlock('latest one ' + 'X'.repeat(20)));
|
|
136
|
+
await new Promise(r => setTimeout(r, 30));
|
|
137
|
+
assert.ok(strategy.hasQueuedChunk(), 'sanity: chunk formed');
|
|
138
|
+
assert.ok(strategy.tickCalls > ticksBefore, 'auto-tick should fire when under cap');
|
|
139
|
+
manager.close();
|
|
140
|
+
});
|
|
141
|
+
it('shouldCompressPreflight=false defers auto-tick', async () => {
|
|
142
|
+
class DeferAlways extends TickCountingStrategy {
|
|
143
|
+
shouldCompressPreflight() { return false; }
|
|
144
|
+
}
|
|
145
|
+
const strategy = new DeferAlways({
|
|
146
|
+
headWindowTokens: 0,
|
|
147
|
+
recentWindowTokens: 5,
|
|
148
|
+
targetChunkTokens: 20,
|
|
149
|
+
autoTickOnNewMessage: true,
|
|
150
|
+
});
|
|
151
|
+
const manager = await setupWithQueuedChunk(strategy);
|
|
152
|
+
assert.ok(strategy.hasQueuedChunk(), 'sanity: chunk formed');
|
|
153
|
+
assert.equal(strategy.tickCalls, 0, 'preflight=false must block auto-tick');
|
|
154
|
+
manager.close();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
//# sourceMappingURL=speculation-cap.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speculation-cap.test.js","sourceRoot":"","sources":["../../test/speculation-cap.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for AutobiographicalStrategy persistence + branch fork.
|
|
3
|
+
*
|
|
4
|
+
* Strategy state (summaries, summaryIdCounter, mergeQueue) was previously
|
|
5
|
+
* in-memory only — first process restart wiped the entire L1/L2/L3 pyramid.
|
|
6
|
+
* Forking the underlying chronicle branch silently shared state across
|
|
7
|
+
* branches because nothing was actually branch-scoped.
|
|
8
|
+
*
|
|
9
|
+
* These tests cover:
|
|
10
|
+
* 1. restart-and-resume: state survives close/reopen of the same path.
|
|
11
|
+
* 2. branch fork: a forked branch has its own summary timeline.
|
|
12
|
+
* 3. branch switch: switching back picks up the right branch's state.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=strategy-persistence.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy-persistence.test.d.ts","sourceRoot":"","sources":["../../test/strategy-persistence.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for AutobiographicalStrategy persistence + branch fork.
|
|
3
|
+
*
|
|
4
|
+
* Strategy state (summaries, summaryIdCounter, mergeQueue) was previously
|
|
5
|
+
* in-memory only — first process restart wiped the entire L1/L2/L3 pyramid.
|
|
6
|
+
* Forking the underlying chronicle branch silently shared state across
|
|
7
|
+
* branches because nothing was actually branch-scoped.
|
|
8
|
+
*
|
|
9
|
+
* These tests cover:
|
|
10
|
+
* 1. restart-and-resume: state survives close/reopen of the same path.
|
|
11
|
+
* 2. branch fork: a forked branch has its own summary timeline.
|
|
12
|
+
* 3. branch switch: switching back picks up the right branch's state.
|
|
13
|
+
*/
|
|
14
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
15
|
+
import assert from 'node:assert';
|
|
16
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
17
|
+
import { ContextManager, AutobiographicalStrategy } from '../src/index.js';
|
|
18
|
+
const TEST_STORE_PATH = './test-strategy-persistence';
|
|
19
|
+
function cleanup() {
|
|
20
|
+
if (existsSync(TEST_STORE_PATH)) {
|
|
21
|
+
rmSync(TEST_STORE_PATH, { recursive: true, force: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function textBlock(text) {
|
|
25
|
+
return [{ type: 'text', text }];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Test subclass that exposes the protected persistence helpers + lets the
|
|
29
|
+
* test seed summaries through the same code path that production uses.
|
|
30
|
+
*/
|
|
31
|
+
class TestableStrategy extends AutobiographicalStrategy {
|
|
32
|
+
seedL1(content, sourceIds) {
|
|
33
|
+
const entry = {
|
|
34
|
+
id: `L1-${this.nextSummaryIdCounter()}`,
|
|
35
|
+
level: 1,
|
|
36
|
+
content,
|
|
37
|
+
tokens: Math.ceil(content.length / 4),
|
|
38
|
+
sourceLevel: 0,
|
|
39
|
+
sourceIds,
|
|
40
|
+
sourceRange: {
|
|
41
|
+
first: sourceIds[0] ?? '',
|
|
42
|
+
last: sourceIds[sourceIds.length - 1] ?? '',
|
|
43
|
+
},
|
|
44
|
+
created: Date.now(),
|
|
45
|
+
};
|
|
46
|
+
this.pushSummary(entry);
|
|
47
|
+
return entry;
|
|
48
|
+
}
|
|
49
|
+
seedL2(sourceL1Ids, content = 'merged') {
|
|
50
|
+
const entry = {
|
|
51
|
+
id: `L2-${this.nextSummaryIdCounter()}`,
|
|
52
|
+
level: 2,
|
|
53
|
+
content,
|
|
54
|
+
tokens: Math.ceil(content.length / 4),
|
|
55
|
+
sourceLevel: 1,
|
|
56
|
+
sourceIds: sourceL1Ids,
|
|
57
|
+
sourceRange: { first: 'a', last: 'z' },
|
|
58
|
+
created: Date.now(),
|
|
59
|
+
};
|
|
60
|
+
this.pushSummary(entry);
|
|
61
|
+
for (const id of sourceL1Ids) {
|
|
62
|
+
const src = this.summaries.find(s => s.id === id);
|
|
63
|
+
if (src)
|
|
64
|
+
this.setMergedInto(src, entry.id);
|
|
65
|
+
}
|
|
66
|
+
return entry;
|
|
67
|
+
}
|
|
68
|
+
seedMerge(level, sourceIds) {
|
|
69
|
+
this.enqueueMerge({ level, sourceIds });
|
|
70
|
+
}
|
|
71
|
+
// Read-only views for assertions
|
|
72
|
+
getSummariesView() { return [...this.summaries]; }
|
|
73
|
+
getCounter() { return this.summaryIdCounter; }
|
|
74
|
+
getMergeQueueView() {
|
|
75
|
+
return [...this.mergeQueue];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
describe('AutobiographicalStrategy — persistence', () => {
|
|
79
|
+
before(cleanup);
|
|
80
|
+
after(cleanup);
|
|
81
|
+
beforeEach(cleanup);
|
|
82
|
+
it('restores summaries, counter, and merge queue across close/reopen', async () => {
|
|
83
|
+
// Phase 1: build state
|
|
84
|
+
{
|
|
85
|
+
const strategy = new TestableStrategy({ targetChunkTokens: 300 });
|
|
86
|
+
const manager = await ContextManager.open({
|
|
87
|
+
path: TEST_STORE_PATH,
|
|
88
|
+
strategy,
|
|
89
|
+
});
|
|
90
|
+
manager.addMessage('User', textBlock('hello'));
|
|
91
|
+
const m2 = manager.addMessage('Claude', textBlock('hi back'));
|
|
92
|
+
strategy.seedL1('summary one', ['a', 'b']);
|
|
93
|
+
strategy.seedL1('summary two', ['c', 'd']);
|
|
94
|
+
strategy.seedMerge(2, ['L1-0', 'L1-1']);
|
|
95
|
+
assert.equal(strategy.getSummariesView().length, 2);
|
|
96
|
+
assert.equal(strategy.getCounter(), 2);
|
|
97
|
+
assert.equal(strategy.getMergeQueueView().length, 1);
|
|
98
|
+
manager.sync();
|
|
99
|
+
manager.close();
|
|
100
|
+
}
|
|
101
|
+
// Phase 2: reopen, verify reload
|
|
102
|
+
{
|
|
103
|
+
const strategy2 = new TestableStrategy({ targetChunkTokens: 300 });
|
|
104
|
+
const manager2 = await ContextManager.open({
|
|
105
|
+
path: TEST_STORE_PATH,
|
|
106
|
+
strategy: strategy2,
|
|
107
|
+
});
|
|
108
|
+
const summaries = strategy2.getSummariesView();
|
|
109
|
+
assert.equal(summaries.length, 2, 'summaries should reload from chronicle');
|
|
110
|
+
assert.equal(summaries[0].content, 'summary one');
|
|
111
|
+
assert.equal(summaries[1].content, 'summary two');
|
|
112
|
+
assert.equal(strategy2.getCounter(), 2, 'counter should reload');
|
|
113
|
+
const queue = strategy2.getMergeQueueView();
|
|
114
|
+
assert.equal(queue.length, 1, 'merge queue should reload');
|
|
115
|
+
assert.equal(queue[0].level, 2);
|
|
116
|
+
assert.deepEqual(queue[0].sourceIds, ['L1-0', 'L1-1']);
|
|
117
|
+
manager2.close();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
it('persists mergedInto edits so merged sources stay merged after restart', async () => {
|
|
121
|
+
{
|
|
122
|
+
const strategy = new TestableStrategy({ targetChunkTokens: 300 });
|
|
123
|
+
const manager = await ContextManager.open({
|
|
124
|
+
path: TEST_STORE_PATH,
|
|
125
|
+
strategy,
|
|
126
|
+
});
|
|
127
|
+
const a = strategy.seedL1('a', ['m1']);
|
|
128
|
+
const b = strategy.seedL1('b', ['m2']);
|
|
129
|
+
const merged = strategy.seedL2([a.id, b.id], 'merged a+b');
|
|
130
|
+
const summaries = strategy.getSummariesView();
|
|
131
|
+
assert.ok(summaries[0].mergedInto === merged.id);
|
|
132
|
+
assert.ok(summaries[1].mergedInto === merged.id);
|
|
133
|
+
manager.sync();
|
|
134
|
+
manager.close();
|
|
135
|
+
}
|
|
136
|
+
{
|
|
137
|
+
const strategy = new TestableStrategy({ targetChunkTokens: 300 });
|
|
138
|
+
const manager = await ContextManager.open({
|
|
139
|
+
path: TEST_STORE_PATH,
|
|
140
|
+
strategy,
|
|
141
|
+
});
|
|
142
|
+
const summaries = strategy.getSummariesView();
|
|
143
|
+
assert.equal(summaries.length, 3);
|
|
144
|
+
assert.equal(summaries[0].mergedInto, 'L2-2', 'L1 mergedInto must persist');
|
|
145
|
+
assert.equal(summaries[1].mergedInto, 'L2-2');
|
|
146
|
+
assert.equal(summaries[2].mergedInto, undefined);
|
|
147
|
+
manager.close();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
it('isolates summary state per chronicle branch (fork)', async () => {
|
|
151
|
+
const strategy = new TestableStrategy({ targetChunkTokens: 300 });
|
|
152
|
+
const manager = await ContextManager.open({
|
|
153
|
+
path: TEST_STORE_PATH,
|
|
154
|
+
strategy,
|
|
155
|
+
});
|
|
156
|
+
// Build pre-fork state, then fork at current head so the forked branch
|
|
157
|
+
// inherits everything we've written so far.
|
|
158
|
+
manager.addMessage('User', textBlock('m1'));
|
|
159
|
+
manager.addMessage('Claude', textBlock('m2'));
|
|
160
|
+
strategy.seedL1('common L1 (pre-fork)', ['m1', 'm2']);
|
|
161
|
+
const mainBranchName = manager.currentBranch().name;
|
|
162
|
+
const forkedName = await manager.fork('experimental');
|
|
163
|
+
assert.equal(forkedName, 'experimental');
|
|
164
|
+
assert.notEqual(forkedName, mainBranchName);
|
|
165
|
+
// On the forked branch, add a divergent summary
|
|
166
|
+
const strategyAfterFork = manager.getStrategy();
|
|
167
|
+
assert.equal(strategyAfterFork.getSummariesView().length, 1, 'forked branch should start with the pre-fork summary');
|
|
168
|
+
strategyAfterFork.seedL1('divergent on fork', ['x', 'y']);
|
|
169
|
+
assert.equal(strategyAfterFork.getSummariesView().length, 2);
|
|
170
|
+
// Switch back to main: should see only the pre-fork summary
|
|
171
|
+
await manager.switchBranch(mainBranchName);
|
|
172
|
+
const mainStrategy = manager.getStrategy();
|
|
173
|
+
const mainSummaries = mainStrategy.getSummariesView();
|
|
174
|
+
assert.equal(mainSummaries.length, 1, 'main branch must NOT see summaries created on fork');
|
|
175
|
+
assert.equal(mainSummaries[0].content, 'common L1 (pre-fork)');
|
|
176
|
+
// Add a summary unique to main
|
|
177
|
+
mainStrategy.seedL1('unique to main', ['p', 'q']);
|
|
178
|
+
assert.equal(mainStrategy.getSummariesView().length, 2);
|
|
179
|
+
// Switch back to fork: should still have its own divergent state
|
|
180
|
+
await manager.switchBranch(forkedName);
|
|
181
|
+
const forkStrategy = manager.getStrategy();
|
|
182
|
+
const forkSummaries = forkStrategy.getSummariesView();
|
|
183
|
+
assert.equal(forkSummaries.length, 2, 'fork branch should still have 2 summaries');
|
|
184
|
+
assert.equal(forkSummaries[1].content, 'divergent on fork');
|
|
185
|
+
assert.ok(!forkSummaries.some(s => s.content === 'unique to main'), 'fork must not see main-only summary');
|
|
186
|
+
manager.close();
|
|
187
|
+
});
|
|
188
|
+
it('keeps merge queue intact when executeMerge throws (no eager dequeue)', async () => {
|
|
189
|
+
// Regression test for the eager-dequeue footgun: dequeueMerge() used to
|
|
190
|
+
// run *before* the LLM call, so a transient failure (429, network drop,
|
|
191
|
+
// timeout) would silently drop the merge from the persisted queue
|
|
192
|
+
// forever. The fix is peek + shift-on-success — these assertions lock
|
|
193
|
+
// that contract in.
|
|
194
|
+
class FailingMergeStrategy extends TestableStrategy {
|
|
195
|
+
executeMergeError = new Error('simulated LLM failure');
|
|
196
|
+
async executeMerge(_level, _sourceIds, _ctx) {
|
|
197
|
+
throw this.executeMergeError;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const stubMembrane = {};
|
|
201
|
+
// Phase 1: enqueue a merge, force tick to fail, verify queue intact
|
|
202
|
+
{
|
|
203
|
+
const strategy = new FailingMergeStrategy({
|
|
204
|
+
targetChunkTokens: 300,
|
|
205
|
+
hierarchical: true,
|
|
206
|
+
});
|
|
207
|
+
const manager = await ContextManager.open({
|
|
208
|
+
path: TEST_STORE_PATH,
|
|
209
|
+
strategy,
|
|
210
|
+
membrane: stubMembrane,
|
|
211
|
+
});
|
|
212
|
+
strategy.seedMerge(2, ['L1-0', 'L1-1']);
|
|
213
|
+
assert.equal(strategy.getMergeQueueView().length, 1, 'merge enqueued');
|
|
214
|
+
// tick() will pick up the merge, invoke (our failing) executeMerge,
|
|
215
|
+
// and surface the error. The queue must still be intact afterward.
|
|
216
|
+
await assert.rejects(() => manager.tick(), /simulated LLM failure/, 'tick should propagate the LLM failure');
|
|
217
|
+
const queue = strategy.getMergeQueueView();
|
|
218
|
+
assert.equal(queue.length, 1, 'merge MUST remain in queue after failure');
|
|
219
|
+
assert.equal(queue[0].level, 2);
|
|
220
|
+
assert.deepEqual(queue[0].sourceIds, ['L1-0', 'L1-1']);
|
|
221
|
+
manager.sync();
|
|
222
|
+
manager.close();
|
|
223
|
+
}
|
|
224
|
+
// Phase 2: reopen the store; the un-merged entry must still be there.
|
|
225
|
+
// This is the part that mattered most pre-fix — the failure was *durable*
|
|
226
|
+
// because dequeueMerge persisted the shorter queue before the LLM call.
|
|
227
|
+
{
|
|
228
|
+
const strategy = new TestableStrategy({
|
|
229
|
+
targetChunkTokens: 300,
|
|
230
|
+
hierarchical: true,
|
|
231
|
+
});
|
|
232
|
+
const manager = await ContextManager.open({
|
|
233
|
+
path: TEST_STORE_PATH,
|
|
234
|
+
strategy,
|
|
235
|
+
});
|
|
236
|
+
const queue = strategy.getMergeQueueView();
|
|
237
|
+
assert.equal(queue.length, 1, 'merge MUST survive close/reopen');
|
|
238
|
+
assert.equal(queue[0].level, 2);
|
|
239
|
+
assert.deepEqual(queue[0].sourceIds, ['L1-0', 'L1-1']);
|
|
240
|
+
manager.close();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
//# sourceMappingURL=strategy-persistence.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy-persistence.test.js","sourceRoot":"","sources":["../../test/strategy-persistence.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for tool-result pruning + tool_use input truncation (audit gap #4).
|
|
3
|
+
*
|
|
4
|
+
* For tool-heavy sessions, dozens or hundreds of repeated tool calls can
|
|
5
|
+
* dominate context. This pruning pass keeps the most recent N results per
|
|
6
|
+
* tool name (older ones get a brief marker), and optionally truncates
|
|
7
|
+
* tool_use input blobs whose serialized JSON exceeds a token cap.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=tool-pruning.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-pruning.test.d.ts","sourceRoot":"","sources":["../../test/tool-pruning.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|