memory-braid 0.4.7 → 0.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.
@@ -1,6 +1,11 @@
1
- import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import type { MemoryBraidResult } from "./types.js";
3
3
 
4
+ type ToolContext = {
5
+ config?: unknown;
6
+ sessionKey?: string;
7
+ };
8
+
4
9
  type AnyTool = {
5
10
  name: string;
6
11
  label?: string;
@@ -44,17 +49,17 @@ function extractDetailsPayload(value: unknown): unknown {
44
49
  return tryParseTextPayload(value);
45
50
  }
46
51
 
47
- export function resolveLocalTools(api: OpenClawPluginApi, ctx: OpenClawPluginToolContext): {
52
+ export function resolveLocalTools(api: OpenClawPluginApi, ctx: ToolContext): {
48
53
  searchTool: AnyTool | null;
49
54
  getTool: AnyTool | null;
50
55
  } {
51
56
  const searchTool = api.runtime.tools.createMemorySearchTool({
52
- config: ctx.config,
57
+ config: ctx.config as never,
53
58
  agentSessionKey: ctx.sessionKey,
54
59
  }) as unknown as AnyTool | null;
55
60
 
56
61
  const getTool = api.runtime.tools.createMemoryGetTool({
57
- config: ctx.config,
62
+ config: ctx.config as never,
58
63
  agentSessionKey: ctx.sessionKey,
59
64
  }) as unknown as AnyTool | null;
60
65
 
package/src/logger.ts CHANGED
@@ -99,10 +99,15 @@ export class MemoryBraidLogger {
99
99
  return;
100
100
  }
101
101
 
102
+ const sanitizedContext = sanitizeValue(
103
+ context,
104
+ this.cfg.maxSnippetChars,
105
+ this.cfg.includePayloads,
106
+ );
102
107
  const payload = {
103
108
  event,
104
109
  ts: new Date().toISOString(),
105
- ...sanitizeValue(context, this.cfg.maxSnippetChars, this.cfg.includePayloads),
110
+ ...(sanitizedContext && typeof sanitizedContext === "object" ? sanitizedContext : {}),
106
111
  };
107
112
  const message = `memory-braid ${JSON.stringify(payload)}`;
108
113
 
@@ -12,6 +12,8 @@ type CloudRecord = {
12
12
  memory?: string;
13
13
  data?: { memory?: string } | null;
14
14
  score?: number;
15
+ created_at?: string | Date;
16
+ updated_at?: string | Date;
15
17
  metadata?: Record<string, unknown> | null;
16
18
  };
17
19
 
@@ -20,7 +22,17 @@ type CloudClientLike = {
20
22
  messages: Array<{ role: "user" | "assistant"; content: string }>,
21
23
  options?: Record<string, unknown>,
22
24
  ) => Promise<CloudRecord[]>;
25
+ get: (memoryId: string) => Promise<CloudRecord>;
26
+ getAll: (options?: Record<string, unknown>) => Promise<CloudRecord[]>;
23
27
  search: (query: string, options?: Record<string, unknown>) => Promise<CloudRecord[]>;
28
+ update: (
29
+ memoryId: string,
30
+ data: {
31
+ text?: string;
32
+ metadata?: Record<string, unknown>;
33
+ timestamp?: number | string;
34
+ },
35
+ ) => Promise<CloudRecord[]>;
24
36
  delete: (memoryId: string) => Promise<unknown>;
25
37
  };
26
38
 
@@ -28,6 +40,8 @@ type OssRecord = {
28
40
  id?: string;
29
41
  memory?: string;
30
42
  score?: number;
43
+ createdAt?: string;
44
+ updatedAt?: string;
31
45
  metadata?: Record<string, unknown>;
32
46
  };
33
47
 
@@ -41,7 +55,16 @@ type OssClientLike = {
41
55
  messages: string | Array<{ role: string; content: string }>,
42
56
  options: Record<string, unknown>,
43
57
  ) => Promise<OssSearchResult>;
58
+ get?: (memoryId: string) => Promise<OssRecord | null>;
59
+ getAll: (options: Record<string, unknown>) => Promise<OssSearchResult>;
44
60
  search: (query: string, options: Record<string, unknown>) => Promise<OssSearchResult>;
61
+ update?: (memoryId: string, data: string) => Promise<{ message: string }>;
62
+ updateMemory?: (
63
+ memoryId: string,
64
+ data: string,
65
+ embeddings?: Record<string, number[]>,
66
+ metadata?: Record<string, unknown>,
67
+ ) => Promise<unknown>;
45
68
  delete: (memoryId: string) => Promise<{ message: string }>;
46
69
  };
47
70
 
@@ -72,20 +95,37 @@ function normalizeMetadata(value: unknown): Record<string, unknown> | undefined
72
95
  return value as Record<string, unknown>;
73
96
  }
