@animalabs/context-manager 0.1.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 (83) hide show
  1. package/dist/src/blob-manager.d.ts +37 -0
  2. package/dist/src/blob-manager.d.ts.map +1 -0
  3. package/dist/src/blob-manager.js +128 -0
  4. package/dist/src/blob-manager.js.map +1 -0
  5. package/dist/src/context-log.d.ts +99 -0
  6. package/dist/src/context-log.d.ts.map +1 -0
  7. package/dist/src/context-log.js +277 -0
  8. package/dist/src/context-log.js.map +1 -0
  9. package/dist/src/context-manager.d.ts +245 -0
  10. package/dist/src/context-manager.d.ts.map +1 -0
  11. package/dist/src/context-manager.js +553 -0
  12. package/dist/src/context-manager.js.map +1 -0
  13. package/dist/src/index.d.ts +12 -0
  14. package/dist/src/index.d.ts.map +1 -0
  15. package/dist/src/index.js +12 -0
  16. package/dist/src/index.js.map +1 -0
  17. package/dist/src/message-store.d.ts +135 -0
  18. package/dist/src/message-store.d.ts.map +1 -0
  19. package/dist/src/message-store.js +372 -0
  20. package/dist/src/message-store.js.map +1 -0
  21. package/dist/src/strategies/autobiographical.d.ts +199 -0
  22. package/dist/src/strategies/autobiographical.d.ts.map +1 -0
  23. package/dist/src/strategies/autobiographical.js +1122 -0
  24. package/dist/src/strategies/autobiographical.js.map +1 -0
  25. package/dist/src/strategies/index.d.ts +3 -0
  26. package/dist/src/strategies/index.d.ts.map +1 -0
  27. package/dist/src/strategies/index.js +3 -0
  28. package/dist/src/strategies/index.js.map +1 -0
  29. package/dist/src/strategies/knowledge.d.ts +46 -0
  30. package/dist/src/strategies/knowledge.d.ts.map +1 -0
  31. package/dist/src/strategies/knowledge.js +270 -0
  32. package/dist/src/strategies/knowledge.js.map +1 -0
  33. package/dist/src/strategies/passthrough.d.ts +17 -0
  34. package/dist/src/strategies/passthrough.d.ts.map +1 -0
  35. package/dist/src/strategies/passthrough.js +69 -0
  36. package/dist/src/strategies/passthrough.js.map +1 -0
  37. package/dist/src/types/context.d.ts +108 -0
  38. package/dist/src/types/context.d.ts.map +1 -0
  39. package/dist/src/types/context.js +2 -0
  40. package/dist/src/types/context.js.map +1 -0
  41. package/dist/src/types/index.d.ts +5 -0
  42. package/dist/src/types/index.d.ts.map +1 -0
  43. package/dist/src/types/index.js +2 -0
  44. package/dist/src/types/index.js.map +1 -0
  45. package/dist/src/types/message.d.ts +129 -0
  46. package/dist/src/types/message.d.ts.map +1 -0
  47. package/dist/src/types/message.js +2 -0
  48. package/dist/src/types/message.js.map +1 -0
  49. package/dist/src/types/strategy.d.ts +233 -0
  50. package/dist/src/types/strategy.d.ts.map +1 -0
  51. package/dist/src/types/strategy.js +32 -0
  52. package/dist/src/types/strategy.js.map +1 -0
  53. package/dist/test/autobiographical.test.d.ts +2 -0
  54. package/dist/test/autobiographical.test.d.ts.map +1 -0
  55. package/dist/test/autobiographical.test.js +46 -0
  56. package/dist/test/autobiographical.test.js.map +1 -0
  57. package/dist/test/head-window-reset.test.d.ts +17 -0
  58. package/dist/test/head-window-reset.test.d.ts.map +1 -0
  59. package/dist/test/head-window-reset.test.js +342 -0
  60. package/dist/test/head-window-reset.test.js.map +1 -0
  61. package/dist/test/integration.test.d.ts +2 -0
  62. package/dist/test/integration.test.d.ts.map +1 -0
  63. package/dist/test/integration.test.js +1341 -0
  64. package/dist/test/integration.test.js.map +1 -0
  65. package/dist/test/knowledge.test.d.ts +2 -0
  66. package/dist/test/knowledge.test.d.ts.map +1 -0
  67. package/dist/test/knowledge.test.js +617 -0
  68. package/dist/test/knowledge.test.js.map +1 -0
  69. package/dist/tsconfig.tsbuildinfo +1 -0
  70. package/package.json +48 -0
  71. package/src/blob-manager.ts +155 -0
  72. package/src/context-log.ts +342 -0
  73. package/src/context-manager.ts +726 -0
  74. package/src/index.ts +50 -0
  75. package/src/message-store.ts +479 -0
  76. package/src/strategies/autobiographical.ts +1355 -0
  77. package/src/strategies/index.ts +2 -0
  78. package/src/strategies/knowledge.ts +336 -0
  79. package/src/strategies/passthrough.ts +98 -0
  80. package/src/types/context.ts +119 -0
  81. package/src/types/index.ts +42 -0
  82. package/src/types/message.ts +140 -0
  83. package/src/types/strategy.ts +282 -0
