mr-memory 3.3.0 → 3.5.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.
Files changed (2) hide show
  1. package/index.ts +39 -8
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -271,6 +271,7 @@ type MemoryRouterConfig = {
271
271
  mode?: "relay" | "proxy";
272
272
  logging?: boolean;
273
273
  embeddings?: string;
274
+ agentKeys?: Record<string, string>;
274
275
  };
275
276
 
276
277
  // ──────────────────────────────────────────────────────
@@ -380,10 +381,28 @@ const memoryRouterPlugin = {
380
381
  const mode = cfg?.mode || "relay";
381
382
  const logging = cfg?.logging ?? false;
382
383
  const embeddings = cfg?.embeddings; // undefined = bge (default), "qwen" = qwen3-8b
384
+ const agentKeys = cfg?.agentKeys;
383
385
  const log = (msg: string) => { if (logging) api.logger.info?.(msg); };
384
386
 
385
- if (memoryKey) {
386
- api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
387
+ // Resolve the correct memory key for a given agent context.
388
+ // Multi-agent gateways: each agent gets its own key via agentKeys map.
389
+ // Single-agent: falls back to the global key.
390
+ function resolveKey(ctx: any): string | undefined {
391
+ if (agentKeys && ctx?.workspaceDir) {
392
+ // Derive agentId from workspace path: ~/.openclaw/workspace-rex → "rex"
393
+ // Default workspace (~/.openclaw/workspace) → "main"
394
+ const dirName = ctx.workspaceDir.split("/").pop() || "";
395
+ const agentId = dirName === "workspace" ? "main" : dirName.replace(/^workspace-/, "");
396
+ if (agentId && agentKeys[agentId]) return agentKeys[agentId];
397
+ }
398
+ return memoryKey;
399
+ }
400
+
401
+ const hasAnyKey = !!(memoryKey || (agentKeys && Object.keys(agentKeys).length > 0));
402
+
403
+ if (hasAnyKey) {
404
+ if (memoryKey) api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
405
+ if (agentKeys) api.logger.info?.(`memoryrouter: ${Object.keys(agentKeys).length} agent key(s) configured`);
387
406
  } else {
388
407
  api.logger.info?.("memoryrouter: no key configured — run: openclaw mr <key>");
389
408
  api.logger.info?.("memoryrouter: get your free API key at https://memoryrouter.ai");
@@ -393,7 +412,7 @@ const memoryRouterPlugin = {
393
412
  // Core: before_agent_start — search memories, inject context
394
413
  // ==================================================================
395
414
 
396
- if (memoryKey) {
415
+ if (hasAnyKey) {
397
416
  // Track whether we've already fired for this prompt (dedup double-fire)
398
417
  let lastPreparedPrompt = "";
399
418
  // Track whether before_prompt_build already handled the first call in this run
@@ -411,6 +430,9 @@ const memoryRouterPlugin = {
411
430
  return;
412
431
  }
413
432
 
433
+ const activeKey = resolveKey(ctx);
434
+ if (!activeKey) return; // No key for this agent
435
+
414
436
  try {
415
437
  const startMs = Date.now();
416
438
  const prompt = event.prompt;
@@ -451,7 +473,7 @@ const memoryRouterPlugin = {
451
473
  method: "POST",
452
474
  headers: {
453
475
  "Content-Type": "application/json",
454
- Authorization: `Bearer ${memoryKey}`,
476
+ Authorization: `Bearer ${activeKey}`,
455
477
  },
456
478
  body: JSON.stringify({
457
479
  messages: contextPayload,
@@ -491,6 +513,8 @@ const memoryRouterPlugin = {
491
513
  // ── before_prompt_build: fires once per run (primary, includes full billing context)
492
514
  api.on("before_prompt_build", async (event, ctx) => {
493
515
  promptBuildFiredThisRun = true;
516
+ const activeKey = resolveKey(ctx);
517
+ if (!activeKey) return; // No key for this agent
494
518
  try {
495
519
  const startMs = Date.now();
496
520
  const prompt = event.prompt;
@@ -561,7 +585,7 @@ const memoryRouterPlugin = {
561
585
  method: "POST",
562
586
  headers: {
563
587
  "Content-Type": "application/json",
564
- Authorization: `Bearer ${memoryKey}`,
588
+ Authorization: `Bearer ${activeKey}`,
565
589
  },
566
590
  body: JSON.stringify({
567
591
  messages: contextPayload,
@@ -574,6 +598,9 @@ const memoryRouterPlugin = {
574
598
  const errBody = await res.json().catch(() => null) as any;
575
599
  if (errBody?.error?.code === "payment_required") {
576
600
  api.logger.warn?.(`⚠️ MemoryRouter: Out of credits. Memory is NOT being injected. Add credits at https://app.memoryrouter.ai/settings/billing`);
601
+ return {
602
+ prependContext: "⚠️ MemoryRouter: This user's memory credits have run out. Memory is NOT active for this conversation. Let them know they need to add a payment method or top up their balance at https://app.memoryrouter.ai to restore memory."
603
+ };
577
604
  } else {
578
605
  log(`memoryrouter: prepare failed (${res.status})`);
579
606
  }
@@ -605,6 +632,8 @@ const memoryRouterPlugin = {
605
632
  // ==================================================================
606
633
 
607
634
  api.on("agent_end", async (event, ctx) => {
635
+ const activeKey = resolveKey(ctx);
636
+ if (!activeKey) return; // No key for this agent
608
637
  try {
609
638
  const msgs = event.messages;
610
639
  if (!msgs || !Array.isArray(msgs) || msgs.length === 0) return;
@@ -677,7 +706,7 @@ const memoryRouterPlugin = {
677
706
  method: "POST",
678
707
  headers: {
679
708
  "Content-Type": "application/json",
680
- Authorization: `Bearer ${memoryKey}`,
709
+ Authorization: `Bearer ${activeKey}`,
681
710
  },
682
711
  body: JSON.stringify({
683
712
  messages: toStore,
@@ -711,6 +740,7 @@ const memoryRouterPlugin = {
711
740
 
712
741
  // memory_search — calls MR /v1/memory/search
713
742
  api.registerTool((ctx) => {
743
+ const toolKey = resolveKey(ctx);
714
744
  return {
715
745
  label: "Memory Search",
716
746
  name: "memory_search",
@@ -725,6 +755,7 @@ const memoryRouterPlugin = {
725
755
  required: ["query"],
726
756
  } as any,
727
757
  execute: async (_toolCallId: string, params: Record<string, unknown>) => {
758
+ if (!toolKey) return jsonToolResult({ results: [], error: "No memory key configured for this agent" });
728
759
  const query = typeof params.query === "string" ? params.query.trim() : "";
729
760
  if (!query) return jsonToolResult({ results: [], error: "query required" });
730
761
  const limit = typeof params.maxResults === "number" ? params.maxResults : 50;
@@ -732,7 +763,7 @@ const memoryRouterPlugin = {
732
763
  const res = await fetch(`${endpoint}/v1/memory/search`, {
733
764
  method: "POST",
734
765
  headers: {
735
- Authorization: `Bearer ${memoryKey}`,
766
+ Authorization: `Bearer ${toolKey}`,
736
767
  "Content-Type": "application/json",
737
768
  ...(embeddings && { "X-Embedding-Model": embeddings }),
738
769
  },
@@ -807,7 +838,7 @@ const memoryRouterPlugin = {
807
838
  },
808
839
  };
809
840
  });
810
- } // end if (memoryKey)
841
+ } // end if (hasAnyKey)
811
842
 
812
843
  // ==================================================================
813
844
  // CLI Commands
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-memory",
3
- "version": "3.3.0",
3
+ "version": "3.5.0",
4
4
  "description": "MemoryRouter persistent memory plugin for OpenClaw — your AI remembers every conversation",
5
5
  "type": "module",
6
6
  "files": [