clawmem 0.2.7 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/hooks.ts +42 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmem",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "On-device context engine and memory for AI agents. Claude Code and OpenClaw. Hooks + MCP server + hybrid RAG search.",
5
5
  "type": "module",
6
6
  "bin": {
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 content = require("fs").readFileSync(transcriptPath, "utf-8");
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, <50MB).
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 > 50 * 1024 * 1024) return null; // 50MB limit
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;