zouroboros-core 2.0.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.
@@ -0,0 +1,428 @@
1
+ /**
2
+ * ECC-004: Instincts — Pattern Auto-Extraction
3
+ *
4
+ * Automatic extraction of behavioral patterns from sessions.
5
+ * Detects recurring patterns, scores confidence, and extracts
6
+ * hot-loadable instinct files for future sessions.
7
+ * Persists to disk (JSON file) matching selfheal persistence pattern.
8
+ */
9
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
10
+ import { join } from 'path';
11
+ const CONFIDENCE_THRESHOLD = 0.6;
12
+ const MIN_EVIDENCE_FOR_ACTIVE = 3;
13
+ export class InstinctEngine {
14
+ instincts = new Map();
15
+ evidence = new Map();
16
+ lastFired = new Map();
17
+ dataFile = null;
18
+ hooks = null;
19
+ pendingObservations = [];
20
+ constructor(dataDir) {
21
+ if (dataDir) {
22
+ mkdirSync(dataDir, { recursive: true });
23
+ this.dataFile = join(dataDir, 'instincts.json');
24
+ this.load();
25
+ }
26
+ }
27
+ wireHooks(hooks) {
28
+ this.hooks = hooks;
29
+ // Auto-extract from task failures
30
+ hooks.on('task.fail', (payload) => {
31
+ const context = String(payload.data.error || payload.data.detail || '');
32
+ if (context.length > 0) {
33
+ this.recordObservation({
34
+ context,
35
+ outcome: String(payload.data.resolution || 'task failed'),
36
+ timestamp: payload.timestamp,
37
+ sessionId: String(payload.data.sessionId || ''),
38
+ });
39
+ }
40
+ }, { priority: 50, description: 'Instinct auto-extraction from task failures' });
41
+ // Auto-extract from error recovery
42
+ hooks.on('error.recovery', (payload) => {
43
+ const context = String(payload.data.error || '');
44
+ const outcome = String(payload.data.recovery || 'recovered');
45
+ if (context.length > 0) {
46
+ this.recordObservation({ context, outcome, timestamp: payload.timestamp });
47
+ }
48
+ }, { priority: 50, description: 'Instinct auto-extraction from error recovery' });
49
+ }
50
+ /** Record a single observation for later batch extraction */
51
+ recordObservation(obs) {
52
+ this.pendingObservations.push(obs);
53
+ // Auto-extract when enough observations accumulate
54
+ if (this.pendingObservations.length >= 5) {
55
+ this.extract(this.pendingObservations.splice(0));
56
+ }
57
+ }
58
+ register(instinct) {
59
+ this.instincts.set(instinct.id, instinct);
60
+ if (!this.evidence.has(instinct.id)) {
61
+ this.evidence.set(instinct.id, []);
62
+ }
63
+ this.save();
64
+ }
65
+ get(id) {
66
+ return this.instincts.get(id) || null;
67
+ }
68
+ list(filter) {
69
+ let results = [...this.instincts.values()];
70
+ if (filter?.status) {
71
+ results = results.filter(i => i.status === filter.status);
72
+ }
73
+ if (filter?.minConfidence !== undefined) {
74
+ results = results.filter(i => i.confidence >= filter.minConfidence);
75
+ }
76
+ if (filter?.tag) {
77
+ results = results.filter(i => i.tags.includes(filter.tag));
78
+ }
79
+ return results.sort((a, b) => b.confidence - a.confidence);
80
+ }
81
+ addEvidence(instinctId, ev) {
82
+ const instinct = this.instincts.get(instinctId);
83
+ if (!instinct)
84
+ return false;
85
+ const evidenceList = this.evidence.get(instinctId) || [];
86
+ evidenceList.push(ev);
87
+ // Keep last 100 evidence items
88
+ if (evidenceList.length > 100) {
89
+ evidenceList.splice(0, evidenceList.length - 100);
90
+ }
91
+ this.evidence.set(instinctId, evidenceList);
92
+ // Update instinct
93
+ instinct.evidenceCount = evidenceList.length;
94
+ instinct.lastSeen = ev.timestamp;
95
+ instinct.pattern.frequency = evidenceList.length;
96
+ // Recalculate confidence
97
+ instinct.confidence = this.calculateConfidence(evidenceList);
98
+ // Auto-promote to active if enough evidence
99
+ if (instinct.status === 'candidate' &&
100
+ instinct.confidence >= CONFIDENCE_THRESHOLD &&
101
+ instinct.evidenceCount >= MIN_EVIDENCE_FOR_ACTIVE) {
102
+ instinct.status = 'active';
103
+ }
104
+ this.save();
105
+ return true;
106
+ }
107
+ /** Record a negative match — the instinct fired but was wrong */
108
+ rejectMatch(instinctId) {
109
+ const instinct = this.instincts.get(instinctId);
110
+ if (!instinct)
111
+ return false;
112
+ this.addEvidence(instinctId, {
113
+ timestamp: new Date().toISOString(),
114
+ context: 'rejected by user',
115
+ matchStrength: -0.5,
116
+ });
117
+ // Auto-suspend if confidence drops too low
118
+ if (instinct.confidence < 0.3 && instinct.status === 'active') {
119
+ instinct.status = 'suspended';
120
+ }
121
+ this.save();
122
+ return true;
123
+ }
124
+ match(context, event) {
125
+ const matches = [];
126
+ for (const instinct of this.instincts.values()) {
127
+ if (instinct.status !== 'active')
128
+ continue;
129
+ // Check cooldown
130
+ const lastFired = this.lastFired.get(instinct.id) || 0;
131
+ if (Date.now() - lastFired < instinct.trigger.cooldownMs)
132
+ continue;
133
+ // Check event match
134
+ if (event && instinct.trigger.event !== '*' && instinct.trigger.event !== event)
135
+ continue;
136
+ // Check pattern signature match
137
+ const matchStrength = this.computeMatch(instinct, context);
138
+ if (matchStrength > 0.3) {
139
+ const evidence = this.evidence.get(instinct.id) || [];
140
+ matches.push({
141
+ instinct,
142
+ confidence: matchStrength * instinct.confidence,
143
+ evidence: evidence.slice(-3),
144
+ });
145
+ }
146
+ }
147
+ return matches.sort((a, b) => b.confidence - a.confidence);
148
+ }
149
+ fire(instinctId) {
150
+ const instinct = this.instincts.get(instinctId);
151
+ if (!instinct || instinct.status !== 'active')
152
+ return false;
153
+ this.lastFired.set(instinctId, Date.now());
154
+ // Emit hook event
155
+ if (this.hooks) {
156
+ this.hooks.emit('instinct.fired', {
157
+ instinctId,
158
+ name: instinct.name,
159
+ confidence: instinct.confidence,
160
+ resolution: instinct.resolution,
161
+ }, 'instinct-engine').catch(() => { });
162
+ }
163
+ return true;
164
+ }
165
+ extract(observations) {
166
+ const result = {
167
+ patternsDetected: 0,
168
+ instinctsCreated: 0,
169
+ instinctsUpdated: 0,
170
+ details: [],
171
+ };
172
+ // Group by context similarity using Jaccard index
173
+ const groups = this.groupByContextSimilarity(observations);
174
+ result.patternsDetected = groups.length;
175
+ for (const group of groups) {
176
+ if (group.items.length < 2) {
177
+ result.details.push({ id: '', action: 'skipped', reason: 'insufficient observations' });
178
+ continue;
179
+ }
180
+ const signature = this.generateSignature(group.items);
181
+ const existingId = this.findBySignature(signature);
182
+ if (existingId) {
183
+ for (const item of group.items) {
184
+ this.addEvidence(existingId, {
185
+ timestamp: item.timestamp,
186
+ sessionId: item.sessionId,
187
+ context: item.context.slice(0, 200),
188
+ matchStrength: 0.8,
189
+ });
190
+ }
191
+ result.instinctsUpdated++;
192
+ result.details.push({ id: existingId, action: 'updated' });
193
+ }
194
+ else {
195
+ const id = `instinct-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
196
+ const instinct = {
197
+ id,
198
+ name: `Auto: ${signature.slice(0, 40)}`,
199
+ description: `Pattern detected from ${group.items.length} observations`,
200
+ confidence: group.items.length >= MIN_EVIDENCE_FOR_ACTIVE ? 0.7 : 0.4,
201
+ pattern: {
202
+ type: 'custom',
203
+ signature,
204
+ frequency: group.items.length,
205
+ windowSize: group.items.length,
206
+ },
207
+ trigger: {
208
+ event: '*',
209
+ condition: signature,
210
+ cooldownMs: 60000,
211
+ },
212
+ resolution: group.items[0].outcome,
213
+ evidenceCount: group.items.length,
214
+ lastSeen: group.items[group.items.length - 1].timestamp,
215
+ createdAt: new Date().toISOString(),
216
+ status: group.items.length >= MIN_EVIDENCE_FOR_ACTIVE ? 'active' : 'candidate',
217
+ tags: ['auto-extracted'],
218
+ };
219
+ this.instincts.set(id, instinct);
220
+ this.evidence.set(id, []);
221
+ for (const item of group.items) {
222
+ this.addEvidence(id, {
223
+ timestamp: item.timestamp,
224
+ sessionId: item.sessionId,
225
+ context: item.context.slice(0, 200),
226
+ matchStrength: 0.8,
227
+ });
228
+ }
229
+ result.instinctsCreated++;
230
+ result.details.push({ id, action: 'created' });
231
+ }
232
+ }
233
+ this.save();
234
+ return result;
235
+ }
236
+ suspend(id) {
237
+ const instinct = this.instincts.get(id);
238
+ if (!instinct)
239
+ return false;
240
+ instinct.status = 'suspended';
241
+ this.save();
242
+ return true;
243
+ }
244
+ activate(id) {
245
+ const instinct = this.instincts.get(id);
246
+ if (!instinct)
247
+ return false;
248
+ instinct.status = 'active';
249
+ this.save();
250
+ return true;
251
+ }
252
+ retire(id) {
253
+ const instinct = this.instincts.get(id);
254
+ if (!instinct)
255
+ return false;
256
+ instinct.status = 'retired';
257
+ this.save();
258
+ return true;
259
+ }
260
+ remove(id) {
261
+ this.evidence.delete(id);
262
+ this.lastFired.delete(id);
263
+ const deleted = this.instincts.delete(id);
264
+ if (deleted)
265
+ this.save();
266
+ return deleted;
267
+ }
268
+ getStats() {
269
+ const all = [...this.instincts.values()];
270
+ const byStatus = { candidate: 0, active: 0, suspended: 0, retired: 0 };
271
+ let totalConfidence = 0;
272
+ let totalEvidence = 0;
273
+ for (const inst of all) {
274
+ byStatus[inst.status] = (byStatus[inst.status] || 0) + 1;
275
+ totalConfidence += inst.confidence;
276
+ totalEvidence += inst.evidenceCount;
277
+ }
278
+ return {
279
+ total: all.length,
280
+ byStatus: byStatus,
281
+ avgConfidence: all.length > 0 ? totalConfidence / all.length : 0,
282
+ totalEvidence,
283
+ };
284
+ }
285
+ exportInstinct(id) {
286
+ const instinct = this.instincts.get(id);
287
+ if (!instinct)
288
+ return null;
289
+ return {
290
+ ...instinct,
291
+ evidence: this.evidence.get(id) || [],
292
+ };
293
+ }
294
+ importInstinct(data) {
295
+ const { evidence: evidenceData, ...instinct } = data;
296
+ this.instincts.set(instinct.id, instinct);
297
+ if (evidenceData) {
298
+ this.evidence.set(instinct.id, evidenceData);
299
+ }
300
+ if (!this.evidence.has(instinct.id)) {
301
+ this.evidence.set(instinct.id, []);
302
+ }
303
+ this.save();
304
+ }
305
+ calculateConfidence(evidenceList) {
306
+ if (evidenceList.length === 0)
307
+ return 0;
308
+ const now = Date.now();
309
+ let weightedSum = 0;
310
+ let totalWeight = 0;
311
+ for (const ev of evidenceList) {
312
+ const age = now - new Date(ev.timestamp).getTime();
313
+ const recencyWeight = Math.exp(-age / (7 * 24 * 3600 * 1000)); // 7-day half-life
314
+ const weight = recencyWeight + 0.1;
315
+ weightedSum += ev.matchStrength * weight;
316
+ totalWeight += weight;
317
+ }
318
+ const avgStrength = weightedSum / totalWeight;
319
+ const countFactor = Math.min(evidenceList.length / 10, 1);
320
+ return Math.max(Math.min(avgStrength * (0.5 + 0.5 * countFactor), 1.0), 0);
321
+ }
322
+ computeMatch(instinct, context) {
323
+ const sigWords = instinct.pattern.signature.toLowerCase().split(/\s+/).filter(w => w.length >= 3);
324
+ const contextLower = context.toLowerCase();
325
+ if (sigWords.length === 0)
326
+ return 0;
327
+ // Use substring matching for better accuracy
328
+ let matches = 0;
329
+ for (const word of sigWords) {
330
+ if (contextLower.includes(word))
331
+ matches++;
332
+ }
333
+ return matches / sigWords.length;
334
+ }
335
+ /**
336
+ * Group observations by context similarity using Jaccard index on word sets.
337
+ */
338
+ groupByContextSimilarity(observations) {
339
+ if (observations.length === 0)
340
+ return [];
341
+ const wordSets = observations.map(obs => new Set(obs.context.toLowerCase().split(/\s+/).filter(w => w.length >= 3)));
342
+ const assigned = new Array(observations.length).fill(false);
343
+ const groups = [];
344
+ for (let i = 0; i < observations.length; i++) {
345
+ if (assigned[i])
346
+ continue;
347
+ assigned[i] = true;
348
+ const group = [observations[i]];
349
+ for (let j = i + 1; j < observations.length; j++) {
350
+ if (assigned[j])
351
+ continue;
352
+ const jaccard = this.jaccardSimilarity(wordSets[i], wordSets[j]);
353
+ if (jaccard >= 0.4) {
354
+ group.push(observations[j]);
355
+ assigned[j] = true;
356
+ }
357
+ }
358
+ groups.push({ items: group });
359
+ }
360
+ return groups;
361
+ }
362
+ jaccardSimilarity(a, b) {
363
+ if (a.size === 0 && b.size === 0)
364
+ return 1;
365
+ let intersection = 0;
366
+ for (const word of a) {
367
+ if (b.has(word))
368
+ intersection++;
369
+ }
370
+ const union = a.size + b.size - intersection;
371
+ return union > 0 ? intersection / union : 0;
372
+ }
373
+ generateSignature(items) {
374
+ const wordCounts = new Map();
375
+ for (const item of items) {
376
+ const words = new Set(item.context.toLowerCase().split(/\s+/).filter(w => w.length >= 3));
377
+ for (const word of words) {
378
+ wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
379
+ }
380
+ }
381
+ const threshold = Math.ceil(items.length * 0.5);
382
+ const commonWords = [...wordCounts.entries()]
383
+ .filter(([, count]) => count >= threshold)
384
+ .sort((a, b) => b[1] - a[1])
385
+ .slice(0, 10)
386
+ .map(([word]) => word)
387
+ .sort(); // Sort alphabetically for stable signatures
388
+ return commonWords.join(' ') || items[0].context.slice(0, 50);
389
+ }
390
+ findBySignature(signature) {
391
+ const sigWords = new Set(signature.split(' '));
392
+ for (const [id, instinct] of this.instincts) {
393
+ const existingWords = new Set(instinct.pattern.signature.split(' '));
394
+ const jaccard = this.jaccardSimilarity(sigWords, existingWords);
395
+ if (jaccard >= 0.7) {
396
+ return id;
397
+ }
398
+ }
399
+ return null;
400
+ }
401
+ load() {
402
+ if (!this.dataFile || !existsSync(this.dataFile))
403
+ return;
404
+ try {
405
+ const store = JSON.parse(readFileSync(this.dataFile, 'utf-8'));
406
+ for (const instinct of store.instincts) {
407
+ this.instincts.set(instinct.id, instinct);
408
+ }
409
+ for (const [id, evidenceList] of Object.entries(store.evidence)) {
410
+ this.evidence.set(id, evidenceList);
411
+ }
412
+ }
413
+ catch { /* start fresh if corrupt */ }
414
+ }
415
+ save() {
416
+ if (!this.dataFile)
417
+ return;
418
+ const store = {
419
+ version: '1.0.0',
420
+ instincts: [...this.instincts.values()],
421
+ evidence: Object.fromEntries(this.evidence),
422
+ };
423
+ writeFileSync(this.dataFile, JSON.stringify(store, null, 2));
424
+ }
425
+ }
426
+ export function createInstinctEngine(dataDir) {
427
+ return new InstinctEngine(dataDir);
428
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Versioned database migration system for Zouroboros.
3
+ *
4
+ * Migrations are defined as numbered entries with up/down SQL.
5
+ * The system tracks which migrations have been applied in a
6
+ * `_migrations` table and applies them in order.
7
+ */
8
+ export interface Migration {
9
+ id: number;
10
+ name: string;
11
+ up: string;
12
+ down: string;
13
+ }
14
+ export interface MigrationRecord {
15
+ id: number;
16
+ name: string;
17
+ applied_at: number;
18
+ }
19
+ export interface MigrationStatus {
20
+ applied: MigrationRecord[];
21
+ pending: Migration[];
22
+ current: number;
23
+ }
24
+ export interface MigrationResult {
25
+ applied: string[];
26
+ skipped: string[];
27
+ errors: {
28
+ name: string;
29
+ error: string;
30
+ }[];
31
+ }
32
+ /**
33
+ * Built-in migrations registry.
34
+ * New migrations are appended here with incrementing IDs.
35
+ * IDs must be sequential and never reused.
36
+ */
37
+ export declare const MIGRATIONS: Migration[];
38
+ export interface MigrationRunner {
39
+ ensureMigrationsTable(): void;
40
+ getApplied(): MigrationRecord[];
41
+ getStatus(): MigrationStatus;
42
+ migrate(targetId?: number): MigrationResult;
43
+ rollback(targetId: number): MigrationResult;
44
+ }
45
+ /**
46
+ * Create a migration runner for a given database.
47
+ * The `db` parameter must support `.exec(sql)`, `.query(sql).all()`,
48
+ * and `.run(sql, params)` — matching bun:sqlite's Database API.
49
+ */
50
+ export declare function createMigrationRunner(db: {
51
+ exec(sql: string): void;
52
+ query(sql: string): {
53
+ all(): unknown[];
54
+ };
55
+ run(sql: string, params?: unknown[]): void;
56
+ }): MigrationRunner;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Versioned database migration system for Zouroboros.
3
+ *
4
+ * Migrations are defined as numbered entries with up/down SQL.
5
+ * The system tracks which migrations have been applied in a
6
+ * `_migrations` table and applies them in order.
7
+ */
8
+ /**
9
+ * Built-in migrations registry.
10
+ * New migrations are appended here with incrementing IDs.
11
+ * IDs must be sequential and never reused.
12
+ */
13
+ export const MIGRATIONS = [
14
+ {
15
+ id: 1,
16
+ name: '001_add_migrations_table',
17
+ up: `
18
+ CREATE TABLE IF NOT EXISTS _migrations (
19
+ id INTEGER PRIMARY KEY,
20
+ name TEXT NOT NULL UNIQUE,
21
+ applied_at INTEGER DEFAULT (strftime('%s', 'now'))
22
+ );
23
+ `,
24
+ down: `DROP TABLE IF EXISTS _migrations;`,
25
+ },
26
+ {
27
+ id: 2,
28
+ name: '002_add_facts_persona_index',
29
+ up: `CREATE INDEX IF NOT EXISTS idx_facts_persona ON facts(persona);`,
30
+ down: `DROP INDEX IF EXISTS idx_facts_persona;`,
31
+ },
32
+ {
33
+ id: 3,
34
+ name: '003_add_facts_confidence_index',
35
+ up: `CREATE INDEX IF NOT EXISTS idx_facts_confidence ON facts(confidence);`,
36
+ down: `DROP INDEX IF EXISTS idx_facts_confidence;`,
37
+ },
38
+ {
39
+ id: 4,
40
+ name: '004_add_episodes_procedure_index',
41
+ up: `CREATE INDEX IF NOT EXISTS idx_episodes_procedure ON episodes(procedure_id);`,
42
+ down: `DROP INDEX IF EXISTS idx_episodes_procedure;`,
43
+ },
44
+ {
45
+ id: 5,
46
+ name: '005_add_open_loops_priority_index',
47
+ up: `CREATE INDEX IF NOT EXISTS idx_open_loops_priority ON open_loops(priority, status);`,
48
+ down: `DROP INDEX IF EXISTS idx_open_loops_priority;`,
49
+ },
50
+ ];
51
+ /**
52
+ * Create a migration runner for a given database.
53
+ * The `db` parameter must support `.exec(sql)`, `.query(sql).all()`,
54
+ * and `.run(sql, params)` — matching bun:sqlite's Database API.
55
+ */
56
+ export function createMigrationRunner(db) {
57
+ function ensureMigrationsTable() {
58
+ db.exec(MIGRATIONS[0].up);
59
+ }
60
+ function getApplied() {
61
+ ensureMigrationsTable();
62
+ return db.query('SELECT id, name, applied_at FROM _migrations ORDER BY id')
63
+ .all();
64
+ }
65
+ function getStatus() {
66
+ const applied = getApplied();
67
+ const appliedIds = new Set(applied.map((m) => m.id));
68
+ const pending = MIGRATIONS.filter((m) => !appliedIds.has(m.id));
69
+ const current = applied.length > 0 ? Math.max(...applied.map((m) => m.id)) : 0;
70
+ return { applied, pending, current };
71
+ }
72
+ function migrate(targetId) {
73
+ const { pending } = getStatus();
74
+ const target = targetId ?? Math.max(...MIGRATIONS.map((m) => m.id));
75
+ const toApply = pending
76
+ .filter((m) => m.id <= target)
77
+ .sort((a, b) => a.id - b.id);
78
+ const result = { applied: [], skipped: [], errors: [] };
79
+ for (const migration of toApply) {
80
+ try {
81
+ db.exec(migration.up);
82
+ db.run('INSERT OR IGNORE INTO _migrations (id, name) VALUES (?, ?)', [migration.id, migration.name]);
83
+ result.applied.push(migration.name);
84
+ }
85
+ catch (err) {
86
+ result.errors.push({
87
+ name: migration.name,
88
+ error: err instanceof Error ? err.message : String(err),
89
+ });
90
+ break; // stop on first error
91
+ }
92
+ }
93
+ return result;
94
+ }
95
+ function rollback(targetId) {
96
+ const { applied } = getStatus();
97
+ const toRollback = applied
98
+ .filter((m) => m.id > targetId)
99
+ .sort((a, b) => b.id - a.id); // reverse order
100
+ const result = { applied: [], skipped: [], errors: [] };
101
+ for (const record of toRollback) {
102
+ const migration = MIGRATIONS.find((m) => m.id === record.id);
103
+ if (!migration) {
104
+ result.skipped.push(`${record.name} (no migration definition found)`);
105
+ continue;
106
+ }
107
+ try {
108
+ db.exec(migration.down);
109
+ db.run('DELETE FROM _migrations WHERE id = ?', [migration.id]);
110
+ result.applied.push(migration.name);
111
+ }
112
+ catch (err) {
113
+ result.errors.push({
114
+ name: migration.name,
115
+ error: err instanceof Error ? err.message : String(err),
116
+ });
117
+ break;
118
+ }
119
+ }
120
+ return result;
121
+ }
122
+ return { ensureMigrationsTable, getApplied, getStatus, migrate, rollback };
123
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * ECC-003: Session Management
3
+ *
4
+ * Active session capabilities: branching, search, compaction, and metrics.
5
+ * Persists to disk (JSON file) matching selfheal persistence pattern.
6
+ * Integrates with hook system for lifecycle events.
7
+ */
8
+ import type { HookSystem } from './hooks.js';
9
+ export interface Session {
10
+ id: string;
11
+ parentId?: string;
12
+ name: string;
13
+ createdAt: string;
14
+ updatedAt: string;
15
+ status: SessionStatus;
16
+ metadata: Record<string, unknown>;
17
+ metrics: SessionMetrics;
18
+ entries: SessionEntry[];
19
+ }
20
+ export type SessionStatus = 'active' | 'paused' | 'completed' | 'archived' | 'branched';
21
+ export interface SessionEntry {
22
+ id: string;
23
+ timestamp: string;
24
+ type: 'message' | 'tool_call' | 'tool_result' | 'checkpoint' | 'note';
25
+ content: string;
26
+ tokens?: number;
27
+ tags?: string[];
28
+ }
29
+ export interface SessionMetrics {
30
+ totalTokens: number;
31
+ entryCount: number;
32
+ toolCalls: number;
33
+ duration: number;
34
+ checkpoints: number;
35
+ }
36
+ export interface SessionSearchResult {
37
+ sessionId: string;
38
+ entryId: string;
39
+ content: string;
40
+ score: number;
41
+ timestamp: string;
42
+ }
43
+ export interface CompactionResult {
44
+ sessionId: string;
45
+ entriesBefore: number;
46
+ entriesAfter: number;
47
+ tokensBefore: number;
48
+ tokensAfter: number;
49
+ summary: string;
50
+ }
51
+ export declare class SessionManager {
52
+ private sessions;
53
+ private dataFile;
54
+ private hooks;
55
+ constructor(dataDir?: string);
56
+ wireHooks(hooks: HookSystem): void;
57
+ create(name: string, metadata?: Record<string, unknown>): Session;
58
+ get(sessionId: string): Session | null;
59
+ list(filter?: {
60
+ status?: SessionStatus;
61
+ }): Session[];
62
+ addEntry(sessionId: string, entry: Omit<SessionEntry, 'id'>): SessionEntry | null;
63
+ branch(sessionId: string, branchName: string, options?: {
64
+ fromEntryIndex?: number;
65
+ freezeParent?: boolean;
66
+ }): Session | null;
67
+ search(query: string, options?: {
68
+ sessionId?: string;
69
+ limit?: number;
70
+ }): SessionSearchResult[];
71
+ compact(sessionId: string, summarizer?: (entries: SessionEntry[]) => string): CompactionResult | null;
72
+ getMetrics(sessionId: string): SessionMetrics | null;
73
+ getAggregateMetrics(): {
74
+ totalSessions: number;
75
+ activeSessions: number;
76
+ totalTokens: number;
77
+ totalEntries: number;
78
+ avgTokensPerSession: number;
79
+ avgEntriesPerSession: number;
80
+ };
81
+ updateStatus(sessionId: string, status: SessionStatus): boolean;
82
+ delete(sessionId: string): boolean;
83
+ clear(): void;
84
+ private defaultSummarize;
85
+ private load;
86
+ private save;
87
+ }
88
+ export declare function createSessionManager(dataDir?: string): SessionManager;