@memtensor/memos-local-openclaw-plugin 1.0.0 → 1.0.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 +198 -68
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +4 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +183 -24
- package/dist/viewer/server.js.map +1 -1
- package/package.json +1 -1
- package/src/storage/sqlite.ts +19 -9
- package/src/viewer/html.ts +198 -68
- package/src/viewer/server.ts +177 -20
package/dist/viewer/server.js
CHANGED
|
@@ -210,6 +210,10 @@ 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);
|
|
213
217
|
else if (p === "/api/auth/logout" && req.method === "POST")
|
|
214
218
|
this.handleLogout(req, res);
|
|
215
219
|
else if (p === "/api/migrate/scan" && req.method === "GET")
|
|
@@ -553,7 +557,8 @@ class ViewerServer {
|
|
|
553
557
|
try {
|
|
554
558
|
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
559
|
}
|
|
556
|
-
catch {
|
|
560
|
+
catch { /* FTS syntax error, fall through */ }
|
|
561
|
+
if (ftsResults.length === 0) {
|
|
557
562
|
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
563
|
}
|
|
559
564
|
const SEMANTIC_THRESHOLD = 0.64;
|
|
@@ -584,16 +589,12 @@ class ViewerServer {
|
|
|
584
589
|
}
|
|
585
590
|
}
|
|
586
591
|
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;
|
|
592
|
+
if (!seenIds.has(r.id)) {
|
|
593
|
+
seenIds.add(r.id);
|
|
594
|
+
merged.push(r);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const results = merged.length > 0 ? merged : ftsResults.slice(0, 20);
|
|
597
598
|
this.store.recordViewerEvent("search");
|
|
598
599
|
this.jsonResponse(res, {
|
|
599
600
|
results,
|
|
@@ -601,7 +602,6 @@ class ViewerServer {
|
|
|
601
602
|
vectorCount: vectorResults.length,
|
|
602
603
|
ftsCount: ftsResults.length,
|
|
603
604
|
total: results.length,
|
|
604
|
-
fallbackFts: fallback,
|
|
605
605
|
});
|
|
606
606
|
}
|
|
607
607
|
// ─── Skills API ───
|
|
@@ -754,9 +754,10 @@ class ViewerServer {
|
|
|
754
754
|
this.jsonResponse(res, { ok: true, skillId, visibility });
|
|
755
755
|
}
|
|
756
756
|
catch (err) {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
res.
|
|
757
|
+
const errMsg = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
758
|
+
this.log.error(`handleSkillVisibility error: skillId=${skillId}, body=${body}, err=${errMsg}`);
|
|
759
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
760
|
+
res.end(JSON.stringify({ error: errMsg }));
|
|
760
761
|
}
|
|
761
762
|
});
|
|
762
763
|
}
|
|
@@ -962,20 +963,27 @@ class ViewerServer {
|
|
|
962
963
|
this.jsonResponse(res, { ok: true, deleted: count });
|
|
963
964
|
}
|
|
964
965
|
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
966
|
try {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
967
|
+
const result = this.store.deleteAll();
|
|
968
|
+
const skillsStoreDir = node_path_1.default.join(this.dataDir, "skills-store");
|
|
969
|
+
try {
|
|
970
|
+
if (node_fs_1.default.existsSync(skillsStoreDir)) {
|
|
971
|
+
node_fs_1.default.rmSync(skillsStoreDir, { recursive: true });
|
|
972
|
+
node_fs_1.default.mkdirSync(skillsStoreDir, { recursive: true });
|
|
973
|
+
this.log.info("Cleared skills-store directory");
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
catch (err) {
|
|
977
|
+
this.log.warn(`Failed to clear skills-store: ${err}`);
|
|
973
978
|
}
|
|
979
|
+
this.jsonResponse(res, { ok: true, deleted: result });
|
|
974
980
|
}
|
|
975
981
|
catch (err) {
|
|
976
|
-
|
|
982
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
983
|
+
this.log.error(`handleDeleteAll error: ${msg}`);
|
|
984
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
985
|
+
res.end(JSON.stringify({ ok: false, error: msg }));
|
|
977
986
|
}
|
|
978
|
-
this.jsonResponse(res, { ok: true, deleted: result });
|
|
979
987
|
}
|
|
980
988
|
// ─── Helpers ───
|
|
981
989
|
// ─── Config API ───
|
|
@@ -1058,6 +1066,157 @@ class ViewerServer {
|
|
|
1058
1066
|
}
|
|
1059
1067
|
});
|
|
1060
1068
|
}
|
|
1069
|
+
handleTestModel(req, res) {
|
|
1070
|
+
this.readBody(req, async (body) => {
|
|
1071
|
+
try {
|
|
1072
|
+
const { type, provider, model, endpoint, apiKey } = JSON.parse(body);
|
|
1073
|
+
if (!provider) {
|
|
1074
|
+
this.jsonResponse(res, { ok: false, error: "provider is required" });
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
if (type === "embedding") {
|
|
1078
|
+
await this.testEmbeddingModel(provider, model, endpoint, apiKey);
|
|
1079
|
+
this.jsonResponse(res, { ok: true, detail: `${provider}/${model}` });
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
await this.testChatModel(provider, model, endpoint, apiKey);
|
|
1083
|
+
this.jsonResponse(res, { ok: true, detail: `${provider}/${model}` });
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
catch (e) {
|
|
1087
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1088
|
+
this.log.warn(`test-model failed: ${msg}`);
|
|
1089
|
+
this.jsonResponse(res, { ok: false, error: msg });
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
serveFallbackModel(res) {
|
|
1094
|
+
try {
|
|
1095
|
+
const cfgPath = this.getOpenClawConfigPath();
|
|
1096
|
+
if (!node_fs_1.default.existsSync(cfgPath)) {
|
|
1097
|
+
this.jsonResponse(res, { available: false });
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const raw = JSON.parse(node_fs_1.default.readFileSync(cfgPath, "utf-8"));
|
|
1101
|
+
const agentModel = raw?.agents?.defaults?.model?.primary;
|
|
1102
|
+
if (!agentModel) {
|
|
1103
|
+
this.jsonResponse(res, { available: false });
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
const [providerKey, modelId] = agentModel.includes("/")
|
|
1107
|
+
? agentModel.split("/", 2)
|
|
1108
|
+
: [undefined, agentModel];
|
|
1109
|
+
const providerCfg = providerKey
|
|
1110
|
+
? raw?.models?.providers?.[providerKey]
|
|
1111
|
+
: Object.values(raw?.models?.providers ?? {})[0];
|
|
1112
|
+
if (!providerCfg || !providerCfg.baseUrl || !providerCfg.apiKey) {
|
|
1113
|
+
this.jsonResponse(res, { available: false });
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
this.jsonResponse(res, { available: true, model: modelId || agentModel, baseUrl: providerCfg.baseUrl });
|
|
1117
|
+
}
|
|
1118
|
+
catch {
|
|
1119
|
+
this.jsonResponse(res, { available: false });
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
async testEmbeddingModel(provider, model, endpoint, apiKey) {
|
|
1123
|
+
if (provider === "local") {
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
const baseUrl = (endpoint || "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
1127
|
+
const embUrl = baseUrl.endsWith("/embeddings") ? baseUrl : `${baseUrl}/embeddings`;
|
|
1128
|
+
const headers = {
|
|
1129
|
+
"Content-Type": "application/json",
|
|
1130
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1131
|
+
};
|
|
1132
|
+
if (provider === "cohere") {
|
|
1133
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
1134
|
+
const resp = await fetch(baseUrl.replace(/\/v\d+.*/, "/v2/embed"), {
|
|
1135
|
+
method: "POST",
|
|
1136
|
+
headers,
|
|
1137
|
+
body: JSON.stringify({ texts: ["test"], model: model || "embed-english-v3.0", input_type: "search_query", embedding_types: ["float"] }),
|
|
1138
|
+
signal: AbortSignal.timeout(15_000),
|
|
1139
|
+
});
|
|
1140
|
+
if (!resp.ok) {
|
|
1141
|
+
const txt = await resp.text();
|
|
1142
|
+
throw new Error(`Cohere embed ${resp.status}: ${txt}`);
|
|
1143
|
+
}
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
if (provider === "gemini") {
|
|
1147
|
+
const url = `https://generativelanguage.googleapis.com/v1/models/${model || "text-embedding-004"}:embedContent?key=${apiKey}`;
|
|
1148
|
+
const resp = await fetch(url, {
|
|
1149
|
+
method: "POST",
|
|
1150
|
+
headers: { "Content-Type": "application/json" },
|
|
1151
|
+
body: JSON.stringify({ content: { parts: [{ text: "test" }] } }),
|
|
1152
|
+
signal: AbortSignal.timeout(15_000),
|
|
1153
|
+
});
|
|
1154
|
+
if (!resp.ok) {
|
|
1155
|
+
const txt = await resp.text();
|
|
1156
|
+
throw new Error(`Gemini embed ${resp.status}: ${txt}`);
|
|
1157
|
+
}
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
const resp = await fetch(embUrl, {
|
|
1161
|
+
method: "POST",
|
|
1162
|
+
headers,
|
|
1163
|
+
body: JSON.stringify({ input: ["test"], model: model || "text-embedding-3-small" }),
|
|
1164
|
+
signal: AbortSignal.timeout(15_000),
|
|
1165
|
+
});
|
|
1166
|
+
if (!resp.ok) {
|
|
1167
|
+
const txt = await resp.text();
|
|
1168
|
+
throw new Error(`${resp.status}: ${txt}`);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
async testChatModel(provider, model, endpoint, apiKey) {
|
|
1172
|
+
const baseUrl = (endpoint || "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
1173
|
+
if (provider === "anthropic") {
|
|
1174
|
+
const url = endpoint || "https://api.anthropic.com/v1/messages";
|
|
1175
|
+
const resp = await fetch(url, {
|
|
1176
|
+
method: "POST",
|
|
1177
|
+
headers: {
|
|
1178
|
+
"Content-Type": "application/json",
|
|
1179
|
+
"x-api-key": apiKey,
|
|
1180
|
+
"anthropic-version": "2023-06-01",
|
|
1181
|
+
},
|
|
1182
|
+
body: JSON.stringify({ model: model || "claude-3-haiku-20240307", max_tokens: 5, messages: [{ role: "user", content: "hi" }] }),
|
|
1183
|
+
signal: AbortSignal.timeout(15_000),
|
|
1184
|
+
});
|
|
1185
|
+
if (!resp.ok) {
|
|
1186
|
+
const txt = await resp.text();
|
|
1187
|
+
throw new Error(`Anthropic ${resp.status}: ${txt}`);
|
|
1188
|
+
}
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if (provider === "gemini") {
|
|
1192
|
+
const url = `https://generativelanguage.googleapis.com/v1/models/${model || "gemini-1.5-flash"}:generateContent?key=${apiKey}`;
|
|
1193
|
+
const resp = await fetch(url, {
|
|
1194
|
+
method: "POST",
|
|
1195
|
+
headers: { "Content-Type": "application/json" },
|
|
1196
|
+
body: JSON.stringify({ contents: [{ parts: [{ text: "hi" }] }], generationConfig: { maxOutputTokens: 5 } }),
|
|
1197
|
+
signal: AbortSignal.timeout(15_000),
|
|
1198
|
+
});
|
|
1199
|
+
if (!resp.ok) {
|
|
1200
|
+
const txt = await resp.text();
|
|
1201
|
+
throw new Error(`Gemini ${resp.status}: ${txt}`);
|
|
1202
|
+
}
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
const chatUrl = baseUrl.endsWith("/chat/completions") ? baseUrl : `${baseUrl}/chat/completions`;
|
|
1206
|
+
const resp = await fetch(chatUrl, {
|
|
1207
|
+
method: "POST",
|
|
1208
|
+
headers: {
|
|
1209
|
+
"Content-Type": "application/json",
|
|
1210
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1211
|
+
},
|
|
1212
|
+
body: JSON.stringify({ model: model || "gpt-4o-mini", max_tokens: 5, messages: [{ role: "user", content: "hi" }] }),
|
|
1213
|
+
signal: AbortSignal.timeout(15_000),
|
|
1214
|
+
});
|
|
1215
|
+
if (!resp.ok) {
|
|
1216
|
+
const txt = await resp.text();
|
|
1217
|
+
throw new Error(`${resp.status}: ${txt}`);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1061
1220
|
serveLogs(res, url) {
|
|
1062
1221
|
const limit = Math.min(Number(url.searchParams.get("limit") ?? 20), 200);
|
|
1063
1222
|
const offset = Math.max(0, Number(url.searchParams.get("offset") ?? 0));
|