@@ -0,0 +1,2 @@
1
+ export { PassthroughStrategy } from './passthrough.js';
2
+ export { AutobiographicalStrategy } from './autobiographical.js';
@@ -0,0 +1,336 @@
1
+ import type {
2
+ StoredMessage,
3
+ MessageStoreView,
4
+ KnowledgeConfig,
5
+ SummaryEntry,
6
+ SummaryLevel,
7
+ PhaseType,
8
+ } from '../types/index.js';
9
+ import { AutobiographicalStrategy, type Chunk } from './autobiographical.js';
10
+
11
+ const DEFAULT_RESEARCH_PREFIXES = ['mcpl:', 'zulip:'];
12
+ const DEFAULT_SUBAGENT_PREFIXES = ['subagent:'];
13
+ const DEFAULT_LESSON_NAMES = ['lessons:create', 'lessons:update'];
14
+
15
+ /**
16
+ * Domain-aware compression strategy for knowledge extraction workflows.
17
+ *
18
+ * Builds on AutobiographicalStrategy's hierarchical pyramid (L1/L2/L3)
19
+ * but changes three things:
20
+ * 1. Semantic chunking — chunks at phase transitions instead of fixed token counts
21
+ * 2. Phase-aware compression — different prompts per phase type
22
+ * 3. Asymmetric budget — caps research, prioritizes synthesis in L1 selection
23
+ *
24
+ * Also adds [LEAD] tracking: unresolved questions/leads are flagged in summaries
25
+ * and preserved across compression levels.
26
+ */
27
+ export class KnowledgeStrategy extends AutobiographicalStrategy {
28
+ readonly name = 'knowledge' as const;
29
+
30
+ private knowledgeConfig: KnowledgeConfig;
31
+
32
+ constructor(config: Partial<KnowledgeConfig> = {}) {
33
+ // Force hierarchical mode
34
+ super({ ...config, hierarchical: true });
35
+ // Merge knowledge-specific fields onto the base config so all KnowledgeConfig
36
+ // fields are actually present (super() only stores AutobiographicalConfig fields).
37
+ this.knowledgeConfig = { ...this.config, ...config } as KnowledgeConfig;
38
+ }
39
+
40
+ // ============================================================================
41
+ // Override: Semantic chunking
42
+ // ============================================================================
43
+
44
+ protected rebuildChunks(store: MessageStoreView): void {
45
+ const messagesToChunk = this.getCompressibleMessages(store);
46
+
47
+ // Preserve existing compressed chunks
48
+ const existingCompressed = new Map<string, Chunk>();
49
+ for (const chunk of this.chunks) {
50
+ if (chunk.compressed) {
51
+ existingCompressed.set(this.chunkKey(chunk), chunk);
52
+ }
53
+ }
54
+
55
+ this.chunks = [];
56
+ this.compressionQueue = [];
57
+
58
+ let currentChunk: StoredMessage[] = [];
59
+ let currentTokens = 0;
60
+ let currentPhase: PhaseType | null = null;
61
+ let lastToolPhase: PhaseType = 'synthesis';
62
+ let chunkFilteredStart = 0;
63
+
64
+ for (let i = 0; i < messagesToChunk.length; i++) {
65
+ const msg = messagesToChunk[i];
66
+ const phase = this.classifyMessage(msg, lastToolPhase);
67
+
68
+ // Track tool phase for tool_result inheritance
69
+ if (this.getToolNames(msg).length > 0) {
70
+ lastToolPhase = phase;
71
+ }
72
+
73
+ let msgTokens = store.estimateTokens(msg);
74
+ if (this.config.attachmentsIgnoreSize) {
75
+ msgTokens = this.estimateTextOnlyTokens(msg);
76
+ }
77
+
78
+ // Semantic chunking uses a minimum of 2 messages (vs parent's 4) because
79
+ // phase transitions are the primary chunk boundary signal. A tool_use + tool_result
80
+ // pair is already a meaningful unit of work worth compressing independently.
81
+ const phaseChanged = currentPhase !== null && phase !== currentPhase;
82
+ const maxTokens = this.getMaxChunkTokens(currentPhase ?? phase);
83
+ const sizeExceeded = currentTokens >= maxTokens;
84
+ const shouldClose = currentChunk.length >= 2 && (phaseChanged || sizeExceeded);
85
+
86
+ if (shouldClose) {
87
+ const chunk = this.createChunk(
88
+ this.chunks.length,
89
+ chunkFilteredStart,
90
+ i,
91
+ currentChunk,
92
+ currentTokens,
93
+ existingCompressed
94
+ );
95
+ chunk.phaseType = currentPhase!;
96
+ this.chunks.push(chunk);
97
+ if (!chunk.compressed) this.compressionQueue.push(chunk.index);
98
+
99
+ currentChunk = [];
100
+ currentTokens = 0;
101
+ chunkFilteredStart = i;
102
+ }
103
+
104
+ currentChunk.push(msg);
105
+ currentTokens += msgTokens;
106
+ currentPhase = phase;
107
+ }
108
+
109
+ // Final remaining chunk (minimum 1 message to avoid silent drops)
110
+ if (currentChunk.length >= 1 && currentPhase) {
111
+ const chunk = this.createChunk(
112
+ this.chunks.length,
113
+ chunkFilteredStart,
114
+ messagesToChunk.length,
115
+ currentChunk,
116
+ currentTokens,
117
+ existingCompressed
118
+ );
119
+ chunk.phaseType = currentPhase;
120
+ this.chunks.push(chunk);
121
+ if (!chunk.compressed) this.compressionQueue.push(chunk.index);
122
+ }
123
+ }
124
+
125
+ // ============================================================================
126
+ // Override: Phase-aware compression prompts
127
+ // ============================================================================
128
+
129
+ protected getCompressionInstruction(chunk: Chunk, targetTokens: number): string {
130
+ const leadSuffix = 'If any leads or open questions were identified but not yet pursued, mark them with [LEAD].';
131
+
132
+ switch (chunk.phaseType) {
133
+ case 'research':
134
+ return (
135
+ `Summarize the research performed. What was searched for, what sources were consulted, ` +
136
+ `and what was found? Note any leads discovered. Be terse — the raw data is not needed, ` +
137
+ `only what was learned. ${leadSuffix} Aim for about ${targetTokens} tokens.`
138
+ );
139
+
140
+ case 'synthesis':
141
+ return (
142
+ `Capture the reasoning and conclusions from this discussion. What connections were drawn? ` +
143
+ `What hypotheses were formed or rejected? What decisions were made about what to ` +
144
+ `investigate next? Preserve the logic chain. ${leadSuffix} Aim for about ${targetTokens} tokens.`
145
+ );
146
+
147
+ case 'lesson':
148
+ return (
149
+ `Record what knowledge was captured. What lessons were created or updated? ` +
150
+ `What was the evidence basis? Note any confidence gaps or unresolved questions. ` +
151
+ `${leadSuffix} Aim for about ${targetTokens} tokens.`
152
+ );
153
+
154
+ case 'subagent':
155
+ return (
156
+ `Summarize the subagent work. What tasks were dispatched, what did each return, ` +
157
+ `and what was the coordinator's synthesis of the results? Collapse dispatch/wait mechanics. ` +
158
+ `${leadSuffix} Aim for about ${targetTokens} tokens.`
159
+ );
160
+
161
+ default:
162
+ return super.getCompressionInstruction(chunk, targetTokens);
163
+ }
164
+ }
165
+
166
+ protected getMergeInstruction(
167
+ targetLevel: SummaryLevel,
168
+ sources: SummaryEntry[],
169
+ targetTokens: number
170
+ ): string {
171
+ // Describe the phase composition of the sources
172
+ const phaseCounts = new Map<string, number>();
173
+ for (const s of sources) {
174
+ const phase = s.phaseType ?? 'unknown';
175
+ phaseCounts.set(phase, (phaseCounts.get(phase) ?? 0) + 1);
176
+ }
177
+ const phaseDesc = [...phaseCounts.entries()]
178
+ .map(([phase, count]) => `${count} ${phase}`)
179
+ .join(', ');
180
+
181
+ const levelLabel = targetLevel === 2 ? 'L1 summaries' : 'L2 summaries';
182
+
183
+ return (
184
+ `Please consolidate these ${sources.length} ${levelLabel} (${phaseDesc}) ` +
185
+ `into a single cohesive memory. ` +
186
+ `Aim for about ${targetTokens} tokens. Write as you would to yourself — this is your ` +
187
+ `autobiography, capturing the arc of what happened. ` +
188
+ `IMPORTANT: Preserve any items marked with [LEAD] — these are unresolved questions ` +
189
+ `that must survive consolidation until explicitly resolved or dropped.`
190
+ );
191
+ }
192
+
193
+ // ============================================================================
194
+ // Override: Asymmetric L1 budget allocation
195
+ // ============================================================================
196
+
197
+ protected selectL1Summaries(
198
+ shownL1: SummaryEntry[],
199
+ budget: number,
200
+ maxTokens: number
201
+ ): { selected: SummaryEntry[]; tokensUsed: number } {
202
+ const effectiveBudget = Math.min(budget, maxTokens);
203
+
204
+ const researchCap = effectiveBudget * (this.knowledgeConfig.researchL1BudgetCap ?? 0.3);
205
+ const synthesisFloor = effectiveBudget * (this.knowledgeConfig.synthesisL1BudgetFloor ?? 0.4);
206
+
207
+ // Partition by phase
208
+ const synthesis: SummaryEntry[] = [];
209
+ const lessons: SummaryEntry[] = [];
210
+ const subagent: SummaryEntry[] = [];
211
+ const research: SummaryEntry[] = [];
212
+ const other: SummaryEntry[] = [];
213
+
214
+ for (const s of shownL1) {
215
+ switch (s.phaseType) {
216
+ case 'synthesis': synthesis.push(s); break;
217
+ case 'lesson': lessons.push(s); break;
218
+ case 'subagent': subagent.push(s); break;
219
+ case 'research': research.push(s); break;
220
+ default: other.push(s); break;
221
+ }
222
+ }
223
+
224
+ const selected: SummaryEntry[] = [];
225
+ let used = 0;
226
+
227
+ // Priority 1: Synthesis — guaranteed floor, can go higher if budget allows
228
+ for (const s of synthesis) {
229
+ if (used + s.tokens > effectiveBudget) break;
230
+ // Keep going past the floor, but cap synthesis to avoid starving other phases
231
+ const synthesisCap = effectiveBudget * (this.knowledgeConfig.synthesisL1BudgetCap ?? 0.7);
232
+ if (used >= synthesisFloor && used + s.tokens > synthesisCap) break;
233
+ selected.push(s);
234
+ used += s.tokens;
235
+ }
236
+
237
+ // Priority 2: Lessons — high value, no cap
238
+ for (const s of lessons) {
239
+ if (used + s.tokens > effectiveBudget) break;
240
+ selected.push(s);
241
+ used += s.tokens;
242
+ }
243
+
244
+ // Priority 3: Subagent — moderate value, no cap
245
+ for (const s of [...subagent, ...other]) {
246
+ if (used + s.tokens > effectiveBudget) break;
247
+ selected.push(s);
248
+ used += s.tokens;
249
+ }
250
+
251
+ // Priority 4: Research — capped
252
+ let researchUsed = 0;
253
+ for (const s of research) {
254
+ if (researchUsed + s.tokens > researchCap) break;
255
+ if (used + s.tokens > effectiveBudget) break;
256
+ selected.push(s);
257
+ researchUsed += s.tokens;
258
+ used += s.tokens;
259
+ }
260
+
261
+ return { selected, tokensUsed: used };
262
+ }
263
+
264
+ // ============================================================================
265
+ // Phase detection
266
+ // ============================================================================
267
+
268
+ /**
269
+ * Classify a single message by the phase it belongs to.
270
+ * Uses tool_use block names for classification.
271
+ * Messages with only tool_result (no tool_use) inherit from the last tool phase.
272
+ */
273
+ private classifyMessage(msg: StoredMessage, lastToolPhase: PhaseType): PhaseType {
274
+ const toolNames = this.getToolNames(msg);
275
+
276
+ if (toolNames.length > 0) {
277
+ return this.classifyByToolNames(toolNames);
278
+ }
279
+
280
+ // tool_result without tool_use → inherit from preceding tool phase
281
+ if (this.hasToolResult(msg)) {
282
+ return lastToolPhase;
283
+ }
284
+
285
+ // Pure dialogue
286
+ return 'synthesis';
287
+ }
288
+
289
+ /**
290
+ * Classify by tool names with priority: lesson > subagent > research > synthesis.
291
+ */
292
+ private classifyByToolNames(toolNames: string[]): PhaseType {
293
+ const lessonNames = this.knowledgeConfig.lessonToolNames ?? DEFAULT_LESSON_NAMES;
294
+ const subagentPrefixes = this.knowledgeConfig.subagentToolPrefixes ?? DEFAULT_SUBAGENT_PREFIXES;
295
+ const researchPrefixes = this.knowledgeConfig.researchToolPrefixes ?? DEFAULT_RESEARCH_PREFIXES;
296
+
297
+ if (toolNames.some(n => lessonNames.includes(n))) return 'lesson';
298
+ if (toolNames.some(n => subagentPrefixes.some(p => n.startsWith(p)))) return 'subagent';
299
+ if (toolNames.some(n => researchPrefixes.some(p => n.startsWith(p)))) return 'research';
300
+
301
+ return 'synthesis';
302
+ }
303
+
304
+ /**
305
+ * Extract tool names from tool_use blocks in a message.
306
+ */
307
+ private getToolNames(msg: StoredMessage): string[] {
308
+ const names: string[] = [];
309
+ for (const block of msg.content) {
310
+ if (block.type === 'tool_use') {
311
+ names.push((block as { type: 'tool_use'; name: string }).name);
312
+ }
313
+ }
314
+ return names;
315
+ }
316
+
317
+ /**
318
+ * Get the maximum chunk token size for a given phase.
319
+ * Research and subagent chunks can grow larger since they compress aggressively.
320
+ */
321
+ private getMaxChunkTokens(phase: PhaseType): number {
322
+ const base = this.config.targetChunkTokens;
323
+ switch (phase) {
324
+ case 'research':
325
+ return this.knowledgeConfig.maxResearchChunkTokens ?? base * 2;
326
+ case 'subagent':
327
+ return this.knowledgeConfig.maxSubagentChunkTokens ?? base * 2;
328
+ case 'synthesis':
329
+ return this.knowledgeConfig.maxSynthesisChunkTokens ?? Math.round(base * 1.5);
330
+ case 'lesson':
331
+ return this.knowledgeConfig.maxLessonChunkTokens ?? base;
332
+ default:
333
+ return base;
334
+ }
335
+ }
336
+ }
@@ -0,0 +1,98 @@
1
+ import type {
2
+ ContextStrategy,
3
+ StrategyContext,
4
+ ReadinessState,
5
+ MessageStoreView,
6
+ ContextLogView,
7
+ TokenBudget,
8
+ ContextEntry,
9
+ StoredMessage,
10
+ } from '../types/index.js';
11
+
12
+ /**
13
+ * Simple passthrough strategy that copies all messages to context.
14
+ * No compression, no background work. Always ready.
15
+ * Good for testing and simple use cases.
16
+ */
17
+ export class PassthroughStrategy implements ContextStrategy {
18
+ readonly name = 'passthrough';
19
+
20
+ checkReadiness(): ReadinessState {
21
+ return { ready: true };
22
+ }
23
+
24
+ async onNewMessage(message: StoredMessage, ctx: StrategyContext): Promise<void> {
25
+ // No-op: context is rebuilt on select()
26
+ }
27
+
28
+ select(
29
+ store: MessageStoreView,
30
+ _log: ContextLogView,
31
+ budget: TokenBudget
32
+ ): ContextEntry[] {
33
+ const messages = store.getAll();
34
+ const entries: ContextEntry[] = [];
35
+ let totalTokens = 0;
36
+ const maxTokens = budget.maxTokens - budget.reserveForResponse;
37
+
38
+ // Add messages until we hit the budget
39
+ for (let i = 0; i < messages.length; i++) {
40
+ const msg = messages[i];
41
+ const tokens = store.estimateTokens(msg);
42
+
43
+ if (totalTokens + tokens > maxTokens) {
44
+ // If we're over budget, start from the end and work backwards
45
+ break;
46
+ }
47
+
48
+ entries.push({
49
+ index: i,
50
+ sourceMessageId: msg.id,
51
+ sourceRelation: 'copy',
52
+ participant: msg.participant,
53
+ content: msg.content,
54
+ });
55
+
56
+ totalTokens += tokens;
57
+ }
58
+
59
+ // If we hit the limit before adding all messages,
60
+ // keep the most recent ones that fit
61
+ if (entries.length < messages.length) {
62
+ return this.selectFromEnd(store, maxTokens);
63
+ }
64
+
65
+ return entries;
66
+ }
67
+
68
+ /**
69
+ * Select messages from the end when we can't fit everything.
70
+ */
71
+ private selectFromEnd(store: MessageStoreView, maxTokens: number): ContextEntry[] {
72
+ const messages = store.getAll();
73
+ const entries: ContextEntry[] = [];
74
+ let totalTokens = 0;
75
+
76
+ // Work backwards from the most recent
77
+ for (let i = messages.length - 1; i >= 0; i--) {
78
+ const msg = messages[i];
79
+ const tokens = store.estimateTokens(msg);
80
+
81
+ if (totalTokens + tokens > maxTokens) {
82
+ break;
83
+ }
84
+
85
+ entries.unshift({
86
+ index: i,
87
+ sourceMessageId: msg.id,
88
+ sourceRelation: 'copy',
89
+ participant: msg.participant,
90
+ content: msg.content,
91
+ });
92
+
93
+ totalTokens += tokens;
94
+ }
95
+
96
+ return entries;
97
+ }
98
+ }
@@ -0,0 +1,119 @@
1
+ import type { ContentBlock, NormalizedMessage } from '@animalabs/membrane';
2
+ import type { MessageId, StoredContentBlock } from './message.js';
3
+
4
+ /**
5
+ * Describes how a context entry relates to its source message.
6
+ * This determines edit propagation behavior.
7
+ */
8
+ export type SourceRelation =
9
+ /** Direct copy - edits MUST propagate */
10
+ | 'copy'
11
+ /** Summary/compression - edits MAY be ignored (stale is acceptable) */
12
+ | 'derived'
13
+ /** Just mentions source - edits DON'T propagate */
14
+ | 'referenced';
15
+
16
+ /**
17
+ * An entry in the context log.
18
+ * The context log is a materialized, editable working set derived from the message store.
19
+ */
20
+ export interface ContextEntry {
21
+ /** Index in the context log */
22
+ index: number;
23
+ /** Source message ID (if derived from message store) */
24
+ sourceMessageId?: MessageId;
25
+ /** How this entry relates to its source */
26
+ sourceRelation?: SourceRelation;
27
+ /** Participant name */
28
+ participant: string;
29
+ /** Materialized content blocks */
30
+ content: ContentBlock[];
31
+ /** For prompt caching (future) */
32
+ cacheMarker?: boolean;
33
+ }
34
+
35
+ /**
36
+ * Internal representation with blob references for storage.
37
+ */
38
+ export interface ContextEntryInternal {
39
+ index: number;
40
+ sourceMessageId?: MessageId;
41
+ sourceRelation?: SourceRelation;
42
+ participant: string;
43
+ content: StoredContentBlock[];
44
+ cacheMarker?: boolean;
45
+ }
46
+
47
+ /**
48
+ * Token budget for context compilation.
49
+ */
50
+ export interface TokenBudget {
51
+ /** Maximum tokens for the context window */
52
+ maxTokens: number;
53
+ /** Reserve this many tokens for model response */
54
+ reserveForResponse: number;
55
+ }
56
+
57
+ /**
58
+ * Information about pending background work.
59
+ */
60
+ export interface PendingWork {
61
+ /** Human-readable description */
62
+ description: string;
63
+ /** Estimated time to completion in ms */
64
+ estimatedMs?: number;
65
+ /** When the work started */
66
+ started: Date;
67
+ }
68
+
69
+ /**
70
+ * An injection into the compiled context.
71
+ * Source-agnostic: may come from MCPL servers, local strategies, or application code.
72
+ */
73
+ export interface ContextInjection {
74
+ /** Server-defined namespace (e.g., "memory", "compliance") */
75
+ namespace: string;
76
+
77
+ /** Where to inject in the message array */
78
+ position: 'system' | 'beforeUser' | 'afterUser';
79
+
80
+ /** Content blocks to inject (multimodal) */
81
+ content: ContentBlock[];
82
+
83
+ /** Arbitrary metadata (passed through, not interpreted) */
84
+ metadata?: Record<string, unknown>;
85
+ }
86
+
87
+ /**
88
+ * Result of context compilation.
89
+ * Separates system-position injections from message-level content.
90
+ */
91
+ export interface CompileResult {
92
+ /** Compiled messages (includes beforeUser/afterUser injections merged in) */
93
+ messages: NormalizedMessage[];
94
+
95
+ /**
96
+ * System-position injections, grouped by namespace.
97
+ * Caller should append these to the system prompt.
98
+ * Separated because the system prompt is outside context-manager's scope.
99
+ */
100
+ systemInjections: ContentBlock[];
101
+ }
102
+
103
+ /**
104
+ * Branch information.
105
+ */
106
+ export interface BranchInfo {
107
+ /** Branch identifier */
108
+ id: string;
109
+ /** Branch name */
110
+ name: string;
111
+ /** Current head sequence */
112
+ head: number;
113
+ /** Parent branch ID */
114
+ parentId?: string;
115
+ /** Sequence at which branch was created */
116
+ branchPoint?: number;
117
+ /** Creation timestamp */
118
+ created: Date;
119
+ }
@@ -0,0 +1,42 @@
1
+ // Message types
2
+ export type {
3
+ MessageId,
4
+ Sequence,
5
+ BranchId,
6
+ MessageMetadata,
7
+ StoredMessage,
8
+ BlobReference,
9
+ StoredContentBlock,
10
+ StoredMessageInternal,
11
+ MessageQuery,
12
+ MessageQueryResult,
13
+ } from './message.js';
14
+
15
+ // Context types
16
+ export type {
17
+ SourceRelation,
18
+ ContextEntry,
19
+ ContextEntryInternal,
20
+ TokenBudget,
21
+ PendingWork,
22
+ BranchInfo,
23
+ ContextInjection,
24
+ CompileResult,
25
+ } from './context.js';
26
+
27
+ // Strategy types
28
+ export type {
29
+ MessageStoreView,
30
+ ContextLogView,
31
+ StrategyContext,
32
+ ReadinessState,
33
+ ContextStrategy,
34
+ AutobiographicalConfig,
35
+ SummaryLevel,
36
+ SummaryEntry,
37
+ PhaseType,
38
+ KnowledgeConfig,
39
+ ResettableStrategy,
40
+ } from './strategy.js';
41
+
42
+ export { DEFAULT_AUTOBIOGRAPHICAL_CONFIG, isResettableStrategy } from './strategy.js';