@ulrichc1/sparn 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -7
- package/dist/cli/index.cjs +574 -33
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +572 -32
- package/dist/cli/index.js.map +1 -1
- package/dist/daemon/index.cjs +728 -159
- package/dist/daemon/index.cjs.map +1 -1
- package/dist/daemon/index.js +697 -150
- package/dist/daemon/index.js.map +1 -1
- package/dist/index.cjs +214 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -1
- package/dist/index.d.ts +34 -1
- package/dist/index.js +213 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.cjs +957 -0
- package/dist/mcp/index.cjs.map +1 -0
- package/dist/mcp/index.d.cts +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +934 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +8 -3
package/dist/daemon/index.cjs
CHANGED
|
@@ -1,10 +1,699 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
2
24
|
|
|
3
25
|
// src/daemon/index.ts
|
|
4
|
-
var
|
|
26
|
+
var import_node_fs5 = require("fs");
|
|
5
27
|
|
|
6
|
-
// src/
|
|
28
|
+
// src/core/kv-memory.ts
|
|
29
|
+
var import_node_fs = require("fs");
|
|
30
|
+
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
31
|
+
function createBackup(dbPath) {
|
|
32
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
33
|
+
const backupPath = `${dbPath}.backup-${timestamp}`;
|
|
34
|
+
try {
|
|
35
|
+
(0, import_node_fs.copyFileSync)(dbPath, backupPath);
|
|
36
|
+
console.log(`\u2713 Database backed up to: ${backupPath}`);
|
|
37
|
+
return backupPath;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error(`Warning: Could not create backup: ${error}`);
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function createKVMemory(dbPath) {
|
|
44
|
+
let db;
|
|
45
|
+
try {
|
|
46
|
+
db = new import_better_sqlite3.default(dbPath);
|
|
47
|
+
const integrityCheck = db.pragma("quick_check", { simple: true });
|
|
48
|
+
if (integrityCheck !== "ok") {
|
|
49
|
+
console.error("\u26A0 Database corruption detected!");
|
|
50
|
+
if ((0, import_node_fs.existsSync)(dbPath)) {
|
|
51
|
+
const backupPath = createBackup(dbPath);
|
|
52
|
+
if (backupPath) {
|
|
53
|
+
console.log(`Backup created at: ${backupPath}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
console.log("Attempting database recovery...");
|
|
57
|
+
db.close();
|
|
58
|
+
db = new import_better_sqlite3.default(dbPath);
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error("\u26A0 Database error detected:", error);
|
|
62
|
+
if ((0, import_node_fs.existsSync)(dbPath)) {
|
|
63
|
+
createBackup(dbPath);
|
|
64
|
+
console.log("Creating new database...");
|
|
65
|
+
}
|
|
66
|
+
db = new import_better_sqlite3.default(dbPath);
|
|
67
|
+
}
|
|
68
|
+
db.pragma("journal_mode = WAL");
|
|
69
|
+
db.exec(`
|
|
70
|
+
CREATE TABLE IF NOT EXISTS entries_index (
|
|
71
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
72
|
+
hash TEXT UNIQUE NOT NULL,
|
|
73
|
+
timestamp INTEGER NOT NULL,
|
|
74
|
+
score REAL NOT NULL DEFAULT 0.0 CHECK(score >= 0.0 AND score <= 1.0),
|
|
75
|
+
ttl INTEGER NOT NULL CHECK(ttl >= 0),
|
|
76
|
+
state TEXT NOT NULL CHECK(state IN ('silent', 'ready', 'active')),
|
|
77
|
+
accessCount INTEGER NOT NULL DEFAULT 0 CHECK(accessCount >= 0),
|
|
78
|
+
isBTSP INTEGER NOT NULL DEFAULT 0 CHECK(isBTSP IN (0, 1)),
|
|
79
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
80
|
+
);
|
|
81
|
+
`);
|
|
82
|
+
db.exec(`
|
|
83
|
+
CREATE TABLE IF NOT EXISTS entries_value (
|
|
84
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
85
|
+
content TEXT NOT NULL,
|
|
86
|
+
tags TEXT,
|
|
87
|
+
metadata TEXT,
|
|
88
|
+
FOREIGN KEY (id) REFERENCES entries_index(id) ON DELETE CASCADE
|
|
89
|
+
);
|
|
90
|
+
`);
|
|
91
|
+
db.exec(`
|
|
92
|
+
CREATE TABLE IF NOT EXISTS optimization_stats (
|
|
93
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
94
|
+
timestamp INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
|
95
|
+
tokens_before INTEGER NOT NULL,
|
|
96
|
+
tokens_after INTEGER NOT NULL,
|
|
97
|
+
entries_pruned INTEGER NOT NULL,
|
|
98
|
+
duration_ms INTEGER NOT NULL
|
|
99
|
+
);
|
|
100
|
+
`);
|
|
101
|
+
db.exec(`
|
|
102
|
+
CREATE INDEX IF NOT EXISTS idx_entries_state ON entries_index(state);
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_entries_score ON entries_index(score DESC);
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_entries_hash ON entries_index(hash);
|
|
105
|
+
CREATE INDEX IF NOT EXISTS idx_entries_timestamp ON entries_index(timestamp DESC);
|
|
106
|
+
CREATE INDEX IF NOT EXISTS idx_stats_timestamp ON optimization_stats(timestamp DESC);
|
|
107
|
+
`);
|
|
108
|
+
const putIndexStmt = db.prepare(`
|
|
109
|
+
INSERT OR REPLACE INTO entries_index
|
|
110
|
+
(id, hash, timestamp, score, ttl, state, accessCount, isBTSP)
|
|
111
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
112
|
+
`);
|
|
113
|
+
const putValueStmt = db.prepare(`
|
|
114
|
+
INSERT OR REPLACE INTO entries_value
|
|
115
|
+
(id, content, tags, metadata)
|
|
116
|
+
VALUES (?, ?, ?, ?)
|
|
117
|
+
`);
|
|
118
|
+
const getStmt = db.prepare(`
|
|
119
|
+
SELECT
|
|
120
|
+
i.id, i.hash, i.timestamp, i.score, i.ttl, i.state, i.accessCount, i.isBTSP,
|
|
121
|
+
v.content, v.tags, v.metadata
|
|
122
|
+
FROM entries_index i
|
|
123
|
+
JOIN entries_value v ON i.id = v.id
|
|
124
|
+
WHERE i.id = ?
|
|
125
|
+
`);
|
|
126
|
+
const deleteIndexStmt = db.prepare("DELETE FROM entries_index WHERE id = ?");
|
|
127
|
+
const deleteValueStmt = db.prepare("DELETE FROM entries_value WHERE id = ?");
|
|
128
|
+
return {
|
|
129
|
+
async put(entry) {
|
|
130
|
+
const transaction = db.transaction(() => {
|
|
131
|
+
putIndexStmt.run(
|
|
132
|
+
entry.id,
|
|
133
|
+
entry.hash,
|
|
134
|
+
entry.timestamp,
|
|
135
|
+
entry.score,
|
|
136
|
+
entry.ttl,
|
|
137
|
+
entry.state,
|
|
138
|
+
entry.accessCount,
|
|
139
|
+
entry.isBTSP ? 1 : 0
|
|
140
|
+
);
|
|
141
|
+
putValueStmt.run(
|
|
142
|
+
entry.id,
|
|
143
|
+
entry.content,
|
|
144
|
+
JSON.stringify(entry.tags),
|
|
145
|
+
JSON.stringify(entry.metadata)
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
transaction();
|
|
149
|
+
},
|
|
150
|
+
async get(id) {
|
|
151
|
+
const row = getStmt.get(id);
|
|
152
|
+
if (!row) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
const r = row;
|
|
156
|
+
return {
|
|
157
|
+
id: r.id,
|
|
158
|
+
content: r.content,
|
|
159
|
+
hash: r.hash,
|
|
160
|
+
timestamp: r.timestamp,
|
|
161
|
+
score: r.score,
|
|
162
|
+
ttl: r.ttl,
|
|
163
|
+
state: r.state,
|
|
164
|
+
accessCount: r.accessCount,
|
|
165
|
+
tags: r.tags ? JSON.parse(r.tags) : [],
|
|
166
|
+
metadata: r.metadata ? JSON.parse(r.metadata) : {},
|
|
167
|
+
isBTSP: r.isBTSP === 1
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
async query(filters) {
|
|
171
|
+
let sql = `
|
|
172
|
+
SELECT
|
|
173
|
+
i.id, i.hash, i.timestamp, i.score, i.ttl, i.state, i.accessCount, i.isBTSP,
|
|
174
|
+
v.content, v.tags, v.metadata
|
|
175
|
+
FROM entries_index i
|
|
176
|
+
JOIN entries_value v ON i.id = v.id
|
|
177
|
+
WHERE 1=1
|
|
178
|
+
`;
|
|
179
|
+
const params = [];
|
|
180
|
+
if (filters.state) {
|
|
181
|
+
sql += " AND i.state = ?";
|
|
182
|
+
params.push(filters.state);
|
|
183
|
+
}
|
|
184
|
+
if (filters.minScore !== void 0) {
|
|
185
|
+
sql += " AND i.score >= ?";
|
|
186
|
+
params.push(filters.minScore);
|
|
187
|
+
}
|
|
188
|
+
if (filters.maxScore !== void 0) {
|
|
189
|
+
sql += " AND i.score <= ?";
|
|
190
|
+
params.push(filters.maxScore);
|
|
191
|
+
}
|
|
192
|
+
if (filters.isBTSP !== void 0) {
|
|
193
|
+
sql += " AND i.isBTSP = ?";
|
|
194
|
+
params.push(filters.isBTSP ? 1 : 0);
|
|
195
|
+
}
|
|
196
|
+
sql += " ORDER BY i.score DESC";
|
|
197
|
+
if (filters.limit) {
|
|
198
|
+
sql += " LIMIT ?";
|
|
199
|
+
params.push(filters.limit);
|
|
200
|
+
}
|
|
201
|
+
if (filters.offset) {
|
|
202
|
+
sql += " OFFSET ?";
|
|
203
|
+
params.push(filters.offset);
|
|
204
|
+
}
|
|
205
|
+
const stmt = db.prepare(sql);
|
|
206
|
+
const rows = stmt.all(...params);
|
|
207
|
+
return rows.map((row) => {
|
|
208
|
+
const r = row;
|
|
209
|
+
return {
|
|
210
|
+
id: r.id,
|
|
211
|
+
content: r.content,
|
|
212
|
+
hash: r.hash,
|
|
213
|
+
timestamp: r.timestamp,
|
|
214
|
+
score: r.score,
|
|
215
|
+
ttl: r.ttl,
|
|
216
|
+
state: r.state,
|
|
217
|
+
accessCount: r.accessCount,
|
|
218
|
+
tags: r.tags ? JSON.parse(r.tags) : [],
|
|
219
|
+
metadata: r.metadata ? JSON.parse(r.metadata) : {},
|
|
220
|
+
isBTSP: r.isBTSP === 1
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
async delete(id) {
|
|
225
|
+
const transaction = db.transaction(() => {
|
|
226
|
+
deleteIndexStmt.run(id);
|
|
227
|
+
deleteValueStmt.run(id);
|
|
228
|
+
});
|
|
229
|
+
transaction();
|
|
230
|
+
},
|
|
231
|
+
async list() {
|
|
232
|
+
const stmt = db.prepare("SELECT id FROM entries_index");
|
|
233
|
+
const rows = stmt.all();
|
|
234
|
+
return rows.map((r) => r.id);
|
|
235
|
+
},
|
|
236
|
+
async compact() {
|
|
237
|
+
const before = db.prepare("SELECT COUNT(*) as count FROM entries_index").get();
|
|
238
|
+
db.exec("DELETE FROM entries_index WHERE ttl <= 0");
|
|
239
|
+
db.exec("VACUUM");
|
|
240
|
+
const after = db.prepare("SELECT COUNT(*) as count FROM entries_index").get();
|
|
241
|
+
return before.count - after.count;
|
|
242
|
+
},
|
|
243
|
+
async close() {
|
|
244
|
+
db.close();
|
|
245
|
+
},
|
|
246
|
+
async recordOptimization(stats) {
|
|
247
|
+
const stmt = db.prepare(`
|
|
248
|
+
INSERT INTO optimization_stats (timestamp, tokens_before, tokens_after, entries_pruned, duration_ms)
|
|
249
|
+
VALUES (?, ?, ?, ?, ?)
|
|
250
|
+
`);
|
|
251
|
+
stmt.run(
|
|
252
|
+
stats.timestamp,
|
|
253
|
+
stats.tokens_before,
|
|
254
|
+
stats.tokens_after,
|
|
255
|
+
stats.entries_pruned,
|
|
256
|
+
stats.duration_ms
|
|
257
|
+
);
|
|
258
|
+
},
|
|
259
|
+
async getOptimizationStats() {
|
|
260
|
+
const stmt = db.prepare(`
|
|
261
|
+
SELECT id, timestamp, tokens_before, tokens_after, entries_pruned, duration_ms
|
|
262
|
+
FROM optimization_stats
|
|
263
|
+
ORDER BY timestamp DESC
|
|
264
|
+
`);
|
|
265
|
+
const rows = stmt.all();
|
|
266
|
+
return rows;
|
|
267
|
+
},
|
|
268
|
+
async clearOptimizationStats() {
|
|
269
|
+
db.exec("DELETE FROM optimization_stats");
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/daemon/consolidation-scheduler.ts
|
|
7
275
|
var import_node_fs2 = require("fs");
|
|
276
|
+
|
|
277
|
+
// src/core/engram-scorer.ts
|
|
278
|
+
function createEngramScorer(config2) {
|
|
279
|
+
const { defaultTTL } = config2;
|
|
280
|
+
function calculateDecay(ageInSeconds, ttlInSeconds) {
|
|
281
|
+
if (ttlInSeconds === 0) return 1;
|
|
282
|
+
if (ageInSeconds <= 0) return 0;
|
|
283
|
+
const ratio = ageInSeconds / ttlInSeconds;
|
|
284
|
+
const decay = 1 - Math.exp(-ratio);
|
|
285
|
+
return Math.max(0, Math.min(1, decay));
|
|
286
|
+
}
|
|
287
|
+
function calculateScore(entry, currentTime = Date.now()) {
|
|
288
|
+
const ageInMilliseconds = currentTime - entry.timestamp;
|
|
289
|
+
const ageInSeconds = Math.max(0, ageInMilliseconds / 1e3);
|
|
290
|
+
const decay = calculateDecay(ageInSeconds, entry.ttl);
|
|
291
|
+
let score = entry.score * (1 - decay);
|
|
292
|
+
if (entry.accessCount > 0) {
|
|
293
|
+
const accessBonus = Math.log(entry.accessCount + 1) * 0.1;
|
|
294
|
+
score = Math.min(1, score + accessBonus);
|
|
295
|
+
}
|
|
296
|
+
if (entry.isBTSP) {
|
|
297
|
+
score = Math.max(score, 0.9);
|
|
298
|
+
}
|
|
299
|
+
return Math.max(0, Math.min(1, score));
|
|
300
|
+
}
|
|
301
|
+
function refreshTTL(entry) {
|
|
302
|
+
return {
|
|
303
|
+
...entry,
|
|
304
|
+
ttl: defaultTTL * 3600,
|
|
305
|
+
// Convert hours to seconds
|
|
306
|
+
timestamp: Date.now()
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
calculateScore,
|
|
311
|
+
refreshTTL,
|
|
312
|
+
calculateDecay
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/core/sleep-compressor.ts
|
|
317
|
+
function createSleepCompressor() {
|
|
318
|
+
const scorer = createEngramScorer({ defaultTTL: 24, decayThreshold: 0.95 });
|
|
319
|
+
function consolidate(entries) {
|
|
320
|
+
const startTime = Date.now();
|
|
321
|
+
const originalCount = entries.length;
|
|
322
|
+
const now = Date.now();
|
|
323
|
+
const nonDecayed = entries.filter((entry) => {
|
|
324
|
+
const ageInSeconds = (now - entry.timestamp) / 1e3;
|
|
325
|
+
const decay = scorer.calculateDecay(ageInSeconds, entry.ttl);
|
|
326
|
+
return decay < 0.95;
|
|
327
|
+
});
|
|
328
|
+
const decayedRemoved = originalCount - nonDecayed.length;
|
|
329
|
+
const duplicateGroups = findDuplicates(nonDecayed);
|
|
330
|
+
const merged = mergeDuplicates(duplicateGroups);
|
|
331
|
+
const duplicateIds = new Set(duplicateGroups.flatMap((g) => g.entries.map((e) => e.id)));
|
|
332
|
+
const nonDuplicates = nonDecayed.filter((e) => !duplicateIds.has(e.id));
|
|
333
|
+
const kept = [...merged, ...nonDuplicates];
|
|
334
|
+
const removed = entries.filter((e) => !kept.some((k) => k.id === e.id));
|
|
335
|
+
const duplicatesRemoved = duplicateGroups.reduce((sum, g) => sum + (g.entries.length - 1), 0);
|
|
336
|
+
return {
|
|
337
|
+
kept,
|
|
338
|
+
removed,
|
|
339
|
+
entriesBefore: originalCount,
|
|
340
|
+
entriesAfter: kept.length,
|
|
341
|
+
decayedRemoved,
|
|
342
|
+
duplicatesRemoved,
|
|
343
|
+
compressionRatio: originalCount > 0 ? kept.length / originalCount : 0,
|
|
344
|
+
durationMs: Date.now() - startTime
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function findDuplicates(entries) {
|
|
348
|
+
const groups = [];
|
|
349
|
+
const processed = /* @__PURE__ */ new Set();
|
|
350
|
+
for (let i = 0; i < entries.length; i++) {
|
|
351
|
+
const entry = entries[i];
|
|
352
|
+
if (!entry || processed.has(entry.id)) continue;
|
|
353
|
+
const duplicates = entries.filter((e, idx) => idx !== i && e.hash === entry.hash);
|
|
354
|
+
if (duplicates.length > 0) {
|
|
355
|
+
const group = {
|
|
356
|
+
entries: [entry, ...duplicates],
|
|
357
|
+
similarity: 1
|
|
358
|
+
// Exact match
|
|
359
|
+
};
|
|
360
|
+
groups.push(group);
|
|
361
|
+
processed.add(entry.id);
|
|
362
|
+
for (const dup of duplicates) {
|
|
363
|
+
processed.add(dup.id);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
for (let i = 0; i < entries.length; i++) {
|
|
368
|
+
const entryI = entries[i];
|
|
369
|
+
if (!entryI || processed.has(entryI.id)) continue;
|
|
370
|
+
for (let j = i + 1; j < entries.length; j++) {
|
|
371
|
+
const entryJ = entries[j];
|
|
372
|
+
if (!entryJ || processed.has(entryJ.id)) continue;
|
|
373
|
+
const similarity = cosineSimilarity(entryI.content, entryJ.content);
|
|
374
|
+
if (similarity >= 0.85) {
|
|
375
|
+
const group = {
|
|
376
|
+
entries: [entryI, entryJ],
|
|
377
|
+
similarity
|
|
378
|
+
};
|
|
379
|
+
groups.push(group);
|
|
380
|
+
processed.add(entryI.id);
|
|
381
|
+
processed.add(entryJ.id);
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return groups;
|
|
387
|
+
}
|
|
388
|
+
function mergeDuplicates(groups) {
|
|
389
|
+
const merged = [];
|
|
390
|
+
for (const group of groups) {
|
|
391
|
+
const sorted = [...group.entries].sort((a, b) => b.score - a.score);
|
|
392
|
+
const best = sorted[0];
|
|
393
|
+
if (!best) continue;
|
|
394
|
+
const totalAccessCount = group.entries.reduce((sum, e) => sum + e.accessCount, 0);
|
|
395
|
+
const allTags = new Set(group.entries.flatMap((e) => e.tags));
|
|
396
|
+
merged.push({
|
|
397
|
+
...best,
|
|
398
|
+
accessCount: totalAccessCount,
|
|
399
|
+
tags: Array.from(allTags)
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return merged;
|
|
403
|
+
}
|
|
404
|
+
function cosineSimilarity(text1, text2) {
|
|
405
|
+
const words1 = tokenize(text1);
|
|
406
|
+
const words2 = tokenize(text2);
|
|
407
|
+
const vocab = /* @__PURE__ */ new Set([...words1, ...words2]);
|
|
408
|
+
const vec1 = {};
|
|
409
|
+
const vec2 = {};
|
|
410
|
+
for (const word of vocab) {
|
|
411
|
+
vec1[word] = words1.filter((w) => w === word).length;
|
|
412
|
+
vec2[word] = words2.filter((w) => w === word).length;
|
|
413
|
+
}
|
|
414
|
+
let dotProduct = 0;
|
|
415
|
+
let mag1 = 0;
|
|
416
|
+
let mag2 = 0;
|
|
417
|
+
for (const word of vocab) {
|
|
418
|
+
const count1 = vec1[word] ?? 0;
|
|
419
|
+
const count2 = vec2[word] ?? 0;
|
|
420
|
+
dotProduct += count1 * count2;
|
|
421
|
+
mag1 += count1 * count1;
|
|
422
|
+
mag2 += count2 * count2;
|
|
423
|
+
}
|
|
424
|
+
mag1 = Math.sqrt(mag1);
|
|
425
|
+
mag2 = Math.sqrt(mag2);
|
|
426
|
+
if (mag1 === 0 || mag2 === 0) return 0;
|
|
427
|
+
return dotProduct / (mag1 * mag2);
|
|
428
|
+
}
|
|
429
|
+
function tokenize(text) {
|
|
430
|
+
return text.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
consolidate,
|
|
434
|
+
findDuplicates,
|
|
435
|
+
mergeDuplicates
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/cli/commands/consolidate.ts
|
|
440
|
+
async function consolidateCommand(options) {
|
|
441
|
+
const { memory: memory2 } = options;
|
|
442
|
+
const allIds = await memory2.list();
|
|
443
|
+
const allEntries = await Promise.all(
|
|
444
|
+
allIds.map(async (id) => {
|
|
445
|
+
const entry = await memory2.get(id);
|
|
446
|
+
return entry;
|
|
447
|
+
})
|
|
448
|
+
);
|
|
449
|
+
const entries = allEntries.filter((e) => e !== null);
|
|
450
|
+
const compressor = createSleepCompressor();
|
|
451
|
+
const result = compressor.consolidate(entries);
|
|
452
|
+
for (const removed of result.removed) {
|
|
453
|
+
await memory2.delete(removed.id);
|
|
454
|
+
}
|
|
455
|
+
for (const kept of result.kept) {
|
|
456
|
+
await memory2.put(kept);
|
|
457
|
+
}
|
|
458
|
+
await memory2.compact();
|
|
459
|
+
return {
|
|
460
|
+
entriesBefore: result.entriesBefore,
|
|
461
|
+
entriesAfter: result.entriesAfter,
|
|
462
|
+
decayedRemoved: result.decayedRemoved,
|
|
463
|
+
duplicatesRemoved: result.duplicatesRemoved,
|
|
464
|
+
compressionRatio: result.compressionRatio,
|
|
465
|
+
durationMs: result.durationMs,
|
|
466
|
+
vacuumCompleted: true
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// src/core/metrics.ts
|
|
471
|
+
function createMetricsCollector() {
|
|
472
|
+
const optimizations = [];
|
|
473
|
+
let daemonMetrics = {
|
|
474
|
+
startTime: Date.now(),
|
|
475
|
+
sessionsWatched: 0,
|
|
476
|
+
totalOptimizations: 0,
|
|
477
|
+
totalTokensSaved: 0,
|
|
478
|
+
averageLatency: 0,
|
|
479
|
+
memoryUsage: 0
|
|
480
|
+
};
|
|
481
|
+
let cacheHits = 0;
|
|
482
|
+
let cacheMisses = 0;
|
|
483
|
+
function recordOptimization(metric) {
|
|
484
|
+
optimizations.push(metric);
|
|
485
|
+
daemonMetrics.totalOptimizations++;
|
|
486
|
+
daemonMetrics.totalTokensSaved += metric.tokensBefore - metric.tokensAfter;
|
|
487
|
+
if (metric.cacheHitRate > 0) {
|
|
488
|
+
const hits = Math.round(metric.entriesProcessed * metric.cacheHitRate);
|
|
489
|
+
cacheHits += hits;
|
|
490
|
+
cacheMisses += metric.entriesProcessed - hits;
|
|
491
|
+
}
|
|
492
|
+
daemonMetrics.averageLatency = (daemonMetrics.averageLatency * (daemonMetrics.totalOptimizations - 1) + metric.duration) / daemonMetrics.totalOptimizations;
|
|
493
|
+
if (optimizations.length > 1e3) {
|
|
494
|
+
optimizations.shift();
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function updateDaemon(metric) {
|
|
498
|
+
daemonMetrics = {
|
|
499
|
+
...daemonMetrics,
|
|
500
|
+
...metric
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function calculatePercentile(values, percentile) {
|
|
504
|
+
if (values.length === 0) return 0;
|
|
505
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
506
|
+
const index = Math.ceil(percentile / 100 * sorted.length) - 1;
|
|
507
|
+
return sorted[index] || 0;
|
|
508
|
+
}
|
|
509
|
+
function getSnapshot() {
|
|
510
|
+
const totalRuns = optimizations.length;
|
|
511
|
+
const totalDuration = optimizations.reduce((sum, m) => sum + m.duration, 0);
|
|
512
|
+
const totalTokensSaved = optimizations.reduce(
|
|
513
|
+
(sum, m) => sum + (m.tokensBefore - m.tokensAfter),
|
|
514
|
+
0
|
|
515
|
+
);
|
|
516
|
+
const totalTokensBefore = optimizations.reduce((sum, m) => sum + m.tokensBefore, 0);
|
|
517
|
+
const averageReduction = totalTokensBefore > 0 ? totalTokensSaved / totalTokensBefore : 0;
|
|
518
|
+
const durations = optimizations.map((m) => m.duration);
|
|
519
|
+
const totalCacheQueries = cacheHits + cacheMisses;
|
|
520
|
+
const hitRate = totalCacheQueries > 0 ? cacheHits / totalCacheQueries : 0;
|
|
521
|
+
return {
|
|
522
|
+
timestamp: Date.now(),
|
|
523
|
+
optimization: {
|
|
524
|
+
totalRuns,
|
|
525
|
+
totalDuration,
|
|
526
|
+
totalTokensSaved,
|
|
527
|
+
averageReduction,
|
|
528
|
+
p50Latency: calculatePercentile(durations, 50),
|
|
529
|
+
p95Latency: calculatePercentile(durations, 95),
|
|
530
|
+
p99Latency: calculatePercentile(durations, 99)
|
|
531
|
+
},
|
|
532
|
+
cache: {
|
|
533
|
+
hitRate,
|
|
534
|
+
totalHits: cacheHits,
|
|
535
|
+
totalMisses: cacheMisses,
|
|
536
|
+
size: optimizations.reduce((sum, m) => sum + m.entriesKept, 0)
|
|
537
|
+
},
|
|
538
|
+
daemon: {
|
|
539
|
+
uptime: Date.now() - daemonMetrics.startTime,
|
|
540
|
+
sessionsWatched: daemonMetrics.sessionsWatched,
|
|
541
|
+
memoryUsage: daemonMetrics.memoryUsage
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function exportMetrics() {
|
|
546
|
+
return JSON.stringify(getSnapshot(), null, 2);
|
|
547
|
+
}
|
|
548
|
+
function reset() {
|
|
549
|
+
optimizations.length = 0;
|
|
550
|
+
cacheHits = 0;
|
|
551
|
+
cacheMisses = 0;
|
|
552
|
+
daemonMetrics = {
|
|
553
|
+
startTime: Date.now(),
|
|
554
|
+
sessionsWatched: 0,
|
|
555
|
+
totalOptimizations: 0,
|
|
556
|
+
totalTokensSaved: 0,
|
|
557
|
+
averageLatency: 0,
|
|
558
|
+
memoryUsage: 0
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
recordOptimization,
|
|
563
|
+
updateDaemon,
|
|
564
|
+
getSnapshot,
|
|
565
|
+
export: exportMetrics,
|
|
566
|
+
reset
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
var globalMetrics = null;
|
|
570
|
+
function getMetrics() {
|
|
571
|
+
if (!globalMetrics) {
|
|
572
|
+
globalMetrics = createMetricsCollector();
|
|
573
|
+
}
|
|
574
|
+
return globalMetrics;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/daemon/consolidation-scheduler.ts
|
|
578
|
+
function createConsolidationScheduler(options) {
|
|
579
|
+
const { memory: memory2, config: config2, logFile: logFile2 } = options;
|
|
580
|
+
const intervalHours = config2.realtime.consolidationInterval;
|
|
581
|
+
let timerId = null;
|
|
582
|
+
let totalRuns = 0;
|
|
583
|
+
let lastRun = null;
|
|
584
|
+
let lastResult = null;
|
|
585
|
+
let nextRun = null;
|
|
586
|
+
function log2(message) {
|
|
587
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
588
|
+
const logMessage = `[${timestamp}] [Consolidation] ${message}
|
|
589
|
+
`;
|
|
590
|
+
const logPath = logFile2 || config2.realtime.logFile;
|
|
591
|
+
if (logPath) {
|
|
592
|
+
try {
|
|
593
|
+
(0, import_node_fs2.appendFileSync)(logPath, logMessage, "utf-8");
|
|
594
|
+
} catch {
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
async function runConsolidation() {
|
|
599
|
+
const startTime = Date.now();
|
|
600
|
+
log2("Starting scheduled consolidation");
|
|
601
|
+
try {
|
|
602
|
+
const result = await consolidateCommand({ memory: memory2 });
|
|
603
|
+
lastRun = startTime;
|
|
604
|
+
totalRuns++;
|
|
605
|
+
lastResult = {
|
|
606
|
+
timestamp: startTime,
|
|
607
|
+
entriesBefore: result.entriesBefore,
|
|
608
|
+
entriesAfter: result.entriesAfter,
|
|
609
|
+
decayedRemoved: result.decayedRemoved,
|
|
610
|
+
duplicatesRemoved: result.duplicatesRemoved,
|
|
611
|
+
compressionRatio: result.compressionRatio,
|
|
612
|
+
durationMs: result.durationMs,
|
|
613
|
+
success: true
|
|
614
|
+
};
|
|
615
|
+
log2(
|
|
616
|
+
`Consolidation completed: ${result.entriesBefore} -> ${result.entriesAfter} entries (${result.decayedRemoved} decayed, ${result.duplicatesRemoved} duplicates, ${Math.round(result.compressionRatio * 100)}% compression) in ${result.durationMs}ms`
|
|
617
|
+
);
|
|
618
|
+
const metrics = getMetrics();
|
|
619
|
+
metrics.recordOptimization({
|
|
620
|
+
timestamp: startTime,
|
|
621
|
+
duration: result.durationMs,
|
|
622
|
+
tokensBefore: result.entriesBefore * 100,
|
|
623
|
+
// Rough estimate
|
|
624
|
+
tokensAfter: result.entriesAfter * 100,
|
|
625
|
+
// Rough estimate
|
|
626
|
+
entriesProcessed: result.entriesBefore,
|
|
627
|
+
entriesKept: result.entriesAfter,
|
|
628
|
+
cacheHitRate: 0,
|
|
629
|
+
// N/A for consolidation
|
|
630
|
+
memoryUsage: process.memoryUsage().heapUsed
|
|
631
|
+
});
|
|
632
|
+
} catch (error) {
|
|
633
|
+
lastRun = startTime;
|
|
634
|
+
totalRuns++;
|
|
635
|
+
lastResult = {
|
|
636
|
+
timestamp: startTime,
|
|
637
|
+
entriesBefore: 0,
|
|
638
|
+
entriesAfter: 0,
|
|
639
|
+
decayedRemoved: 0,
|
|
640
|
+
duplicatesRemoved: 0,
|
|
641
|
+
compressionRatio: 0,
|
|
642
|
+
durationMs: Date.now() - startTime,
|
|
643
|
+
success: false,
|
|
644
|
+
error: error instanceof Error ? error.message : String(error)
|
|
645
|
+
};
|
|
646
|
+
log2(`Consolidation failed: ${lastResult.error}`);
|
|
647
|
+
}
|
|
648
|
+
if (intervalHours !== null && intervalHours > 0) {
|
|
649
|
+
nextRun = Date.now() + intervalHours * 60 * 60 * 1e3;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function start() {
|
|
653
|
+
if (intervalHours === null || intervalHours <= 0) {
|
|
654
|
+
log2("Consolidation scheduler disabled (consolidationInterval not set)");
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
if (timerId !== null) {
|
|
658
|
+
log2("Consolidation scheduler already running");
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const intervalMs = intervalHours * 60 * 60 * 1e3;
|
|
662
|
+
timerId = setInterval(() => {
|
|
663
|
+
void runConsolidation();
|
|
664
|
+
}, intervalMs);
|
|
665
|
+
nextRun = Date.now() + intervalMs;
|
|
666
|
+
log2(
|
|
667
|
+
`Consolidation scheduler started (interval: ${intervalHours}h, next run: ${new Date(nextRun).toISOString()})`
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
function stop() {
|
|
671
|
+
if (timerId !== null) {
|
|
672
|
+
clearInterval(timerId);
|
|
673
|
+
timerId = null;
|
|
674
|
+
nextRun = null;
|
|
675
|
+
log2("Consolidation scheduler stopped");
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function getStatus() {
|
|
679
|
+
return {
|
|
680
|
+
running: timerId !== null,
|
|
681
|
+
intervalHours,
|
|
682
|
+
nextRun,
|
|
683
|
+
totalRuns,
|
|
684
|
+
lastRun,
|
|
685
|
+
lastResult
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
start,
|
|
690
|
+
stop,
|
|
691
|
+
getStatus
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/daemon/session-watcher.ts
|
|
696
|
+
var import_node_fs4 = require("fs");
|
|
8
697
|
var import_node_os = require("os");
|
|
9
698
|
var import_node_path = require("path");
|
|
10
699
|
|
|
@@ -94,45 +783,6 @@ function estimateTokens(text) {
|
|
|
94
783
|
return Math.max(wordEstimate, charEstimate);
|
|
95
784
|
}
|
|
96
785
|
|
|
97
|
-
// src/core/engram-scorer.ts
|
|
98
|
-
function createEngramScorer(config2) {
|
|
99
|
-
const { defaultTTL } = config2;
|
|
100
|
-
function calculateDecay(ageInSeconds, ttlInSeconds) {
|
|
101
|
-
if (ttlInSeconds === 0) return 1;
|
|
102
|
-
if (ageInSeconds <= 0) return 0;
|
|
103
|
-
const ratio = ageInSeconds / ttlInSeconds;
|
|
104
|
-
const decay = 1 - Math.exp(-ratio);
|
|
105
|
-
return Math.max(0, Math.min(1, decay));
|
|
106
|
-
}
|
|
107
|
-
function calculateScore(entry, currentTime = Date.now()) {
|
|
108
|
-
const ageInMilliseconds = currentTime - entry.timestamp;
|
|
109
|
-
const ageInSeconds = Math.max(0, ageInMilliseconds / 1e3);
|
|
110
|
-
const decay = calculateDecay(ageInSeconds, entry.ttl);
|
|
111
|
-
let score = entry.score * (1 - decay);
|
|
112
|
-
if (entry.accessCount > 0) {
|
|
113
|
-
const accessBonus = Math.log(entry.accessCount + 1) * 0.1;
|
|
114
|
-
score = Math.min(1, score + accessBonus);
|
|
115
|
-
}
|
|
116
|
-
if (entry.isBTSP) {
|
|
117
|
-
score = Math.max(score, 0.9);
|
|
118
|
-
}
|
|
119
|
-
return Math.max(0, Math.min(1, score));
|
|
120
|
-
}
|
|
121
|
-
function refreshTTL(entry) {
|
|
122
|
-
return {
|
|
123
|
-
...entry,
|
|
124
|
-
ttl: defaultTTL * 3600,
|
|
125
|
-
// Convert hours to seconds
|
|
126
|
-
timestamp: Date.now()
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
calculateScore,
|
|
131
|
-
refreshTTL,
|
|
132
|
-
calculateDecay
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
786
|
// src/core/budget-pruner.ts
|
|
137
787
|
function createBudgetPruner(config2) {
|
|
138
788
|
const { tokenBudget, decay } = config2;
|
|
@@ -231,113 +881,6 @@ function createBudgetPruner(config2) {
|
|
|
231
881
|
};
|
|
232
882
|
}
|
|
233
883
|
|
|
234
|
-
// src/core/metrics.ts
|
|
235
|
-
function createMetricsCollector() {
|
|
236
|
-
const optimizations = [];
|
|
237
|
-
let daemonMetrics = {
|
|
238
|
-
startTime: Date.now(),
|
|
239
|
-
sessionsWatched: 0,
|
|
240
|
-
totalOptimizations: 0,
|
|
241
|
-
totalTokensSaved: 0,
|
|
242
|
-
averageLatency: 0,
|
|
243
|
-
memoryUsage: 0
|
|
244
|
-
};
|
|
245
|
-
let cacheHits = 0;
|
|
246
|
-
let cacheMisses = 0;
|
|
247
|
-
function recordOptimization(metric) {
|
|
248
|
-
optimizations.push(metric);
|
|
249
|
-
daemonMetrics.totalOptimizations++;
|
|
250
|
-
daemonMetrics.totalTokensSaved += metric.tokensBefore - metric.tokensAfter;
|
|
251
|
-
if (metric.cacheHitRate > 0) {
|
|
252
|
-
const hits = Math.round(metric.entriesProcessed * metric.cacheHitRate);
|
|
253
|
-
cacheHits += hits;
|
|
254
|
-
cacheMisses += metric.entriesProcessed - hits;
|
|
255
|
-
}
|
|
256
|
-
daemonMetrics.averageLatency = (daemonMetrics.averageLatency * (daemonMetrics.totalOptimizations - 1) + metric.duration) / daemonMetrics.totalOptimizations;
|
|
257
|
-
if (optimizations.length > 1e3) {
|
|
258
|
-
optimizations.shift();
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
function updateDaemon(metric) {
|
|
262
|
-
daemonMetrics = {
|
|
263
|
-
...daemonMetrics,
|
|
264
|
-
...metric
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
function calculatePercentile(values, percentile) {
|
|
268
|
-
if (values.length === 0) return 0;
|
|
269
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
270
|
-
const index = Math.ceil(percentile / 100 * sorted.length) - 1;
|
|
271
|
-
return sorted[index] || 0;
|
|
272
|
-
}
|
|
273
|
-
function getSnapshot() {
|
|
274
|
-
const totalRuns = optimizations.length;
|
|
275
|
-
const totalDuration = optimizations.reduce((sum, m) => sum + m.duration, 0);
|
|
276
|
-
const totalTokensSaved = optimizations.reduce(
|
|
277
|
-
(sum, m) => sum + (m.tokensBefore - m.tokensAfter),
|
|
278
|
-
0
|
|
279
|
-
);
|
|
280
|
-
const totalTokensBefore = optimizations.reduce((sum, m) => sum + m.tokensBefore, 0);
|
|
281
|
-
const averageReduction = totalTokensBefore > 0 ? totalTokensSaved / totalTokensBefore : 0;
|
|
282
|
-
const durations = optimizations.map((m) => m.duration);
|
|
283
|
-
const totalCacheQueries = cacheHits + cacheMisses;
|
|
284
|
-
const hitRate = totalCacheQueries > 0 ? cacheHits / totalCacheQueries : 0;
|
|
285
|
-
return {
|
|
286
|
-
timestamp: Date.now(),
|
|
287
|
-
optimization: {
|
|
288
|
-
totalRuns,
|
|
289
|
-
totalDuration,
|
|
290
|
-
totalTokensSaved,
|
|
291
|
-
averageReduction,
|
|
292
|
-
p50Latency: calculatePercentile(durations, 50),
|
|
293
|
-
p95Latency: calculatePercentile(durations, 95),
|
|
294
|
-
p99Latency: calculatePercentile(durations, 99)
|
|
295
|
-
},
|
|
296
|
-
cache: {
|
|
297
|
-
hitRate,
|
|
298
|
-
totalHits: cacheHits,
|
|
299
|
-
totalMisses: cacheMisses,
|
|
300
|
-
size: optimizations.reduce((sum, m) => sum + m.entriesKept, 0)
|
|
301
|
-
},
|
|
302
|
-
daemon: {
|
|
303
|
-
uptime: Date.now() - daemonMetrics.startTime,
|
|
304
|
-
sessionsWatched: daemonMetrics.sessionsWatched,
|
|
305
|
-
memoryUsage: daemonMetrics.memoryUsage
|
|
306
|
-
}
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
function exportMetrics() {
|
|
310
|
-
return JSON.stringify(getSnapshot(), null, 2);
|
|
311
|
-
}
|
|
312
|
-
function reset() {
|
|
313
|
-
optimizations.length = 0;
|
|
314
|
-
cacheHits = 0;
|
|
315
|
-
cacheMisses = 0;
|
|
316
|
-
daemonMetrics = {
|
|
317
|
-
startTime: Date.now(),
|
|
318
|
-
sessionsWatched: 0,
|
|
319
|
-
totalOptimizations: 0,
|
|
320
|
-
totalTokensSaved: 0,
|
|
321
|
-
averageLatency: 0,
|
|
322
|
-
memoryUsage: 0
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
return {
|
|
326
|
-
recordOptimization,
|
|
327
|
-
updateDaemon,
|
|
328
|
-
getSnapshot,
|
|
329
|
-
export: exportMetrics,
|
|
330
|
-
reset
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
var globalMetrics = null;
|
|
334
|
-
function getMetrics() {
|
|
335
|
-
if (!globalMetrics) {
|
|
336
|
-
globalMetrics = createMetricsCollector();
|
|
337
|
-
}
|
|
338
|
-
return globalMetrics;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
884
|
// src/core/incremental-optimizer.ts
|
|
342
885
|
function createIncrementalOptimizer(config2) {
|
|
343
886
|
const pruner = createBudgetPruner(config2);
|
|
@@ -580,12 +1123,12 @@ function createContextPipeline(config2) {
|
|
|
580
1123
|
}
|
|
581
1124
|
|
|
582
1125
|
// src/daemon/file-tracker.ts
|
|
583
|
-
var
|
|
1126
|
+
var import_node_fs3 = require("fs");
|
|
584
1127
|
function createFileTracker() {
|
|
585
1128
|
const positions = /* @__PURE__ */ new Map();
|
|
586
1129
|
function readNewLines(filePath) {
|
|
587
1130
|
try {
|
|
588
|
-
const stats = (0,
|
|
1131
|
+
const stats = (0, import_node_fs3.statSync)(filePath);
|
|
589
1132
|
const currentSize = stats.size;
|
|
590
1133
|
const currentModified = stats.mtimeMs;
|
|
591
1134
|
let pos = positions.get(filePath);
|
|
@@ -607,7 +1150,7 @@ function createFileTracker() {
|
|
|
607
1150
|
return [];
|
|
608
1151
|
}
|
|
609
1152
|
const buffer = Buffer.alloc(currentSize - pos.position);
|
|
610
|
-
const fd = (0,
|
|
1153
|
+
const fd = (0, import_node_fs3.readFileSync)(filePath);
|
|
611
1154
|
fd.copy(buffer, 0, pos.position, currentSize);
|
|
612
1155
|
const newContent = (pos.partialLine + buffer.toString("utf-8")).split("\n");
|
|
613
1156
|
const partialLine = newContent.pop() || "";
|
|
@@ -708,10 +1251,10 @@ function createSessionWatcher(config2) {
|
|
|
708
1251
|
function findJsonlFiles(dir) {
|
|
709
1252
|
const files = [];
|
|
710
1253
|
try {
|
|
711
|
-
const entries = (0,
|
|
1254
|
+
const entries = (0, import_node_fs4.readdirSync)(dir);
|
|
712
1255
|
for (const entry of entries) {
|
|
713
1256
|
const fullPath = (0, import_node_path.join)(dir, entry);
|
|
714
|
-
const stat = (0,
|
|
1257
|
+
const stat = (0, import_node_fs4.statSync)(fullPath);
|
|
715
1258
|
if (stat.isDirectory()) {
|
|
716
1259
|
files.push(...findJsonlFiles(fullPath));
|
|
717
1260
|
} else if (entry.endsWith(".jsonl")) {
|
|
@@ -750,7 +1293,7 @@ function createSessionWatcher(config2) {
|
|
|
750
1293
|
for (const file of jsonlFiles) {
|
|
751
1294
|
const dir = (0, import_node_path.dirname)(file);
|
|
752
1295
|
if (!watchedDirs.has(dir)) {
|
|
753
|
-
const watcher2 = (0,
|
|
1296
|
+
const watcher2 = (0, import_node_fs4.watch)(dir, { recursive: false }, (_eventType, filename) => {
|
|
754
1297
|
if (filename?.endsWith(".jsonl")) {
|
|
755
1298
|
const fullPath = (0, import_node_path.join)(dir, filename);
|
|
756
1299
|
handleFileChange(fullPath);
|
|
@@ -760,7 +1303,7 @@ function createSessionWatcher(config2) {
|
|
|
760
1303
|
watchedDirs.add(dir);
|
|
761
1304
|
}
|
|
762
1305
|
}
|
|
763
|
-
const projectsWatcher = (0,
|
|
1306
|
+
const projectsWatcher = (0, import_node_fs4.watch)(projectsDir, { recursive: true }, (_eventType, filename) => {
|
|
764
1307
|
if (filename?.endsWith(".jsonl")) {
|
|
765
1308
|
const fullPath = (0, import_node_path.join)(projectsDir, filename);
|
|
766
1309
|
handleFileChange(fullPath);
|
|
@@ -832,20 +1375,26 @@ function log(message) {
|
|
|
832
1375
|
`;
|
|
833
1376
|
if (logFile) {
|
|
834
1377
|
try {
|
|
835
|
-
(0,
|
|
1378
|
+
(0, import_node_fs5.appendFileSync)(logFile, logMessage, "utf-8");
|
|
836
1379
|
} catch {
|
|
837
1380
|
}
|
|
838
1381
|
}
|
|
839
1382
|
}
|
|
840
1383
|
function cleanup() {
|
|
841
1384
|
log("Daemon shutting down");
|
|
842
|
-
if (pidFile && (0,
|
|
1385
|
+
if (pidFile && (0, import_node_fs5.existsSync)(pidFile)) {
|
|
843
1386
|
try {
|
|
844
|
-
(0,
|
|
1387
|
+
(0, import_node_fs5.unlinkSync)(pidFile);
|
|
845
1388
|
} catch {
|
|
846
1389
|
}
|
|
847
1390
|
}
|
|
1391
|
+
if (scheduler) {
|
|
1392
|
+
scheduler.stop();
|
|
1393
|
+
}
|
|
848
1394
|
watcher.stop();
|
|
1395
|
+
if (memory) {
|
|
1396
|
+
void memory.close();
|
|
1397
|
+
}
|
|
849
1398
|
process.exit(0);
|
|
850
1399
|
}
|
|
851
1400
|
process.on("SIGTERM", cleanup);
|
|
@@ -859,6 +1408,8 @@ process.on("unhandledRejection", (reason) => {
|
|
|
859
1408
|
log(`Unhandled rejection: ${reason}`);
|
|
860
1409
|
cleanup();
|
|
861
1410
|
});
|
|
1411
|
+
var memory = null;
|
|
1412
|
+
var scheduler = null;
|
|
862
1413
|
log("Daemon starting");
|
|
863
1414
|
var watcher = createSessionWatcher({
|
|
864
1415
|
config,
|
|
@@ -871,8 +1422,26 @@ var watcher = createSessionWatcher({
|
|
|
871
1422
|
log(`Error: ${error.message}`);
|
|
872
1423
|
}
|
|
873
1424
|
});
|
|
874
|
-
watcher.start().then(() => {
|
|
1425
|
+
watcher.start().then(async () => {
|
|
875
1426
|
log("Daemon ready - watching Claude Code sessions");
|
|
1427
|
+
if (config.realtime.consolidationInterval !== null && config.realtime.consolidationInterval > 0) {
|
|
1428
|
+
try {
|
|
1429
|
+
memory = await createKVMemory(".sparn/memory.db");
|
|
1430
|
+
scheduler = createConsolidationScheduler({
|
|
1431
|
+
memory,
|
|
1432
|
+
config,
|
|
1433
|
+
logFile
|
|
1434
|
+
});
|
|
1435
|
+
scheduler.start();
|
|
1436
|
+
log(
|
|
1437
|
+
`Consolidation scheduler started (interval: ${config.realtime.consolidationInterval}h)`
|
|
1438
|
+
);
|
|
1439
|
+
} catch (error) {
|
|
1440
|
+
log(
|
|
1441
|
+
`Failed to start consolidation scheduler: ${error instanceof Error ? error.message : String(error)}`
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
876
1445
|
}).catch((error) => {
|
|
877
1446
|
log(`Failed to start: ${error.message}`);
|
|
878
1447
|
cleanup();
|