@memtensor/memos-local-openclaw-plugin 1.0.4-beta.9 → 1.0.4
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/.env.example +7 -0
- package/README.md +94 -27
- package/dist/capture/index.js +3 -1
- package/dist/capture/index.js.map +1 -1
- package/dist/client/connector.d.ts +5 -0
- package/dist/client/connector.d.ts.map +1 -1
- package/dist/client/connector.js +89 -8
- package/dist/client/connector.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/hub/server.d.ts +2 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +240 -35
- package/dist/hub/server.js.map +1 -1
- package/dist/hub/user-manager.d.ts +9 -0
- package/dist/hub/user-manager.d.ts.map +1 -1
- package/dist/hub/user-manager.js +26 -2
- package/dist/hub/user-manager.js.map +1 -1
- package/dist/ingest/chunker.d.ts +2 -1
- package/dist/ingest/chunker.d.ts.map +1 -1
- package/dist/ingest/chunker.js +14 -10
- package/dist/ingest/chunker.js.map +1 -1
- package/dist/ingest/providers/index.js +2 -2
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +22 -4
- package/dist/recall/engine.js.map +1 -1
- package/dist/shared/llm-call.d.ts.map +1 -1
- package/dist/shared/llm-call.js +2 -1
- package/dist/shared/llm-call.js.map +1 -1
- package/dist/sharing/types.d.ts +1 -1
- package/dist/sharing/types.d.ts.map +1 -1
- package/dist/skill/evolver.d.ts +2 -0
- package/dist/skill/evolver.d.ts.map +1 -1
- package/dist/skill/evolver.js +56 -5
- package/dist/skill/evolver.js.map +1 -1
- package/dist/skill/generator.d.ts +2 -0
- package/dist/skill/generator.d.ts.map +1 -1
- package/dist/skill/generator.js +45 -3
- package/dist/skill/generator.js.map +1 -1
- package/dist/skill/installer.d.ts +26 -0
- package/dist/skill/installer.d.ts.map +1 -1
- package/dist/skill/installer.js +80 -4
- package/dist/skill/installer.js.map +1 -1
- package/dist/skill/upgrader.d.ts +2 -0
- package/dist/skill/upgrader.d.ts.map +1 -1
- package/dist/skill/upgrader.js +139 -1
- package/dist/skill/upgrader.js.map +1 -1
- package/dist/skill/validator.d.ts +3 -0
- package/dist/skill/validator.d.ts.map +1 -1
- package/dist/skill/validator.js +75 -0
- package/dist/skill/validator.js.map +1 -1
- package/dist/storage/sqlite.d.ts +57 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +290 -35
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +27 -8
- package/dist/telemetry.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +564 -225
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +9 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +357 -108
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +411 -52
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
- package/prebuilds/darwin-x64/better_sqlite3.node +0 -0
- package/prebuilds/linux-x64/better_sqlite3.node +0 -0
- package/prebuilds/win32-x64/better_sqlite3.node +0 -0
- package/src/capture/index.ts +4 -1
- package/src/client/connector.ts +92 -8
- package/src/config.ts +2 -1
- package/src/hub/server.ts +235 -35
- package/src/hub/user-manager.ts +42 -6
- package/src/ingest/chunker.ts +19 -13
- package/src/ingest/providers/index.ts +2 -2
- package/src/recall/engine.ts +20 -4
- package/src/shared/llm-call.ts +2 -1
- package/src/sharing/types.ts +1 -1
- package/src/skill/evolver.ts +58 -6
- package/src/skill/generator.ts +44 -5
- package/src/skill/installer.ts +107 -4
- package/src/skill/upgrader.ts +139 -1
- package/src/skill/validator.ts +79 -0
- package/src/storage/sqlite.ts +318 -40
- package/src/telemetry.ts +27 -9
- package/src/types.ts +11 -0
- package/src/viewer/html.ts +564 -225
- package/src/viewer/server.ts +333 -105
- package/telemetry.credentials.json +5 -0
package/src/skill/upgrader.ts
CHANGED
|
@@ -91,18 +91,30 @@ export class SkillUpgrader {
|
|
|
91
91
|
return { upgraded: false, qualityScore: null };
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
const backupDir = skill.dirPath + ".backup-" + Date.now();
|
|
95
|
+
try { fs.cpSync(skill.dirPath, backupDir, { recursive: true }); } catch { /* best-effort */ }
|
|
96
|
+
|
|
94
97
|
fs.writeFileSync(path.join(skill.dirPath, "SKILL.md"), newContent, "utf-8");
|
|
95
98
|
|
|
99
|
+
await this.rebuildCompanionFiles(skill, newContent, task);
|
|
100
|
+
|
|
96
101
|
const validation = await this.validator.validate(skill.dirPath, {
|
|
97
102
|
previousContent: currentContent,
|
|
98
103
|
});
|
|
99
104
|
|
|
100
105
|
if (!validation.valid) {
|
|
101
106
|
this.ctx.log.warn(`SkillUpgrader: validation failed for "${skill.name}", reverting: ${validation.errors.join("; ")}`);
|
|
102
|
-
fs.
|
|
107
|
+
if (fs.existsSync(backupDir)) {
|
|
108
|
+
fs.rmSync(skill.dirPath, { recursive: true });
|
|
109
|
+
fs.renameSync(backupDir, skill.dirPath);
|
|
110
|
+
} else {
|
|
111
|
+
fs.writeFileSync(path.join(skill.dirPath, "SKILL.md"), currentContent, "utf-8");
|
|
112
|
+
}
|
|
103
113
|
return { upgraded: false, qualityScore: null };
|
|
104
114
|
}
|
|
105
115
|
|
|
116
|
+
try { if (fs.existsSync(backupDir)) fs.rmSync(backupDir, { recursive: true }); } catch { /* cleanup */ }
|
|
117
|
+
|
|
106
118
|
const newVersion = skill.version + 1;
|
|
107
119
|
const newDescription = this.parseDescription(newContent) || skill.description;
|
|
108
120
|
|
|
@@ -216,4 +228,130 @@ export class SkillUpgrader {
|
|
|
216
228
|
if (match2) return match2[1];
|
|
217
229
|
return "";
|
|
218
230
|
}
|
|
231
|
+
|
|
232
|
+
private async rebuildCompanionFiles(skill: Skill, newContent: string, task: Task): Promise<void> {
|
|
233
|
+
const chain = buildSkillConfigChain(this.ctx);
|
|
234
|
+
if (chain.length === 0) return;
|
|
235
|
+
|
|
236
|
+
const chunks = this.store.getChunksByTask(task.id);
|
|
237
|
+
const conversationText = chunks
|
|
238
|
+
.filter(c => c.role === "user" || c.role === "assistant" || c.role === "tool")
|
|
239
|
+
.map(c => `[${c.role === "user" ? "User" : c.role === "assistant" ? "Assistant" : "Tool"}]: ${c.content.slice(0, 500)}`)
|
|
240
|
+
.join("\n\n")
|
|
241
|
+
.slice(0, 6000);
|
|
242
|
+
|
|
243
|
+
const scriptsPrompt = `Based on the following upgraded SKILL.md and task record, extract reusable automation scripts.
|
|
244
|
+
Rules:
|
|
245
|
+
- Only extract if the task record contains concrete shell commands, Python scripts, or TypeScript code that form a complete, reusable automation.
|
|
246
|
+
- Each script must be self-contained and runnable.
|
|
247
|
+
- If there are no automatable scripts, return an empty array.
|
|
248
|
+
- Don't fabricate scripts — only extract what was actually used.
|
|
249
|
+
|
|
250
|
+
SKILL.md:
|
|
251
|
+
${newContent.slice(0, 4000)}
|
|
252
|
+
|
|
253
|
+
Task conversation highlights:
|
|
254
|
+
${conversationText}
|
|
255
|
+
|
|
256
|
+
Reply with a JSON array only:
|
|
257
|
+
[{"filename": "deploy.sh", "content": "#!/bin/bash\\n..."}]
|
|
258
|
+
If no scripts, reply with: []`;
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const raw = await callLLMWithFallback(chain, scriptsPrompt, this.ctx.log, "SkillUpgrader.scripts", {
|
|
262
|
+
maxTokens: 3000, temperature: 0.1, timeoutMs: 60_000, openclawAPI: this.ctx.openclawAPI,
|
|
263
|
+
});
|
|
264
|
+
const scripts = this.parseJSONArray<{ filename: string; content: string }>(raw);
|
|
265
|
+
|
|
266
|
+
const scriptsDir = path.join(skill.dirPath, "scripts");
|
|
267
|
+
if (fs.existsSync(scriptsDir)) fs.rmSync(scriptsDir, { recursive: true });
|
|
268
|
+
if (scripts.length > 0) {
|
|
269
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
270
|
+
for (const s of scripts) {
|
|
271
|
+
fs.writeFileSync(path.join(scriptsDir, s.filename), s.content, "utf-8");
|
|
272
|
+
}
|
|
273
|
+
this.ctx.log.info(`SkillUpgrader: rebuilt ${scripts.length} scripts for "${skill.name}"`);
|
|
274
|
+
}
|
|
275
|
+
} catch (err) {
|
|
276
|
+
this.ctx.log.warn(`SkillUpgrader: companion scripts rebuild failed: ${err}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const evalsPrompt = `Based on the following skill, generate 3-4 realistic test prompts that should trigger this skill.
|
|
281
|
+
Requirements:
|
|
282
|
+
- Write test prompts that a real user would type, mix direct and indirect phrasings
|
|
283
|
+
- LANGUAGE RULE: Write in the SAME language as the skill content.
|
|
284
|
+
|
|
285
|
+
Skill:
|
|
286
|
+
${newContent.slice(0, 4000)}
|
|
287
|
+
|
|
288
|
+
Reply with a JSON array only:
|
|
289
|
+
[{"id": 1, "prompt": "A realistic user message", "expectations": ["Expected behavior 1"], "trigger_confidence": "high"}]`;
|
|
290
|
+
|
|
291
|
+
const raw = await callLLMWithFallback(chain, evalsPrompt, this.ctx.log, "SkillUpgrader.evals", {
|
|
292
|
+
maxTokens: 2000, temperature: 0.3, timeoutMs: 60_000, openclawAPI: this.ctx.openclawAPI,
|
|
293
|
+
});
|
|
294
|
+
const evals = this.parseJSONArray<{ id: number; prompt: string; expectations: string[] }>(raw);
|
|
295
|
+
|
|
296
|
+
const evalsDir = path.join(skill.dirPath, "evals");
|
|
297
|
+
if (fs.existsSync(evalsDir)) fs.rmSync(evalsDir, { recursive: true });
|
|
298
|
+
if (evals.length > 0) {
|
|
299
|
+
fs.mkdirSync(evalsDir, { recursive: true });
|
|
300
|
+
fs.writeFileSync(
|
|
301
|
+
path.join(evalsDir, "evals.json"),
|
|
302
|
+
JSON.stringify({ skill_name: skill.name, evals }, null, 2),
|
|
303
|
+
"utf-8",
|
|
304
|
+
);
|
|
305
|
+
this.ctx.log.info(`SkillUpgrader: rebuilt ${evals.length} evals for "${skill.name}"`);
|
|
306
|
+
}
|
|
307
|
+
} catch (err) {
|
|
308
|
+
this.ctx.log.warn(`SkillUpgrader: companion evals rebuild failed: ${err}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const refsPrompt = `Based on the following upgraded SKILL.md and task record, extract reference documentation worth preserving.
|
|
313
|
+
Rules:
|
|
314
|
+
- Only extract real reference content that appeared in the task (API docs, config examples, architecture notes).
|
|
315
|
+
- Each reference should be a standalone document useful for understanding the skill's domain.
|
|
316
|
+
- If there are no meaningful references, return an empty array.
|
|
317
|
+
- Don't fabricate content — only extract what was actually discussed or used.
|
|
318
|
+
- LANGUAGE RULE: Write in the SAME language as the skill content.
|
|
319
|
+
|
|
320
|
+
SKILL.md:
|
|
321
|
+
${newContent.slice(0, 4000)}
|
|
322
|
+
|
|
323
|
+
Task conversation highlights:
|
|
324
|
+
${conversationText}
|
|
325
|
+
|
|
326
|
+
Reply with a JSON array only:
|
|
327
|
+
[{"filename": "api-notes.md", "content": "# API Reference\\n..."}]
|
|
328
|
+
If no references, reply with: []`;
|
|
329
|
+
|
|
330
|
+
const raw = await callLLMWithFallback(chain, refsPrompt, this.ctx.log, "SkillUpgrader.references", {
|
|
331
|
+
maxTokens: 3000, temperature: 0.1, timeoutMs: 60_000, openclawAPI: this.ctx.openclawAPI,
|
|
332
|
+
});
|
|
333
|
+
const refs = this.parseJSONArray<{ filename: string; content: string }>(raw);
|
|
334
|
+
|
|
335
|
+
const refsDir = path.join(skill.dirPath, "references");
|
|
336
|
+
if (fs.existsSync(refsDir)) fs.rmSync(refsDir, { recursive: true });
|
|
337
|
+
if (refs.length > 0) {
|
|
338
|
+
fs.mkdirSync(refsDir, { recursive: true });
|
|
339
|
+
for (const r of refs) {
|
|
340
|
+
fs.writeFileSync(path.join(refsDir, r.filename), r.content, "utf-8");
|
|
341
|
+
}
|
|
342
|
+
this.ctx.log.info(`SkillUpgrader: rebuilt ${refs.length} references for "${skill.name}"`);
|
|
343
|
+
}
|
|
344
|
+
} catch (err) {
|
|
345
|
+
this.ctx.log.warn(`SkillUpgrader: companion references rebuild failed: ${err}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private parseJSONArray<T>(raw: string): T[] {
|
|
350
|
+
const match = raw.match(/\[[\s\S]*\]/);
|
|
351
|
+
if (!match) return [];
|
|
352
|
+
try {
|
|
353
|
+
const arr = JSON.parse(match[0]);
|
|
354
|
+
return Array.isArray(arr) ? arr : [];
|
|
355
|
+
} catch { return []; }
|
|
356
|
+
}
|
|
219
357
|
}
|
package/src/skill/validator.ts
CHANGED
|
@@ -31,6 +31,9 @@ export class SkillValidator {
|
|
|
31
31
|
this.validateFormat(dirPath, result);
|
|
32
32
|
if (!result.valid) return result;
|
|
33
33
|
|
|
34
|
+
this.checkCompanionConsistency(dirPath, result);
|
|
35
|
+
this.scanSecrets(dirPath, result);
|
|
36
|
+
|
|
34
37
|
if (opts?.previousContent) {
|
|
35
38
|
this.regressionCheck(dirPath, opts.previousContent, result);
|
|
36
39
|
}
|
|
@@ -133,6 +136,82 @@ export class SkillValidator {
|
|
|
133
136
|
}
|
|
134
137
|
}
|
|
135
138
|
|
|
139
|
+
private checkCompanionConsistency(dirPath: string, result: ValidationResult): void {
|
|
140
|
+
const skillMdPath = path.join(dirPath, "SKILL.md");
|
|
141
|
+
const content = fs.readFileSync(skillMdPath, "utf-8");
|
|
142
|
+
|
|
143
|
+
const referencedScripts = [...content.matchAll(/`scripts\/([^`]+)`/g)].map(m => m[1]);
|
|
144
|
+
const referencedRefs = [...content.matchAll(/`references\/([^`]+)`/g)].map(m => m[1]);
|
|
145
|
+
|
|
146
|
+
const scriptsDir = path.join(dirPath, "scripts");
|
|
147
|
+
const refsDir = path.join(dirPath, "references");
|
|
148
|
+
|
|
149
|
+
for (const f of referencedScripts) {
|
|
150
|
+
if (!fs.existsSync(path.join(scriptsDir, f))) {
|
|
151
|
+
result.warnings.push(`SKILL.md references scripts/${f} but file does not exist`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (const f of referencedRefs) {
|
|
155
|
+
if (!fs.existsSync(path.join(refsDir, f))) {
|
|
156
|
+
result.warnings.push(`SKILL.md references references/${f} but file does not exist`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (fs.existsSync(scriptsDir)) {
|
|
161
|
+
try {
|
|
162
|
+
const actualScripts = fs.readdirSync(scriptsDir);
|
|
163
|
+
for (const f of actualScripts) {
|
|
164
|
+
if (!referencedScripts.includes(f)) {
|
|
165
|
+
result.warnings.push(`scripts/${f} exists but is not referenced in SKILL.md`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch { /* best-effort */ }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const evalsPath = path.join(dirPath, "evals", "evals.json");
|
|
172
|
+
if (fs.existsSync(evalsPath)) {
|
|
173
|
+
try {
|
|
174
|
+
const evalsData = JSON.parse(fs.readFileSync(evalsPath, "utf-8"));
|
|
175
|
+
if (!Array.isArray(evalsData?.evals) && !Array.isArray(evalsData)) {
|
|
176
|
+
result.warnings.push("evals/evals.json exists but has unexpected structure");
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
result.warnings.push("evals/evals.json exists but is not valid JSON");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private static readonly SECRET_PATTERNS: Array<{ label: string; regex: RegExp }> = [
|
|
185
|
+
{ label: "API key (sk-...)", regex: /\bsk-[a-zA-Z0-9]{20,}\b/ },
|
|
186
|
+
{ label: "Bearer token", regex: /\bBearer\s+[a-zA-Z0-9_\-.]{20,}\b/ },
|
|
187
|
+
{ label: "AWS key", regex: /\bAKIA[0-9A-Z]{16}\b/ },
|
|
188
|
+
{ label: "Generic secret assignment", regex: /(api[_-]?key|secret|token|password|credential)\s*[:=]\s*["'][^"']{8,}["']/i },
|
|
189
|
+
{ label: "Base64 encoded secret (long)", regex: /\b[A-Za-z0-9+/]{40,}={0,2}\b/ },
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
private scanSecrets(dirPath: string, result: ValidationResult): void {
|
|
193
|
+
const filesToScan = ["SKILL.md"];
|
|
194
|
+
const scriptsDir = path.join(dirPath, "scripts");
|
|
195
|
+
if (fs.existsSync(scriptsDir)) {
|
|
196
|
+
try {
|
|
197
|
+
for (const f of fs.readdirSync(scriptsDir)) filesToScan.push(path.join("scripts", f));
|
|
198
|
+
} catch { /* best-effort */ }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (const relPath of filesToScan) {
|
|
202
|
+
const fullPath = path.join(dirPath, relPath);
|
|
203
|
+
if (!fs.existsSync(fullPath)) continue;
|
|
204
|
+
try {
|
|
205
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
206
|
+
for (const { label, regex } of SkillValidator.SECRET_PATTERNS) {
|
|
207
|
+
if (regex.test(content)) {
|
|
208
|
+
result.warnings.push(`Potential secret detected in ${relPath}: ${label}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch { /* best-effort */ }
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
136
215
|
private async assessQuality(dirPath: string, result: ValidationResult): Promise<void> {
|
|
137
216
|
const chain = buildSkillConfigChain(this.ctx);
|
|
138
217
|
if (chain.length === 0) return;
|