74
97
 
75
- function buildCloudEntity(scope: ScopeKey): { user_id: string; agent_id: string; run_id?: string } {
98
+ function normalizeTimestamp(value: unknown): string | undefined {
99
+ if (value instanceof Date) {
100
+ return value.toISOString();
101
+ }
102
+ if (typeof value !== "string") {
103
+ return undefined;
104
+ }
105
+ const trimmed = value.trim();
106
+ return trimmed || undefined;
107
+ }
108
+
109
+ type ScopeFilter = {
110
+ workspaceHash: string;
111
+ agentId?: string;
112
+ sessionKey?: string;
113
+ };
114
+
115
+ function buildCloudEntity(scope: ScopeFilter): { user_id: string; agent_id?: string; run_id?: string } {
76
116
  const userId = `memory-braid:${scope.workspaceHash}`;
77
117
  return {
78
118
  user_id: userId,
79
- agent_id: scope.agentId,
119
+ ...(scope.agentId ? { agent_id: scope.agentId } : {}),
80
120
  run_id: scope.sessionKey,
81
121
  };
82
122
  }
83
123
 
84
- function buildOssEntity(scope: ScopeKey): { userId: string; agentId: string; runId?: string } {
124
+ function buildOssEntity(scope: ScopeFilter): { userId: string; agentId?: string; runId?: string } {
85
125
  const userId = `memory-braid:${scope.workspaceHash}`;
86
126
  return {
87
127
  userId,
88
- agentId: scope.agentId,
128
+ ...(scope.agentId ? { agentId: scope.agentId } : {}),
89
129
  runId: scope.sessionKey,
90
130
  };
91
131
  }
@@ -262,6 +302,52 @@ function resolveStateDir(explicitStateDir?: string): string {
262
302
  return path.resolve(resolved);
263
303
  }
264
304
 
305
+ function mapCloudRecord(record: CloudRecord): MemoryBraidResult | null {
306
+ const snippet = extractCloudText(record);
307
+ if (!snippet) {
308
+ return null;
309
+ }
310
+ const metadata = normalizeMetadata(record.metadata);
311
+ return {
312
+ id: record.id,
313
+ source: "mem0",
314
+ path: typeof metadata?.path === "string" ? metadata.path : undefined,
315
+ snippet,
316
+ score: typeof record.score === "number" ? record.score : 0,
317
+ metadata: {
318
+ ...(metadata ?? {}),
319
+ ...(normalizeTimestamp(record.created_at) ? { createdAt: normalizeTimestamp(record.created_at) } : {}),
320
+ ...(normalizeTimestamp(record.updated_at) ? { updatedAt: normalizeTimestamp(record.updated_at) } : {}),
321
+ },
322
+ chunkKey: typeof metadata?.chunkKey === "string" ? metadata.chunkKey : undefined,
323
+ contentHash:
324
+ typeof metadata?.contentHash === "string" ? metadata.contentHash : undefined,
325
+ };
326
+ }
327
+
328
+ function mapOssRecord(record: OssRecord): MemoryBraidResult | null {
329
+ const snippet = typeof record.memory === "string" ? record.memory.trim() : "";
330
+ if (!snippet) {
331
+ return null;
332
+ }
333
+ const metadata = normalizeMetadata(record.metadata);
334
+ return {
335
+ id: record.id,
336
+ source: "mem0",
337
+ path: typeof metadata?.path === "string" ? metadata.path : undefined,
338
+ snippet,
339
+ score: typeof record.score === "number" ? record.score : 0,
340
+ metadata: {
341
+ ...(metadata ?? {}),
342
+ ...(normalizeTimestamp(record.createdAt) ? { createdAt: normalizeTimestamp(record.createdAt) } : {}),
343
+ ...(normalizeTimestamp(record.updatedAt) ? { updatedAt: normalizeTimestamp(record.updatedAt) } : {}),
344
+ },
345
+ chunkKey: typeof metadata?.chunkKey === "string" ? metadata.chunkKey : undefined,
346
+ contentHash:
347
+ typeof metadata?.contentHash === "string" ? metadata.contentHash : undefined,
348
+ };
349
+ }
350
+
265
351
  export function resolveDefaultOssStoragePaths(stateDir?: string): {
266
352
  rootDir: string;
267
353
  historyDbPath: string;
@@ -640,8 +726,9 @@ export class Mem0Adapter {
640
726
 
641
727
  try {
642
728
  if (prepared.mode === "cloud") {
729
+ const client = prepared.client as CloudClientLike;
643
730
  const entity = buildCloudEntity(params.scope);
644
- const result = await prepared.client.add(
731
+ const result = await client.add(
645
732
  [{ role: "user", content: params.text }],
646
733
  {
647
734
  ...entity,
@@ -662,8 +749,9 @@ export class Mem0Adapter {
662
749
  return { id };
663
750
  }
664
751
 
752
+ const client = prepared.client as OssClientLike;
665
753
  const entity = buildOssEntity(params.scope);
666
- const result = await prepared.client.add([{ role: "user", content: params.text }], {
754
+ const result = await client.add([{ role: "user", content: params.text }], {
667
755
  ...entity,
668
756
  metadata: params.metadata,
669
757
  infer: true,
@@ -717,58 +805,25 @@ export class Mem0Adapter {
717
805
  try {
718
806
  let mapped: MemoryBraidResult[] = [];
719
807
  if (prepared.mode === "cloud") {
808
+ const client = prepared.client as CloudClientLike;
720
809
  const entity = buildCloudEntity(params.scope);
721
- const records = await prepared.client.search(params.query, {
810
+ const records = await client.search(params.query, {
722
811
  ...entity,
723
812
  limit: params.maxResults,
724
813
  });
725
-
726
814
  mapped = records
727
- .map((record) => {
728
- const snippet = extractCloudText(record);
729
- if (!snippet) {
730
- return null;
731
- }
732
- const metadata = normalizeMetadata(record.metadata);
733
- return {
734
- id: record.id,
735
- source: "mem0" as const,
736
- path: typeof metadata?.path === "string" ? metadata.path : undefined,
737
- snippet,
738
- score: typeof record.score === "number" ? record.score : 0,
739
- metadata,
740
- chunkKey: typeof metadata?.chunkKey === "string" ? metadata.chunkKey : undefined,
741
- contentHash:
742
- typeof metadata?.contentHash === "string" ? metadata.contentHash : undefined,
743
- };
744
- })
815
+ .map((record) => mapCloudRecord(record))
745
816
  .filter((entry): entry is MemoryBraidResult => Boolean(entry));
746
817
  } else {
818
+ const client = prepared.client as OssClientLike;
747
819
  const entity = buildOssEntity(params.scope);
748
- const result = await prepared.client.search(params.query, {
820
+ const result = await client.search(params.query, {
749
821
  ...entity,
750
822
  limit: params.maxResults,
751
823
  });
752
824
  const records = result.results ?? [];
753
825
  mapped = records
754
- .map((record) => {
755
- const snippet = typeof record.memory === "string" ? record.memory.trim() : "";
756
- if (!snippet) {
757
- return null;
758
- }
759
- const metadata = normalizeMetadata(record.metadata);
760
- return {
761
- id: record.id,
762
- source: "mem0" as const,
763
- path: typeof metadata?.path === "string" ? metadata.path : undefined,
764
- snippet,
765
- score: typeof record.score === "number" ? record.score : 0,
766
- metadata,
767
- chunkKey: typeof metadata?.chunkKey === "string" ? metadata.chunkKey : undefined,
768
- contentHash:
769
- typeof metadata?.contentHash === "string" ? metadata.contentHash : undefined,
770
- };
771
- })
826
+ .map((record) => mapOssRecord(record))
772
827
  .filter((entry): entry is MemoryBraidResult => Boolean(entry));
773
828
  }
774
829
 
@@ -797,6 +852,201 @@ export class Mem0Adapter {
797
852
  }
798
853
  }
799
854
 
855
+ async getMemory(params: {
856
+ memoryId?: string;
857
+ scope: ScopeFilter;
858
+ runId?: string;
859
+ }): Promise<MemoryBraidResult | undefined> {
860
+ if (!params.memoryId) {
861
+ return undefined;
862
+ }
863
+
864
+ const prepared = await this.ensureClient();
865
+ if (!prepared) {
866
+ return undefined;
867
+ }
868
+
869
+ const startedAt = Date.now();
870
+ try {
871
+ if (prepared.mode === "cloud") {
872
+ const client = prepared.client as CloudClientLike;
873
+ const record = await client.get(params.memoryId);
874
+ const mapped = mapCloudRecord(record);
875
+ this.log.debug("memory_braid.mem0.response", {
876
+ runId: params.runId,
877
+ action: "get",
878
+ mode: prepared.mode,
879
+ workspaceHash: params.scope.workspaceHash,
880
+ agentId: params.scope.agentId,
881
+ durMs: Date.now() - startedAt,
882
+ found: Boolean(mapped),
883
+ });
884
+ return mapped ?? undefined;
885
+ }
886
+
887
+ const client = prepared.client as OssClientLike;
888
+ if (!client.get) {
889
+ return undefined;
890
+ }
891
+ const record = await client.get(params.memoryId);
892
+ const mapped = record ? mapOssRecord(record) : null;
893
+ this.log.debug("memory_braid.mem0.response", {
894
+ runId: params.runId,
895
+ action: "get",
896
+ mode: prepared.mode,
897
+ workspaceHash: params.scope.workspaceHash,
898
+ agentId: params.scope.agentId,
899
+ durMs: Date.now() - startedAt,
900
+ found: Boolean(mapped),
901
+ });
902
+ return mapped ?? undefined;
903
+ } catch (err) {
904
+ this.log.warn("memory_braid.mem0.error", {
905
+ runId: params.runId,
906
+ action: "get",
907
+ mode: prepared.mode,
908
+ workspaceHash: params.scope.workspaceHash,
909
+ agentId: params.scope.agentId,
910
+ durMs: Date.now() - startedAt,
911
+ error: err instanceof Error ? err.message : String(err),
912
+ });
913
+ return undefined;
914
+ }
915
+ }
916
+
917
+ async getAllMemories(params: {
918
+ scope: ScopeFilter;
919
+ limit?: number;
920
+ runId?: string;
921
+ }): Promise<MemoryBraidResult[]> {
922
+ const prepared = await this.ensureClient();
923
+ if (!prepared) {
924
+ return [];
925
+ }
926
+
927
+ const startedAt = Date.now();
928
+ const limit = Math.max(1, Math.min(2000, Math.round(params.limit ?? 500)));
929
+ try {
930
+ let mapped: MemoryBraidResult[] = [];
931
+ if (prepared.mode === "cloud") {
932
+ const client = prepared.client as CloudClientLike;
933
+ const entity = buildCloudEntity(params.scope);
934
+ const pageSize = Math.min(100, limit);
935
+ const records: CloudRecord[] = [];
936
+ let page = 1;
937
+
938
+ while (records.length < limit) {
939
+ const batch = await client.getAll({
940
+ ...entity,
941
+ page,
942
+ page_size: pageSize,
943
+ });
944
+ if (!Array.isArray(batch) || batch.length === 0) {
945
+ break;
946
+ }
947
+ records.push(...batch);
948
+ if (batch.length < pageSize) {
949
+ break;
950
+ }
951
+ page += 1;
952
+ }
953
+
954
+ mapped = records
955
+ .slice(0, limit)
956
+ .map((record) => mapCloudRecord(record))
957
+ .filter((entry): entry is MemoryBraidResult => Boolean(entry));
958
+ } else {
959
+ const client = prepared.client as OssClientLike;
960
+ const entity = buildOssEntity(params.scope);
961
+ const result = await client.getAll({
962
+ ...entity,
963
+ limit,
964
+ });
965
+ mapped = (result.results ?? [])
966
+ .map((record) => mapOssRecord(record))
967
+ .filter((entry): entry is MemoryBraidResult => Boolean(entry));
968
+ }
969
+
970
+ this.log.debug("memory_braid.mem0.response", {
971
+ runId: params.runId,
972
+ action: "get_all",
973
+ mode: prepared.mode,
974
+ workspaceHash: params.scope.workspaceHash,
975
+ agentId: params.scope.agentId,
976
+ durMs: Date.now() - startedAt,
977
+ count: mapped.length,
978
+ });
979
+ return mapped;
980
+ } catch (err) {
981
+ this.log.warn("memory_braid.mem0.error", {
982
+ runId: params.runId,
983
+ action: "get_all",
984
+ mode: prepared.mode,
985
+ workspaceHash: params.scope.workspaceHash,
986
+ agentId: params.scope.agentId,
987
+ durMs: Date.now() - startedAt,
988
+ error: err instanceof Error ? err.message : String(err),
989
+ });
990
+ return [];
991
+ }
992
+ }
993
+
994
+ async updateMemoryMetadata(params: {
995
+ memoryId?: string;
996
+ scope: ScopeFilter;
997
+ text: string;
998
+ metadata: Record<string, unknown>;
999
+ runId?: string;
1000
+ }): Promise<boolean> {
1001
+ if (!params.memoryId) {
1002
+ return false;
1003
+ }
1004
+
1005
+ const prepared = await this.ensureClient();
1006
+ if (!prepared) {
1007
+ return false;
1008
+ }
1009
+
1010
+ const startedAt = Date.now();
1011
+ try {
1012
+ if (prepared.mode === "cloud") {
1013
+ const client = prepared.client as CloudClientLike;
1014
+ await client.update(params.memoryId, {
1015
+ text: params.text,
1016
+ metadata: params.metadata,
1017
+ });
1018
+ } else {
1019
+ const client = prepared.client as OssClientLike;
1020
+ if (typeof client.updateMemory !== "function") {
1021
+ return false;
1022
+ }
1023
+ await client.updateMemory(params.memoryId, params.text, {}, params.metadata);
1024
+ }
1025
+
1026
+ this.log.debug("memory_braid.mem0.response", {
1027
+ runId: params.runId,
1028
+ action: "update_metadata",
1029
+ mode: prepared.mode,
1030
+ workspaceHash: params.scope.workspaceHash,
1031
+ agentId: params.scope.agentId,
1032
+ durMs: Date.now() - startedAt,
1033
+ updated: true,
1034
+ });
1035
+ return true;
1036
+ } catch (err) {
1037
+ this.log.warn("memory_braid.mem0.error", {
1038
+ runId: params.runId,
1039
+ action: "update_metadata",
1040
+ mode: prepared.mode,
1041
+ workspaceHash: params.scope.workspaceHash,
1042
+ agentId: params.scope.agentId,
1043
+ durMs: Date.now() - startedAt,
1044
+ error: err instanceof Error ? err.message : String(err),
1045
+ });
1046
+ return false;
1047
+ }
1048
+ }
1049
+
800
1050
  async deleteMemory(params: {
801
1051
  memoryId?: string;
802
1052
  scope: ScopeKey;