mr-memory 3.7.3 → 3.7.5
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.js +20 -68
- package/index.ts +18 -64
- package/memoryrouter-api.js +49 -0
- package/memoryrouter-api.ts +60 -0
- package/package.json +5 -1
- package/upload-api.js +40 -0
- package/upload-api.ts +45 -0
- package/upload.js +9 -34
- package/upload.ts +9 -39
package/index.js
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* BYOK — provider API keys never touch MemoryRouter.
|
|
9
9
|
*/
|
|
10
10
|
import { readFile, lstat, writeFile, copyFile, mkdir } from "node:fs/promises";
|
|
11
|
+
import { deleteMemories, getMemoryStats, ingestMemory, prepareMemory, searchMemory } from "./memoryrouter-api.js";
|
|
11
12
|
import { join, resolve, relative, isAbsolute, sep, dirname } from "node:path";
|
|
13
|
+
import os from "node:os";
|
|
12
14
|
const DEFAULT_ENDPOINT = "https://api.memoryrouter.ai";
|
|
13
15
|
/** Strip media-attached references and OpenClaw media instruction text so
|
|
14
16
|
* old screenshots/photos/audio don't get stored and re-injected forever. */
|
|
@@ -264,9 +266,6 @@ const WORKSPACE_FILES = [
|
|
|
264
266
|
"IDENTITY.md", "USER.md", "MEMORY.md", "HEARTBEAT.md",
|
|
265
267
|
"TOOLS.md", "AGENTS.md", "SOUL.md", "BOOTSTRAP.md",
|
|
266
268
|
];
|
|
267
|
-
// ──────────────────────────────────────────────────────
|
|
268
|
-
// Helpers
|
|
269
|
-
// ──────────────────────────────────────────────────────
|
|
270
269
|
function openClawConfigPath() {
|
|
271
270
|
return process.env.OPENCLAW_CONFIG || join(os.homedir(), ".openclaw", "openclaw.json");
|
|
272
271
|
}
|
|
@@ -290,7 +289,8 @@ async function writeOpenClawConfigFile(config) {
|
|
|
290
289
|
if (err?.code !== "ENOENT")
|
|
291
290
|
throw err;
|
|
292
291
|
}
|
|
293
|
-
await writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
292
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
293
|
+
`, "utf8");
|
|
294
294
|
}
|
|
295
295
|
async function updateOpenClawPluginEntry(api, patch) {
|
|
296
296
|
const config = await readOpenClawConfigFile();
|
|
@@ -544,17 +544,10 @@ If setting up MemoryRouter for another agent:
|
|
|
544
544
|
contextPayload.push({ role: "user", content: taskPrompt });
|
|
545
545
|
}
|
|
546
546
|
}
|
|
547
|
-
const res = await
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
Authorization: `Bearer ${activeKey}`,
|
|
552
|
-
},
|
|
553
|
-
body: JSON.stringify({
|
|
554
|
-
messages: contextPayload,
|
|
555
|
-
density,
|
|
556
|
-
...(embeddings && { embeddings }),
|
|
557
|
-
}),
|
|
547
|
+
const res = await prepareMemory(endpoint, activeKey, {
|
|
548
|
+
messages: contextPayload,
|
|
549
|
+
density,
|
|
550
|
+
...(embeddings && { embeddings }),
|
|
558
551
|
});
|
|
559
552
|
if (!res.ok) {
|
|
560
553
|
const errBody = await res.json().catch(() => null);
|
|
@@ -649,17 +642,10 @@ If setting up MemoryRouter for another agent:
|
|
|
649
642
|
}
|
|
650
643
|
}
|
|
651
644
|
// 4. Call /v1/memory/prepare (no session_id — always search core vault)
|
|
652
|
-
const res = await
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
Authorization: `Bearer ${activeKey}`,
|
|
657
|
-
},
|
|
658
|
-
body: JSON.stringify({
|
|
659
|
-
messages: contextPayload,
|
|
660
|
-
density,
|
|
661
|
-
...(embeddings && { embeddings }),
|
|
662
|
-
}),
|
|
645
|
+
const res = await prepareMemory(endpoint, activeKey, {
|
|
646
|
+
messages: contextPayload,
|
|
647
|
+
density,
|
|
648
|
+
...(embeddings && { embeddings }),
|
|
663
649
|
});
|
|
664
650
|
if (!res.ok) {
|
|
665
651
|
const errBody = await res.json().catch(() => null);
|
|
@@ -761,17 +747,10 @@ If setting up MemoryRouter for another agent:
|
|
|
761
747
|
return;
|
|
762
748
|
}
|
|
763
749
|
// Fire and forget — don't block message delivery waiting for ingest
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
Authorization: `Bearer ${activeKey}`,
|
|
769
|
-
},
|
|
770
|
-
body: JSON.stringify({
|
|
771
|
-
messages: toStore,
|
|
772
|
-
model: "unknown",
|
|
773
|
-
...(embeddings && { embeddings }),
|
|
774
|
-
}),
|
|
750
|
+
ingestMemory(endpoint, activeKey, {
|
|
751
|
+
messages: toStore,
|
|
752
|
+
model: "unknown",
|
|
753
|
+
...(embeddings && { embeddings }),
|
|
775
754
|
}).then(async (res) => {
|
|
776
755
|
if (!res.ok) {
|
|
777
756
|
const details = await res.text().catch(() => "");
|
|
@@ -821,15 +800,7 @@ If setting up MemoryRouter for another agent:
|
|
|
821
800
|
return jsonToolResult({ results: [], error: "query required" });
|
|
822
801
|
const limit = typeof params.maxResults === "number" ? params.maxResults : 50;
|
|
823
802
|
try {
|
|
824
|
-
const res = await
|
|
825
|
-
method: "POST",
|
|
826
|
-
headers: {
|
|
827
|
-
Authorization: `Bearer ${toolKey}`,
|
|
828
|
-
"Content-Type": "application/json",
|
|
829
|
-
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
830
|
-
},
|
|
831
|
-
body: JSON.stringify({ query, limit }),
|
|
832
|
-
});
|
|
803
|
+
const res = await searchMemory(endpoint, toolKey, { query, limit, embeddings });
|
|
833
804
|
if (!res.ok) {
|
|
834
805
|
const errBody = await res.text().catch(() => "");
|
|
835
806
|
return jsonToolResult({ results: [], error: `Search failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
|
|
@@ -1022,12 +993,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1022
993
|
return;
|
|
1023
994
|
}
|
|
1024
995
|
try {
|
|
1025
|
-
const
|
|
1026
|
-
? `${endpoint}/v1/memory/stats?embeddings=${encodeURIComponent(embeddings)}`
|
|
1027
|
-
: `${endpoint}/v1/memory/stats`;
|
|
1028
|
-
const res = await fetch(statsUrl, {
|
|
1029
|
-
headers: { Authorization: `Bearer ${effectiveKey}` },
|
|
1030
|
-
});
|
|
996
|
+
const res = await getMemoryStats(endpoint, effectiveKey, embeddings);
|
|
1031
997
|
const data = (await res.json());
|
|
1032
998
|
if (opts.json) {
|
|
1033
999
|
console.log(JSON.stringify({ enabled: true, key: effectiveKey, density, stats: data }, null, 2));
|
|
@@ -1085,15 +1051,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1085
1051
|
}
|
|
1086
1052
|
const limit = parseInt(opts.limit, 10) || 50;
|
|
1087
1053
|
try {
|
|
1088
|
-
const res = await
|
|
1089
|
-
method: "POST",
|
|
1090
|
-
headers: {
|
|
1091
|
-
Authorization: `Bearer ${effectiveKey}`,
|
|
1092
|
-
"Content-Type": "application/json",
|
|
1093
|
-
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
1094
|
-
},
|
|
1095
|
-
body: JSON.stringify({ query, limit }),
|
|
1096
|
-
});
|
|
1054
|
+
const res = await searchMemory(endpoint, effectiveKey, { query, limit, embeddings });
|
|
1097
1055
|
if (!res.ok) {
|
|
1098
1056
|
const err = await res.json();
|
|
1099
1057
|
console.error(`Search failed: ${err.error || `HTTP ${res.status}`}`);
|
|
@@ -1141,13 +1099,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1141
1099
|
return;
|
|
1142
1100
|
}
|
|
1143
1101
|
try {
|
|
1144
|
-
const
|
|
1145
|
-
? `${endpoint}/v1/memory?embeddings=${encodeURIComponent(embeddings)}`
|
|
1146
|
-
: `${endpoint}/v1/memory`;
|
|
1147
|
-
const res = await fetch(deleteUrl, {
|
|
1148
|
-
method: "DELETE",
|
|
1149
|
-
headers: { Authorization: `Bearer ${effectiveKey}` },
|
|
1150
|
-
});
|
|
1102
|
+
const res = await deleteMemories(endpoint, effectiveKey, embeddings);
|
|
1151
1103
|
const data = (await res.json());
|
|
1152
1104
|
const modelLabel = embeddings ? ` (${embeddings})` : "";
|
|
1153
1105
|
console.log(`✓ ${data.message || `Vault cleared${modelLabel}`}`);
|
package/index.ts
CHANGED
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { readFile, readdir, stat, lstat, writeFile, copyFile, mkdir } from "node:fs/promises";
|
|
12
|
+
import { deleteMemories, getMemoryStats, ingestMemory, prepareMemory, searchMemory } from "./memoryrouter-api.js";
|
|
12
13
|
import { join, resolve, relative, isAbsolute, sep, dirname } from "node:path";
|
|
13
14
|
import path from "node:path";
|
|
15
|
+
import os from "node:os";
|
|
14
16
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
15
17
|
|
|
16
18
|
const DEFAULT_ENDPOINT = "https://api.memoryrouter.ai";
|
|
@@ -569,17 +571,10 @@ If setting up MemoryRouter for another agent:
|
|
|
569
571
|
}
|
|
570
572
|
|
|
571
573
|
|
|
572
|
-
const res = await
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
Authorization: `Bearer ${activeKey}`,
|
|
577
|
-
},
|
|
578
|
-
body: JSON.stringify({
|
|
579
|
-
messages: contextPayload,
|
|
580
|
-
density,
|
|
581
|
-
...(embeddings && { embeddings }),
|
|
582
|
-
}),
|
|
574
|
+
const res = await prepareMemory(endpoint, activeKey, {
|
|
575
|
+
messages: contextPayload,
|
|
576
|
+
density,
|
|
577
|
+
...(embeddings && { embeddings }),
|
|
583
578
|
});
|
|
584
579
|
|
|
585
580
|
if (!res.ok) {
|
|
@@ -685,17 +680,10 @@ If setting up MemoryRouter for another agent:
|
|
|
685
680
|
|
|
686
681
|
// 4. Call /v1/memory/prepare (no session_id — always search core vault)
|
|
687
682
|
|
|
688
|
-
const res = await
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
Authorization: `Bearer ${activeKey}`,
|
|
693
|
-
},
|
|
694
|
-
body: JSON.stringify({
|
|
695
|
-
messages: contextPayload,
|
|
696
|
-
density,
|
|
697
|
-
...(embeddings && { embeddings }),
|
|
698
|
-
}),
|
|
683
|
+
const res = await prepareMemory(endpoint, activeKey, {
|
|
684
|
+
messages: contextPayload,
|
|
685
|
+
density,
|
|
686
|
+
...(embeddings && { embeddings }),
|
|
699
687
|
});
|
|
700
688
|
|
|
701
689
|
if (!res.ok) {
|
|
@@ -810,17 +798,10 @@ If setting up MemoryRouter for another agent:
|
|
|
810
798
|
}
|
|
811
799
|
|
|
812
800
|
// Fire and forget — don't block message delivery waiting for ingest
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
Authorization: `Bearer ${activeKey}`,
|
|
818
|
-
},
|
|
819
|
-
body: JSON.stringify({
|
|
820
|
-
messages: toStore,
|
|
821
|
-
model: "unknown",
|
|
822
|
-
...(embeddings && { embeddings }),
|
|
823
|
-
}),
|
|
801
|
+
ingestMemory(endpoint, activeKey, {
|
|
802
|
+
messages: toStore,
|
|
803
|
+
model: "unknown",
|
|
804
|
+
...(embeddings && { embeddings }),
|
|
824
805
|
}).then(async (res) => {
|
|
825
806
|
if (!res.ok) {
|
|
826
807
|
const details = await res.text().catch(() => "");
|
|
@@ -868,15 +849,7 @@ If setting up MemoryRouter for another agent:
|
|
|
868
849
|
if (!query) return jsonToolResult({ results: [], error: "query required" });
|
|
869
850
|
const limit = typeof params.maxResults === "number" ? params.maxResults : 50;
|
|
870
851
|
try {
|
|
871
|
-
const res = await
|
|
872
|
-
method: "POST",
|
|
873
|
-
headers: {
|
|
874
|
-
Authorization: `Bearer ${toolKey}`,
|
|
875
|
-
"Content-Type": "application/json",
|
|
876
|
-
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
877
|
-
},
|
|
878
|
-
body: JSON.stringify({ query, limit }),
|
|
879
|
-
});
|
|
852
|
+
const res = await searchMemory(endpoint, toolKey, { query, limit, embeddings });
|
|
880
853
|
if (!res.ok) {
|
|
881
854
|
const errBody = await res.text().catch(() => "");
|
|
882
855
|
return jsonToolResult({ results: [], error: `Search failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
|
|
@@ -1074,12 +1047,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1074
1047
|
return;
|
|
1075
1048
|
}
|
|
1076
1049
|
try {
|
|
1077
|
-
const
|
|
1078
|
-
? `${endpoint}/v1/memory/stats?embeddings=${encodeURIComponent(embeddings)}`
|
|
1079
|
-
: `${endpoint}/v1/memory/stats`;
|
|
1080
|
-
const res = await fetch(statsUrl, {
|
|
1081
|
-
headers: { Authorization: `Bearer ${effectiveKey}` },
|
|
1082
|
-
});
|
|
1050
|
+
const res = await getMemoryStats(endpoint, effectiveKey, embeddings);
|
|
1083
1051
|
const data = (await res.json()) as { totalVectors?: number; totalTokens?: number };
|
|
1084
1052
|
if (opts.json) {
|
|
1085
1053
|
console.log(JSON.stringify({ enabled: true, key: effectiveKey, density, stats: data }, null, 2));
|
|
@@ -1131,15 +1099,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1131
1099
|
if (!effectiveKey) { console.error("Not configured. Run: openclaw mr <key> or pass --key <key>"); return; }
|
|
1132
1100
|
const limit = parseInt(opts.limit, 10) || 50;
|
|
1133
1101
|
try {
|
|
1134
|
-
const res = await
|
|
1135
|
-
method: "POST",
|
|
1136
|
-
headers: {
|
|
1137
|
-
Authorization: `Bearer ${effectiveKey}`,
|
|
1138
|
-
"Content-Type": "application/json",
|
|
1139
|
-
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
1140
|
-
},
|
|
1141
|
-
body: JSON.stringify({ query, limit }),
|
|
1142
|
-
});
|
|
1102
|
+
const res = await searchMemory(endpoint, effectiveKey, { query, limit, embeddings });
|
|
1143
1103
|
if (!res.ok) {
|
|
1144
1104
|
const err = await res.json() as { error?: string };
|
|
1145
1105
|
console.error(`Search failed: ${err.error || `HTTP ${res.status}`}`);
|
|
@@ -1204,13 +1164,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1204
1164
|
const effectiveKey = opts.key || memoryKey;
|
|
1205
1165
|
if (!effectiveKey) { console.error("Not configured. Run: openclaw mr <key> or pass --key <key>"); return; }
|
|
1206
1166
|
try {
|
|
1207
|
-
const
|
|
1208
|
-
? `${endpoint}/v1/memory?embeddings=${encodeURIComponent(embeddings)}`
|
|
1209
|
-
: `${endpoint}/v1/memory`;
|
|
1210
|
-
const res = await fetch(deleteUrl, {
|
|
1211
|
-
method: "DELETE",
|
|
1212
|
-
headers: { Authorization: `Bearer ${effectiveKey}` },
|
|
1213
|
-
});
|
|
1167
|
+
const res = await deleteMemories(endpoint, effectiveKey, embeddings);
|
|
1214
1168
|
const data = (await res.json()) as { message?: string };
|
|
1215
1169
|
const modelLabel = embeddings ? ` (${embeddings})` : "";
|
|
1216
1170
|
console.log(`✓ ${data.message || `Vault cleared${modelLabel}`}`);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const JSON_HEADERS = { "Content-Type": "application/json" };
|
|
2
|
+
export async function prepareMemory(endpoint, key, payload) {
|
|
3
|
+
return fetch(`${endpoint}/v1/memory/prepare`, {
|
|
4
|
+
method: "POST",
|
|
5
|
+
headers: {
|
|
6
|
+
...JSON_HEADERS,
|
|
7
|
+
Authorization: `Bearer ${key}`,
|
|
8
|
+
},
|
|
9
|
+
body: JSON.stringify(payload),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export function ingestMemory(endpoint, key, payload) {
|
|
13
|
+
return fetch(`${endpoint}/v1/memory/ingest`, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: {
|
|
16
|
+
...JSON_HEADERS,
|
|
17
|
+
Authorization: `Bearer ${key}`,
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify(payload),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export async function searchMemory(endpoint, key, payload) {
|
|
23
|
+
return fetch(`${endpoint}/v1/memory/search`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${key}`,
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify({ query: payload.query, limit: payload.limit }),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export async function getMemoryStats(endpoint, key, embeddings) {
|
|
34
|
+
const statsUrl = embeddings
|
|
35
|
+
? `${endpoint}/v1/memory/stats?embeddings=${encodeURIComponent(embeddings)}`
|
|
36
|
+
: `${endpoint}/v1/memory/stats`;
|
|
37
|
+
return fetch(statsUrl, {
|
|
38
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export async function deleteMemories(endpoint, key, embeddings) {
|
|
42
|
+
const deleteUrl = embeddings
|
|
43
|
+
? `${endpoint}/v1/memory?embeddings=${encodeURIComponent(embeddings)}`
|
|
44
|
+
: `${endpoint}/v1/memory`;
|
|
45
|
+
return fetch(deleteUrl, {
|
|
46
|
+
method: "DELETE",
|
|
47
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const JSON_HEADERS = { "Content-Type": "application/json" };
|
|
2
|
+
|
|
3
|
+
type PreparePayload = {
|
|
4
|
+
messages: Array<{ role: string; content: string }>;
|
|
5
|
+
density: string;
|
|
6
|
+
embeddings?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export async function prepareMemory(endpoint: string, key: string, payload: PreparePayload): Promise<Response> {
|
|
10
|
+
return fetch(`${endpoint}/v1/memory/prepare`, {
|
|
11
|
+
method: "POST",
|
|
12
|
+
headers: {
|
|
13
|
+
...JSON_HEADERS,
|
|
14
|
+
Authorization: `Bearer ${key}`,
|
|
15
|
+
},
|
|
16
|
+
body: JSON.stringify(payload),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ingestMemory(endpoint: string, key: string, payload: { messages: Array<{ role: string; content: string }>; model: string; embeddings?: string }): Promise<Response> {
|
|
21
|
+
return fetch(`${endpoint}/v1/memory/ingest`, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: {
|
|
24
|
+
...JSON_HEADERS,
|
|
25
|
+
Authorization: `Bearer ${key}`,
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify(payload),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function searchMemory(endpoint: string, key: string, payload: { query: string; limit: number; embeddings?: string }): Promise<Response> {
|
|
32
|
+
return fetch(`${endpoint}/v1/memory/search`, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: {
|
|
35
|
+
Authorization: `Bearer ${key}`,
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({ query: payload.query, limit: payload.limit }),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function getMemoryStats(endpoint: string, key: string, embeddings?: string): Promise<Response> {
|
|
44
|
+
const statsUrl = embeddings
|
|
45
|
+
? `${endpoint}/v1/memory/stats?embeddings=${encodeURIComponent(embeddings)}`
|
|
46
|
+
: `${endpoint}/v1/memory/stats`;
|
|
47
|
+
return fetch(statsUrl, {
|
|
48
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function deleteMemories(endpoint: string, key: string, embeddings?: string): Promise<Response> {
|
|
53
|
+
const deleteUrl = embeddings
|
|
54
|
+
? `${endpoint}/v1/memory?embeddings=${encodeURIComponent(embeddings)}`
|
|
55
|
+
: `${endpoint}/v1/memory`;
|
|
56
|
+
return fetch(deleteUrl, {
|
|
57
|
+
method: "DELETE",
|
|
58
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
59
|
+
});
|
|
60
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mr-memory",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.5",
|
|
4
4
|
"description": "mr-memory is the MemoryRouter plugin for OpenClaw — persistent memory across every conversation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
"upload.js",
|
|
10
10
|
"index.ts",
|
|
11
11
|
"upload.ts",
|
|
12
|
+
"memoryrouter-api.js",
|
|
13
|
+
"memoryrouter-api.ts",
|
|
14
|
+
"upload-api.js",
|
|
15
|
+
"upload-api.ts",
|
|
12
16
|
"openclaw.plugin.json"
|
|
13
17
|
],
|
|
14
18
|
"keywords": [
|
package/upload-api.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const MAX_HTTP_RETRIES = 3;
|
|
2
|
+
function sleep(ms) {
|
|
3
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4
|
+
}
|
|
5
|
+
export async function checkMemoryRouterHealth(endpoint) {
|
|
6
|
+
try {
|
|
7
|
+
const res = await fetch(`${endpoint}/health`, { signal: AbortSignal.timeout(5000) });
|
|
8
|
+
return res.ok;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function uploadMemoryBatch(params) {
|
|
15
|
+
const uploadUrl = `${params.endpoint}/v1/memory/upload`;
|
|
16
|
+
let lastError = null;
|
|
17
|
+
for (let attempt = 1; attempt <= MAX_HTTP_RETRIES; attempt++) {
|
|
18
|
+
try {
|
|
19
|
+
const res = await fetch(uploadUrl, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: {
|
|
22
|
+
Authorization: `Bearer ${params.memoryKey}`,
|
|
23
|
+
"Content-Type": "text/plain",
|
|
24
|
+
...(params.embeddings && { "X-Embedding-Model": params.embeddings }),
|
|
25
|
+
},
|
|
26
|
+
body: params.jsonlBody,
|
|
27
|
+
signal: AbortSignal.timeout(30000),
|
|
28
|
+
});
|
|
29
|
+
if (res.ok || res.status < 500)
|
|
30
|
+
return res;
|
|
31
|
+
lastError = new Error(`HTTP ${res.status}`);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
35
|
+
}
|
|
36
|
+
if (attempt < MAX_HTTP_RETRIES)
|
|
37
|
+
await sleep(1000 * attempt);
|
|
38
|
+
}
|
|
39
|
+
throw lastError ?? new Error(`${params.label}: failed after ${MAX_HTTP_RETRIES} attempts`);
|
|
40
|
+
}
|
package/upload-api.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const MAX_HTTP_RETRIES = 3;
|
|
2
|
+
|
|
3
|
+
function sleep(ms: number): Promise<void> {
|
|
4
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function checkMemoryRouterHealth(endpoint: string): Promise<boolean> {
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch(`${endpoint}/health`, { signal: AbortSignal.timeout(5000) });
|
|
10
|
+
return res.ok;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function uploadMemoryBatch(params: {
|
|
17
|
+
endpoint: string;
|
|
18
|
+
memoryKey: string;
|
|
19
|
+
jsonlBody: string;
|
|
20
|
+
embeddings?: string;
|
|
21
|
+
label: string;
|
|
22
|
+
}): Promise<Response> {
|
|
23
|
+
const uploadUrl = `${params.endpoint}/v1/memory/upload`;
|
|
24
|
+
let lastError: Error | null = null;
|
|
25
|
+
for (let attempt = 1; attempt <= MAX_HTTP_RETRIES; attempt++) {
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(uploadUrl, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
Authorization: `Bearer ${params.memoryKey}`,
|
|
31
|
+
"Content-Type": "text/plain",
|
|
32
|
+
...(params.embeddings && { "X-Embedding-Model": params.embeddings }),
|
|
33
|
+
},
|
|
34
|
+
body: params.jsonlBody,
|
|
35
|
+
signal: AbortSignal.timeout(30000),
|
|
36
|
+
});
|
|
37
|
+
if (res.ok || res.status < 500) return res;
|
|
38
|
+
lastError = new Error(`HTTP ${res.status}`);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
41
|
+
}
|
|
42
|
+
if (attempt < MAX_HTTP_RETRIES) await sleep(1000 * attempt);
|
|
43
|
+
}
|
|
44
|
+
throw lastError ?? new Error(`${params.label}: failed after ${MAX_HTTP_RETRIES} attempts`);
|
|
45
|
+
}
|
package/upload.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { checkMemoryRouterHealth, uploadMemoryBatch } from "./upload-api.js";
|
|
6
7
|
// ── Sanitization utilities (shared with index.ts ingest logic)
|
|
7
8
|
// These MUST stay in sync with the patterns in index.ts
|
|
8
9
|
const MEMORY_TAG_RE = /<mr-memory>[\s\S]*?<\/mr-memory>\s*/g;
|
|
@@ -132,7 +133,6 @@ const MAX_BATCH_BYTES = 2_000_000;
|
|
|
132
133
|
const MAX_BATCH_COUNT_DEFAULT = 100;
|
|
133
134
|
const MAX_BATCH_COUNT_QWEN = 25;
|
|
134
135
|
const BATCH_SLEEP_MS = 150;
|
|
135
|
-
const MAX_HTTP_RETRIES = 3;
|
|
136
136
|
function sleep(ms) {
|
|
137
137
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
138
138
|
}
|
|
@@ -302,33 +302,10 @@ async function discoverBrainFiles(stateDir) {
|
|
|
302
302
|
}
|
|
303
303
|
return files;
|
|
304
304
|
}
|
|
305
|
-
async function fetchWithRetry(url, init, label) {
|
|
306
|
-
let lastError = null;
|
|
307
|
-
for (let attempt = 1; attempt <= MAX_HTTP_RETRIES; attempt++) {
|
|
308
|
-
try {
|
|
309
|
-
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(30000) });
|
|
310
|
-
if (res.ok || res.status < 500)
|
|
311
|
-
return res;
|
|
312
|
-
lastError = new Error(`HTTP ${res.status}`);
|
|
313
|
-
}
|
|
314
|
-
catch (err) {
|
|
315
|
-
lastError = err instanceof Error ? err : new Error(String(err));
|
|
316
|
-
}
|
|
317
|
-
if (attempt < MAX_HTTP_RETRIES)
|
|
318
|
-
await sleep(1000 * attempt);
|
|
319
|
-
}
|
|
320
|
-
throw lastError ?? new Error(`${label}: failed after ${MAX_HTTP_RETRIES} attempts`);
|
|
321
|
-
}
|
|
322
305
|
export async function runUpload(params) {
|
|
323
306
|
const { memoryKey, endpoint, targetPath, stateDir, embeddings } = params;
|
|
324
|
-
const uploadUrl = `${endpoint}/v1/memory/upload`;
|
|
325
307
|
// Validate API reachability
|
|
326
|
-
|
|
327
|
-
const res = await fetch(`${endpoint}/health`, { signal: AbortSignal.timeout(5000) });
|
|
328
|
-
if (!res.ok)
|
|
329
|
-
throw new Error(`HTTP ${res.status}`);
|
|
330
|
-
}
|
|
331
|
-
catch {
|
|
308
|
+
if (!(await checkMemoryRouterHealth(endpoint))) {
|
|
332
309
|
console.error("Error: Could not reach MemoryRouter API.");
|
|
333
310
|
return;
|
|
334
311
|
}
|
|
@@ -427,15 +404,13 @@ export async function runUpload(params) {
|
|
|
427
404
|
process.stdout.write(` Batch ${i + 1}/${batches.length} (${batch.length} items)... `);
|
|
428
405
|
}
|
|
429
406
|
try {
|
|
430
|
-
const response = await
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
body: jsonlBody,
|
|
438
|
-
}, `Batch ${i + 1}`);
|
|
407
|
+
const response = await uploadMemoryBatch({
|
|
408
|
+
endpoint,
|
|
409
|
+
memoryKey,
|
|
410
|
+
jsonlBody,
|
|
411
|
+
embeddings,
|
|
412
|
+
label: `Batch ${i + 1}`,
|
|
413
|
+
});
|
|
439
414
|
const result = (await response.json());
|
|
440
415
|
const batchStored = result.stats?.stored ?? result.stats?.inputItems ?? batch.length;
|
|
441
416
|
const batchFailed = result.stats?.failed ?? 0;
|
package/upload.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import { checkMemoryRouterHealth, uploadMemoryBatch } from "./upload-api.js";
|
|
7
8
|
|
|
8
9
|
// ── Sanitization utilities (shared with index.ts ingest logic)
|
|
9
10
|
// These MUST stay in sync with the patterns in index.ts
|
|
@@ -146,7 +147,6 @@ const MAX_BATCH_BYTES = 2_000_000;
|
|
|
146
147
|
const MAX_BATCH_COUNT_DEFAULT = 100;
|
|
147
148
|
const MAX_BATCH_COUNT_QWEN = 25;
|
|
148
149
|
const BATCH_SLEEP_MS = 150;
|
|
149
|
-
const MAX_HTTP_RETRIES = 3;
|
|
150
150
|
|
|
151
151
|
function sleep(ms: number): Promise<void> {
|
|
152
152
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -327,25 +327,6 @@ async function discoverBrainFiles(stateDir: string): Promise<string[]> {
|
|
|
327
327
|
return files;
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
async function fetchWithRetry(
|
|
331
|
-
url: string,
|
|
332
|
-
init: RequestInit,
|
|
333
|
-
label: string,
|
|
334
|
-
): Promise<Response> {
|
|
335
|
-
let lastError: Error | null = null;
|
|
336
|
-
for (let attempt = 1; attempt <= MAX_HTTP_RETRIES; attempt++) {
|
|
337
|
-
try {
|
|
338
|
-
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(30000) });
|
|
339
|
-
if (res.ok || res.status < 500) return res;
|
|
340
|
-
lastError = new Error(`HTTP ${res.status}`);
|
|
341
|
-
} catch (err) {
|
|
342
|
-
lastError = err instanceof Error ? err : new Error(String(err));
|
|
343
|
-
}
|
|
344
|
-
if (attempt < MAX_HTTP_RETRIES) await sleep(1000 * attempt);
|
|
345
|
-
}
|
|
346
|
-
throw lastError ?? new Error(`${label}: failed after ${MAX_HTTP_RETRIES} attempts`);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
330
|
export async function runUpload(params: {
|
|
350
331
|
memoryKey: string;
|
|
351
332
|
endpoint: string;
|
|
@@ -357,13 +338,8 @@ export async function runUpload(params: {
|
|
|
357
338
|
embeddings?: string;
|
|
358
339
|
}): Promise<void> {
|
|
359
340
|
const { memoryKey, endpoint, targetPath, stateDir, embeddings } = params;
|
|
360
|
-
const uploadUrl = `${endpoint}/v1/memory/upload`;
|
|
361
|
-
|
|
362
341
|
// Validate API reachability
|
|
363
|
-
|
|
364
|
-
const res = await fetch(`${endpoint}/health`, { signal: AbortSignal.timeout(5000) });
|
|
365
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
366
|
-
} catch {
|
|
342
|
+
if (!(await checkMemoryRouterHealth(endpoint))) {
|
|
367
343
|
console.error("Error: Could not reach MemoryRouter API.");
|
|
368
344
|
return;
|
|
369
345
|
}
|
|
@@ -467,19 +443,13 @@ export async function runUpload(params: {
|
|
|
467
443
|
}
|
|
468
444
|
|
|
469
445
|
try {
|
|
470
|
-
const response = await
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
478
|
-
},
|
|
479
|
-
body: jsonlBody,
|
|
480
|
-
},
|
|
481
|
-
`Batch ${i + 1}`,
|
|
482
|
-
);
|
|
446
|
+
const response = await uploadMemoryBatch({
|
|
447
|
+
endpoint,
|
|
448
|
+
memoryKey,
|
|
449
|
+
jsonlBody,
|
|
450
|
+
embeddings,
|
|
451
|
+
label: `Batch ${i + 1}`,
|
|
452
|
+
});
|
|
483
453
|
|
|
484
454
|
const result = (await response.json()) as {
|
|
485
455
|
stats?: { stored?: number; failed?: number; inputItems?: number; path?: string; diagnostic?: string };
|