@animalabs/context-manager 0.4.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 (128) 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 +5 -0
  30. package/dist/src/context-manager.d.ts.map +1 -1
  31. package/dist/src/context-manager.js +22 -0
  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 +217 -2
  41. package/dist/src/strategies/autobiographical.d.ts.map +1 -1
  42. package/dist/src/strategies/autobiographical.js +1479 -37
  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/message.d.ts +38 -0
  48. package/dist/src/types/message.d.ts.map +1 -1
  49. package/dist/src/types/strategy.d.ts +94 -10
  50. package/dist/src/types/strategy.d.ts.map +1 -1
  51. package/dist/src/types/strategy.js +7 -0
  52. package/dist/src/types/strategy.js.map +1 -1
  53. package/dist/test/adaptive/branching.test.d.ts +20 -0
  54. package/dist/test/adaptive/branching.test.d.ts.map +1 -0
  55. package/dist/test/adaptive/branching.test.js +176 -0
  56. package/dist/test/adaptive/branching.test.js.map +1 -0
  57. package/dist/test/adaptive/chunker.test.d.ts +2 -0
  58. package/dist/test/adaptive/chunker.test.d.ts.map +1 -0
  59. package/dist/test/adaptive/chunker.test.js +120 -0
  60. package/dist/test/adaptive/chunker.test.js.map +1 -0
  61. package/dist/test/adaptive/deep-levels.test.d.ts +10 -0
  62. package/dist/test/adaptive/deep-levels.test.d.ts.map +1 -0
  63. package/dist/test/adaptive/deep-levels.test.js +167 -0
  64. package/dist/test/adaptive/deep-levels.test.js.map +1 -0
  65. package/dist/test/adaptive/doc-plus-chat.test.d.ts +10 -0
  66. package/dist/test/adaptive/doc-plus-chat.test.d.ts.map +1 -0
  67. package/dist/test/adaptive/doc-plus-chat.test.js +194 -0
  68. package/dist/test/adaptive/doc-plus-chat.test.js.map +1 -0
  69. package/dist/test/adaptive/hard-fail.test.d.ts +9 -0
  70. package/dist/test/adaptive/hard-fail.test.d.ts.map +1 -0
  71. package/dist/test/adaptive/hard-fail.test.js +154 -0
  72. package/dist/test/adaptive/hard-fail.test.js.map +1 -0
  73. package/dist/test/adaptive/harness.d.ts +109 -0
  74. package/dist/test/adaptive/harness.d.ts.map +1 -0
  75. package/dist/test/adaptive/harness.js +263 -0
  76. package/dist/test/adaptive/harness.js.map +1 -0
  77. package/dist/test/adaptive/ingestion.test.d.ts +13 -0
  78. package/dist/test/adaptive/ingestion.test.d.ts.map +1 -0
  79. package/dist/test/adaptive/ingestion.test.js +306 -0
  80. package/dist/test/adaptive/ingestion.test.js.map +1 -0
  81. package/dist/test/adaptive/integration.test.d.ts +12 -0
  82. package/dist/test/adaptive/integration.test.d.ts.map +1 -0
  83. package/dist/test/adaptive/integration.test.js +247 -0
  84. package/dist/test/adaptive/integration.test.js.map +1 -0
  85. package/dist/test/adaptive/long-chronicle.test.d.ts +13 -0
  86. package/dist/test/adaptive/long-chronicle.test.d.ts.map +1 -0
  87. package/dist/test/adaptive/long-chronicle.test.js +186 -0
  88. package/dist/test/adaptive/long-chronicle.test.js.map +1 -0
  89. package/dist/test/adaptive/persistence.test.d.ts +11 -0
  90. package/dist/test/adaptive/persistence.test.d.ts.map +1 -0
  91. package/dist/test/adaptive/persistence.test.js +200 -0
  92. package/dist/test/adaptive/persistence.test.js.map +1 -0
  93. package/dist/test/adaptive/picker.test.d.ts +2 -0
  94. package/dist/test/adaptive/picker.test.d.ts.map +1 -0
  95. package/dist/test/adaptive/picker.test.js +333 -0
  96. package/dist/test/adaptive/picker.test.js.map +1 -0
  97. package/dist/test/adaptive/render.test.d.ts +2 -0
  98. package/dist/test/adaptive/render.test.d.ts.map +1 -0
  99. package/dist/test/adaptive/render.test.js +109 -0
  100. package/dist/test/adaptive/render.test.js.map +1 -0
  101. package/dist/test/adaptive/shard-immutability.test.d.ts +14 -0
  102. package/dist/test/adaptive/shard-immutability.test.d.ts.map +1 -0
  103. package/dist/test/adaptive/shard-immutability.test.js +184 -0
  104. package/dist/test/adaptive/shard-immutability.test.js.map +1 -0
  105. package/dist/test/adaptive/strategy-integration.test.d.ts +9 -0
  106. package/dist/test/adaptive/strategy-integration.test.d.ts.map +1 -0
  107. package/dist/test/adaptive/strategy-integration.test.js +293 -0
  108. package/dist/test/adaptive/strategy-integration.test.js.map +1 -0
  109. package/dist/test/message-store-sequence.test.d.ts +25 -0
  110. package/dist/test/message-store-sequence.test.d.ts.map +1 -0
  111. package/dist/test/message-store-sequence.test.js +157 -0
  112. package/dist/test/message-store-sequence.test.js.map +1 -0
  113. package/dist/tsconfig.tsbuildinfo +1 -1
  114. package/package.json +1 -1
  115. package/src/adaptive/chunker.ts +323 -0
  116. package/src/adaptive/folding-strategy.ts +129 -0
  117. package/src/adaptive/index.ts +51 -0
  118. package/src/adaptive/picker.ts +442 -0
  119. package/src/adaptive/render.ts +115 -0
  120. package/src/adaptive/strategies/flat-profile.ts +96 -0
  121. package/src/adaptive/strategies/oldest-first.ts +48 -0
  122. package/src/context-manager.ts +32 -0
  123. package/src/index.ts +1 -1
  124. package/src/message-store.ts +147 -8
  125. package/src/strategies/autobiographical.ts +1586 -38
  126. package/src/strategies/index.ts +1 -1
  127. package/src/types/message.ts +43 -0
  128. package/src/types/strategy.ts +109 -10
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Realistic Lena-migration scenario: a chronicle that starts with a large doc,
3
+ * then has many turns of chat about/with that doc.
4
+ *
5
+ * Verifies the doc gets sharded at ingestion, the chat turns coexist with
6
+ * the doc shards, the picker handles both correctly, and the compiled
7
+ * output structure makes sense throughout.
8
+ */
9
+ import { describe, it, before, after, beforeEach } from 'node:test';
10
+ import assert from 'node:assert/strict';
11
+ import { rmSync, existsSync } from 'node:fs';
12
+ import { ContextManager, AutobiographicalStrategy } from '../../src/index.js';
13
+ const TEST_STORE_PATH = './test-adaptive-doc-plus-chat-store';
14
+ function cleanup() {
15
+ if (existsSync(TEST_STORE_PATH)) {
16
+ rmSync(TEST_STORE_PATH, { recursive: true, force: true });
17
+ }
18
+ }
19
+ function makeMockMembrane() {
20
+ let callCount = 0;
21
+ return {
22
+ complete: async () => {
23
+ callCount++;
24
+ return {
25
+ content: [{ type: 'text', text: `[mock summary #${callCount}] short content.` }],
26
+ };
27
+ },
28
+ get callCount() {
29
+ return callCount;
30
+ },
31
+ };
32
+ }
33
+ function compiledTokens(compiled) {
34
+ let total = 0;
35
+ for (const m of compiled.messages) {
36
+ for (const block of m.content) {
37
+ if (block.type === 'text')
38
+ total += Math.ceil(block.text.length / 4);
39
+ }
40
+ }
41
+ return total;
42
+ }
43
+ describe('AutobiographicalStrategy — doc + chat workload (Lena-like)', () => {
44
+ before(() => cleanup());
45
+ after(() => cleanup());
46
+ beforeEach(() => cleanup());
47
+ it('500K-equivalent doc + 50 chat turns: budget honored throughout', async () => {
48
+ const mock = makeMockMembrane();
49
+ const BUDGET = 30000;
50
+ const strategy = new AutobiographicalStrategy({
51
+ adaptiveResolution: true,
52
+ targetChunkTokens: 1000, // ~4K chars per shard
53
+ recentWindowTokens: 2000,
54
+ mergeThreshold: 6,
55
+ compressionSlackRatio: 0.1,
56
+ });
57
+ const manager = await ContextManager.open({
58
+ path: TEST_STORE_PATH,
59
+ strategy,
60
+ membrane: mock,
61
+ });
62
+ // Synthetic ~500K-equivalent doc. Use 50K chars ≈ 12.5K tokens for test
63
+ // speed; chunker treats ~1K tokens per shard. ~12 shards.
64
+ const sections = [];
65
+ for (let s = 0; s < 12; s++) {
66
+ sections.push(`# Section ${s}\n\n` + 'paragraph content with some words. '.repeat(40));
67
+ }
68
+ const doc = sections.join('\n\n');
69
+ manager.addMessage('User', [{ type: 'text', text: doc }]);
70
+ // Verify sharding happened at ingestion
71
+ const allMessages = manager.getAllMessages();
72
+ assert.ok(allMessages.length > 1, `expected doc to shard; got ${allMessages.length} messages`);
73
+ const shardCount = allMessages.length;
74
+ const groupId = allMessages[0].bodyGroupId;
75
+ assert.ok(groupId, 'first shard should have bodyGroupId');
76
+ for (const m of allMessages) {
77
+ assert.equal(m.bodyGroupId, groupId, 'all shards share bodyGroupId');
78
+ }
79
+ while (!manager.isReady()) {
80
+ await manager.tick();
81
+ }
82
+ // First compile after doc — verify budget honored
83
+ const compiledAfterDoc = await manager.compile({ maxTokens: BUDGET, reserveForResponse: 1000 });
84
+ assert.ok(compiledTokens(compiledAfterDoc) <= BUDGET - 1000, `after-doc tokens exceed budget: ${compiledTokens(compiledAfterDoc)}`);
85
+ // Now 50 chat turns about the doc
86
+ for (let i = 0; i < 50; i++) {
87
+ manager.addMessage('User', [{ type: 'text', text: `Question ${i} about section ${i % 12}: ` + 'word '.repeat(20) }]);
88
+ manager.addMessage('Claude', [{ type: 'text', text: `Answer ${i}: ` + 'word '.repeat(25) }]);
89
+ while (!manager.isReady()) {
90
+ await manager.tick();
91
+ }
92
+ const compiled = await manager.compile({ maxTokens: BUDGET, reserveForResponse: 1000 });
93
+ const tokens = compiledTokens(compiled);
94
+ assert.ok(tokens <= BUDGET - 1000, `turn ${i}: ${tokens} tokens exceeds budget ${BUDGET - 1000}`);
95
+ }
96
+ // The compiled output should still reflect the doc somewhere
97
+ const finalCompile = await manager.compile({ maxTokens: BUDGET, reserveForResponse: 1000 });
98
+ const finalText = finalCompile.messages
99
+ .flatMap((m) => m.content)
100
+ .filter((b) => b.type === 'text')
101
+ .map((b) => b.text)
102
+ .join('\n');
103
+ // Either the doc is still raw-rendered, OR a summary referring to it appears
104
+ assert.ok(finalText.includes('Section') || finalText.includes('summary'), 'final compile should reference doc content via either raw or summary');
105
+ manager.close();
106
+ });
107
+ it('agent can lock a critical doc section and it survives folding pressure', async () => {
108
+ const mock = makeMockMembrane();
109
+ const strategy = new AutobiographicalStrategy({
110
+ adaptiveResolution: true,
111
+ targetChunkTokens: 500,
112
+ recentWindowTokens: 500,
113
+ });
114
+ const manager = await ContextManager.open({
115
+ path: TEST_STORE_PATH,
116
+ strategy,
117
+ membrane: mock,
118
+ });
119
+ // Doc with distinct sections — big enough to shard at targetChunkTokens=500
120
+ // (chunker threshold = 2x targetChunkTokens = 1000 tokens; we need ~10 sections
121
+ // × 200 tokens = 2000+ tokens to trigger sharding).
122
+ const sections = Array.from({ length: 10 }, (_, i) => `# Section ${i}\n\nCRITICAL-MARKER-${i} ` + 'distinctive word here. '.repeat(40));
123
+ manager.addMessage('User', [{ type: 'text', text: sections.join('\n\n') }]);
124
+ const shards = manager.getAllMessages();
125
+ assert.ok(shards.length > 1, `doc should shard; got ${shards.length} messages`);
126
+ // Lock shard 3 specifically
127
+ const criticalShard = shards[3];
128
+ strategy.lockChunk(criticalShard.id);
129
+ // Add chat to push pressure
130
+ for (let i = 0; i < 30; i++) {
131
+ manager.addMessage('User', [{ type: 'text', text: `T${i}` }]);
132
+ }
133
+ while (!manager.isReady()) {
134
+ await manager.tick();
135
+ }
136
+ // Tight compile
137
+ const compiled = await manager.compile({ maxTokens: 3500, reserveForResponse: 500 });
138
+ // The locked shard's resolution should remain 0 in strategy.resolutions
139
+ const resolutions = strategy.resolutions;
140
+ const lockedRes = resolutions.get(criticalShard.id) ?? 0;
141
+ assert.equal(lockedRes, 0, `locked shard should remain L0, got L${lockedRes}`);
142
+ // The compiled output should contain the locked shard's content verbatim
143
+ // (or at least the CRITICAL-MARKER-3 substring from its raw text).
144
+ const allText = compiled.messages
145
+ .flatMap((m) => m.content)
146
+ .filter((b) => b.type === 'text')
147
+ .map((b) => b.text)
148
+ .join('\n');
149
+ assert.ok(allText.includes('CRITICAL-MARKER-3'), 'compiled output should contain the locked shard\'s content verbatim');
150
+ manager.close();
151
+ });
152
+ it('doc + many turns: shards in different regions render correctly', async () => {
153
+ const mock = makeMockMembrane();
154
+ const strategy = new AutobiographicalStrategy({
155
+ adaptiveResolution: true,
156
+ targetChunkTokens: 400,
157
+ recentWindowTokens: 800,
158
+ mergeThreshold: 3,
159
+ });
160
+ const manager = await ContextManager.open({
161
+ path: TEST_STORE_PATH,
162
+ strategy,
163
+ membrane: mock,
164
+ });
165
+ // Doc → some shards in middle, some in tail
166
+ const doc = Array.from({ length: 15 }, (_, i) => `Section ${i}: ` + 'word '.repeat(60)).join('\n\n');
167
+ manager.addMessage('User', [{ type: 'text', text: doc }]);
168
+ // Trailing chat so the doc isn't all-tail
169
+ for (let i = 0; i < 8; i++) {
170
+ manager.addMessage('User', [{ type: 'text', text: `Followup ${i}` }]);
171
+ }
172
+ while (!manager.isReady()) {
173
+ await manager.tick();
174
+ }
175
+ const compiled = await manager.compile({ maxTokens: 5000, reserveForResponse: 500 });
176
+ // The compile should have produced output containing both doc content
177
+ // (some sections) and chat content (followups). The doc should be a
178
+ // combined entry (or entries) representing its shards.
179
+ const userMessages = compiled.messages.filter((m) => m.participant.toLowerCase() === 'user');
180
+ assert.ok(userMessages.length >= 2, 'should have multiple user messages (doc + chat)');
181
+ // Find a user message containing "Section" (from the doc)
182
+ const docEntry = userMessages.find((m) => m.content
183
+ .filter((b) => b.type === 'text')
184
+ .some((b) => b.text.includes('Section ')));
185
+ assert.ok(docEntry, 'should have a user entry with doc section content');
186
+ // Find a user message containing "Followup" (from chat)
187
+ const chatEntry = userMessages.find((m) => m.content
188
+ .filter((b) => b.type === 'text')
189
+ .some((b) => b.text.includes('Followup')));
190
+ assert.ok(chatEntry, 'should have a user entry with chat followup content');
191
+ manager.close();
192
+ });
193
+ });
194
+ //# sourceMappingURL=doc-plus-chat.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doc-plus-chat.test.js","sourceRoot":"","sources":["../../../test/adaptive/doc-plus-chat.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Tests that selectAdaptive throws OverBudgetError when the picker has
3
+ * folded everything it can but the result still exceeds the hard budget.
4
+ *
5
+ * The strategy's philosophy: surface the overage rather than silently
6
+ * dropping entries. The host decides how to recover.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=hard-fail.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hard-fail.test.d.ts","sourceRoot":"","sources":["../../../test/adaptive/hard-fail.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Tests that selectAdaptive throws OverBudgetError when the picker has
3
+ * folded everything it can but the result still exceeds the hard budget.
4
+ *
5
+ * The strategy's philosophy: surface the overage rather than silently
6
+ * dropping entries. The host decides how to recover.
7
+ */
8
+ import { describe, it, before, after, beforeEach } from 'node:test';
9
+ import assert from 'node:assert/strict';
10
+ import { rmSync, existsSync } from 'node:fs';
11
+ import { ContextManager, AutobiographicalStrategy } from '../../src/index.js';
12
+ import { OverBudgetError } from '../../src/adaptive/index.js';
13
+ const TEST_STORE_PATH = './test-adaptive-hard-fail-store';
14
+ function cleanup() {
15
+ if (existsSync(TEST_STORE_PATH)) {
16
+ rmSync(TEST_STORE_PATH, { recursive: true, force: true });
17
+ }
18
+ }
19
+ function makeMockMembrane() {
20
+ let callCount = 0;
21
+ return {
22
+ complete: async () => {
23
+ callCount++;
24
+ return {
25
+ content: [{ type: 'text', text: `[mock summary #${callCount}]` }],
26
+ };
27
+ },
28
+ get callCount() {
29
+ return callCount;
30
+ },
31
+ };
32
+ }
33
+ describe('AutobiographicalStrategy — adaptive OverBudgetError', () => {
34
+ before(() => cleanup());
35
+ after(() => cleanup());
36
+ beforeEach(() => cleanup());
37
+ it('throws OverBudgetError when tail alone exceeds budget', async () => {
38
+ const mock = makeMockMembrane();
39
+ const strategy = new AutobiographicalStrategy({
40
+ adaptiveResolution: true,
41
+ targetChunkTokens: 200,
42
+ // Large tail window: everything we add will be tail-resident, NOT
43
+ // foldable. The picker has nothing to do with it.
44
+ recentWindowTokens: 100_000,
45
+ });
46
+ const manager = await ContextManager.open({
47
+ path: TEST_STORE_PATH,
48
+ strategy,
49
+ membrane: mock,
50
+ });
51
+ // Add enough content that the tail alone is huge.
52
+ for (let i = 0; i < 30; i++) {
53
+ manager.addMessage('User', [{ type: 'text', text: `Msg ${i}. ` + 'word '.repeat(80) }]);
54
+ }
55
+ while (!manager.isReady()) {
56
+ await manager.tick();
57
+ }
58
+ // Budget so tight that even the tail can't fit. Picker has nothing it
59
+ // can fold (tail is treated as pinned).
60
+ await assert.rejects(() => manager.compile({ maxTokens: 500, reserveForResponse: 100 }), (err) => {
61
+ assert.ok(err instanceof OverBudgetError, `expected OverBudgetError, got ${err?.constructor?.name}`);
62
+ const e = err;
63
+ assert.ok(e.actual > e.budget, `actual ${e.actual} should exceed budget ${e.budget}`);
64
+ assert.ok(e.diagnostics.tailTokens > 0, 'diagnostics should include tail tokens');
65
+ return true;
66
+ });
67
+ manager.close();
68
+ });
69
+ it('does NOT throw when picker can fold its way under budget', async () => {
70
+ const mock = makeMockMembrane();
71
+ const strategy = new AutobiographicalStrategy({
72
+ adaptiveResolution: true,
73
+ targetChunkTokens: 100,
74
+ recentWindowTokens: 200,
75
+ });
76
+ const manager = await ContextManager.open({
77
+ path: TEST_STORE_PATH,
78
+ strategy,
79
+ membrane: mock,
80
+ });
81
+ for (let i = 0; i < 30; i++) {
82
+ manager.addMessage('User', [{ type: 'text', text: `Msg ${i}. ` + 'word '.repeat(40) }]);
83
+ }
84
+ while (!manager.isReady()) {
85
+ await manager.tick();
86
+ }
87
+ // Tight but reachable budget — picker should fold middle content to fit.
88
+ const compiled = await manager.compile({ maxTokens: 3000, reserveForResponse: 200 });
89
+ assert.ok(compiled.messages.length > 0);
90
+ manager.close();
91
+ });
92
+ it('does NOT throw under generous budget', async () => {
93
+ const mock = makeMockMembrane();
94
+ const strategy = new AutobiographicalStrategy({
95
+ adaptiveResolution: true,
96
+ targetChunkTokens: 100,
97
+ recentWindowTokens: 200,
98
+ });
99
+ const manager = await ContextManager.open({
100
+ path: TEST_STORE_PATH,
101
+ strategy,
102
+ membrane: mock,
103
+ });
104
+ for (let i = 0; i < 10; i++) {
105
+ manager.addMessage('User', [{ type: 'text', text: `Msg ${i}` }]);
106
+ }
107
+ const compiled = await manager.compile({ maxTokens: 100_000, reserveForResponse: 200 });
108
+ assert.ok(compiled.messages.length > 0);
109
+ manager.close();
110
+ });
111
+ it('error diagnostics include useful state info', async () => {
112
+ const mock = makeMockMembrane();
113
+ const strategy = new AutobiographicalStrategy({
114
+ adaptiveResolution: true,
115
+ targetChunkTokens: 200,
116
+ recentWindowTokens: 100_000,
117
+ });
118
+ const manager = await ContextManager.open({
119
+ path: TEST_STORE_PATH,
120
+ strategy,
121
+ membrane: mock,
122
+ });
123
+ for (let i = 0; i < 20; i++) {
124
+ manager.addMessage('User', [{ type: 'text', text: `Msg ${i}. ` + 'word '.repeat(80) }]);
125
+ }
126
+ while (!manager.isReady()) {
127
+ await manager.tick();
128
+ }
129
+ let caught = null;
130
+ try {
131
+ await manager.compile({ maxTokens: 400, reserveForResponse: 100 });
132
+ }
133
+ catch (err) {
134
+ if (err instanceof OverBudgetError)
135
+ caught = err;
136
+ }
137
+ assert.ok(caught, 'should have caught OverBudgetError');
138
+ if (!caught) {
139
+ manager.close();
140
+ return;
141
+ }
142
+ assert.equal(typeof caught.budget, 'number');
143
+ assert.equal(typeof caught.actual, 'number');
144
+ assert.equal(typeof caught.diagnostics.headTokens, 'number');
145
+ assert.equal(typeof caught.diagnostics.tailTokens, 'number');
146
+ assert.equal(typeof caught.diagnostics.middleTokens, 'number');
147
+ assert.equal(typeof caught.diagnostics.deepestLevel, 'number');
148
+ // The error message should be informative
149
+ assert.ok(caught.message.includes('exhausted'));
150
+ assert.ok(caught.message.includes('hard budget'));
151
+ manager.close();
152
+ });
153
+ });
154
+ //# sourceMappingURL=hard-fail.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hard-fail.test.js","sourceRoot":"","sources":["../../../test/adaptive/hard-fail.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Synthetic test harness for the adaptive-resolution picker.
3
+ *
4
+ * Builds a minimal in-memory chronicle simulator with:
5
+ * - Chunks (PickerChunk shape) with deterministic token counts.
6
+ * - A mocked LLM that produces deterministic "summaries" by hashing input.
7
+ * - A summary tree that grows as the LLM is called.
8
+ * - A function to run the picker against a built state.
9
+ *
10
+ * The mock LLM doesn't actually call any API — it constructs fake summary
11
+ * content so we can validate picker logic, fold semantics, and cache stickiness
12
+ * without external dependencies.
13
+ */
14
+ import type { SummaryEntry } from '../../src/types/strategy.js';
15
+ import type { PickerChunk } from '../../src/adaptive/picker.js';
16
+ import type { ChunkId, SummaryId } from '../../src/adaptive/folding-strategy.js';
17
+ /**
18
+ * Default tokens per recall pair, regardless of source level.
19
+ * The design specifies that L_k summaries target a constant size (~2K tokens
20
+ * by convention) so each level's recall pair has predictable footprint.
21
+ */
22
+ export declare const DEFAULT_RECALL_PAIR_TOKENS = 200;
23
+ /** Default group size: how many L_k items combine into one L_{k+1}. */
24
+ export declare const DEFAULT_MERGE_THRESHOLD = 6;
25
+ export interface MockChronicleOptions {
26
+ /** Tokens for each recall pair. */
27
+ recallPairTokens?: number;
28
+ /** L_k items per L_{k+1} parent. */
29
+ mergeThreshold?: number;
30
+ }
31
+ /**
32
+ * Mutable in-memory chronicle. Mirrors what the real strategy would manage
33
+ * but trimmed to just what the picker needs.
34
+ */
35
+ export declare class MockChronicle {
36
+ readonly chunks: PickerChunk[];
37
+ readonly summaries: Map<string, SummaryEntry>;
38
+ readonly recallPairTokens: Map<string, number>;
39
+ private summaryCounter;
40
+ readonly mergeThreshold: number;
41
+ readonly defaultRecallPairTokens: number;
42
+ /** Mock LLM call count, for telemetry assertions. */
43
+ llmCalls: number;
44
+ constructor(opts?: MockChronicleOptions);
45
+ /**
46
+ * Add a chunk to the chronicle (e.g., a chat message or a doc shard).
47
+ *
48
+ * @returns the created PickerChunk
49
+ */
50
+ addChunk(opts: {
51
+ id: ChunkId;
52
+ rawTokens: number;
53
+ bodyGroupId?: string;
54
+ pinned?: boolean;
55
+ lockedByAgent?: boolean;
56
+ }): PickerChunk;
57
+ /**
58
+ * Produce an L1 summary for a range of chunks. Mocks the LLM call.
59
+ * Idempotent: the same chunk range produces the same summary id.
60
+ *
61
+ * @returns the produced SummaryEntry (or the existing one if already produced)
62
+ */
63
+ produceL1(chunkIds: ChunkId[]): SummaryEntry;
64
+ /**
65
+ * Produce an L_{k+1} summary from N L_k summaries.
66
+ */
67
+ produceUpper(level: number, sourceSummaryIds: SummaryId[]): SummaryEntry;
68
+ /**
69
+ * Speculative pre-producer: when N siblings exist sharing the same potential
70
+ * parent at level k+1, eagerly produce L_{k+1}. Bottom-up, idempotent.
71
+ * Mirrors the design's background pre-producer (§7 #2 settled decisions).
72
+ */
73
+ preProduceUpperLevels(): void;
74
+ /**
75
+ * Build a head/tail bookkeeping from chunk indices.
76
+ */
77
+ computeHeadTail(opts: {
78
+ headTokens: number;
79
+ tailTokens: number;
80
+ }): {
81
+ headTokens: number;
82
+ tailTokens: number;
83
+ headChunkIds: Set<ChunkId>;
84
+ tailChunkIds: Set<ChunkId>;
85
+ };
86
+ /**
87
+ * Apply a snapshot of resolutions back onto the chunks (used after running
88
+ * the picker to commit the new state for subsequent turns).
89
+ */
90
+ applyResolutions(resolutions: ReadonlyMap<ChunkId, number>): void;
91
+ private linkChunksToL1;
92
+ private allocSummaryId;
93
+ private firstChunkSeq;
94
+ private sumTokens;
95
+ }
96
+ /**
97
+ * Convenience: build N chunks with uniform token size, then auto-produce
98
+ * the full summary chain (L1, L2, …) bottom-up via the mergeThreshold rule.
99
+ *
100
+ * Returns the populated MockChronicle.
101
+ */
102
+ export declare function buildChronicleWithChain(opts: {
103
+ chunkCount: number;
104
+ tokensPerChunk: number;
105
+ mergeThreshold?: number;
106
+ recallPairTokens?: number;
107
+ produceChain?: boolean;
108
+ }): MockChronicle;
109
+ //# sourceMappingURL=harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../../test/adaptive/harness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAEjF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,uEAAuE;AACvE,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,MAAM,WAAW,oBAAoB;IACnC,mCAAmC;IACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oCAAoC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,qBAAa,aAAa;IACxB,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,CAAM;IACpC,QAAQ,CAAC,SAAS,4BAAsC;IACxD,QAAQ,CAAC,gBAAgB,sBAAgC;IACzD,OAAO,CAAC,cAAc,CAA6B;IAEnD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IAEzC,qDAAqD;IACrD,QAAQ,SAAK;gBAED,IAAI,GAAE,oBAAyB;IAK3C;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE;QACb,EAAE,EAAE,OAAO,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,aAAa,CAAC,EAAE,OAAO,CAAC;KACzB,GAAG,WAAW;IAcf;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,YAAY;IA2B5C;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,GAAG,YAAY;IA2CxE;;;;OAIG;IACH,qBAAqB,IAAI,IAAI;IA8B7B;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG;QACF,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3B,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;KAC5B;IA4BD;;;OAGG;IACH,gBAAgB,CAAC,WAAW,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,IAAI;IASjE,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,SAAS;CAKlB;AAMD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GAAG,aAAa,CAmBhB"}