@animalabs/context-manager 0.3.0 → 0.5.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/adaptive/chunker.d.ts +70 -0
- package/dist/src/adaptive/chunker.d.ts.map +1 -0
- package/dist/src/adaptive/chunker.js +233 -0
- package/dist/src/adaptive/chunker.js.map +1 -0
- package/dist/src/adaptive/folding-strategy.d.ts +118 -0
- package/dist/src/adaptive/folding-strategy.d.ts.map +1 -0
- package/dist/src/adaptive/folding-strategy.js +12 -0
- package/dist/src/adaptive/folding-strategy.js.map +1 -0
- package/dist/src/adaptive/index.d.ts +17 -0
- package/dist/src/adaptive/index.d.ts.map +1 -0
- package/dist/src/adaptive/index.js +23 -0
- package/dist/src/adaptive/index.js.map +1 -0
- package/dist/src/adaptive/picker.d.ts +103 -0
- package/dist/src/adaptive/picker.d.ts.map +1 -0
- package/dist/src/adaptive/picker.js +357 -0
- package/dist/src/adaptive/picker.js.map +1 -0
- package/dist/src/adaptive/render.d.ts +39 -0
- package/dist/src/adaptive/render.d.ts.map +1 -0
- package/dist/src/adaptive/render.js +104 -0
- package/dist/src/adaptive/render.js.map +1 -0
- package/dist/src/adaptive/strategies/flat-profile.d.ts +19 -0
- package/dist/src/adaptive/strategies/flat-profile.d.ts.map +1 -0
- package/dist/src/adaptive/strategies/flat-profile.js +86 -0
- package/dist/src/adaptive/strategies/flat-profile.js.map +1 -0
- package/dist/src/adaptive/strategies/oldest-first.d.ts +19 -0
- package/dist/src/adaptive/strategies/oldest-first.d.ts.map +1 -0
- package/dist/src/adaptive/strategies/oldest-first.js +39 -0
- package/dist/src/adaptive/strategies/oldest-first.js.map +1 -0
- package/dist/src/context-manager.d.ts +74 -2
- package/dist/src/context-manager.d.ts.map +1 -1
- package/dist/src/context-manager.js +150 -10
- package/dist/src/context-manager.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/message-store.d.ts +36 -1
- package/dist/src/message-store.d.ts.map +1 -1
- package/dist/src/message-store.js +134 -8
- package/dist/src/message-store.js.map +1 -1
- package/dist/src/strategies/autobiographical.d.ts +422 -5
- package/dist/src/strategies/autobiographical.d.ts.map +1 -1
- package/dist/src/strategies/autobiographical.js +2248 -98
- package/dist/src/strategies/autobiographical.js.map +1 -1
- package/dist/src/strategies/index.d.ts +1 -1
- package/dist/src/strategies/index.d.ts.map +1 -1
- package/dist/src/strategies/index.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/message.d.ts +38 -0
- package/dist/src/types/message.d.ts.map +1 -1
- package/dist/src/types/strategy.d.ts +302 -10
- package/dist/src/types/strategy.d.ts.map +1 -1
- package/dist/src/types/strategy.js +28 -0
- package/dist/src/types/strategy.js.map +1 -1
- package/dist/test/adaptive/branching.test.d.ts +20 -0
- package/dist/test/adaptive/branching.test.d.ts.map +1 -0
- package/dist/test/adaptive/branching.test.js +176 -0
- package/dist/test/adaptive/branching.test.js.map +1 -0
- package/dist/test/adaptive/chunker.test.d.ts +2 -0
- package/dist/test/adaptive/chunker.test.d.ts.map +1 -0
- package/dist/test/adaptive/chunker.test.js +120 -0
- package/dist/test/adaptive/chunker.test.js.map +1 -0
- package/dist/test/adaptive/deep-levels.test.d.ts +10 -0
- package/dist/test/adaptive/deep-levels.test.d.ts.map +1 -0
- package/dist/test/adaptive/deep-levels.test.js +167 -0
- package/dist/test/adaptive/deep-levels.test.js.map +1 -0
- package/dist/test/adaptive/doc-plus-chat.test.d.ts +10 -0
- package/dist/test/adaptive/doc-plus-chat.test.d.ts.map +1 -0
- package/dist/test/adaptive/doc-plus-chat.test.js +194 -0
- package/dist/test/adaptive/doc-plus-chat.test.js.map +1 -0
- package/dist/test/adaptive/hard-fail.test.d.ts +9 -0
- package/dist/test/adaptive/hard-fail.test.d.ts.map +1 -0
- package/dist/test/adaptive/hard-fail.test.js +154 -0
- package/dist/test/adaptive/hard-fail.test.js.map +1 -0
- package/dist/test/adaptive/harness.d.ts +109 -0
- package/dist/test/adaptive/harness.d.ts.map +1 -0
- package/dist/test/adaptive/harness.js +263 -0
- package/dist/test/adaptive/harness.js.map +1 -0
- package/dist/test/adaptive/ingestion.test.d.ts +13 -0
- package/dist/test/adaptive/ingestion.test.d.ts.map +1 -0
- package/dist/test/adaptive/ingestion.test.js +306 -0
- package/dist/test/adaptive/ingestion.test.js.map +1 -0
- package/dist/test/adaptive/integration.test.d.ts +12 -0
- package/dist/test/adaptive/integration.test.d.ts.map +1 -0
- package/dist/test/adaptive/integration.test.js +247 -0
- package/dist/test/adaptive/integration.test.js.map +1 -0
- package/dist/test/adaptive/long-chronicle.test.d.ts +13 -0
- package/dist/test/adaptive/long-chronicle.test.d.ts.map +1 -0
- package/dist/test/adaptive/long-chronicle.test.js +186 -0
- package/dist/test/adaptive/long-chronicle.test.js.map +1 -0
- package/dist/test/adaptive/persistence.test.d.ts +11 -0
- package/dist/test/adaptive/persistence.test.d.ts.map +1 -0
- package/dist/test/adaptive/persistence.test.js +200 -0
- package/dist/test/adaptive/persistence.test.js.map +1 -0
- package/dist/test/adaptive/picker.test.d.ts +2 -0
- package/dist/test/adaptive/picker.test.d.ts.map +1 -0
- package/dist/test/adaptive/picker.test.js +333 -0
- package/dist/test/adaptive/picker.test.js.map +1 -0
- package/dist/test/adaptive/render.test.d.ts +2 -0
- package/dist/test/adaptive/render.test.d.ts.map +1 -0
- package/dist/test/adaptive/render.test.js +109 -0
- package/dist/test/adaptive/render.test.js.map +1 -0
- package/dist/test/adaptive/shard-immutability.test.d.ts +14 -0
- package/dist/test/adaptive/shard-immutability.test.d.ts.map +1 -0
- package/dist/test/adaptive/shard-immutability.test.js +184 -0
- package/dist/test/adaptive/shard-immutability.test.js.map +1 -0
- package/dist/test/adaptive/strategy-integration.test.d.ts +9 -0
- package/dist/test/adaptive/strategy-integration.test.d.ts.map +1 -0
- package/dist/test/adaptive/strategy-integration.test.js +293 -0
- package/dist/test/adaptive/strategy-integration.test.js.map +1 -0
- package/dist/test/message-store-sequence.test.d.ts +25 -0
- package/dist/test/message-store-sequence.test.d.ts.map +1 -0
- package/dist/test/message-store-sequence.test.js +157 -0
- package/dist/test/message-store-sequence.test.js.map +1 -0
- 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/adaptive/chunker.ts +323 -0
- package/src/adaptive/folding-strategy.ts +129 -0
- package/src/adaptive/index.ts +51 -0
- package/src/adaptive/picker.ts +442 -0
- package/src/adaptive/render.ts +115 -0
- package/src/adaptive/strategies/flat-profile.ts +96 -0
- package/src/adaptive/strategies/oldest-first.ts +48 -0
- package/src/context-manager.ts +174 -10
- package/src/index.ts +1 -1
- package/src/message-store.ts +147 -8
- package/src/strategies/autobiographical.ts +2468 -166
- package/src/strategies/index.ts +1 -1
- package/src/types/index.ts +14 -1
- package/src/types/message.ts +43 -0
- package/src/types/strategy.ts +332 -10
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for per-region recall pair emission (audit gap #2).
|
|
3
|
+
*
|
|
4
|
+
* Pre-fix, AutobiographicalStrategy concatenated ALL selected summaries with
|
|
5
|
+
* `\n\n---\n\n` separators into ONE Q/A pair placed between the head window
|
|
6
|
+
* and the recent window. Per the spec audit, this is the structural
|
|
7
|
+
* equivalent of Lena's dual-recall corruption pattern from Hermes: the agent
|
|
8
|
+
* sees memories as a wall of summaries from another speaker, with no
|
|
9
|
+
* positional or temporal coherence per individual summary.
|
|
10
|
+
*
|
|
11
|
+
* Post-fix, each selected summary emits as its own Q/A recall pair, sorted
|
|
12
|
+
* chronologically by source-range position. The legacy combined-pair shape
|
|
13
|
+
* is still available via `positionedRecallPairs: false`.
|
|
14
|
+
*/
|
|
15
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
16
|
+
import assert from 'node:assert';
|
|
17
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
18
|
+
import { ContextManager, AutobiographicalStrategy } from '../src/index.js';
|
|
19
|
+
const TEST_STORE_PATH = './test-recall-positioning';
|
|
20
|
+
function cleanup() {
|
|
21
|
+
if (existsSync(TEST_STORE_PATH)) {
|
|
22
|
+
rmSync(TEST_STORE_PATH, { recursive: true, force: true });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function textBlock(text) {
|
|
26
|
+
return [{ type: 'text', text }];
|
|
27
|
+
}
|
|
28
|
+
class TestableStrategy extends AutobiographicalStrategy {
|
|
29
|
+
/** Seed an L1 against a specific source-message id. */
|
|
30
|
+
seedL1Against(content, firstMsgId, lastMsgId) {
|
|
31
|
+
const entry = {
|
|
32
|
+
id: `L1-${this.nextSummaryIdCounter()}`,
|
|
33
|
+
level: 1,
|
|
34
|
+
content,
|
|
35
|
+
tokens: Math.ceil(content.length / 4),
|
|
36
|
+
sourceLevel: 0,
|
|
37
|
+
sourceIds: [firstMsgId, lastMsgId],
|
|
38
|
+
sourceRange: { first: firstMsgId, last: lastMsgId },
|
|
39
|
+
created: Date.now(),
|
|
40
|
+
};
|
|
41
|
+
this.pushSummary(entry);
|
|
42
|
+
return entry;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function recallEntries(messages) {
|
|
46
|
+
const pairs = [];
|
|
47
|
+
for (let i = 0; i < messages.length - 1; i++) {
|
|
48
|
+
const a = messages[i];
|
|
49
|
+
const b = messages[i + 1];
|
|
50
|
+
if (a.participant === 'Context Manager') {
|
|
51
|
+
const q = a.content.map(c => (c.type === 'text' ? c.text : '')).join('');
|
|
52
|
+
const ans = b.content.map(c => (c.type === 'text' ? c.text : '')).join('');
|
|
53
|
+
pairs.push({ q, a: ans });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return pairs;
|
|
57
|
+
}
|
|
58
|
+
describe('AutobiographicalStrategy — per-region recall pairs (gap #2)', () => {
|
|
59
|
+
before(cleanup);
|
|
60
|
+
after(cleanup);
|
|
61
|
+
beforeEach(cleanup);
|
|
62
|
+
it('emits one Q/A pair per selected summary, in chronological order', async () => {
|
|
63
|
+
// recentWindowTokens deliberately small so the three "old" messages we
|
|
64
|
+
// seed summaries against fall OUTSIDE the recent window — otherwise
|
|
65
|
+
// anti-redundancy (correctly) hides summaries about messages that are
|
|
66
|
+
// already shown verbatim in the recent window.
|
|
67
|
+
const strategy = new TestableStrategy({
|
|
68
|
+
headWindowTokens: 0,
|
|
69
|
+
recentWindowTokens: 8,
|
|
70
|
+
});
|
|
71
|
+
const manager = await ContextManager.open({
|
|
72
|
+
path: TEST_STORE_PATH,
|
|
73
|
+
strategy,
|
|
74
|
+
});
|
|
75
|
+
// Three "old" messages whose summaries we'll test, plus one message
|
|
76
|
+
// big enough to dominate the recent window so the older three fall out.
|
|
77
|
+
const m1 = manager.addMessage('user', textBlock('first'));
|
|
78
|
+
const m2 = manager.addMessage('user', textBlock('second'));
|
|
79
|
+
const m3 = manager.addMessage('user', textBlock('third'));
|
|
80
|
+
manager.addMessage('user', textBlock('latest message ' + 'X'.repeat(50)));
|
|
81
|
+
// Seed three summaries. Seed in the *opposite* order from chronological
|
|
82
|
+
// so we can verify the strategy sorts them by source position rather
|
|
83
|
+
// than by creation order.
|
|
84
|
+
strategy.seedL1Against('summary about THIRD', m3, m3);
|
|
85
|
+
strategy.seedL1Against('summary about FIRST', m1, m1);
|
|
86
|
+
strategy.seedL1Against('summary about SECOND', m2, m2);
|
|
87
|
+
const compiled = await manager.compile({ maxTokens: 100_000, reserveForResponse: 0 });
|
|
88
|
+
const pairs = recallEntries(compiled.messages);
|
|
89
|
+
assert.equal(pairs.length, 3, 'should emit 3 distinct recall pairs');
|
|
90
|
+
// Order should follow source position (m1 < m2 < m3), not seed order.
|
|
91
|
+
assert.match(pairs[0].a, /FIRST/, 'first pair must cover m1');
|
|
92
|
+
assert.match(pairs[1].a, /SECOND/, 'second pair must cover m2');
|
|
93
|
+
assert.match(pairs[2].a, /THIRD/, 'third pair must cover m3');
|
|
94
|
+
// No --- separators (the legacy concat-marker should be gone).
|
|
95
|
+
for (const p of pairs) {
|
|
96
|
+
assert.ok(!p.a.includes('---'), `pair ${p.q} unexpectedly contains '---' separator`);
|
|
97
|
+
}
|
|
98
|
+
manager.close();
|
|
99
|
+
});
|
|
100
|
+
it('renders the recall header template with summary id by default', async () => {
|
|
101
|
+
const strategy = new TestableStrategy({
|
|
102
|
+
headWindowTokens: 0,
|
|
103
|
+
recentWindowTokens: 8,
|
|
104
|
+
});
|
|
105
|
+
const manager = await ContextManager.open({
|
|
106
|
+
path: TEST_STORE_PATH,
|
|
107
|
+
strategy,
|
|
108
|
+
});
|
|
109
|
+
const m1 = manager.addMessage('user', textBlock('foo'));
|
|
110
|
+
manager.addMessage('user', textBlock('latest ' + 'X'.repeat(50)));
|
|
111
|
+
const seeded = strategy.seedL1Against('content', m1, m1);
|
|
112
|
+
const compiled = await manager.compile({ maxTokens: 100_000, reserveForResponse: 0 });
|
|
113
|
+
const pairs = recallEntries(compiled.messages);
|
|
114
|
+
assert.equal(pairs.length, 1);
|
|
115
|
+
assert.equal(pairs[0].q, `[Recall ${seeded.id}]`, 'default template should be [Recall {id}]');
|
|
116
|
+
});
|
|
117
|
+
it('honors a custom recall header template with all substitutions', async () => {
|
|
118
|
+
const strategy = new TestableStrategy({
|
|
119
|
+
headWindowTokens: 0,
|
|
120
|
+
recentWindowTokens: 8,
|
|
121
|
+
recallHeaderTemplate: 'memory {id} (L{level}) covering {first}..{last}',
|
|
122
|
+
});
|
|
123
|
+
const manager = await ContextManager.open({
|
|
124
|
+
path: TEST_STORE_PATH,
|
|
125
|
+
strategy,
|
|
126
|
+
});
|
|
127
|
+
const m1 = manager.addMessage('user', textBlock('foo'));
|
|
128
|
+
const m2 = manager.addMessage('user', textBlock('bar'));
|
|
129
|
+
manager.addMessage('user', textBlock('latest ' + 'X'.repeat(50)));
|
|
130
|
+
const seeded = strategy.seedL1Against('content', m1, m2);
|
|
131
|
+
const compiled = await manager.compile({ maxTokens: 100_000, reserveForResponse: 0 });
|
|
132
|
+
const pairs = recallEntries(compiled.messages);
|
|
133
|
+
assert.equal(pairs[0].q, `memory ${seeded.id} (L1) covering ${m1}..${m2}`);
|
|
134
|
+
});
|
|
135
|
+
it('reverts to combined single-pair shape when positionedRecallPairs is false', async () => {
|
|
136
|
+
const strategy = new TestableStrategy({
|
|
137
|
+
headWindowTokens: 0,
|
|
138
|
+
recentWindowTokens: 8,
|
|
139
|
+
positionedRecallPairs: false,
|
|
140
|
+
});
|
|
141
|
+
const manager = await ContextManager.open({
|
|
142
|
+
path: TEST_STORE_PATH,
|
|
143
|
+
strategy,
|
|
144
|
+
});
|
|
145
|
+
const m1 = manager.addMessage('user', textBlock('a'));
|
|
146
|
+
const m2 = manager.addMessage('user', textBlock('b'));
|
|
147
|
+
const m3 = manager.addMessage('user', textBlock('c'));
|
|
148
|
+
manager.addMessage('user', textBlock('latest ' + 'X'.repeat(50)));
|
|
149
|
+
strategy.seedL1Against('A', m1, m1);
|
|
150
|
+
strategy.seedL1Against('B', m2, m2);
|
|
151
|
+
strategy.seedL1Against('C', m3, m3);
|
|
152
|
+
const compiled = await manager.compile({ maxTokens: 100_000, reserveForResponse: 0 });
|
|
153
|
+
const pairs = recallEntries(compiled.messages);
|
|
154
|
+
assert.equal(pairs.length, 1, 'legacy mode should emit a single combined pair');
|
|
155
|
+
assert.equal(pairs[0].q, 'What do you remember from earlier?');
|
|
156
|
+
assert.match(pairs[0].a, /A\n\n---\n\nB\n\n---\n\nC/);
|
|
157
|
+
});
|
|
158
|
+
it('stops emitting pairs when overall budget would be exceeded', async () => {
|
|
159
|
+
const strategy = new TestableStrategy({
|
|
160
|
+
headWindowTokens: 0,
|
|
161
|
+
recentWindowTokens: 8,
|
|
162
|
+
});
|
|
163
|
+
const manager = await ContextManager.open({
|
|
164
|
+
path: TEST_STORE_PATH,
|
|
165
|
+
strategy,
|
|
166
|
+
});
|
|
167
|
+
const m1 = manager.addMessage('user', textBlock('a'));
|
|
168
|
+
const m2 = manager.addMessage('user', textBlock('b'));
|
|
169
|
+
const m3 = manager.addMessage('user', textBlock('c'));
|
|
170
|
+
manager.addMessage('user', textBlock('latest ' + 'X'.repeat(50)));
|
|
171
|
+
// Each summary is ~400 chars (~100 tokens). Three pairs ≈ 300+ tokens of recall.
|
|
172
|
+
const big = 'X'.repeat(400);
|
|
173
|
+
strategy.seedL1Against(big + ' first', m1, m1);
|
|
174
|
+
strategy.seedL1Against(big + ' second', m2, m2);
|
|
175
|
+
strategy.seedL1Against(big + ' third', m3, m3);
|
|
176
|
+
const compiled = await manager.compile({ maxTokens: 250, reserveForResponse: 0 });
|
|
177
|
+
const pairs = recallEntries(compiled.messages);
|
|
178
|
+
assert.ok(pairs.length > 0 && pairs.length < 3, `expected partial recall emission under tight budget, got ${pairs.length}`);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
//# sourceMappingURL=recall-positioning.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall-positioning.test.js","sourceRoot":"","sources":["../../test/recall-positioning.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=search.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.test.d.ts","sourceRoot":"","sources":["../../test/search.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -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":"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"}
|
|
@@ -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"}
|