memorylake-openclaw 0.0.6 → 0.0.7

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/index.ts CHANGED
@@ -10,6 +10,8 @@
10
10
  * - CLI: openclaw memorylake search, openclaw memorylake stats
11
11
  */
12
12
 
13
+ import fs from "node:fs";
14
+ import path from "node:path";
13
15
  import got from "got";
14
16
  import { Type } from "@sinclair/typebox";
15
17
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
@@ -530,6 +532,52 @@ const memoryPlugin = {
530
532
  const cfg = memoryLakeConfigSchema.parse(api.pluginConfig);
531
533
  const provider: MemoryLakeProvider = new PlatformProvider(cfg.host, cfg.apiKey, cfg.projectId);
532
534
 
535
+ // Provider cache: avoids re-creating providers for the same host+apiKey+projectId
536
+ const providerCache = new Map<string, MemoryLakeProvider>();
537
+ const globalProviderKey = `${cfg.host}|${cfg.apiKey}|${cfg.projectId}`;
538
+ providerCache.set(globalProviderKey, provider);
539
+
540
+ function getProvider(effectiveCfg: MemoryLakeConfig): MemoryLakeProvider {
541
+ const key = `${effectiveCfg.host}|${effectiveCfg.apiKey}|${effectiveCfg.projectId}`;
542
+ let p = providerCache.get(key);
543
+ if (!p) {
544
+ p = new PlatformProvider(effectiveCfg.host, effectiveCfg.apiKey, effectiveCfg.projectId);
545
+ providerCache.set(key, p);
546
+ }
547
+ return p;
548
+ }
549
+
550
+ function resolveConfig(ctx: any): MemoryLakeConfig {
551
+ const workspaceDir = ctx?.workspaceDir;
552
+ if (!workspaceDir) return cfg;
553
+
554
+ const localPath = path.join(workspaceDir, ".memorylake", "config.json");
555
+ if (!fs.existsSync(localPath)) return cfg;
556
+
557
+ try {
558
+ const raw = JSON.parse(fs.readFileSync(localPath, "utf-8"));
559
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
560
+ api.logger.warn(
561
+ `memorylake-openclaw: workspace config exists but is not a JSON object; falling back to global config (path: ${localPath})`,
562
+ );
563
+ return cfg;
564
+ }
565
+
566
+ const allowed = new Set(ALLOWED_KEYS);
567
+ const overrides: Record<string, unknown> = {};
568
+ for (const [key, value] of Object.entries(raw)) {
569
+ if (allowed.has(key)) overrides[key] = value;
570
+ }
571
+
572
+ return { ...cfg, ...overrides } as MemoryLakeConfig;
573
+ } catch {
574
+ api.logger.warn(
575
+ `memorylake-openclaw: failed to parse workspace config JSON; falling back to global config (path: ${localPath})`,
576
+ );
577
+ return cfg;
578
+ }
579
+ }
580
+
533
581
  // Track current session ID for tool-level session scoping
534
582
  let currentSessionId: string | undefined;
535
583
 
@@ -538,9 +586,9 @@ const memoryPlugin = {
538
586
  );
539
587
 
540
588
  // Helper: build add options
