@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,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for the message-store sequence-vs-index bug.
|
|
3
|
+
*
|
|
4
|
+
* Before this fix, `MessageStore.get(id).sequence` returned the message's
|
|
5
|
+
* SLOT INDEX (its position in the message-store append-log) rather than the
|
|
6
|
+
* CHRONICLE RECORD SEQUENCE at which it was appended. Those two numbers
|
|
7
|
+
* diverge whenever any non-message record is appended between messages —
|
|
8
|
+
* which the autobio strategy does heavily, with `state_update` records for
|
|
9
|
+
* summary appends, mergeQueue snapshots, counter increments, etc. The
|
|
10
|
+
* typical ratio is ~1:2 (slot index : chronicle seq).
|
|
11
|
+
*
|
|
12
|
+
* `ContextManager.branchAt(messageId)` then passed `message.sequence` to
|
|
13
|
+
* `chronicle.createBranchAt(name, from, atSequence)`, which expects the
|
|
14
|
+
* chronicle sequence. Result: forks landed at chronicle sequence equal to
|
|
15
|
+
* the slot index — silently dropping every chronicle record between the
|
|
16
|
+
* intended fork point and the actual one. For an 800-message conversation
|
|
17
|
+
* this could fork ~400 messages back instead of one turn back.
|
|
18
|
+
*
|
|
19
|
+
* The fix returns `internal.sequence` (which is correctly populated from
|
|
20
|
+
* `record.sequence` at append time). These tests pin the contract so a
|
|
21
|
+
* future refactor can't reintroduce the "use index as sequence for now"
|
|
22
|
+
* shortcut.
|
|
23
|
+
*/
|
|
24
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
25
|
+
import assert from 'node:assert';
|
|
26
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
27
|
+
import { ContextManager, AutobiographicalStrategy } from '../src/index.js';
|
|
28
|
+
const TEST_STORE_PATH = './test-message-store-sequence';
|
|
29
|
+
function cleanup() {
|
|
30
|
+
if (existsSync(TEST_STORE_PATH)) {
|
|
31
|
+
rmSync(TEST_STORE_PATH, { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function textBlock(text) {
|
|
35
|
+
return [{ type: 'text', text }];
|
|
36
|
+
}
|
|
37
|
+
describe('MessageStore — sequence semantics', () => {
|
|
38
|
+
before(cleanup);
|
|
39
|
+
after(cleanup);
|
|
40
|
+
beforeEach(cleanup);
|
|
41
|
+
it('returned sequence equals the chronicle record sequence (not the slot index)', async () => {
|
|
42
|
+
// Use the autobiographical strategy so its background state appends
|
|
43
|
+
// (counter, summaries, etc.) interleave with message appends — that's
|
|
44
|
+
// what makes slot-index and chronicle-sequence diverge.
|
|
45
|
+
const strategy = new AutobiographicalStrategy({ targetChunkTokens: 300 });
|
|
46
|
+
const manager = await ContextManager.open({
|
|
47
|
+
path: TEST_STORE_PATH,
|
|
48
|
+
strategy,
|
|
49
|
+
});
|
|
50
|
+
const ids = [];
|
|
51
|
+
for (let i = 0; i < 5; i++) {
|
|
52
|
+
const id = manager.addMessage('user', textBlock(`m${i}`));
|
|
53
|
+
ids.push(id);
|
|
54
|
+
}
|
|
55
|
+
// Walk the store: each successive message must have a STRICTLY GREATER
|
|
56
|
+
// chronicle sequence than the previous, AND the sequence must not equal
|
|
57
|
+
// the slot index (which is what the old code returned). The "not equal"
|
|
58
|
+
// half is the actual regression assertion.
|
|
59
|
+
const { messages } = manager.queryMessages({});
|
|
60
|
+
assert.equal(messages.length, 5);
|
|
61
|
+
let prevSeq = -1;
|
|
62
|
+
for (let i = 0; i < messages.length; i++) {
|
|
63
|
+
const m = messages[i];
|
|
64
|
+
assert.ok(typeof m.sequence === 'number' && Number.isFinite(m.sequence), `message[${i}].sequence must be a finite number`);
|
|
65
|
+
assert.ok(m.sequence > prevSeq, `message[${i}].sequence (${m.sequence}) must be > previous (${prevSeq})`);
|
|
66
|
+
assert.notEqual(m.sequence, i, `message[${i}].sequence (${m.sequence}) must not equal its slot index (${i}) — ` +
|
|
67
|
+
'that was the bug. Chronicle sequences are dense across all record types, not just messages.');
|
|
68
|
+
prevSeq = m.sequence;
|
|
69
|
+
}
|
|
70
|
+
// Belt-and-suspenders: also confirm get() agrees with query()'s view.
|
|
71
|
+
const last = manager.getMessage(ids[ids.length - 1]);
|
|
72
|
+
assert.ok(last, 'get(lastId) must return the message');
|
|
73
|
+
assert.equal(last.sequence, messages[messages.length - 1].sequence, 'get() and query() must report the same sequence for the same message');
|
|
74
|
+
manager.close();
|
|
75
|
+
});
|
|
76
|
+
it('branchAt forks at the chronicle sequence — branch sees N messages, not slot-index-many', async () => {
|
|
77
|
+
// The end-to-end shape of the original bug. If sequence-vs-index is
|
|
78
|
+
// wrong, branchAt(messages[N-1].id) creates a fork at chronicle seq
|
|
79
|
+
// == (N-1), which lands far earlier than intended and the new branch
|
|
80
|
+
// sees only ~half the messages.
|
|
81
|
+
const strategy = new AutobiographicalStrategy({ targetChunkTokens: 300 });
|
|
82
|
+
const manager = await ContextManager.open({
|
|
83
|
+
path: TEST_STORE_PATH,
|
|
84
|
+
strategy,
|
|
85
|
+
});
|
|
86
|
+
const ids = [];
|
|
87
|
+
for (let i = 0; i < 6; i++) {
|
|
88
|
+
const id = manager.addMessage('user', textBlock(`m${i}`));
|
|
89
|
+
ids.push(id);
|
|
90
|
+
}
|
|
91
|
+
// Fork at the 5th message (index 4). The new branch must see exactly
|
|
92
|
+
// the first 5 messages — anything fewer means the fork landed at
|
|
93
|
+
// a too-early chronicle sequence (the bug).
|
|
94
|
+
const newBranch = manager.branchAt(ids[4]);
|
|
95
|
+
await manager.switchBranch(newBranch);
|
|
96
|
+
const { messages: onBranch } = manager.queryMessages({});
|
|
97
|
+
assert.equal(onBranch.length, 5, `forked branch must see exactly 5 messages, got ${onBranch.length}. ` +
|
|
98
|
+
'A smaller count means branchAt forked at the slot index instead of the chronicle sequence.');
|
|
99
|
+
// Verify the contents AND that fork-point message has full id/sequence
|
|
100
|
+
// (not undefined). Pre-fix #2, the fork-point message was visible only
|
|
101
|
+
// in its pre-patch form on the new branch — id and sequence undefined —
|
|
102
|
+
// because MessageStore.add() does append-then-editStateItem, and
|
|
103
|
+
// forking at the original append's sequence captured the pre-patch
|
|
104
|
+
// state. Fix #2 stores the EDIT's sequence as the canonical
|
|
105
|
+
// commit-sequence, so a fork at messageId.sequence now lands at the
|
|
106
|
+
// edit record and the patched payload is inherited intact.
|
|
107
|
+
for (let i = 0; i < 5; i++) {
|
|
108
|
+
const text = onBranch[i]?.content?.[0];
|
|
109
|
+
assert.ok(text && text.type === 'text' && text.text === `m${i}`, `forked branch message[${i}] should be "m${i}", got ${JSON.stringify(text)}`);
|
|
110
|
+
assert.ok(onBranch[i].id !== undefined && onBranch[i].id !== null, `forked branch message[${i}].id must be defined (was the chained-branching bug), got ${onBranch[i].id}`);
|
|
111
|
+
assert.ok(typeof onBranch[i].sequence === 'number' && Number.isFinite(onBranch[i].sequence), `forked branch message[${i}].sequence must be a finite number, got ${onBranch[i].sequence}`);
|
|
112
|
+
}
|
|
113
|
+
// The most specific check: the fork-point message on the new branch
|
|
114
|
+
// must equal the fork-point message on main (same id).
|
|
115
|
+
assert.equal(onBranch[onBranch.length - 1].id, ids[4], 'fork-point message on the new branch must have the same id as on main');
|
|
116
|
+
manager.close();
|
|
117
|
+
});
|
|
118
|
+
it('chained branching at the fork-point message preserves full message state', async () => {
|
|
119
|
+
// The bonus chained-branching bug: pre-fix, the FORK-POINT message
|
|
120
|
+
// on a new branch had id and sequence = undefined, because the
|
|
121
|
+
// append-then-patch dance left its patched payload at a chronicle
|
|
122
|
+
// sequence one PAST the fork-point. Anyone trying to branchAt the
|
|
123
|
+
// fork-point message on a forked branch would hit
|
|
124
|
+
// `idToIndex.get(undefined)` returning nothing → "Message not found".
|
|
125
|
+
// This test catches that by chaining two forks where the second
|
|
126
|
+
// forks AT the prior fork's fork-point message.
|
|
127
|
+
const strategy = new AutobiographicalStrategy({ targetChunkTokens: 300 });
|
|
128
|
+
const manager = await ContextManager.open({
|
|
129
|
+
path: TEST_STORE_PATH,
|
|
130
|
+
strategy,
|
|
131
|
+
});
|
|
132
|
+
const ids = [];
|
|
133
|
+
for (let i = 0; i < 6; i++) {
|
|
134
|
+
const id = manager.addMessage('user', textBlock(`m${i}`));
|
|
135
|
+
ids.push(id);
|
|
136
|
+
}
|
|
137
|
+
// Fork 1: at message index 4 (sees first 5 messages)
|
|
138
|
+
const fork1 = manager.branchAt(ids[4], 'chained-fork-1');
|
|
139
|
+
await manager.switchBranch(fork1);
|
|
140
|
+
const { messages: onFork1 } = manager.queryMessages({});
|
|
141
|
+
assert.equal(onFork1.length, 5);
|
|
142
|
+
// The fork-point message on fork1 must have full id/sequence — that's
|
|
143
|
+
// the precondition for branchAt to work from it.
|
|
144
|
+
const forkPointMsg = onFork1[onFork1.length - 1];
|
|
145
|
+
assert.ok(forkPointMsg.id !== undefined && forkPointMsg.id !== null, 'fork-point message on a forked branch must have a defined id (was the chained-branching bug)');
|
|
146
|
+
assert.ok(typeof forkPointMsg.sequence === 'number' && Number.isFinite(forkPointMsg.sequence), 'fork-point message on a forked branch must have a finite sequence');
|
|
147
|
+
// Fork 2: from fork1, AT the fork-point message of fork1.
|
|
148
|
+
// Pre-fix this throws "Message not found: undefined" because
|
|
149
|
+
// forkPointMsg.id is undefined on fork1.
|
|
150
|
+
const fork2 = manager.branchAt(forkPointMsg.id, 'chained-fork-2');
|
|
151
|
+
await manager.switchBranch(fork2);
|
|
152
|
+
const { messages: onFork2 } = manager.queryMessages({});
|
|
153
|
+
assert.equal(onFork2.length, 5, 'fork2 forked at fork1.fork-point should see the same 5 messages as fork1');
|
|
154
|
+
manager.close();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
//# sourceMappingURL=message-store-sequence.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-store-sequence.test.js","sourceRoot":"","sources":["../../test/message-store-sequence.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `compile()`'s non-blocking contract + the uncompressed-chunk
|
|
3
|
+
* fallback that prevents silent message loss.
|
|
4
|
+
*
|
|
5
|
+
* Background: commit `3e42e98` dropped the `await readiness.pendingWork`
|
|
6
|
+
* inside `compile()` to keep turn latency low (compression can take
|
|
7
|
+
* 30+s; awaiting it per-turn was the wrong tradeoff). The side effect
|
|
8
|
+
* was that messages caught in a queued-but-not-yet-compressed chunk
|
|
9
|
+
* would vanish from rendered context in hierarchical mode — no summary
|
|
10
|
+
* existed yet for the chunk, and `selectHierarchical` only emitted raw
|
|
11
|
+
* messages from `recentStart` onward.
|
|
12
|
+
*
|
|
13
|
+
* The fix (these tests pin in place): `selectHierarchical` now emits
|
|
14
|
+
* raw messages for any uncompressed chunk that overlaps the middle
|
|
15
|
+
* region between the head and recent windows, mirroring
|
|
16
|
+
* `selectLegacy`'s "Uncompressed: emit raw" behavior. Once compression
|
|
17
|
+
* finishes and the chunk transitions to `compressed: true`, the raw
|
|
18
|
+
* emission stops (the chunk is now covered by an L1 summary), so
|
|
19
|
+
* there's no double-counting across the transition.
|
|
20
|
+
*/
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=non-blocking-compile.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"non-blocking-compile.test.d.ts","sourceRoot":"","sources":["../../test/non-blocking-compile.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `compile()`'s non-blocking contract + the uncompressed-chunk
|
|
3
|
+
* fallback that prevents silent message loss.
|
|
4
|
+
*
|
|
5
|
+
* Background: commit `3e42e98` dropped the `await readiness.pendingWork`
|
|
6
|
+
* inside `compile()` to keep turn latency low (compression can take
|
|
7
|
+
* 30+s; awaiting it per-turn was the wrong tradeoff). The side effect
|
|
8
|
+
* was that messages caught in a queued-but-not-yet-compressed chunk
|
|
9
|
+
* would vanish from rendered context in hierarchical mode — no summary
|
|
10
|
+
* existed yet for the chunk, and `selectHierarchical` only emitted raw
|
|
11
|
+
* messages from `recentStart` onward.
|
|
12
|
+
*
|
|
13
|
+
* The fix (these tests pin in place): `selectHierarchical` now emits
|
|
14
|
+
* raw messages for any uncompressed chunk that overlaps the middle
|
|
15
|
+
* region between the head and recent windows, mirroring
|
|
16
|
+
* `selectLegacy`'s "Uncompressed: emit raw" behavior. Once compression
|
|
17
|
+
* finishes and the chunk transitions to `compressed: true`, the raw
|
|
18
|
+
* emission stops (the chunk is now covered by an L1 summary), so
|
|
19
|
+
* there's no double-counting across the transition.
|
|
20
|
+
*/
|
|
21
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
22
|
+
import assert from 'node:assert';
|
|
23
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
24
|
+
import { ContextManager, AutobiographicalStrategy } from '../src/index.js';
|
|
25
|
+
const TEST_STORE_PATH = './test-non-blocking-compile';
|
|
26
|
+
function cleanup() {
|
|
27
|
+
if (existsSync(TEST_STORE_PATH)) {
|
|
28
|
+
rmSync(TEST_STORE_PATH, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function textBlock(text) {
|
|
32
|
+
return [{ type: 'text', text }];
|
|
33
|
+
}
|
|
34
|
+
function compiledHas(compiled, fragment) {
|
|
35
|
+
for (const m of compiled.messages) {
|
|
36
|
+
for (const block of m.content) {
|
|
37
|
+
if (block.type === 'text' && block.text.includes(fragment))
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
describe('AutobiographicalStrategy — non-blocking compile + uncompressed-chunk fallback', () => {
|
|
44
|
+
before(cleanup);
|
|
45
|
+
after(cleanup);
|
|
46
|
+
beforeEach(cleanup);
|
|
47
|
+
it('emits raw messages for uncompressed chunks in the middle region (no silent drop)', async () => {
|
|
48
|
+
// Configure for a small middle region:
|
|
49
|
+
// - No head window (so all messages compete for visibility via the
|
|
50
|
+
// summary path and the recent window).
|
|
51
|
+
// - Small recentWindowTokens so older messages fall out of recent.
|
|
52
|
+
// - Small targetChunkTokens so a single old message ends up in
|
|
53
|
+
// its own chunk.
|
|
54
|
+
// - Hierarchical mode so `selectHierarchical` is the codepath
|
|
55
|
+
// under test (selectLegacy already had this fallback).
|
|
56
|
+
const strategy = new AutobiographicalStrategy({
|
|
57
|
+
headWindowTokens: 0,
|
|
58
|
+
recentWindowTokens: 12,
|
|
59
|
+
targetChunkTokens: 4,
|
|
60
|
+
hierarchical: true,
|
|
61
|
+
});
|
|
62
|
+
const manager = await ContextManager.open({
|
|
63
|
+
path: TEST_STORE_PATH,
|
|
64
|
+
strategy,
|
|
65
|
+
});
|
|
66
|
+
// Three "old" messages whose chunks will be queued but un-ticked,
|
|
67
|
+
// plus a "latest" big enough to dominate the small recent window.
|
|
68
|
+
manager.addMessage('user', textBlock('ANCIENT_MARKER_1'));
|
|
69
|
+
manager.addMessage('user', textBlock('ANCIENT_MARKER_2'));
|
|
70
|
+
manager.addMessage('user', textBlock('ANCIENT_MARKER_3'));
|
|
71
|
+
manager.addMessage('user', textBlock('LATEST ' + 'X'.repeat(80)));
|
|
72
|
+
// Critically: we do NOT call manager.tick(). The chunks for the
|
|
73
|
+
// ancient messages exist in the strategy's compression queue but
|
|
74
|
+
// never run through executeMerge/compressChunk, so they stay
|
|
75
|
+
// `chunk.compressed === false`. This is the exact state where the
|
|
76
|
+
// bug used to silently drop messages.
|
|
77
|
+
const compiled = await manager.compile({
|
|
78
|
+
maxTokens: 100_000,
|
|
79
|
+
reserveForResponse: 0,
|
|
80
|
+
});
|
|
81
|
+
assert.ok(compiledHas(compiled, 'ANCIENT_MARKER_1'), 'ANCIENT_MARKER_1 must appear in compiled output (uncompressed-chunk fallback)');
|
|
82
|
+
assert.ok(compiledHas(compiled, 'ANCIENT_MARKER_2'), 'ANCIENT_MARKER_2 must appear in compiled output');
|
|
83
|
+
assert.ok(compiledHas(compiled, 'ANCIENT_MARKER_3'), 'ANCIENT_MARKER_3 must appear in compiled output');
|
|
84
|
+
// The recent message must of course still be there.
|
|
85
|
+
assert.ok(compiledHas(compiled, 'LATEST'), 'LATEST message must appear in compiled output');
|
|
86
|
+
manager.close();
|
|
87
|
+
});
|
|
88
|
+
it('compile() returns promptly without awaiting pending compression', async () => {
|
|
89
|
+
// Sanity check on the freshness contract change: compile() must
|
|
90
|
+
// not block on `pendingCompression`. We can't easily inject a slow
|
|
91
|
+
// mock LLM here, but we *can* assert that compile() completes
|
|
92
|
+
// before any tick would have time to finish.
|
|
93
|
+
const strategy = new AutobiographicalStrategy({
|
|
94
|
+
headWindowTokens: 0,
|
|
95
|
+
recentWindowTokens: 12,
|
|
96
|
+
targetChunkTokens: 4,
|
|
97
|
+
hierarchical: true,
|
|
98
|
+
});
|
|
99
|
+
const manager = await ContextManager.open({
|
|
100
|
+
path: TEST_STORE_PATH,
|
|
101
|
+
strategy,
|
|
102
|
+
});
|
|
103
|
+
manager.addMessage('user', textBlock('older'));
|
|
104
|
+
manager.addMessage('user', textBlock('latest ' + 'Y'.repeat(80)));
|
|
105
|
+
const t0 = Date.now();
|
|
106
|
+
await manager.compile({ maxTokens: 100_000, reserveForResponse: 0 });
|
|
107
|
+
const elapsed = Date.now() - t0;
|
|
108
|
+
// No LLM, no real awaiting — compile should be effectively instant.
|
|
109
|
+
// 500ms is wildly generous; the actual time is sub-10ms in practice.
|
|
110
|
+
assert.ok(elapsed < 500, `compile() should return promptly; took ${elapsed}ms`);
|
|
111
|
+
manager.close();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
//# sourceMappingURL=non-blocking-compile.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"non-blocking-compile.test.js","sourceRoot":"","sources":["../../test/non-blocking-compile.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for pins / documents / protected ranges (audit gap #3).
|
|
3
|
+
*
|
|
4
|
+
* Pinned messages are excluded from compression and render raw at their
|
|
5
|
+
* original chronological position. Marks survive process restart and follow
|
|
6
|
+
* Chronicle branches (state is persisted as a snapshot under
|
|
7
|
+
* `${ns}/autobio:pins`).
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=pins.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pins.test.d.ts","sourceRoot":"","sources":["../../test/pins.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for pins / documents / protected ranges (audit gap #3).
|
|
3
|
+
*
|
|
4
|
+
* Pinned messages are excluded from compression and render raw at their
|
|
5
|
+
* original chronological position. Marks survive process restart and follow
|
|
6
|
+
* Chronicle branches (state is persisted as a snapshot under
|
|
7
|
+
* `${ns}/autobio:pins`).
|
|
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-pins';
|
|
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 TestableStrategy extends AutobiographicalStrategy {
|
|
23
|
+
seedL1Against(content, firstMsgId, lastMsgId) {
|
|
24
|
+
const entry = {
|
|
25
|
+
id: `L1-${this.nextSummaryIdCounter()}`,
|
|
26
|
+
level: 1,
|
|
27
|
+
content,
|
|
28
|
+
tokens: Math.ceil(content.length / 4),
|
|
29
|
+
sourceLevel: 0,
|
|
30
|
+
sourceIds: [firstMsgId, lastMsgId],
|
|
31
|
+
sourceRange: { first: firstMsgId, last: lastMsgId },
|
|
32
|
+
created: Date.now(),
|
|
33
|
+
};
|
|
34
|
+
this.pushSummary(entry);
|
|
35
|
+
return entry;
|
|
36
|
+
}
|
|
37
|
+
// Expose the protected getter for assertions
|
|
38
|
+
exposeCompressibleIds(store) {
|
|
39
|
+
return this.getCompressibleMessages(store).map((m) => m.id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
describe('AutobiographicalStrategy — pins / documents (gap #3)', () => {
|
|
43
|
+
before(cleanup);
|
|
44
|
+
after(cleanup);
|
|
45
|
+
beforeEach(cleanup);
|
|
46
|
+
it('pinned messages are excluded from the compressible set', async () => {
|
|
47
|
+
const strategy = new TestableStrategy({ headWindowTokens: 0, recentWindowTokens: 5 });
|
|
48
|
+
const manager = await ContextManager.open({
|
|
49
|
+
path: TEST_STORE_PATH,
|
|
50
|
+
strategy,
|
|
51
|
+
});
|
|
52
|
+
const m1 = manager.addMessage('user', textBlock('one'));
|
|
53
|
+
const m2 = manager.addMessage('user', textBlock('two'));
|
|
54
|
+
const m3 = manager.addMessage('user', textBlock('three'));
|
|
55
|
+
manager.addMessage('user', textBlock('latest ' + 'X'.repeat(50)));
|
|
56
|
+
// Without any pins, all old messages are compressible.
|
|
57
|
+
const beforePin = strategy.exposeCompressibleIds(manager.messageStore.createView());
|
|
58
|
+
assert.ok(beforePin.includes(m2), 'm2 should be compressible before pin');
|
|
59
|
+
manager.pinRange(m2, m2);
|
|
60
|
+
const afterPin = strategy.exposeCompressibleIds(manager.messageStore.createView());
|
|
61
|
+
assert.ok(!afterPin.includes(m2), 'm2 must NOT be compressible after pin');
|
|
62
|
+
assert.ok(afterPin.includes(m1), 'm1 still compressible');
|
|
63
|
+
assert.ok(afterPin.includes(m3), 'm3 still compressible');
|
|
64
|
+
manager.close();
|
|
65
|
+
});
|
|
66
|
+
it('renders pinned messages raw at their chronological position', async () => {
|
|
67
|
+
const strategy = new TestableStrategy({ headWindowTokens: 0, recentWindowTokens: 5 });
|
|
68
|
+
const manager = await ContextManager.open({
|
|
69
|
+
path: TEST_STORE_PATH,
|
|
70
|
+
strategy,
|
|
71
|
+
});
|
|
72
|
+
const m1 = manager.addMessage('user', textBlock('alpha'));
|
|
73
|
+
const m2 = manager.addMessage('user', textBlock('PINNED-DOCUMENT-CONTENT'));
|
|
74
|
+
const m3 = manager.addMessage('user', textBlock('gamma'));
|
|
75
|
+
manager.addMessage('user', textBlock('latest ' + 'X'.repeat(50)));
|
|
76
|
+
// Seed summaries that cover m1 and m3 (NOT m2 — m2 is pinned).
|
|
77
|
+
strategy.seedL1Against('summary about alpha', m1, m1);
|
|
78
|
+
strategy.seedL1Against('summary about gamma', m3, m3);
|
|
79
|
+
manager.markDocument(m2, { name: 'big-doc' });
|
|
80
|
+
const compiled = await manager.compile({ maxTokens: 100_000, reserveForResponse: 0 });
|
|
81
|
+
const allTexts = compiled.messages.map(m => m.content.filter(c => c.type === 'text').map(c => c.text).join('|'));
|
|
82
|
+
// The pinned message's full content should be rendered raw, not via a summary.
|
|
83
|
+
const pinIdx = allTexts.findIndex(t => t.includes('PINNED-DOCUMENT-CONTENT'));
|
|
84
|
+
assert.ok(pinIdx >= 0, 'pinned message text should be visible verbatim in output');
|
|
85
|
+
// The two summaries should also be present.
|
|
86
|
+
const alphaIdx = allTexts.findIndex(t => t.includes('summary about alpha'));
|
|
87
|
+
const gammaIdx = allTexts.findIndex(t => t.includes('summary about gamma'));
|
|
88
|
+
assert.ok(alphaIdx >= 0, 'alpha summary should appear');
|
|
89
|
+
assert.ok(gammaIdx >= 0, 'gamma summary should appear');
|
|
90
|
+
// Chronological order: alpha summary < pinned m2 < gamma summary
|
|
91
|
+
assert.ok(alphaIdx < pinIdx, `alpha summary should come before pinned m2 (alpha=${alphaIdx} pin=${pinIdx})`);
|
|
92
|
+
assert.ok(pinIdx < gammaIdx, `pinned m2 should come before gamma summary (pin=${pinIdx} gamma=${gammaIdx})`);
|
|
93
|
+
manager.close();
|
|
94
|
+
});
|
|
95
|
+
it('persists pins across close/reopen', async () => {
|
|
96
|
+
let pinId;
|
|
97
|
+
{
|
|
98
|
+
const strategy = new TestableStrategy({ headWindowTokens: 0, recentWindowTokens: 5 });
|
|
99
|
+
const manager = await ContextManager.open({
|
|
100
|
+
path: TEST_STORE_PATH,
|
|
101
|
+
strategy,
|
|
102
|
+
});
|
|
103
|
+
const m1 = manager.addMessage('user', textBlock('survive me'));
|
|
104
|
+
manager.addMessage('user', textBlock('latest'));
|
|
105
|
+
pinId = manager.pinRange(m1, m1, { name: 'first-test-pin' });
|
|
106
|
+
assert.equal(manager.listPins().length, 1);
|
|
107
|
+
manager.sync();
|
|
108
|
+
manager.close();
|
|
109
|
+
}
|
|
110
|
+
{
|
|
111
|
+
const strategy = new TestableStrategy({ headWindowTokens: 0, recentWindowTokens: 5 });
|
|
112
|
+
const manager = await ContextManager.open({
|
|
113
|
+
path: TEST_STORE_PATH,
|
|
114
|
+
strategy,
|
|
115
|
+
});
|
|
116
|
+
const pins = manager.listPins();
|
|
117
|
+
assert.equal(pins.length, 1, 'pin should reload from chronicle');
|
|
118
|
+
assert.equal(pins[0].id, pinId);
|
|
119
|
+
assert.equal(pins[0].name, 'first-test-pin');
|
|
120
|
+
manager.close();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
it('unpin removes the protection and persists', async () => {
|
|
124
|
+
const strategy = new TestableStrategy({ headWindowTokens: 0, recentWindowTokens: 5 });
|
|
125
|
+
const manager = await ContextManager.open({
|
|
126
|
+
path: TEST_STORE_PATH,
|
|
127
|
+
strategy,
|
|
128
|
+
});
|
|
129
|
+
const m1 = manager.addMessage('user', textBlock('toggle me'));
|
|
130
|
+
manager.addMessage('user', textBlock('latest'));
|
|
131
|
+
const pinId = manager.pinRange(m1, m1);
|
|
132
|
+
assert.equal(manager.listPins().length, 1);
|
|
133
|
+
const removed = manager.unpin(pinId);
|
|
134
|
+
assert.equal(removed, true);
|
|
135
|
+
assert.equal(manager.listPins().length, 0);
|
|
136
|
+
// Re-unpinning is a no-op (returns false)
|
|
137
|
+
assert.equal(manager.unpin(pinId), false);
|
|
138
|
+
manager.close();
|
|
139
|
+
});
|
|
140
|
+
it('multiple pins isolate per Chronicle branch (via fork)', async () => {
|
|
141
|
+
const strategy = new TestableStrategy({ headWindowTokens: 0, recentWindowTokens: 5 });
|
|
142
|
+
const manager = await ContextManager.open({
|
|
143
|
+
path: TEST_STORE_PATH,
|
|
144
|
+
strategy,
|
|
145
|
+
});
|
|
146
|
+
const m1 = manager.addMessage('user', textBlock('shared'));
|
|
147
|
+
manager.addMessage('user', textBlock('latest'));
|
|
148
|
+
const sharedPin = manager.pinRange(m1, m1, { name: 'shared' });
|
|
149
|
+
const mainBranch = manager.currentBranch().name;
|
|
150
|
+
await manager.fork('experimental');
|
|
151
|
+
// Adding a pin only on the experimental branch
|
|
152
|
+
const m2 = manager.addMessage('user', textBlock('only on fork'));
|
|
153
|
+
manager.addMessage('user', textBlock('latest 2'));
|
|
154
|
+
const forkPin = manager.pinRange(m2, m2, { name: 'fork-only' });
|
|
155
|
+
assert.equal(manager.listPins().length, 2, 'fork should have shared + fork-only pin');
|
|
156
|
+
await manager.switchBranch(mainBranch);
|
|
157
|
+
const mainPins = manager.listPins();
|
|
158
|
+
assert.equal(mainPins.length, 1, 'main should only have the shared pin');
|
|
159
|
+
assert.equal(mainPins[0].id, sharedPin);
|
|
160
|
+
assert.ok(!mainPins.some(p => p.id === forkPin), 'main must NOT see the fork-only pin');
|
|
161
|
+
manager.close();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
//# sourceMappingURL=pins.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pins.test.js","sourceRoot":"","sources":["../../test/pins.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=recall-positioning.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall-positioning.test.d.ts","sourceRoot":"","sources":["../../test/recall-positioning.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
|