clawmem 0.2.6 → 0.2.8
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/package.json +1 -1
- package/src/hooks.ts +42 -3
- package/src/mcp.ts +6 -5
package/package.json
CHANGED
package/src/hooks.ts
CHANGED
|
@@ -243,7 +243,46 @@ export function readTranscript(
|
|
|
243
243
|
roleFilter?: "user" | "assistant"
|
|
244
244
|
): TranscriptMessage[] {
|
|
245
245
|
try {
|
|
246
|
-
const
|
|
246
|
+
const fs = require("fs");
|
|
247
|
+
const stat = fs.statSync(transcriptPath);
|
|
248
|
+
let content: string;
|
|
249
|
+
|
|
250
|
+
// For large transcripts (>10MB), read backwards in chunks until we have enough lines
|
|
251
|
+
if (stat.size > 10 * 1024 * 1024) {
|
|
252
|
+
const chunkSize = 2 * 1024 * 1024; // 2MB chunks
|
|
253
|
+
const maxChunks = 5; // Up to 10MB of tail
|
|
254
|
+
const targetLines = lastN * 3; // Overshoot — not all lines are role messages
|
|
255
|
+
const buffers: Buffer[] = [];
|
|
256
|
+
let totalRead = 0;
|
|
257
|
+
|
|
258
|
+
// Accumulate raw Buffers (decode once after assembly to avoid UTF-8 boundary corruption)
|
|
259
|
+
const fd = fs.openSync(transcriptPath, "r");
|
|
260
|
+
try {
|
|
261
|
+
for (let chunk = 0; chunk < maxChunks; chunk++) {
|
|
262
|
+
const readSize = Math.min(chunkSize, stat.size - totalRead);
|
|
263
|
+
if (readSize <= 0) break;
|
|
264
|
+
const offset = Math.max(0, stat.size - totalRead - readSize);
|
|
265
|
+
const buf = Buffer.alloc(readSize);
|
|
266
|
+
fs.readSync(fd, buf, 0, readSize, offset);
|
|
267
|
+
buffers.unshift(buf);
|
|
268
|
+
totalRead += readSize;
|
|
269
|
+
|
|
270
|
+
// Check line count on decoded text to see if we have enough
|
|
271
|
+
const decoded = Buffer.concat(buffers).toString("utf-8");
|
|
272
|
+
if (decoded.split("\n").length >= targetLines) break;
|
|
273
|
+
}
|
|
274
|
+
} finally {
|
|
275
|
+
fs.closeSync(fd);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const assembled = Buffer.concat(buffers).toString("utf-8");
|
|
279
|
+
// Drop first partial line (we likely started mid-line)
|
|
280
|
+
const firstNewline = assembled.indexOf("\n");
|
|
281
|
+
content = firstNewline > 0 ? assembled.slice(firstNewline + 1) : assembled;
|
|
282
|
+
} else {
|
|
283
|
+
content = fs.readFileSync(transcriptPath, "utf-8");
|
|
284
|
+
}
|
|
285
|
+
|
|
247
286
|
const lines = content.split("\n").filter((l: string) => l.trim());
|
|
248
287
|
const messages: TranscriptMessage[] = [];
|
|
249
288
|
|
|
@@ -284,7 +323,7 @@ export function readTranscript(
|
|
|
284
323
|
}
|
|
285
324
|
|
|
286
325
|
/**
|
|
287
|
-
* Validate a transcript path (security: must be absolute, .jsonl, regular file, <
|
|
326
|
+
* Validate a transcript path (security: must be absolute, .jsonl, regular file, <1GB).
|
|
288
327
|
*/
|
|
289
328
|
export function validateTranscriptPath(path: string | undefined): string | null {
|
|
290
329
|
if (!path) return null;
|
|
@@ -294,7 +333,7 @@ export function validateTranscriptPath(path: string | undefined): string | null
|
|
|
294
333
|
try {
|
|
295
334
|
const stat = require("fs").statSync(path);
|
|
296
335
|
if (!stat.isFile()) return null;
|
|
297
|
-
if (stat.size >
|
|
336
|
+
if (stat.size > 1024 * 1024 * 1024) return null; // 1GB sanity limit (readTranscript tail-reads large files)
|
|
298
337
|
return path;
|
|
299
338
|
} catch {
|
|
300
339
|
return null;
|
package/src/mcp.ts
CHANGED
|
@@ -878,11 +878,12 @@ This is the recommended entry point for ALL memory queries.`,
|
|
|
878
878
|
if (tokens.length > 0) {
|
|
879
879
|
const minMatch = Math.max(2, Math.ceil(tokens.length / 2));
|
|
880
880
|
const titleHits = store.db.prepare(`
|
|
881
|
-
SELECT
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
881
|
+
SELECT displayPath, title, match_count FROM (
|
|
882
|
+
SELECT collection || '/' || path as displayPath, title, modified_at,
|
|
883
|
+
${tokens.map(() => `(CASE WHEN LOWER(title) LIKE ? THEN 1 ELSE 0 END)`).join(" + ")} as match_count
|
|
884
|
+
FROM documents
|
|
885
|
+
WHERE active = 1 AND invalidated_at IS NULL
|
|
886
|
+
) WHERE match_count >= ?
|
|
886
887
|
ORDER BY match_count DESC, modified_at DESC
|
|
887
888
|
LIMIT ?
|
|
888
889
|
`).all(...tokens.map(t => `%${t}%`), minMatch, limit) as { displayPath: string; title: string; match_count: number }[];
|