541
- function buildAddOptions(userIdOverride?: string, sessionId?: string): AddOptions {
589
+ function buildAddOptions(effectiveCfg: MemoryLakeConfig, userIdOverride?: string, sessionId?: string): AddOptions {
542
590
  const opts: AddOptions = {
543
- user_id: userIdOverride || cfg.userId,
591
+ user_id: userIdOverride || effectiveCfg.userId,
544
592
  infer: true,
545
593
  metadata: { source: "OPENCLAW" },
546
594
  };
@@ -550,14 +598,15 @@ const memoryPlugin = {
550
598
 
551
599
  // Helper: build search options
552
600
  function buildSearchOptions(
601
+ effectiveCfg: MemoryLakeConfig,
553
602
  userIdOverride?: string,
554
603
  limit?: number,
555
604
  ): SearchOptions {
556
605
  return {
557
- user_id: userIdOverride || cfg.userId,
558
- top_k: limit ?? cfg.topK,
559
- threshold: cfg.searchThreshold,
560
- rerank: cfg.rerank,
606
+ user_id: userIdOverride || effectiveCfg.userId,
607
+ top_k: limit ?? effectiveCfg.topK,
608
+ threshold: effectiveCfg.searchThreshold,
609
+ rerank: effectiveCfg.rerank,
561
610
  };
562
611
  }
563
612
 
@@ -566,7 +615,7 @@ const memoryPlugin = {
566
615
  // ========================================================================
567
616
 
568
617
  api.registerTool(
569
- {
618
+ (ctx) => ({
570
619
  name: "memory_search",
571
620
  label: "Memory Search",
572
621
  description:
@@ -596,6 +645,8 @@ const memoryPlugin = {
596
645
  ),
597
646
  }),
598
647
  async execute(_toolCallId, params) {
648
+ const effectiveCfg = resolveConfig(ctx);
649
+ const effectiveProvider = getProvider(effectiveCfg);
599
650
  const { query, limit, userId, scope = "all" } = params as {
600
651
  query: string;
601
652
  limit?: number;
@@ -604,9 +655,9 @@ const memoryPlugin = {
604
655
  };
605
656
 
606
657
  try {
607
- const results = await provider.search(
658
+ const results = await effectiveProvider.search(
608
659
  query,
609
- buildSearchOptions(userId, limit),
660
+ buildSearchOptions(effectiveCfg, userId, limit),
610
661
  );
611
662
 
612
663
  if (!results || results.length === 0) {
@@ -652,12 +703,12 @@ const memoryPlugin = {
652
703
  };
653
704
  }
654
705
  },
655
- },
706
+ }),
656
707
  { name: "memory_search" },
657
708
  );
658
709
 
659
710
  api.registerTool(
660
- {
711
+ (ctx) => ({
661
712
  name: "memory_store",
662
713
  label: "Memory Store",
663
714
  description:
@@ -676,6 +727,8 @@ const memoryPlugin = {
676
727
  ),
677
728
  }),
678
729
  async execute(_toolCallId, params) {
730
+ const effectiveCfg = resolveConfig(ctx);
731
+ const effectiveProvider = getProvider(effectiveCfg);
679
732
  const { text, userId } = params as {
680
733
  text: string;
681
734
  userId?: string;
@@ -683,9 +736,9 @@ const memoryPlugin = {
683
736
  };
684
737
 
685
738
  try {
686
- const result = await provider.add(
739
+ const result = await effectiveProvider.add(
687
740
  [{ role: "user", content: text }],
688
- buildAddOptions(userId, currentSessionId),
741
+ buildAddOptions(effectiveCfg, userId, currentSessionId),
689
742
  );
690
743
 
691
744
  const count = result.results?.length ?? 0;
@@ -716,12 +769,12 @@ const memoryPlugin = {
716
769
  };
717
770
  }
718
771
  },
719
- },
772
+ }),
720
773
  { name: "memory_store" },
721
774
  );
722
775
 
723
776
  api.registerTool(
724
- {
777
+ (ctx) => ({
725
778
  name: "memory_get",
726
779
  label: "Memory Get",
727
780
  description: "Retrieve a specific memory by its ID from MemoryLake.",
@@ -729,10 +782,12 @@ const memoryPlugin = {
729
782
  memoryId: Type.String({ description: "The memory ID to retrieve" }),
730
783
  }),
731
784
  async execute(_toolCallId, params) {
785
+ const effectiveCfg = resolveConfig(ctx);
786
+ const effectiveProvider = getProvider(effectiveCfg);
732
787
  const { memoryId } = params as { memoryId: string };
733
788
 
734
789
  try {
735
- const memory = await provider.get(memoryId);
790
+ const memory = await effectiveProvider.get(memoryId);
736
791
 
737
792
  return {
738
793
  content: [
@@ -755,12 +810,12 @@ const memoryPlugin = {
755
810
  };
756
811
  }
757
812
  },
758
- },
813
+ }),
759
814
  { name: "memory_get" },
760
815
  );
761
816
 
762
817
  api.registerTool(
763
- {
818
+ (ctx) => ({
764
819
  name: "memory_list",
765
820
  label: "Memory List",
766
821
  description:
@@ -784,11 +839,13 @@ const memoryPlugin = {
784
839
  ),
785
840
  }),
786
841
  async execute(_toolCallId, params) {
842
+ const effectiveCfg = resolveConfig(ctx);
843
+ const effectiveProvider = getProvider(effectiveCfg);
787
844
  const { userId, scope = "all" } = params as { userId?: string; scope?: "session" | "long-term" | "all" };
788
845
 
789
846
  try {
790
- const uid = userId || cfg.userId;
791
- const memories = await provider.getAll({ user_id: uid });
847
+ const uid = userId || effectiveCfg.userId;
848
+ const memories = await effectiveProvider.getAll({ user_id: uid });
792
849
 
793
850
  if (!memories || memories.length === 0) {
794
851
  return {
@@ -833,12 +890,12 @@ const memoryPlugin = {
833
890
  };
834
891
  }
835
892
  },
836
- },
893
+ }),
837
894
  { name: "memory_list" },
838
895
  );
839
896
 
840
897
  api.registerTool(
841
- {
898
+ (ctx) => ({
842
899
  name: "memory_forget",
843
900
  label: "Memory Forget",
844
901
  description:
@@ -847,10 +904,12 @@ const memoryPlugin = {
847
904
  memoryId: Type.String({ description: "Memory ID to delete" }),
848
905
  }),
849
906
  async execute(_toolCallId, params) {
907
+ const effectiveCfg = resolveConfig(ctx);
908
+ const effectiveProvider = getProvider(effectiveCfg);
850
909
  const { memoryId } = params as { memoryId: string };
851
910
 
852
911
  try {
853
- await provider.delete(memoryId);
912
+ await effectiveProvider.delete(memoryId);
854
913
  return {
855
914
  content: [
856
915
  { type: "text", text: `Memory ${memoryId} forgotten.` },
@@ -869,12 +928,12 @@ const memoryPlugin = {
869
928
  };
870
929
  }
871
930
  },
872
- },
931
+ }),
873
932
  { name: "memory_forget" },
874
933
  );
875
934
 
876
935
  api.registerTool(
877
- {
936
+ (ctx) => ({
878
937
  name: "document_search",
879
938
  label: "Document Search",
880
939
  description:
@@ -889,12 +948,14 @@ const memoryPlugin = {
889
948
  ),
890
949
  }),
891
950
  async execute(_toolCallId, params) {
951
+ const effectiveCfg = resolveConfig(ctx);
952
+ const effectiveProvider = getProvider(effectiveCfg);
892
953
  const { query, topN } = params as { query: string; topN?: number };
893
954
 
894
955
  try {
895
- const response = await provider.searchDocuments(
956
+ const response = await effectiveProvider.searchDocuments(
896
957
  query,
897
- topN ?? cfg.topK,
958
+ topN ?? effectiveCfg.topK,
898
959
  );
899
960
 
900
961
  if (!response.results || response.results.length === 0) {
@@ -929,12 +990,12 @@ const memoryPlugin = {
929
990
  };
930
991
  }
931
992
  },
932
- },
993
+ }),
933
994
  { name: "document_search" },
934
995
  );
935
996
 
936
997
  api.registerTool(
937
- {
998
+ (ctx) => ({
938
999
  name: "advanced_web_search",
939
1000
  label: "Advanced Web Search",
940
1001
  description:
@@ -986,6 +1047,8 @@ const memoryPlugin = {
986
1047
  ),
987
1048
  }),
988
1049
  async execute(_toolCallId, params) {
1050
+ const effectiveCfg = resolveConfig(ctx);
1051
+ const effectiveProvider = getProvider(effectiveCfg);
989
1052
  const {
990
1053
  query,
991
1054
  domain: rawDomain,
@@ -1005,18 +1068,18 @@ const memoryPlugin = {
1005
1068
  : normalizeWebSearchDomain(rawDomain);
1006
1069
 
1007
1070
  try {
1008
- const response = await provider.searchWeb(query, {
1071
+ const response = await effectiveProvider.searchWeb(query, {
1009
1072
  domain,
1010
- max_results: maxResults ?? cfg.topK,
1073
+ max_results: maxResults ?? effectiveCfg.topK,
1011
1074
  start_date: startDate,
1012
1075
  end_date: endDate,
1013
- include_domains: cfg.webSearchIncludeDomains,
1014
- exclude_domains: cfg.webSearchExcludeDomains,
1076
+ include_domains: effectiveCfg.webSearchIncludeDomains,
1077
+ exclude_domains: effectiveCfg.webSearchExcludeDomains,
1015
1078
  user_location:
1016
- cfg.webSearchCountry || cfg.webSearchTimezone
1079
+ effectiveCfg.webSearchCountry || effectiveCfg.webSearchTimezone
1017
1080
  ? {
1018
- country: cfg.webSearchCountry,
1019
- timezone: cfg.webSearchTimezone,
1081
+ country: effectiveCfg.webSearchCountry,
1082
+ timezone: effectiveCfg.webSearchTimezone,
1020
1083
  }
1021
1084
  : undefined,
1022
1085
  });
@@ -1057,7 +1120,7 @@ const memoryPlugin = {
1057
1120
  };
1058
1121
  }
1059
1122
  },
1060
- },
1123
+ }),
1061
1124
  { optional: true },
1062
1125
  );
1063
1126
 
@@ -1081,7 +1144,7 @@ const memoryPlugin = {
1081
1144
  const limit = parseInt(opts.limit, 10);
1082
1145
  const results = await provider.search(
1083
1146
  query,
1084
- buildSearchOptions(undefined, limit),
1147
+ buildSearchOptions(cfg, undefined, limit),
1085
1148
  );
1086
1149
 
1087
1150
  if (!results.length) {
@@ -1133,13 +1196,17 @@ const memoryPlugin = {
1133
1196
  api.on("before_agent_start", async (event, ctx) => {
1134
1197
  if (!event.prompt || event.prompt.length < 5) return;
1135
1198
 
1199
+ // Resolve per-workspace config override
1200
+ const effectiveCfg = resolveConfig(ctx);
1201
+ const effectiveProvider = getProvider(effectiveCfg);
1202
+
1136
1203
  // Track session ID
1137
1204
  const sessionId = (ctx as any)?.sessionKey ?? undefined;
1138
1205
  if (sessionId) currentSessionId = sessionId;
1139
1206
 
1140
1207
  const [memoryResult, docResult] = await Promise.allSettled([
1141
- provider.search(event.prompt, buildSearchOptions()),
1142
- provider.searchDocuments(event.prompt, cfg.topK),
1208
+ effectiveProvider.search(event.prompt, buildSearchOptions(effectiveCfg)),
1209
+ effectiveProvider.searchDocuments(event.prompt, effectiveCfg.topK),
1143
1210
  ]);
1144
1211
 
1145
1212
  const contextParts: string[] = [];
@@ -1183,6 +1250,10 @@ const memoryPlugin = {
1183
1250
  return;
1184
1251
  }
1185
1252
 
1253
+ // Resolve per-workspace config override
1254
+ const effectiveCfg = resolveConfig(ctx);
1255
+ const effectiveProvider = getProvider(effectiveCfg);
1256
+
1186
1257
  // Track session ID
1187
1258
  const sessionId = (ctx as any)?.sessionKey ?? undefined;
1188
1259
  if (sessionId) currentSessionId = sessionId;
@@ -1240,8 +1311,8 @@ const memoryPlugin = {
1240
1311
 
1241
1312
  if (formattedMessages.length === 0) return;
1242
1313
 
1243
- const addOpts = buildAddOptions(undefined, currentSessionId);
1244
- const result = await provider.add(
1314
+ const addOpts = buildAddOptions(effectiveCfg, undefined, currentSessionId);
1315
+ const result = await effectiveProvider.add(
1245
1316
  formattedMessages,
1246
1317
  addOpts,
1247
1318
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorylake-openclaw",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "type": "module",
5
5
  "description": "MemoryLake memory backend for OpenClaw",
6
6
  "license": "MIT",
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: agent-memorylake-config
3
+ description: Use when the user asks to configure agent-specific memorylake properties (e.g. projectId) for the current agent. Writes to the agent-specific config file which overrides the global config.
4
+ ---
5
+
6
+ # Agent MemoryLake Config
7
+
8
+ Configure agent-specific memorylake properties for the current agent. The config file is located at `{workspace}/.memorylake/config.json`, where `{workspace}` is the agent's workspace directory. This config overrides corresponding properties from the global config.
9
+
10
+ ## Step 1 — Confirm projectId
11
+
12
+ Ask the user for the `projectId` to configure.
13
+
14
+ If the user has already provided the `projectId` in their message, skip the question and proceed directly.
15
+
16
+ ## Step 2 — Write Config
17
+
18
+ 1. Ensure the `.memorylake/` directory exists inside the agent's workspace directory:
19
+
20
+ ```bash
21
+ cd {workspace}
22
+ mkdir -p .memorylake
23
+ ```
24
+
25
+ 2. If `.memorylake/config.json` already exists, read it first and merge the new properties into the existing config. Do NOT overwrite properties the user did not mention.
26
+
27
+ 3. If `.memorylake/config.json` does not exist, create it with the provided properties.
28
+
29
+ 4. Write the config file. Example format:
30
+
31
+ ```json
32
+ {
33
+ "projectId": "xxx"
34
+ }
35
+ ```
36
+
37
+ ## Step 3 — Confirm Result
38
+
39
+ Read the written `.memorylake/config.json` and confirm to the user that the configuration is complete.
40
+
41
+ ## Common Mistakes
42
+
43
+ - Do NOT overwrite existing properties that the user did not mention — always merge