mr-memory 3.2.0 → 3.2.2

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 (3) hide show
  1. package/index.ts +35 -25
  2. package/package.json +1 -1
  3. package/upload.ts +52 -13
package/index.ts CHANGED
@@ -392,6 +392,36 @@ const memoryRouterPlugin = {
392
392
  api.logger.info?.("memoryrouter: get your free API key at https://memoryrouter.ai");
393
393
  }
394
394
 
395
+ // ==================================================================
396
+ // Workspace sync (debounced, fire-and-forget)
397
+ // ==================================================================
398
+
399
+ let lastSyncMs = 0;
400
+ const SYNC_DEBOUNCE_MS = 60_000; // At most once per 60 seconds
401
+
402
+ const triggerSync = (workspaceDir: string) => {
403
+ if (!memoryKey) return;
404
+ const now = Date.now();
405
+ if (now - lastSyncMs < SYNC_DEBOUNCE_MS) return;
406
+ lastSyncMs = now;
407
+ syncWorkspaceFiles({ workspaceDir, endpoint, memoryKey, embeddings, logging })
408
+ .then((result) => {
409
+ if (result.uploaded > 0 || result.deleted > 0) {
410
+ api.logger.info?.(
411
+ `memoryrouter: sync complete — ${result.uploaded} uploaded, ${result.deleted} deleted, ${result.unchanged} unchanged`,
412
+ );
413
+ } else {
414
+ log(`memoryrouter: sync complete — ${result.unchanged} files unchanged`);
415
+ }
416
+ if (result.errors.length > 0) {
417
+ api.logger.warn?.(`memoryrouter: sync had ${result.errors.length} errors`);
418
+ }
419
+ })
420
+ .catch((err) => {
421
+ log(`memoryrouter: sync error — ${err instanceof Error ? err.message : String(err)}`);
422
+ });
423
+ };
424
+
395
425
  // ==================================================================
396
426
  // Core: before_agent_start — search memories, inject context
397
427
  // ==================================================================
