@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.
Files changed (161) hide show
  1. package/dist/src/adaptive/chunker.d.ts +70 -0
  2. package/dist/src/adaptive/chunker.d.ts.map +1 -0
  3. package/dist/src/adaptive/chunker.js +233 -0
  4. package/dist/src/adaptive/chunker.js.map +1 -0
  5. package/dist/src/adaptive/folding-strategy.d.ts +118 -0
  6. package/dist/src/adaptive/folding-strategy.d.ts.map +1 -0
  7. package/dist/src/adaptive/folding-strategy.js +12 -0
  8. package/dist/src/adaptive/folding-strategy.js.map +1 -0
  9. package/dist/src/adaptive/index.d.ts +17 -0
  10. package/dist/src/adaptive/index.d.ts.map +1 -0
  11. package/dist/src/adaptive/index.js +23 -0
  12. package/dist/src/adaptive/index.js.map +1 -0
  13. package/dist/src/adaptive/picker.d.ts +103 -0
  14. package/dist/src/adaptive/picker.d.ts.map +1 -0
  15. package/dist/src/adaptive/picker.js +357 -0
  16. package/dist/src/adaptive/picker.js.map +1 -0
  17. package/dist/src/adaptive/render.d.ts +39 -0
  18. package/dist/src/adaptive/render.d.ts.map +1 -0
  19. package/dist/src/adaptive/render.js +104 -0
  20. package/dist/src/adaptive/render.js.map +1 -0
  21. package/dist/src/adaptive/strategies/flat-profile.d.ts +19 -0
  22. package/dist/src/adaptive/strategies/flat-profile.d.ts.map +1 -0
  23. package/dist/src/adaptive/strategies/flat-profile.js +86 -0
  24. package/dist/src/adaptive/strategies/flat-profile.js.map +1 -0
  25. package/dist/src/adaptive/strategies/oldest-first.d.ts +19 -0
  26. package/dist/src/adaptive/strategies/oldest-first.d.ts.map +1 -0
  27. package/dist/src/adaptive/strategies/oldest-first.js +39 -0
  28. package/dist/src/adaptive/strategies/oldest-first.js.map +1 -0
  29. package/dist/src/context-manager.d.ts +74 -2
  30. package/dist/src/context-manager.d.ts.map +1 -1
  31. package/dist/src/context-manager.js +150 -10
  32. package/dist/src/context-manager.js.map +1 -1
  33. package/dist/src/index.d.ts +1 -1
  34. package/dist/src/index.d.ts.map +1 -1
  35. package/dist/src/index.js.map +1 -1
  36. package/dist/src/message-store.d.ts +36 -1
  37. package/dist/src/message-store.d.ts.map +1 -1
  38. package/dist/src/message-store.js +134 -8
  39. package/dist/src/message-store.js.map +1 -1
  40. package/dist/src/strategies/autobiographical.d.ts +422 -5
  41. package/dist/src/strategies/autobiographical.d.ts.map +1 -1
  42. package/dist/src/strategies/autobiographical.js +2248 -98
  43. package/dist/src/strategies/autobiographical.js.map +1 -1
  44. package/dist/src/strategies/index.d.ts +1 -1
  45. package/dist/src/strategies/index.d.ts.map +1 -1
  46. package/dist/src/strategies/index.js.map +1 -1
  47. package/dist/src/types/index.d.ts +2 -2
  48. package/dist/src/types/index.d.ts.map +1 -1
  49. package/dist/src/types/index.js +1 -1
  50. package/dist/src/types/index.js.map +1 -1
  51. package/dist/src/types/message.d.ts +38 -0
  52. package/dist/src/types/message.d.ts.map +1 -1
  53. package/dist/src/types/strategy.d.ts +302 -10
  54. package/dist/src/types/strategy.d.ts.map +1 -1
  55. package/dist/src/types/strategy.js +28 -0
  56. package/dist/src/types/strategy.js.map +1 -1
  57. package/dist/test/adaptive/branching.test.d.ts +20 -0
  58. package/dist/test/adaptive/branching.test.d.ts.map +1 -0
  59. package/dist/test/adaptive/branching.test.js +176 -0
  60. package/dist/test/adaptive/branching.test.js.map +1 -0
  61. package/dist/test/adaptive/chunker.test.d.ts +2 -0
  62. package/dist/test/adaptive/chunker.test.d.ts.map +1 -0
  63. package/dist/test/adaptive/chunker.test.js +120 -0
  64. package/dist/test/adaptive/chunker.test.js.map +1 -0
  65. package/dist/test/adaptive/deep-levels.test.d.ts +10 -0
  66. package/dist/test/adaptive/deep-levels.test.d.ts.map +1 -0
  67. package/dist/test/adaptive/deep-levels.test.js +167 -0
  68. package/dist/test/adaptive/deep-levels.test.js.map +1 -0
  69. package/dist/test/adaptive/doc-plus-chat.test.d.ts +10 -0
  70. package/dist/test/adaptive/doc-plus-chat.test.d.ts.map +1 -0
  71. package/dist/test/adaptive/doc-plus-chat.test.js +194 -0
  72. package/dist/test/adaptive/doc-plus-chat.test.js.map +1 -0
  73. package/dist/test/adaptive/hard-fail.test.d.ts +9 -0
  74. package/dist/test/adaptive/hard-fail.test.d.ts.map +1 -0
  75. package/dist/test/adaptive/hard-fail.test.js +154 -0
  76. package/dist/test/adaptive/hard-fail.test.js.map +1 -0
  77. package/dist/test/adaptive/harness.d.ts +109 -0
  78. package/dist/test/adaptive/harness.d.ts.map +1 -0
  79. package/dist/test/adaptive/harness.js +263 -0
  80. package/dist/test/adaptive/harness.js.map +1 -0
  81. package/dist/test/adaptive/ingestion.test.d.ts +13 -0
  82. package/dist/test/adaptive/ingestion.test.d.ts.map +1 -0
  83. package/dist/test/adaptive/ingestion.test.js +306 -0
  84. package/dist/test/adaptive/ingestion.test.js.map +1 -0
  85. package/dist/test/adaptive/integration.test.d.ts +12 -0
  86. package/dist/test/adaptive/integration.test.d.ts.map +1 -0
  87. package/dist/test/adaptive/integration.test.js +247 -0
  88. package/dist/test/adaptive/integration.test.js.map +1 -0
  89. package/dist/test/adaptive/long-chronicle.test.d.ts +13 -0
  90. package/dist/test/adaptive/long-chronicle.test.d.ts.map +1 -0
  91. package/dist/test/adaptive/long-chronicle.test.js +186 -0
  92. package/dist/test/adaptive/long-chronicle.test.js.map +1 -0
  93. package/dist/test/adaptive/persistence.test.d.ts +11 -0
  94. package/dist/test/adaptive/persistence.test.d.ts.map +1 -0
  95. package/dist/test/adaptive/persistence.test.js +200 -0
  96. package/dist/test/adaptive/persistence.test.js.map +1 -0
  97. package/dist/test/adaptive/picker.test.d.ts +2 -0
  98. package/dist/test/adaptive/picker.test.d.ts.map +1 -0
  99. package/dist/test/adaptive/picker.test.js +333 -0
  100. package/dist/test/adaptive/picker.test.js.map +1 -0
  101. package/dist/test/adaptive/render.test.d.ts +2 -0
  102. package/dist/test/adaptive/render.test.d.ts.map +1 -0
  103. package/dist/test/adaptive/render.test.js +109 -0
  104. package/dist/test/adaptive/render.test.js.map +1 -0
  105. package/dist/test/adaptive/shard-immutability.test.d.ts +14 -0
  106. package/dist/test/adaptive/shard-immutability.test.d.ts.map +1 -0
  107. package/dist/test/adaptive/shard-immutability.test.js +184 -0
  108. package/dist/test/adaptive/shard-immutability.test.js.map +1 -0
  109. package/dist/test/adaptive/strategy-integration.test.d.ts +9 -0
  110. package/dist/test/adaptive/strategy-integration.test.d.ts.map +1 -0
  111. package/dist/test/adaptive/strategy-integration.test.js +293 -0
  112. package/dist/test/adaptive/strategy-integration.test.js.map +1 -0
  113. package/dist/test/message-store-sequence.test.d.ts +25 -0
  114. package/dist/test/message-store-sequence.test.d.ts.map +1 -0
  115. package/dist/test/message-store-sequence.test.js +157 -0
  116. package/dist/test/message-store-sequence.test.js.map +1 -0
  117. package/dist/test/non-blocking-compile.test.d.ts +22 -0
  118. package/dist/test/non-blocking-compile.test.d.ts.map +1 -0
  119. package/dist/test/non-blocking-compile.test.js +114 -0
  120. package/dist/test/non-blocking-compile.test.js.map +1 -0
  121. package/dist/test/pins.test.d.ts +10 -0
  122. package/dist/test/pins.test.d.ts.map +1 -0
  123. package/dist/test/pins.test.js +164 -0
  124. package/dist/test/pins.test.js.map +1 -0
  125. package/dist/test/recall-positioning.test.d.ts +16 -0
  126. package/dist/test/recall-positioning.test.d.ts.map +1 -0
  127. package/dist/test/recall-positioning.test.js +181 -0
  128. package/dist/test/recall-positioning.test.js.map +1 -0
  129. package/dist/test/search.test.d.ts +10 -0
  130. package/dist/test/search.test.d.ts.map +1 -0
  131. package/dist/test/search.test.js +141 -0
  132. package/dist/test/search.test.js.map +1 -0
  133. package/dist/test/speculation-cap.test.d.ts +17 -0
  134. package/dist/test/speculation-cap.test.d.ts.map +1 -0
  135. package/dist/test/speculation-cap.test.js +157 -0
  136. package/dist/test/speculation-cap.test.js.map +1 -0
  137. package/dist/test/strategy-persistence.test.d.ts +15 -0
  138. package/dist/test/strategy-persistence.test.d.ts.map +1 -0
  139. package/dist/test/strategy-persistence.test.js +244 -0
  140. package/dist/test/strategy-persistence.test.js.map +1 -0
  141. package/dist/test/tool-pruning.test.d.ts +10 -0
  142. package/dist/test/tool-pruning.test.d.ts.map +1 -0
  143. package/dist/test/tool-pruning.test.js +140 -0
  144. package/dist/test/tool-pruning.test.js.map +1 -0
  145. package/dist/tsconfig.tsbuildinfo +1 -1
  146. package/package.json +1 -1
  147. package/src/adaptive/chunker.ts +323 -0
  148. package/src/adaptive/folding-strategy.ts +129 -0
  149. package/src/adaptive/index.ts +51 -0
  150. package/src/adaptive/picker.ts +442 -0
  151. package/src/adaptive/render.ts +115 -0
  152. package/src/adaptive/strategies/flat-profile.ts +96 -0
  153. package/src/adaptive/strategies/oldest-first.ts +48 -0
  154. package/src/context-manager.ts +174 -10
  155. package/src/index.ts +1 -1
  156. package/src/message-store.ts +147 -8
  157. package/src/strategies/autobiographical.ts +2468 -166
  158. package/src/strategies/index.ts +1 -1
  159. package/src/types/index.ts +14 -1
  160. package/src/types/message.ts +43 -0
  161. 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"}