@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/src/viewer/server.ts
CHANGED
|
@@ -215,6 +215,8 @@ export class ViewerServer {
|
|
|
215
215
|
else if (p === "/api/log-tools" && req.method === "GET") this.serveLogTools(res);
|
|
216
216
|
else if (p === "/api/config" && req.method === "GET") this.serveConfig(res);
|
|
217
217
|
else if (p === "/api/config" && req.method === "PUT") this.handleSaveConfig(req, res);
|
|
218
|
+
else if (p === "/api/test-model" && req.method === "POST") this.handleTestModel(req, res);
|
|
219
|
+
else if (p === "/api/fallback-model" && req.method === "GET") this.serveFallbackModel(res);
|
|
218
220
|
else if (p === "/api/auth/logout" && req.method === "POST") this.handleLogout(req, res);
|
|
219
221
|
else if (p === "/api/migrate/scan" && req.method === "GET") this.handleMigrateScan(res);
|
|
220
222
|
else if (p === "/api/migrate/start" && req.method === "POST") this.handleMigrateStart(req, res);
|
|
@@ -545,7 +547,8 @@ export class ViewerServer {
|
|
|
545
547
|
ftsResults = db.prepare(
|
|
546
548
|
"SELECT c.* FROM chunks_fts f JOIN chunks c ON f.rowid = c.rowid WHERE chunks_fts MATCH ? ORDER BY rank LIMIT 100",
|
|
547
549
|
).all(q).filter(passesFilter);
|
|
548
|
-
} catch {
|
|
550
|
+
} catch { /* FTS syntax error, fall through */ }
|
|
551
|
+
if (ftsResults.length === 0) {
|
|
549
552
|
ftsResults = db.prepare(
|
|
550
553
|
"SELECT * FROM chunks WHERE content LIKE ? OR summary LIKE ? ORDER BY created_at DESC LIMIT 100",
|
|
551
554
|
).all(`%${q}%`, `%${q}%`).filter(passesFilter);
|
|
@@ -576,14 +579,10 @@ export class ViewerServer {
|
|
|
576
579
|
if (!seenIds.has(r.id)) { seenIds.add(r.id); merged.push(r); }
|
|
577
580
|
}
|
|
578
581
|
for (const r of ftsResults) {
|
|
579
|
-
if (seenIds.has(r.id))
|
|
580
|
-
const vscore = scoreMap.get(r.id);
|
|
581
|
-
if (vscore !== undefined && vscore < SEMANTIC_THRESHOLD) continue;
|
|
582
|
-
seenIds.add(r.id); merged.push(r);
|
|
582
|
+
if (!seenIds.has(r.id)) { seenIds.add(r.id); merged.push(r); }
|
|
583
583
|
}
|
|
584
584
|
|
|
585
|
-
const
|
|
586
|
-
const results = fallback ? ftsResults.slice(0, 20) : merged;
|
|
585
|
+
const results = merged.length > 0 ? merged : ftsResults.slice(0, 20);
|
|
587
586
|
|
|
588
587
|
this.store.recordViewerEvent("search");
|
|
589
588
|
this.jsonResponse(res, {
|
|
@@ -592,7 +591,6 @@ export class ViewerServer {
|
|
|
592
591
|
vectorCount: vectorResults.length,
|
|
593
592
|
ftsCount: ftsResults.length,
|
|
594
593
|
total: results.length,
|
|
595
|
-
fallbackFts: fallback,
|
|
596
594
|
});
|
|
597
595
|
}
|
|
598
596
|
|
|
@@ -751,9 +749,10 @@ export class ViewerServer {
|
|
|
751
749
|
this.store.setSkillVisibility(skillId, visibility);
|
|
752
750
|
this.jsonResponse(res, { ok: true, skillId, visibility });
|
|
753
751
|
} catch (err) {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
res.
|
|
752
|
+
const errMsg = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
753
|
+
this.log.error(`handleSkillVisibility error: skillId=${skillId}, body=${body}, err=${errMsg}`);
|
|
754
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
755
|
+
res.end(JSON.stringify({ error: errMsg }));
|
|
757
756
|
}
|
|
758
757
|
});
|
|
759
758
|
}
|
|
@@ -930,19 +929,25 @@ export class ViewerServer {
|
|
|
930
929
|
}
|
|
931
930
|
|
|
932
931
|
private handleDeleteAll(res: http.ServerResponse): void {
|
|
933
|
-
const result = this.store.deleteAll();
|
|
934
|
-
// Clean up skills-store directory
|
|
935
|
-
const skillsStoreDir = path.join(this.dataDir, "skills-store");
|
|
936
932
|
try {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
933
|
+
const result = this.store.deleteAll();
|
|
934
|
+
const skillsStoreDir = path.join(this.dataDir, "skills-store");
|
|
935
|
+
try {
|
|
936
|
+
if (fs.existsSync(skillsStoreDir)) {
|
|
937
|
+
fs.rmSync(skillsStoreDir, { recursive: true });
|
|
938
|
+
fs.mkdirSync(skillsStoreDir, { recursive: true });
|
|
939
|
+
this.log.info("Cleared skills-store directory");
|
|
940
|
+
}
|
|
941
|
+
} catch (err) {
|
|
942
|
+
this.log.warn(`Failed to clear skills-store: ${err}`);
|
|
941
943
|
}
|
|
944
|
+
this.jsonResponse(res, { ok: true, deleted: result });
|
|
942
945
|
} catch (err) {
|
|
943
|
-
|
|
946
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
947
|
+
this.log.error(`handleDeleteAll error: ${msg}`);
|
|
948
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
949
|
+
res.end(JSON.stringify({ ok: false, error: msg }));
|
|
944
950
|
}
|
|
945
|
-
this.jsonResponse(res, { ok: true, deleted: result });
|
|
946
951
|
}
|
|
947
952
|
|
|
948
953
|
// ─── Helpers ───
|
|
@@ -1023,6 +1028,158 @@ export class ViewerServer {
|
|
|
1023
1028
|
});
|
|
1024
1029
|
}
|
|
1025
1030
|
|
|
1031
|
+
private handleTestModel(req: http.IncomingMessage, res: http.ServerResponse): void {
|
|
1032
|
+
this.readBody(req, async (body) => {
|
|
1033
|
+
try {
|
|
1034
|
+
const { type, provider, model, endpoint, apiKey } = JSON.parse(body);
|
|
1035
|
+
if (!provider) {
|
|
1036
|
+
this.jsonResponse(res, { ok: false, error: "provider is required" });
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
if (type === "embedding") {
|
|
1040
|
+
await this.testEmbeddingModel(provider, model, endpoint, apiKey);
|
|
1041
|
+
this.jsonResponse(res, { ok: true, detail: `${provider}/${model}` });
|
|
1042
|
+
} else {
|
|
1043
|
+
await this.testChatModel(provider, model, endpoint, apiKey);
|
|
1044
|
+
this.jsonResponse(res, { ok: true, detail: `${provider}/${model}` });
|
|
1045
|
+
}
|
|
1046
|
+
} catch (e: unknown) {
|
|
1047
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1048
|
+
this.log.warn(`test-model failed: ${msg}`);
|
|
1049
|
+
this.jsonResponse(res, { ok: false, error: msg });
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
private serveFallbackModel(res: http.ServerResponse): void {
|
|
1055
|
+
try {
|
|
1056
|
+
const cfgPath = this.getOpenClawConfigPath();
|
|
1057
|
+
if (!fs.existsSync(cfgPath)) {
|
|
1058
|
+
this.jsonResponse(res, { available: false });
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
const raw = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
1062
|
+
const agentModel: string | undefined = raw?.agents?.defaults?.model?.primary;
|
|
1063
|
+
if (!agentModel) {
|
|
1064
|
+
this.jsonResponse(res, { available: false });
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
const [providerKey, modelId] = agentModel.includes("/")
|
|
1068
|
+
? agentModel.split("/", 2)
|
|
1069
|
+
: [undefined, agentModel];
|
|
1070
|
+
const providerCfg = providerKey
|
|
1071
|
+
? raw?.models?.providers?.[providerKey]
|
|
1072
|
+
: Object.values(raw?.models?.providers ?? {})[0] as Record<string, unknown> | undefined;
|
|
1073
|
+
if (!providerCfg || !providerCfg.baseUrl || !providerCfg.apiKey) {
|
|
1074
|
+
this.jsonResponse(res, { available: false });
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
this.jsonResponse(res, { available: true, model: modelId || agentModel, baseUrl: providerCfg.baseUrl });
|
|
1078
|
+
} catch {
|
|
1079
|
+
this.jsonResponse(res, { available: false });
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
private async testEmbeddingModel(provider: string, model: string, endpoint: string, apiKey: string): Promise<void> {
|
|
1084
|
+
if (provider === "local") {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
const baseUrl = (endpoint || "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
1088
|
+
const embUrl = baseUrl.endsWith("/embeddings") ? baseUrl : `${baseUrl}/embeddings`;
|
|
1089
|
+
const headers: Record<string, string> = {
|
|
1090
|
+
"Content-Type": "application/json",
|
|
1091
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1092
|
+
};
|
|
1093
|
+
if (provider === "cohere") {
|
|
1094
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
1095
|
+
const resp = await fetch(baseUrl.replace(/\/v\d+.*/, "/v2/embed"), {
|
|
1096
|
+
method: "POST",
|
|
1097
|
+
headers,
|
|
1098
|
+
body: JSON.stringify({ texts: ["test"], model: model || "embed-english-v3.0", input_type: "search_query", embedding_types: ["float"] }),
|
|
1099
|
+
signal: AbortSignal.timeout(15_000),
|
|
1100
|
+
});
|
|
1101
|
+
if (!resp.ok) {
|
|
1102
|
+
const txt = await resp.text();
|
|
1103
|
+
throw new Error(`Cohere embed ${resp.status}: ${txt}`);
|
|
1104
|
+
}
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
if (provider === "gemini") {
|
|
1108
|
+
const url = `https://generativelanguage.googleapis.com/v1/models/${model || "text-embedding-004"}:embedContent?key=${apiKey}`;
|
|
1109
|
+
const resp = await fetch(url, {
|
|
1110
|
+
method: "POST",
|
|
1111
|
+
headers: { "Content-Type": "application/json" },
|
|
1112
|
+
body: JSON.stringify({ content: { parts: [{ text: "test" }] } }),
|
|
1113
|
+
signal: AbortSignal.timeout(15_000),
|
|
1114
|
+
});
|
|
1115
|
+
if (!resp.ok) {
|
|
1116
|
+
const txt = await resp.text();
|
|
1117
|
+
throw new Error(`Gemini embed ${resp.status}: ${txt}`);
|
|
1118
|
+
}
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const resp = await fetch(embUrl, {
|
|
1122
|
+
method: "POST",
|
|
1123
|
+
headers,
|
|
1124
|
+
body: JSON.stringify({ input: ["test"], model: model || "text-embedding-3-small" }),
|
|
1125
|
+
signal: AbortSignal.timeout(15_000),
|
|
1126
|
+
});
|
|
1127
|
+
if (!resp.ok) {
|
|
1128
|
+
const txt = await resp.text();
|
|
1129
|
+
throw new Error(`${resp.status}: ${txt}`);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
private async testChatModel(provider: string, model: string, endpoint: string, apiKey: string): Promise<void> {
|
|
1134
|
+
const baseUrl = (endpoint || "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
1135
|
+
if (provider === "anthropic") {
|
|
1136
|
+
const url = endpoint || "https://api.anthropic.com/v1/messages";
|
|
1137
|
+
const resp = await fetch(url, {
|
|
1138
|
+
method: "POST",
|
|
1139
|
+
headers: {
|
|
1140
|
+
"Content-Type": "application/json",
|
|
1141
|
+
"x-api-key": apiKey,
|
|
1142
|
+
"anthropic-version": "2023-06-01",
|
|
1143
|
+
},
|
|
1144
|
+
body: JSON.stringify({ model: model || "claude-3-haiku-20240307", max_tokens: 5, messages: [{ role: "user", content: "hi" }] }),
|
|
1145
|
+
signal: AbortSignal.timeout(15_000),
|
|
1146
|
+
});
|
|
1147
|
+
if (!resp.ok) {
|
|
1148
|
+
const txt = await resp.text();
|
|
1149
|
+
throw new Error(`Anthropic ${resp.status}: ${txt}`);
|
|
1150
|
+
}
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
if (provider === "gemini") {
|
|
1154
|
+
const url = `https://generativelanguage.googleapis.com/v1/models/${model || "gemini-1.5-flash"}:generateContent?key=${apiKey}`;
|
|
1155
|
+
const resp = await fetch(url, {
|
|
1156
|
+
method: "POST",
|
|
1157
|
+
headers: { "Content-Type": "application/json" },
|
|
1158
|
+
body: JSON.stringify({ contents: [{ parts: [{ text: "hi" }] }], generationConfig: { maxOutputTokens: 5 } }),
|
|
1159
|
+
signal: AbortSignal.timeout(15_000),
|
|
1160
|
+
});
|
|
1161
|
+
if (!resp.ok) {
|
|
1162
|
+
const txt = await resp.text();
|
|
1163
|
+
throw new Error(`Gemini ${resp.status}: ${txt}`);
|
|
1164
|
+
}
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
const chatUrl = baseUrl.endsWith("/chat/completions") ? baseUrl : `${baseUrl}/chat/completions`;
|
|
1168
|
+
const resp = await fetch(chatUrl, {
|
|
1169
|
+
method: "POST",
|
|
1170
|
+
headers: {
|
|
1171
|
+
"Content-Type": "application/json",
|
|
1172
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1173
|
+
},
|
|
1174
|
+
body: JSON.stringify({ model: model || "gpt-4o-mini", max_tokens: 5, messages: [{ role: "user", content: "hi" }] }),
|
|
1175
|
+
signal: AbortSignal.timeout(15_000),
|
|
1176
|
+
});
|
|
1177
|
+
if (!resp.ok) {
|
|
1178
|
+
const txt = await resp.text();
|
|
1179
|
+
throw new Error(`${resp.status}: ${txt}`);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1026
1183
|
private serveLogs(res: http.ServerResponse, url: URL): void {
|
|
1027
1184
|
const limit = Math.min(Number(url.searchParams.get("limit") ?? 20), 200);
|
|
1028
1185
|
const offset = Math.max(0, Number(url.searchParams.get("offset") ?? 0));
|