@@ -496,6 +526,10 @@ const memoryRouterPlugin = {
496
526
  api.on("before_prompt_build", async (event, ctx) => {
497
527
  promptBuildFiredThisRun = true;
498
528
  log(`memoryrouter: before_prompt_build fired (sessionKey=${ctx.sessionKey}, promptLen=${event.prompt?.length})`);
529
+
530
+ // Trigger workspace sync (fire-and-forget, debounced — catches new files mid-run)
531
+ if (ctx.workspaceDir) triggerSync(ctx.workspaceDir);
532
+
499
533
  try {
500
534
  const prompt = event.prompt;
501
535
 
@@ -1236,35 +1270,11 @@ const memoryRouterPlugin = {
1236
1270
  api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
1237
1271
 
1238
1272
  // ── Auto-sync workspace files (non-blocking) ──
1239
- // Resolve workspace directory
1240
1273
  const configWorkspace = (api.config as any).workspace || (api.config as any).agents?.defaults?.workspace;
1241
1274
  const workspaceDir = configWorkspace
1242
1275
  ? path.resolve(configWorkspace.replace(/^~/, homedir()))
1243
1276
  : path.join(homedir(), ".openclaw", "workspace");
1244
-
1245
- // Fire and forget — don't block service startup
1246
- syncWorkspaceFiles({
1247
- workspaceDir,
1248
- endpoint,
1249
- memoryKey,
1250
- embeddings,
1251
- logging,
1252
- })
1253
- .then((result) => {
1254
- if (result.uploaded > 0 || result.deleted > 0) {
1255
- api.logger.info?.(
1256
- `memoryrouter: sync complete — ${result.uploaded} uploaded, ${result.deleted} deleted, ${result.unchanged} unchanged`,
1257
- );
1258
- } else {
1259
- log(`memoryrouter: sync complete — ${result.unchanged} files unchanged`);
1260
- }
1261
- if (result.errors.length > 0) {
1262
- api.logger.warn?.(`memoryrouter: sync had ${result.errors.length} errors`);
1263
- }
1264
- })
1265
- .catch((err) => {
1266
- api.logger.warn?.(`memoryrouter: sync error — ${err instanceof Error ? err.message : String(err)}`);
1267
- });
1277
+ triggerSync(workspaceDir);
1268
1278
  }
1269
1279
  },
1270
1280
  stop: () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-memory",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "description": "MemoryRouter persistent memory plugin for OpenClaw — your AI remembers every conversation",
5
5
  "type": "module",
6
6
  "files": [
package/upload.ts CHANGED
@@ -370,10 +370,11 @@ export async function runUpload(params: {
370
370
 
371
371
  const workspacePath = params.workspacePath ?? process.cwd();
372
372
 
373
- let files: string[];
373
+ // ── Target path: specific file/dir upload (old behavior, no sync) ──
374
374
  if (targetPath) {
375
375
  const resolved = path.resolve(targetPath);
376
376
  const stat = await fs.stat(resolved);
377
+ let files: string[];
377
378
  if (stat.isDirectory()) {
378
379
  const allDirFiles = await fs.readdir(resolved, { recursive: true });
379
380
  files = (allDirFiles as string[])
@@ -385,19 +386,58 @@ export async function runUpload(params: {
385
386
  } else {
386
387
  files = [resolved];
387
388
  }
388
- } else if (params.hasBrainFlag && !params.hasWorkspaceFlag) {
389
- // --brain only: upload sessions from brain path
390
- files = await discoverBrainFiles(stateDir);
391
- } else if (params.hasWorkspaceFlag && !params.hasBrainFlag) {
392
- // --workspace only: upload workspace files only
393
- files = await discoverWorkspaceFiles(workspacePath);
394
- } else {
395
- // No flags or both flags: upload both workspace + sessions
396
- const wsFiles = await discoverWorkspaceFiles(workspacePath);
397
- const brainFiles = await discoverBrainFiles(stateDir);
398
- files = [...wsFiles, ...brainFiles];
389
+ await uploadFilesLegacy(files, memoryKey, endpoint, embeddings);
390
+ return;
391
+ }
392
+
393
+ // ── Determine what to upload ──
394
+ const doWorkspace = !params.hasBrainFlag || params.hasWorkspaceFlag; // default: yes
395
+ const doSessions = !params.hasWorkspaceFlag || params.hasBrainFlag; // default: yes
396
+
397
+ // ── Workspace files: use source_hash sync (smart, tracks changes) ──
398
+ if (doWorkspace) {
399
+ console.log("📁 Syncing workspace files...");
400
+ const { syncWorkspaceFiles } = await import("./sync.js");
401
+ const syncResult = await syncWorkspaceFiles({
402
+ workspaceDir: workspacePath,
403
+ endpoint,
404
+ memoryKey,
405
+ embeddings,
406
+ logging: true,
407
+ });
408
+ console.log(`\n Workspace: ${syncResult.uploaded} uploaded, ${syncResult.deleted} deleted, ${syncResult.unchanged} unchanged`);
409
+ if (syncResult.errors.length > 0) {
410
+ console.log(` ⚠️ ${syncResult.errors.length} errors`);
411
+ for (const err of syncResult.errors.slice(0, 3)) {
412
+ console.log(` • ${err}`);
413
+ }
414
+ }
399
415
  }
400
416
 
417
+ // ── Session transcripts: batch upload (append-only, no sync needed) ──
418
+ if (doSessions) {
419
+ console.log("\n📝 Uploading session transcripts...");
420
+ const sessionFiles = await discoverBrainFiles(stateDir);
421
+ if (sessionFiles.length === 0) {
422
+ console.log(" No session files found.");
423
+ } else {
424
+ await uploadFilesLegacy(sessionFiles, memoryKey, endpoint, embeddings);
425
+ }
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Legacy batch upload for session transcripts and specific-path uploads.
431
+ * Sessions are append-only, so they don't need source_hash tracking.
432
+ */
433
+ async function uploadFilesLegacy(
434
+ files: string[],
435
+ memoryKey: string,
436
+ endpoint: string,
437
+ embeddings?: string,
438
+ ): Promise<void> {
439
+ const uploadUrl = `${endpoint}/v1/memory/upload`;
440
+
401
441
  if (files.length === 0) {
402
442
  console.log("No files found to upload.");
403
443
  return;
@@ -496,7 +536,6 @@ export async function runUpload(params: {
496
536
  if (batchFailed > 0) {
497
537
  const errHint = result.errors?.[0] ? ` (${result.errors[0].slice(0, 120)})` : "";
498
538
  console.log(`⚠ ${batchStored} stored, ${batchFailed} skipped${errHint}`);
499
- // Show detailed error diagnostics (up to 5 per batch)
500
539
  if (result.errors && result.errors.length > 1) {
501
540
  for (const err of result.errors.slice(0, 5)) {
502
541
  console.log(` → ${err.slice(0, 200)}`);