chore: Lint extensions folder.
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import fs from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
@@ -24,7 +23,9 @@ function resolveDefaultDbPath(): string {
|
||||
const home = homedir();
|
||||
const preferred = join(home, ".openclaw", "memory", "lancedb");
|
||||
try {
|
||||
if (fs.existsSync(preferred)) return preferred;
|
||||
if (fs.existsSync(preferred)) {
|
||||
return preferred;
|
||||
}
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
@@ -32,7 +33,9 @@ function resolveDefaultDbPath(): string {
|
||||
for (const legacy of LEGACY_STATE_DIRS) {
|
||||
const candidate = join(home, legacy, "memory", "lancedb");
|
||||
try {
|
||||
if (fs.existsSync(candidate)) return candidate;
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
@@ -50,7 +53,9 @@ const EMBEDDING_DIMENSIONS: Record<string, number> = {
|
||||
|
||||
function assertAllowedKeys(value: Record<string, unknown>, allowed: string[], label: string) {
|
||||
const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
|
||||
if (unknown.length === 0) return;
|
||||
if (unknown.length === 0) {
|
||||
return;
|
||||
}
|
||||
throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
@@ -42,6 +41,7 @@ describe("memory plugin e2e", () => {
|
||||
expect(memoryPlugin.name).toBe("Memory (LanceDB)");
|
||||
expect(memoryPlugin.kind).toBe("memory");
|
||||
expect(memoryPlugin.configSchema).toBeDefined();
|
||||
// oxlint-disable-next-line typescript/unbound-method
|
||||
expect(memoryPlugin.register).toBeInstanceOf(Function);
|
||||
});
|
||||
|
||||
@@ -217,14 +217,16 @@ describeLive("memory plugin live tests", () => {
|
||||
registeredServices.push(service);
|
||||
},
|
||||
on: (hookName: string, handler: any) => {
|
||||
if (!registeredHooks[hookName]) registeredHooks[hookName] = [];
|
||||
if (!registeredHooks[hookName]) {
|
||||
registeredHooks[hookName] = [];
|
||||
}
|
||||
registeredHooks[hookName].push(handler);
|
||||
},
|
||||
resolvePath: (p: string) => p,
|
||||
};
|
||||
|
||||
// Register plugin
|
||||
await memoryPlugin.register(mockApi as any);
|
||||
memoryPlugin.register(mockApi as any);
|
||||
|
||||
// Check registration
|
||||
expect(registeredTools.length).toBe(3);
|
||||
|
||||
@@ -55,8 +55,12 @@ class MemoryDB {
|
||||
) {}
|
||||
|
||||
private async ensureInitialized(): Promise<void> {
|
||||
if (this.table) return;
|
||||
if (this.initPromise) return this.initPromise;
|
||||
if (this.table) {
|
||||
return;
|
||||
}
|
||||
if (this.initPromise) {
|
||||
return this.initPromise;
|
||||
}
|
||||
|
||||
this.initPromise = this.doInitialize();
|
||||
return this.initPromise;
|
||||
@@ -73,7 +77,7 @@ class MemoryDB {
|
||||
{
|
||||
id: "__schema__",
|
||||
text: "",
|
||||
vector: new Array(this.vectorDim).fill(0),
|
||||
vector: Array.from({ length: this.vectorDim }).fill(0),
|
||||
importance: 0,
|
||||
category: "other",
|
||||
createdAt: 0,
|
||||
@@ -179,25 +183,43 @@ const MEMORY_TRIGGERS = [
|
||||
];
|
||||
|
||||
function shouldCapture(text: string): boolean {
|
||||
if (text.length < 10 || text.length > 500) return false;
|
||||
if (text.length < 10 || text.length > 500) {
|
||||
return false;
|
||||
}
|
||||
// Skip injected context from memory recall
|
||||
if (text.includes("<relevant-memories>")) return false;
|
||||
if (text.includes("<relevant-memories>")) {
|
||||
return false;
|
||||
}
|
||||
// Skip system-generated content
|
||||
if (text.startsWith("<") && text.includes("</")) return false;
|
||||
if (text.startsWith("<") && text.includes("</")) {
|
||||
return false;
|
||||
}
|
||||
// Skip agent summary responses (contain markdown formatting)
|
||||
if (text.includes("**") && text.includes("\n-")) return false;
|
||||
if (text.includes("**") && text.includes("\n-")) {
|
||||
return false;
|
||||
}
|
||||
// Skip emoji-heavy responses (likely agent output)
|
||||
const emojiCount = (text.match(/[\u{1F300}-\u{1F9FF}]/gu) || []).length;
|
||||
if (emojiCount > 3) return false;
|
||||
if (emojiCount > 3) {
|
||||
return false;
|
||||
}
|
||||
return MEMORY_TRIGGERS.some((r) => r.test(text));
|
||||
}
|
||||
|
||||
function detectCategory(text: string): MemoryCategory {
|
||||
const lower = text.toLowerCase();
|
||||
if (/prefer|radši|like|love|hate|want/i.test(lower)) return "preference";
|
||||
if (/rozhodli|decided|will use|budeme/i.test(lower)) return "decision";
|
||||
if (/\+\d{10,}|@[\w.-]+\.\w+|is called|jmenuje se/i.test(lower)) return "entity";
|
||||
if (/is|are|has|have|je|má|jsou/i.test(lower)) return "fact";
|
||||
if (/prefer|radši|like|love|hate|want/i.test(lower)) {
|
||||
return "preference";
|
||||
}
|
||||
if (/rozhodli|decided|will use|budeme/i.test(lower)) {
|
||||
return "decision";
|
||||
}
|
||||
if (/\+\d{10,}|@[\w.-]+\.\w+|is called|jmenuje se/i.test(lower)) {
|
||||
return "entity";
|
||||
}
|
||||
if (/is|are|has|have|je|má|jsou/i.test(lower)) {
|
||||
return "fact";
|
||||
}
|
||||
return "other";
|
||||
}
|
||||
|
||||
@@ -455,13 +477,17 @@ const memoryPlugin = {
|
||||
// Auto-recall: inject relevant memories before agent starts
|
||||
if (cfg.autoRecall) {
|
||||
api.on("before_agent_start", async (event) => {
|
||||
if (!event.prompt || event.prompt.length < 5) return;
|
||||
if (!event.prompt || event.prompt.length < 5) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const vector = await embeddings.embed(event.prompt);
|
||||
const results = await db.search(vector, 3, 0.3);
|
||||
|
||||
if (results.length === 0) return;
|
||||
if (results.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const memoryContext = results
|
||||
.map((r) => `- [${r.entry.category}] ${r.entry.text}`)
|
||||
@@ -490,12 +516,16 @@ const memoryPlugin = {
|
||||
const texts: string[] = [];
|
||||
for (const msg of event.messages) {
|
||||
// Type guard for message object
|
||||
if (!msg || typeof msg !== "object") continue;
|
||||
if (!msg || typeof msg !== "object") {
|
||||
continue;
|
||||
}
|
||||
const msgObj = msg as Record<string, unknown>;
|
||||
|
||||
// Only process user and assistant messages
|
||||
const role = msgObj.role;
|
||||
if (role !== "user" && role !== "assistant") continue;
|
||||
if (role !== "user" && role !== "assistant") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = msgObj.content;
|
||||
|
||||
@@ -524,7 +554,9 @@ const memoryPlugin = {
|
||||
|
||||
// Filter for capturable content
|
||||
const toCapture = texts.filter((text) => text && shouldCapture(text));
|
||||
if (toCapture.length === 0) return;
|
||||
if (toCapture.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store each capturable piece (limit to 3 per conversation)
|
||||
let stored = 0;
|
||||
@@ -534,7 +566,9 @@ const memoryPlugin = {
|
||||
|
||||
// Check for duplicates (high similarity threshold)
|
||||
const existing = await db.search(vector, 1, 0.95);
|
||||
if (existing.length > 0) continue;
|
||||
if (existing.length > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await db.store({
|
||||
text,
|
||||
|
||||
Reference in New Issue
Block a user