@memtensor/memos-local-openclaw-plugin 1.0.0 → 1.0.2-beta.1
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/README.md +8 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +20 -9
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/viewer/html.d.ts +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +223 -70
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +6 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +236 -24
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +41 -0
- package/package.json +1 -1
- package/scripts/postinstall.cjs +72 -0
- package/src/storage/sqlite.ts +19 -9
- package/src/viewer/html.ts +223 -70
- package/src/viewer/server.ts +228 -20
package/dist/viewer/server.js
CHANGED
|
@@ -210,6 +210,12 @@ class ViewerServer {
|
|
|
210
210
|
this.serveConfig(res);
|
|
211
211
|
else if (p === "/api/config" && req.method === "PUT")
|
|
212
212
|
this.handleSaveConfig(req, res);
|
|
213
|
+
else if (p === "/api/test-model" && req.method === "POST")
|
|
214
|
+
this.handleTestModel(req, res);
|
|
215
|
+
else if (p === "/api/fallback-model" && req.method === "GET")
|
|
216
|
+
this.serveFallbackModel(res);
|
|
217
|
+
else if (p === "/api/update-check" && req.method === "GET")
|
|
218
|
+
this.handleUpdateCheck(res);
|
|
213
219
|
else if (p === "/api/auth/logout" && req.method === "POST")
|
|
214
220
|
this.handleLogout(req, res);
|
|
215
221
|
else if (p === "/api/migrate/scan" && req.method === "GET")
|
|
@@ -553,7 +559,8 @@ class ViewerServer {
|
|
|
553
559
|
try {
|
|
554
560
|
ftsResults = db.prepare("SELECT c.* FROM chunks_fts f JOIN chunks c ON f.rowid = c.rowid WHERE chunks_fts MATCH ? ORDER BY rank LIMIT 100").all(q).filter(passesFilter);
|
|
555
561
|
}
|
|
556
|
-
catch {
|
|
562
|
+
catch { /* FTS syntax error, fall through */ }
|
|
563
|
+
if (ftsResults.length === 0) {
|
|
557
564
|
ftsResults = db.prepare("SELECT * FROM chunks WHERE content LIKE ? OR summary LIKE ? ORDER BY created_at DESC LIMIT 100").all(`%${q}%`, `%${q}%`).filter(passesFilter);
|
|
558
565
|
}
|
|
559
566
|
const SEMANTIC_THRESHOLD = 0.64;
|
|
@@ -584,16 +591,12 @@ class ViewerServer {
|
|
|
584
591
|
}
|
|
585
592
|
}
|
|
586
593
|
for (const r of ftsResults) {
|
|
587
|
-
if (seenIds.has(r.id))
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
merged.push(r);
|
|
594
|
-
}
|
|
595
|
-
const fallback = merged.length === 0 && ftsResults.length > 0;
|
|
596
|
-
const results = fallback ? ftsResults.slice(0, 20) : merged;
|
|
594
|
+
if (!seenIds.has(r.id)) {
|
|
595
|
+
seenIds.add(r.id);
|
|
596
|
+
merged.push(r);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const results = merged.length > 0 ? merged : ftsResults.slice(0, 20);
|
|
597
600
|
this.store.recordViewerEvent("search");
|
|
598
601
|
this.jsonResponse(res, {
|
|
599
602
|
results,
|
|
@@ -601,7 +604,6 @@ class ViewerServer {
|
|
|
601
604
|
vectorCount: vectorResults.length,
|
|
602
605
|
ftsCount: ftsResults.length,
|
|
603
606
|
total: results.length,
|
|
604
|
-
fallbackFts: fallback,
|
|
605
607
|
});
|
|
606
608
|
}
|
|
607
609
|
// ─── Skills API ───
|
|
@@ -754,9 +756,10 @@ class ViewerServer {
|
|
|
754
756
|
this.jsonResponse(res, { ok: true, skillId, visibility });
|
|
755
757
|
}
|
|
756
758
|
catch (err) {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
res.
|
|
759
|
+
const errMsg = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
760
|
+
this.log.error(`handleSkillVisibility error: skillId=${skillId}, body=${body}, err=${errMsg}`);
|
|
761
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
762
|
+
res.end(JSON.stringify({ error: errMsg }));
|
|
760
763
|
}
|
|
761
764
|
});
|
|
762
765
|
}
|
|
@@ -962,20 +965,27 @@ class ViewerServer {
|
|
|
962
965
|
this.jsonResponse(res, { ok: true, deleted: count });
|
|
963
966
|
}
|
|
964
967
|
handleDeleteAll(res) {
|
|
965
|
-
const result = this.store.deleteAll();
|
|
966
|
-
// Clean up skills-store directory
|
|
967
|
-
const skillsStoreDir = node_path_1.default.join(this.dataDir, "skills-store");
|
|
968
968
|
try {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
969
|
+
const result = this.store.deleteAll();
|
|
970
|
+
const skillsStoreDir = node_path_1.default.join(this.dataDir, "skills-store");
|
|
971
|
+
try {
|
|
972
|
+
if (node_fs_1.default.existsSync(skillsStoreDir)) {
|
|
973
|
+
node_fs_1.default.rmSync(skillsStoreDir, { recursive: true });
|
|
974
|
+
node_fs_1.default.mkdirSync(skillsStoreDir, { recursive: true });
|
|
975
|
+
this.log.info("Cleared skills-store directory");
|
|
976
|
+
}
|
|
973
977
|
}
|
|
978
|
+
catch (err) {
|
|
979
|
+
this.log.warn(`Failed to clear skills-store: ${err}`);
|
|
980
|
+
}
|
|
981
|
+
this.jsonResponse(res, { ok: true, deleted: result });
|
|
974
982
|
}
|
|
975
983
|
catch (err) {
|
|
976
|
-
|
|
984
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
985
|
+
this.log.error(`handleDeleteAll error: ${msg}`);
|
|
986
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
987
|
+
res.end(JSON.stringify({ ok: false, error: msg }));
|
|
977
988
|
}
|
|
978
|
-
this.jsonResponse(res, { ok: true, deleted: result });
|
|
979
989
|
}
|
|
980
990
|
// ─── Helpers ───
|
|
981
991
|
// ─── Config API ───
|
|
@@ -1058,6 +1068,208 @@ class ViewerServer {
|
|
|
1058
1068
|
}
|
|
1059
1069
|
});
|
|
1060
1070
|
}
|
|
1071
|
+
handleTestModel(req, res) {
|
|
1072
|
+
this.readBody(req, async (body) => {
|
|
1073
|
+
try {
|
|
1074
|
+
const { type, provider, model, endpoint, apiKey } = JSON.parse(body);
|
|
1075
|
+
if (!provider) {
|
|
1076
|
+
this.jsonResponse(res, { ok: false, error: "provider is required" });
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
if (type === "embedding") {
|
|
1080
|
+
await this.testEmbeddingModel(provider, model, endpoint, apiKey);
|
|
1081
|
+
this.jsonResponse(res, { ok: true, detail: `${provider}/${model}` });
|
|
1082
|
+
}
|
|
1083
|
+
else {
|
|
1084
|
+
await this.testChatModel(provider, model, endpoint, apiKey);
|
|
1085
|
+
this.jsonResponse(res, { ok: true, detail: `${provider}/${model}` });
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
catch (e) {
|
|
1089
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1090
|
+
this.log.warn(`test-model failed: ${msg}`);
|
|
1091
|
+
this.jsonResponse(res, { ok: false, error: msg });
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
serveFallbackModel(res) {
|
|
1096
|
+
try {
|
|
1097
|
+
const cfgPath = this.getOpenClawConfigPath();
|
|
1098
|
+
if (!node_fs_1.default.existsSync(cfgPath)) {
|
|
1099
|
+
this.jsonResponse(res, { available: false });
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
const raw = JSON.parse(node_fs_1.default.readFileSync(cfgPath, "utf-8"));
|
|
1103
|
+
const agentModel = raw?.agents?.defaults?.model?.primary;
|
|
1104
|
+
if (!agentModel) {
|
|
1105
|
+
this.jsonResponse(res, { available: false });
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
const [providerKey, modelId] = agentModel.includes("/")
|
|
1109
|
+
? agentModel.split("/", 2)
|
|
1110
|
+
: [undefined, agentModel];
|
|
1111
|
+
const providerCfg = providerKey
|
|
1112
|
+
? raw?.models?.providers?.[providerKey]
|
|
1113
|
+
: Object.values(raw?.models?.providers ?? {})[0];
|
|
1114
|
+
if (!providerCfg || !providerCfg.baseUrl || !providerCfg.apiKey) {
|
|
1115
|
+
this.jsonResponse(res, { available: false });
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
this.jsonResponse(res, { available: true, model: modelId || agentModel, baseUrl: providerCfg.baseUrl });
|
|
1119
|
+
}
|
|
1120
|
+
catch {
|
|
1121
|
+
this.jsonResponse(res, { available: false });
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
findPluginPackageJson() {
|
|
1125
|
+
let dir = __dirname;
|
|
1126
|
+
for (let i = 0; i < 6; i++) {
|
|
1127
|
+
const candidate = node_path_1.default.join(dir, "package.json");
|
|
1128
|
+
if (node_fs_1.default.existsSync(candidate)) {
|
|
1129
|
+
try {
|
|
1130
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(candidate, "utf-8"));
|
|
1131
|
+
if (pkg.name && pkg.name.includes("memos-local"))
|
|
1132
|
+
return candidate;
|
|
1133
|
+
}
|
|
1134
|
+
catch { /* skip */ }
|
|
1135
|
+
}
|
|
1136
|
+
dir = node_path_1.default.dirname(dir);
|
|
1137
|
+
}
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
async handleUpdateCheck(res) {
|
|
1141
|
+
try {
|
|
1142
|
+
const pkgPath = this.findPluginPackageJson();
|
|
1143
|
+
if (!pkgPath) {
|
|
1144
|
+
this.jsonResponse(res, { updateAvailable: false, error: "package.json not found" });
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, "utf-8"));
|
|
1148
|
+
const current = pkg.version;
|
|
1149
|
+
const name = pkg.name;
|
|
1150
|
+
if (!current || !name) {
|
|
1151
|
+
this.jsonResponse(res, { updateAvailable: false, current });
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
const npmResp = await fetch(`https://registry.npmjs.org/${name}/latest`, {
|
|
1155
|
+
signal: AbortSignal.timeout(6_000),
|
|
1156
|
+
});
|
|
1157
|
+
if (!npmResp.ok) {
|
|
1158
|
+
this.jsonResponse(res, { updateAvailable: false, current });
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
const data = await npmResp.json();
|
|
1162
|
+
const latest = data.version ?? current;
|
|
1163
|
+
this.jsonResponse(res, {
|
|
1164
|
+
updateAvailable: latest !== current,
|
|
1165
|
+
current,
|
|
1166
|
+
latest,
|
|
1167
|
+
packageName: name,
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
catch (e) {
|
|
1171
|
+
this.log.warn(`handleUpdateCheck error: ${e}`);
|
|
1172
|
+
this.jsonResponse(res, { updateAvailable: false, error: String(e) });
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
async testEmbeddingModel(provider, model, endpoint, apiKey) {
|
|
1176
|
+
if (provider === "local") {
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
const baseUrl = (endpoint || "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
1180
|
+
const embUrl = baseUrl.endsWith("/embeddings") ? baseUrl : `${baseUrl}/embeddings`;
|
|
1181
|
+
const headers = {
|
|
1182
|
+
"Content-Type": "application/json",
|
|
1183
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1184
|
+
};
|
|
1185
|
+
if (provider === "cohere") {
|
|
1186
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
1187
|
+
const resp = await fetch(baseUrl.replace(/\/v\d+.*/, "/v2/embed"), {
|
|
1188
|
+
method: "POST",
|
|
1189
|
+
headers,
|
|
1190
|
+
body: JSON.stringify({ texts: ["test"], model: model || "embed-english-v3.0", input_type: "search_query", embedding_types: ["float"] }),
|
|
1191
|
+
signal: AbortSignal.timeout(15_000),
|
|
1192
|
+
});
|
|
1193
|
+
if (!resp.ok) {
|
|
1194
|
+
const txt = await resp.text();
|
|
1195
|
+
throw new Error(`Cohere embed ${resp.status}: ${txt}`);
|
|
1196
|
+
}
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
if (provider === "gemini") {
|
|
1200
|
+
const url = `https://generativelanguage.googleapis.com/v1/models/${model || "text-embedding-004"}:embedContent?key=${apiKey}`;
|
|
1201
|
+
const resp = await fetch(url, {
|
|
1202
|
+
method: "POST",
|
|
1203
|
+
headers: { "Content-Type": "application/json" },
|
|
1204
|
+
body: JSON.stringify({ content: { parts: [{ text: "test" }] } }),
|
|
1205
|
+
signal: AbortSignal.timeout(15_000),
|
|
1206
|
+
});
|
|
1207
|
+
if (!resp.ok) {
|
|
1208
|
+
const txt = await resp.text();
|
|
1209
|
+
throw new Error(`Gemini embed ${resp.status}: ${txt}`);
|
|
1210
|
+
}
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
const resp = await fetch(embUrl, {
|
|
1214
|
+
method: "POST",
|
|
1215
|
+
headers,
|
|
1216
|
+
body: JSON.stringify({ input: ["test"], model: model || "text-embedding-3-small" }),
|
|
1217
|
+
signal: AbortSignal.timeout(15_000),
|
|
1218
|
+
});
|
|
1219
|
+
if (!resp.ok) {
|
|
1220
|
+
const txt = await resp.text();
|
|
1221
|
+
throw new Error(`${resp.status}: ${txt}`);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
async testChatModel(provider, model, endpoint, apiKey) {
|
|
1225
|
+
const baseUrl = (endpoint || "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
1226
|
+
if (provider === "anthropic") {
|
|
1227
|
+
const url = endpoint || "https://api.anthropic.com/v1/messages";
|
|
1228
|
+
const resp = await fetch(url, {
|
|
1229
|
+
method: "POST",
|
|
1230
|
+
headers: {
|
|
1231
|
+
"Content-Type": "application/json",
|
|
1232
|
+
"x-api-key": apiKey,
|
|
1233
|
+
"anthropic-version": "2023-06-01",
|
|
1234
|
+
},
|
|
1235
|
+
body: JSON.stringify({ model: model || "claude-3-haiku-20240307", max_tokens: 5, messages: [{ role: "user", content: "hi" }] }),
|
|
1236
|
+
signal: AbortSignal.timeout(15_000),
|
|
1237
|
+
});
|
|
1238
|
+
if (!resp.ok) {
|
|
1239
|
+
const txt = await resp.text();
|
|
1240
|
+
throw new Error(`Anthropic ${resp.status}: ${txt}`);
|
|
1241
|
+
}
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
if (provider === "gemini") {
|
|
1245
|
+
const url = `https://generativelanguage.googleapis.com/v1/models/${model || "gemini-1.5-flash"}:generateContent?key=${apiKey}`;
|
|
1246
|
+
const resp = await fetch(url, {
|
|
1247
|
+
method: "POST",
|
|
1248
|
+
headers: { "Content-Type": "application/json" },
|
|
1249
|
+
body: JSON.stringify({ contents: [{ parts: [{ text: "hi" }] }], generationConfig: { maxOutputTokens: 5 } }),
|
|
1250
|
+
signal: AbortSignal.timeout(15_000),
|
|
1251
|
+
});
|
|
1252
|
+
if (!resp.ok) {
|
|
1253
|
+
const txt = await resp.text();
|
|
1254
|
+
throw new Error(`Gemini ${resp.status}: ${txt}`);
|
|
1255
|
+
}
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
const chatUrl = baseUrl.endsWith("/chat/completions") ? baseUrl : `${baseUrl}/chat/completions`;
|
|
1259
|
+
const resp = await fetch(chatUrl, {
|
|
1260
|
+
method: "POST",
|
|
1261
|
+
headers: {
|
|
1262
|
+
"Content-Type": "application/json",
|
|
1263
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1264
|
+
},
|
|
1265
|
+
body: JSON.stringify({ model: model || "gpt-4o-mini", max_tokens: 5, messages: [{ role: "user", content: "hi" }] }),
|
|
1266
|
+
signal: AbortSignal.timeout(15_000),
|
|
1267
|
+
});
|
|
1268
|
+
if (!resp.ok) {
|
|
1269
|
+
const txt = await resp.text();
|
|
1270
|
+
throw new Error(`${resp.status}: ${txt}`);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1061
1273
|
serveLogs(res, url) {
|
|
1062
1274
|
const limit = Math.min(Number(url.searchParams.get("limit") ?? 20), 200);
|
|
1063
1275
|
const offset = Math.max(0, Number(url.searchParams.get("offset") ?? 0));
|