diff --git a/.gitignore b/.gitignore index ece7e45ba..b9f5f17b4 100644 --- a/.gitignore +++ b/.gitignore @@ -226,6 +226,9 @@ cython_debug/ # DS_Store .DS_Store +# OpenWork integration assets (managed separately) +apps/openwork-memos-integration/apps/desktop/public/assets/usecases/ + # Outputs and Evaluation Results outputs diff --git a/apps/memos-local-openclaw/.gitignore b/apps/memos-local-openclaw/.gitignore index de41320ce..cd7130691 100644 --- a/apps/memos-local-openclaw/.gitignore +++ b/apps/memos-local-openclaw/.gitignore @@ -14,8 +14,6 @@ Thumbs.db # Generated / non-essential package-lock.json .installed-version -www/ -docs/ ppt/ # Database files diff --git a/apps/memos-local-openclaw/README.md b/apps/memos-local-openclaw/README.md index cd0957893..c7dedc327 100644 --- a/apps/memos-local-openclaw/README.md +++ b/apps/memos-local-openclaw/README.md @@ -504,7 +504,7 @@ TELEMETRY_ENABLED=false ### Technical details -- Uses [PostHog](https://posthog.com) for event collection +- Uses Aliyun ARMS RUM for event collection - Each installation gets a random anonymous UUID (stored at `~/.openclaw/memos-local/.anonymous-id`) - Events are batched and sent in the background; failures are silently ignored - The anonymous ID is never linked to any personal information diff --git a/apps/memos-local-openclaw/index.ts b/apps/memos-local-openclaw/index.ts index c64e2b1ec..b18d14a91 100644 --- a/apps/memos-local-openclaw/index.ts +++ b/apps/memos-local-openclaw/index.ts @@ -9,6 +9,7 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { Type } from "@sinclair/typebox"; import * as fs from "fs"; import * as path from "path"; +import { fileURLToPath } from "url"; import { buildContext } from "./src/config"; import { SqliteStore } from "./src/storage/sqlite"; import { Embedder } from "./src/embedding"; @@ -76,13 +77,20 @@ const memosLocalPlugin = { register(api: OpenClawPluginApi) { // ─── Ensure better-sqlite3 native module is available ─── - const pluginDir = path.dirname(new URL(import.meta.url).pathname); + const pluginDir = path.dirname(fileURLToPath(import.meta.url)); + + function normalizeFsPath(p: string): string { + return path.resolve(p).replace(/\\/g, "/").toLowerCase(); + } + let sqliteReady = false; function trySqliteLoad(): boolean { try { const resolved = require.resolve("better-sqlite3", { paths: [pluginDir] }); - if (!resolved.startsWith(pluginDir)) { + const resolvedNorm = normalizeFsPath(resolved); + const pluginNorm = normalizeFsPath(pluginDir); + if (!resolvedNorm.startsWith(pluginNorm + "/") && resolvedNorm !== pluginNorm) { api.logger.warn(`memos-local: better-sqlite3 resolved outside plugin dir: ${resolved}`); return false; } @@ -182,6 +190,7 @@ const memosLocalPlugin = { const workspaceDir = api.resolvePath("~/.openclaw/workspace"); const skillCtx = { ...ctx, workspaceDir }; const skillEvolver = new SkillEvolver(store, engine, skillCtx); + skillEvolver.onSkillEvolved = (name, type) => telemetry.trackSkillEvolved(name, type); const skillInstaller = new SkillInstaller(store, skillCtx); let pluginVersion = "0.0.0"; @@ -257,6 +266,7 @@ const memosLocalPlugin = { return result; } catch (e) { ok = false; + telemetry.trackError(toolName, (e as Error)?.name ?? "unknown"); throw e; } finally { const dur = performance.now() - t0; @@ -720,6 +730,7 @@ const memosLocalPlugin = { parameters: Type.Object({}), execute: trackTool("memory_viewer", async () => { ctx.log.debug(`memory_viewer called`); + telemetry.trackViewerOpened(); const url = `http://127.0.0.1:${viewerPort}`; return { content: [ diff --git a/apps/memos-local-openclaw/package.json b/apps/memos-local-openclaw/package.json index 441636e3b..cfe0c5af0 100644 --- a/apps/memos-local-openclaw/package.json +++ b/apps/memos-local-openclaw/package.json @@ -50,7 +50,6 @@ "@huggingface/transformers": "^3.8.0", "@sinclair/typebox": "^0.34.48", "better-sqlite3": "^12.6.2", - "posthog-node": "^5.28.0", "puppeteer": "^24.38.0", "semver": "^7.7.4", "uuid": "^10.0.0" diff --git a/apps/memos-local-openclaw/scripts/postinstall.cjs b/apps/memos-local-openclaw/scripts/postinstall.cjs index 526c0524e..486a02669 100644 --- a/apps/memos-local-openclaw/scripts/postinstall.cjs +++ b/apps/memos-local-openclaw/scripts/postinstall.cjs @@ -112,7 +112,7 @@ try { function ensureDependencies() { phase(0, "检测核心依赖 / Check core dependencies"); - const coreDeps = ["@sinclair/typebox", "uuid", "posthog-node", "@huggingface/transformers"]; + const coreDeps = ["@sinclair/typebox", "uuid", "@huggingface/transformers"]; const missing = []; for (const dep of coreDeps) { try { diff --git a/apps/memos-local-openclaw/scripts/refresh-summaries.ts b/apps/memos-local-openclaw/scripts/refresh-summaries.ts index e15204b91..46577c09b 100644 --- a/apps/memos-local-openclaw/scripts/refresh-summaries.ts +++ b/apps/memos-local-openclaw/scripts/refresh-summaries.ts @@ -62,36 +62,66 @@ async function main() { process.exit(1); } + const isAnthropic = cfg.provider === "anthropic" + || cfg.endpoint?.toLowerCase().includes("anthropic"); + console.log(`Summarizer: ${cfg.provider} / ${cfg.model}`); let endpoint = cfg.endpoint.replace(/\/+$/, ""); - if (!endpoint.endsWith("/chat/completions")) endpoint += "/chat/completions"; + if (isAnthropic) { + if (!endpoint.endsWith("/v1/messages") && !endpoint.endsWith("/messages")) { + endpoint += "/v1/messages"; + } + } else { + if (!endpoint.endsWith("/chat/completions")) endpoint += "/chat/completions"; + } async function callLLM(text: string): Promise { + const headers: Record = isAnthropic + ? { + "Content-Type": "application/json", + "x-api-key": cfg.apiKey, + "anthropic-version": "2023-06-01", + } + : { + "Content-Type": "application/json", + Authorization: `Bearer ${cfg.apiKey}`, + }; + + const body = isAnthropic + ? JSON.stringify({ + model: cfg.model, + temperature: 0.1, + max_tokens: 4096, + system: TASK_SUMMARY_PROMPT, + messages: [{ role: "user", content: text }], + }) + : JSON.stringify({ + model: cfg.model, + temperature: 0.1, + max_tokens: 4096, + messages: [ + { role: "system", content: TASK_SUMMARY_PROMPT }, + { role: "user", content: text }, + ], + }); + const resp = await fetch(endpoint, { method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${cfg.apiKey}`, - }, - body: JSON.stringify({ - model: cfg.model, - temperature: 0.1, - max_tokens: 4096, - messages: [ - { role: "system", content: TASK_SUMMARY_PROMPT }, - { role: "user", content: text }, - ], - }), + headers, + body, signal: AbortSignal.timeout(60_000), }); if (!resp.ok) { - const body = await resp.text(); - throw new Error(`API ${resp.status}: ${body.slice(0, 200)}`); + const respBody = await resp.text(); + throw new Error(`API ${resp.status}: ${respBody.slice(0, 200)}`); } const json = (await resp.json()) as any; + if (isAnthropic) { + return json.content?.find((c: any) => c.type === "text")?.text?.trim() ?? ""; + } return json.choices[0]?.message?.content?.trim() ?? ""; } diff --git a/apps/memos-local-openclaw/src/capture/index.ts b/apps/memos-local-openclaw/src/capture/index.ts index 482f9fbe1..0eaee1af3 100644 --- a/apps/memos-local-openclaw/src/capture/index.ts +++ b/apps/memos-local-openclaw/src/capture/index.ts @@ -70,6 +70,7 @@ export function captureMessages( if (role === "user") { content = stripInboundMetadata(content); } else { + content = stripThinkingTags(content); content = stripEvidenceWrappers(content, evidenceTag); } if (!content.trim()) continue; @@ -149,6 +150,13 @@ export function stripInboundMetadata(text: string): string { return stripEnvelopePrefix(result.join("\n")).trim(); } +/** Strip …][\s\S]*?<\/think>\s*/gi; + +function stripThinkingTags(text: string): string { + return text.replace(THINKING_TAG_RE, ""); +} + function stripEnvelopePrefix(text: string): string { return text.replace(ENVELOPE_PREFIX_RE, ""); } diff --git a/apps/memos-local-openclaw/src/config.ts b/apps/memos-local-openclaw/src/config.ts index 64acc97e5..b58ba282f 100644 --- a/apps/memos-local-openclaw/src/config.ts +++ b/apps/memos-local-openclaw/src/config.ts @@ -51,8 +51,6 @@ export function resolveConfig(raw: Partial | undefined, stateD }, telemetry: { enabled: telemetryEnabled, - posthogApiKey: cfg.telemetry?.posthogApiKey ?? process.env.POSTHOG_API_KEY ?? "", - posthogHost: cfg.telemetry?.posthogHost ?? process.env.POSTHOG_HOST ?? "", }, }; } diff --git a/apps/memos-local-openclaw/src/ingest/providers/index.ts b/apps/memos-local-openclaw/src/ingest/providers/index.ts index 8b43f14b1..98f1f6793 100644 --- a/apps/memos-local-openclaw/src/ingest/providers/index.ts +++ b/apps/memos-local-openclaw/src/ingest/providers/index.ts @@ -1,13 +1,47 @@ import * as fs from "fs"; import * as path from "path"; -import type { SummarizerConfig, Logger } from "../../types"; -import { summarizeOpenAI, summarizeTaskOpenAI, generateTaskTitleOpenAI, judgeNewTopicOpenAI, filterRelevantOpenAI, judgeDedupOpenAI } from "./openai"; +import type { SummarizerConfig, SummaryProvider, Logger } from "../../types"; +import { summarizeOpenAI, summarizeTaskOpenAI, judgeNewTopicOpenAI, filterRelevantOpenAI, judgeDedupOpenAI } from "./openai"; import type { FilterResult, DedupResult } from "./openai"; export type { FilterResult, DedupResult } from "./openai"; import { summarizeAnthropic, summarizeTaskAnthropic, generateTaskTitleAnthropic, judgeNewTopicAnthropic, filterRelevantAnthropic, judgeDedupAnthropic } from "./anthropic"; import { summarizeGemini, summarizeTaskGemini, generateTaskTitleGemini, judgeNewTopicGemini, filterRelevantGemini, judgeDedupGemini } from "./gemini"; import { summarizeBedrock, summarizeTaskBedrock, generateTaskTitleBedrock, judgeNewTopicBedrock, filterRelevantBedrock, judgeDedupBedrock } from "./bedrock"; +/** + * Detect provider type from provider key name or base URL. + */ +function detectProvider( + providerKey: string | undefined, + baseUrl: string, +): SummaryProvider { + const key = providerKey?.toLowerCase() ?? ""; + const url = baseUrl.toLowerCase(); + if (key.includes("anthropic") || url.includes("anthropic")) return "anthropic"; + if (key.includes("gemini") || url.includes("generativelanguage.googleapis.com")) { + return "gemini"; + } + if (key.includes("bedrock") || url.includes("bedrock")) return "bedrock"; + return "openai_compatible"; +} + +/** + * Return the correct endpoint for a given provider and base URL. + */ +function normalizeEndpointForProvider( + provider: SummaryProvider, + baseUrl: string, +): string { + const stripped = baseUrl.replace(/\/+$/, ""); + if (provider === "anthropic") { + if (stripped.endsWith("/v1/messages")) return stripped; + return `${stripped}/v1/messages`; + } + if (stripped.endsWith("/chat/completions")) return stripped; + if (stripped.endsWith("/completions")) return stripped; + return `${stripped}/chat/completions`; +} + /** * Build a SummarizerConfig from OpenClaw's native model configuration (openclaw.json). * This serves as the final fallback when both strongCfg and plugin summarizer fail or are absent. @@ -15,7 +49,8 @@ import { summarizeBedrock, summarizeTaskBedrock, generateTaskTitleBedrock, judge function loadOpenClawFallbackConfig(log: Logger): SummarizerConfig | undefined { try { const home = process.env.HOME ?? process.env.USERPROFILE ?? ""; - const cfgPath = path.join(home, ".openclaw", "openclaw.json"); + const ocHome = process.env.OPENCLAW_STATE_DIR || path.join(home, ".openclaw"); + const cfgPath = path.join(ocHome, "openclaw.json"); if (!fs.existsSync(cfgPath)) return undefined; const raw = JSON.parse(fs.readFileSync(cfgPath, "utf-8")); @@ -36,13 +71,12 @@ function loadOpenClawFallbackConfig(log: Logger): SummarizerConfig | undefined { const apiKey: string | undefined = providerCfg.apiKey; if (!baseUrl || !apiKey) return undefined; - const endpoint = baseUrl.endsWith("/chat/completions") - ? baseUrl - : baseUrl.replace(/\/+$/, "") + "/chat/completions"; + const provider = detectProvider(providerKey, baseUrl); + const endpoint = normalizeEndpointForProvider(provider, baseUrl); - log.debug(`OpenClaw fallback model: ${modelId} via ${baseUrl}`); + log.debug(`OpenClaw fallback model: ${modelId} via ${baseUrl} (${provider})`); return { - provider: "openai_compatible", + provider, endpoint, apiKey, model: modelId, diff --git a/apps/memos-local-openclaw/src/shared/llm-call.ts b/apps/memos-local-openclaw/src/shared/llm-call.ts index 1b149562f..142ffbbc2 100644 --- a/apps/memos-local-openclaw/src/shared/llm-call.ts +++ b/apps/memos-local-openclaw/src/shared/llm-call.ts @@ -1,6 +1,35 @@ import * as fs from "fs"; import * as path from "path"; -import type { SummarizerConfig, Logger, PluginContext } from "../types"; +import type { SummarizerConfig, SummaryProvider, Logger, PluginContext } from "../types"; + +/** + * Detect provider type from provider key name or base URL. + */ +function detectProvider(providerKey: string | undefined, baseUrl: string): SummaryProvider { + const key = providerKey?.toLowerCase() ?? ""; + const url = baseUrl.toLowerCase(); + if (key.includes("anthropic") || url.includes("anthropic")) return "anthropic"; + if (key.includes("gemini") || url.includes("generativelanguage.googleapis.com")) { + return "gemini"; + } + if (key.includes("bedrock") || url.includes("bedrock")) return "bedrock"; + return "openai_compatible"; +} + +/** + * Return the correct default endpoint for a given provider. + */ +function defaultEndpointForProvider(provider: SummaryProvider, baseUrl: string): string { + const stripped = baseUrl.replace(/\/+$/, ""); + if (provider === "anthropic") { + if (stripped.endsWith("/v1/messages")) return stripped; + return `${stripped}/v1/messages`; + } + // OpenAI-compatible providers + if (stripped.endsWith("/chat/completions")) return stripped; + if (stripped.endsWith("/completions")) return stripped; + return `${stripped}/chat/completions`; +} /** * Build a SummarizerConfig from OpenClaw's native model configuration (openclaw.json). @@ -30,13 +59,12 @@ export function loadOpenClawFallbackConfig(log: Logger): SummarizerConfig | unde const apiKey: string | undefined = providerCfg.apiKey; if (!baseUrl || !apiKey) return undefined; - const endpoint = baseUrl.endsWith("/chat/completions") - ? baseUrl - : baseUrl.replace(/\/+$/, "") + "/chat/completions"; + const provider = detectProvider(providerKey, baseUrl); + const endpoint = defaultEndpointForProvider(provider, baseUrl); - log.debug(`OpenClaw fallback model: ${modelId} via ${baseUrl}`); + log.debug(`OpenClaw fallback model: ${modelId} via ${baseUrl} (${provider})`); return { - provider: "openai_compatible", + provider, endpoint, apiKey, model: modelId, @@ -68,22 +96,84 @@ export interface LLMCallOptions { timeoutMs?: number; } -function normalizeEndpoint(url: string): string { +function normalizeOpenAIEndpoint(url: string): string { const stripped = url.replace(/\/+$/, ""); if (stripped.endsWith("/chat/completions")) return stripped; if (stripped.endsWith("/completions")) return stripped; return `${stripped}/chat/completions`; } +function normalizeAnthropicEndpoint(url: string): string { + const stripped = url.replace(/\/+$/, ""); + if (stripped.endsWith("/v1/messages")) return stripped; + if (stripped.endsWith("/messages")) return stripped; + return `${stripped}/v1/messages`; +} + +function isAnthropicProvider(cfg: SummarizerConfig): boolean { + return cfg.provider === "anthropic"; +} + /** * Make a single LLM call with the given config. Throws on failure. + * Dispatches to Anthropic or OpenAI-compatible format based on provider. */ export async function callLLMOnce( cfg: SummarizerConfig, prompt: string, opts: LLMCallOptions = {}, ): Promise { - const endpoint = normalizeEndpoint(cfg.endpoint ?? "https://api.openai.com/v1/chat/completions"); + if (isAnthropicProvider(cfg)) { + return callLLMOnceAnthropic(cfg, prompt, opts); + } + return callLLMOnceOpenAI(cfg, prompt, opts); +} + +async function callLLMOnceAnthropic( + cfg: SummarizerConfig, + prompt: string, + opts: LLMCallOptions = {}, +): Promise { + const endpoint = normalizeAnthropicEndpoint( + cfg.endpoint ?? "https://api.anthropic.com/v1/messages", + ); + const model = cfg.model ?? "claude-3-haiku-20240307"; + const headers: Record = { + "Content-Type": "application/json", + "x-api-key": cfg.apiKey ?? "", + "anthropic-version": "2023-06-01", + ...cfg.headers, + }; + + const resp = await fetch(endpoint, { + method: "POST", + headers, + body: JSON.stringify({ + model, + temperature: opts.temperature ?? 0.1, + max_tokens: opts.maxTokens ?? 1024, + messages: [{ role: "user", content: prompt }], + }), + signal: AbortSignal.timeout(opts.timeoutMs ?? 30_000), + }); + + if (!resp.ok) { + const body = await resp.text(); + throw new Error(`LLM call failed (${resp.status}): ${body}`); + } + + const json = (await resp.json()) as { content: Array<{ type: string; text: string }> }; + return json.content.find((c) => c.type === "text")?.text?.trim() ?? ""; +} + +async function callLLMOnceOpenAI( + cfg: SummarizerConfig, + prompt: string, + opts: LLMCallOptions = {}, +): Promise { + const endpoint = normalizeOpenAIEndpoint( + cfg.endpoint ?? "https://api.openai.com/v1/chat/completions", + ); const model = cfg.model ?? "gpt-4o-mini"; const headers: Record = { "Content-Type": "application/json", diff --git a/apps/memos-local-openclaw/src/skill/evolver.ts b/apps/memos-local-openclaw/src/skill/evolver.ts index 1dd47c2c2..80f723a9b 100644 --- a/apps/memos-local-openclaw/src/skill/evolver.ts +++ b/apps/memos-local-openclaw/src/skill/evolver.ts @@ -12,6 +12,8 @@ import { SkillUpgrader } from "./upgrader"; import { SkillInstaller } from "./installer"; import { buildSkillConfigChain, callLLMWithFallback } from "../shared/llm-call"; +export type SkillEvolvedCallback = (skillName: string, upgradeType: "created" | "upgraded") => void; + export class SkillEvolver { private evaluator: SkillEvaluator; private generator: SkillGenerator; @@ -19,6 +21,7 @@ export class SkillEvolver { private installer: SkillInstaller; private processing = false; private queue: Task[] = []; + onSkillEvolved: SkillEvolvedCallback | null = null; constructor( private store: SqliteStore, @@ -279,6 +282,7 @@ Use selectedIndex 0 when none is highly relevant.`; if (upgraded) { this.store.linkTaskSkill(task.id, freshSkill.id, "evolved_from", freshSkill.version + 1); this.installer.syncIfInstalled(freshSkill.name); + this.onSkillEvolved?.(freshSkill.name, "upgraded"); } else { this.store.linkTaskSkill(task.id, freshSkill.id, "applied_to", freshSkill.version); } @@ -307,6 +311,7 @@ Use selectedIndex 0 when none is highly relevant.`; this.markChunksWithSkill(chunks, skill.id); this.store.linkTaskSkill(task.id, skill.id, "generated_from", 1); this.store.setTaskSkillMeta(task.id, { skillStatus: "generated", skillReason: evalResult.reason }); + this.onSkillEvolved?.(skill.name, "created"); const autoInstall = this.ctx.config.skillEvolution?.autoInstall ?? DEFAULTS.skillAutoInstall; if (autoInstall && skill.status === "active") { diff --git a/apps/memos-local-openclaw/src/telemetry.ts b/apps/memos-local-openclaw/src/telemetry.ts index ac50c5d9d..4bf999fc2 100644 --- a/apps/memos-local-openclaw/src/telemetry.ts +++ b/apps/memos-local-openclaw/src/telemetry.ts @@ -1,5 +1,5 @@ /** - * Telemetry module — anonymous usage analytics via PostHog. + * Telemetry module — anonymous usage analytics via Aliyun ARMS RUM. * * Privacy-first design: * - Enabled by default with anonymous data only; opt-out via TELEMETRY_ENABLED=false @@ -8,7 +8,6 @@ * - Only sends aggregate counts, tool names, latencies, and version info */ -import { PostHog } from "posthog-node"; import * as fs from "fs"; import * as path from "path"; import * as os from "os"; @@ -17,45 +16,61 @@ import type { Logger } from "./types"; export interface TelemetryConfig { enabled?: boolean; - posthogApiKey?: string; - posthogHost?: string; } -const DEFAULT_POSTHOG_API_KEY = "phc_7lae6UC5jyImDefX6uub7zCxWyswCGNoBifCKqjvDrI"; -const DEFAULT_POSTHOG_HOST = "https://eu.i.posthog.com"; +const ARMS_ENDPOINT = + "https://proj-xtrace-e218d9316b328f196a3c640cc7ca84-cn-hangzhou.cn-hangzhou.log.aliyuncs.com" + + "/rum/web/v2" + + "?workspace=default-cms-1026429231103299-cn-hangzhou" + + "&service_id=a3u72ukxmr@066657d42a13a9a9f337f"; + +const ARMS_PID = "a3u72ukxmr@066657d42a13a9a9f337f"; +const ARMS_ENV = "prod"; + +const FLUSH_AT = 10; +const FLUSH_INTERVAL_MS = 30_000; +const SEND_TIMEOUT_MS = 30_000; +const SESSION_TTL_MS = 30 * 60_000; // 30 min inactivity → new session +interface ArmsEvent { + event_type: "custom"; + type: string; + name: string; + group: string; + value: number; + properties: Record; + timestamp: number; + event_id: string; + times: number; +} export class Telemetry { - private client: PostHog | null = null; private distinctId: string; private enabled: boolean; private pluginVersion: string; private log: Logger; private dailyPingSent = false; private dailyPingDate = ""; + private buffer: ArmsEvent[] = []; + private flushTimer: ReturnType | null = null; + private sessionId: string; + private firstSeenDate: string; constructor(config: TelemetryConfig, stateDir: string, pluginVersion: string, log: Logger) { this.log = log; this.pluginVersion = pluginVersion; this.enabled = config.enabled !== false; this.distinctId = this.loadOrCreateAnonymousId(stateDir); + this.firstSeenDate = this.loadOrCreateFirstSeen(stateDir); + this.sessionId = this.loadOrCreateSessionId(stateDir); if (!this.enabled) { this.log.debug("Telemetry disabled (opt-out via TELEMETRY_ENABLED=false)"); return; } - const apiKey = config.posthogApiKey || DEFAULT_POSTHOG_API_KEY; - try { - this.client = new PostHog(apiKey, { - host: config.posthogHost || DEFAULT_POSTHOG_HOST, - flushAt: 10, - flushInterval: 30_000, - }); - this.log.debug("Telemetry initialized (PostHog)"); - } catch (err) { - this.log.warn(`Telemetry init failed: ${err}`); - this.enabled = false; - } + this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS); + if (this.flushTimer.unref) this.flushTimer.unref(); + this.log.debug("Telemetry initialized (ARMS)"); } private loadOrCreateAnonymousId(stateDir: string): string { @@ -81,24 +96,113 @@ export class Telemetry { return newId; } + private loadOrCreateSessionId(stateDir: string): string { + const filePath = path.join(stateDir, "memos-local", ".session"); + try { + const raw = fs.readFileSync(filePath, "utf-8").trim(); + const sep = raw.indexOf("|"); + if (sep > 0) { + const ts = parseInt(raw.slice(0, sep), 10); + const id = raw.slice(sep + 1); + if (id.length > 10 && Date.now() - ts < SESSION_TTL_MS) { + this.touchSession(filePath, id); + return id; + } + } + } catch {} + const newId = uuidv4(); + this.touchSession(filePath, newId); + return newId; + } + + private touchSession(filePath: string, id: string): void { + try { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, `${Date.now()}|${id}`, "utf-8"); + } catch {} + } + + private loadOrCreateFirstSeen(stateDir: string): string { + const filePath = path.join(stateDir, "memos-local", ".first-seen"); + try { + const existing = fs.readFileSync(filePath, "utf-8").trim(); + if (existing.length === 10) return existing; + } catch {} + const today = new Date().toISOString().slice(0, 10); + try { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, today, "utf-8"); + } catch {} + return today; + } + private capture(event: string, properties?: Record): void { - if (!this.enabled || !this.client) return; + if (!this.enabled) return; + + const safeProps: Record = { + plugin_version: this.pluginVersion, + os: os.platform(), + os_version: os.release(), + node_version: process.version, + arch: os.arch(), + }; + if (properties) { + for (const [k, v] of Object.entries(properties)) { + if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") { + safeProps[k] = v; + } + } + } + + this.buffer.push({ + event_type: "custom", + type: "memos_plugin", + name: event, + group: "memos_local", + value: 1, + properties: safeProps, + timestamp: Date.now(), + event_id: uuidv4(), + times: 1, + }); + + if (this.buffer.length >= FLUSH_AT) { + this.flush(); + } + } + + private buildPayload(events: ArmsEvent[]): Record { + return { + app: { + id: ARMS_PID, + env: ARMS_ENV, + version: this.pluginVersion, + type: "node", + }, + user: { id: this.distinctId }, + session: { id: this.sessionId }, + net: {}, + view: { id: "plugin", name: "memos-local-openclaw" }, + events, + _v: "1.0.0", + }; + } + + private async flush(): Promise { + if (this.buffer.length === 0) return; + const batch = this.buffer.splice(0); + const payload = this.buildPayload(batch); try { - this.client.capture({ - distinctId: this.distinctId, - event, - properties: { - plugin_version: this.pluginVersion, - os: os.platform(), - os_version: os.release(), - node_version: process.version, - arch: os.arch(), - ...properties, - }, + const resp = await fetch(ARMS_ENDPOINT, { + method: "POST", + headers: { "Content-Type": "text/plain" }, + body: JSON.stringify(payload), + signal: AbortSignal.timeout(SEND_TIMEOUT_MS), }); - } catch { - // best-effort, never throw + this.log.debug(`Telemetry flush: ${batch.length} events → ${resp.status}`); + } catch (err) { + this.log.debug(`Telemetry flush failed: ${err}`); } } @@ -131,7 +235,7 @@ export class Telemetry { }); } - trackSkillEvolved(skillName: string, upgradeType: string): void { + trackSkillEvolved(skillName: string, upgradeType: "created" | "upgraded"): void { this.capture("skill_evolved", { skill_name: skillName, upgrade_type: upgradeType, @@ -150,19 +254,28 @@ export class Telemetry { }); } + trackError(source: string, errorType: string): void { + this.capture("plugin_error", { + error_source: source, + error_type: errorType, + }); + } + private maybeSendDailyPing(): void { const today = new Date().toISOString().slice(0, 10); if (this.dailyPingSent && this.dailyPingDate === today) return; this.dailyPingSent = true; this.dailyPingDate = today; - this.capture("daily_active"); + this.capture("daily_active", { + first_seen_date: this.firstSeenDate, + }); } async shutdown(): Promise { - if (this.client) { - try { - await this.client.shutdown(); - } catch {} + if (this.flushTimer) { + clearInterval(this.flushTimer); + this.flushTimer = null; } + await this.flush(); } } diff --git a/apps/memos-local-openclaw/src/types.ts b/apps/memos-local-openclaw/src/types.ts index 0396e94be..ef02ed565 100644 --- a/apps/memos-local-openclaw/src/types.ts +++ b/apps/memos-local-openclaw/src/types.ts @@ -251,8 +251,6 @@ export interface SkillEvolutionConfig { export interface TelemetryConfig { enabled?: boolean; - posthogApiKey?: string; - posthogHost?: string; } export interface MemosLocalConfig { diff --git a/apps/memos-local-openclaw/src/viewer/server.ts b/apps/memos-local-openclaw/src/viewer/server.ts index 8bb7efced..4c1159544 100644 --- a/apps/memos-local-openclaw/src/viewer/server.ts +++ b/apps/memos-local-openclaw/src/viewer/server.ts @@ -992,7 +992,8 @@ export class ViewerServer { private getOpenClawConfigPath(): string { const home = process.env.HOME || process.env.USERPROFILE || ""; - return path.join(home, ".openclaw", "openclaw.json"); + const ocHome = process.env.OPENCLAW_STATE_DIR || path.join(home, ".openclaw"); + return path.join(ocHome, "openclaw.json"); } private serveConfig(res: http.ServerResponse): void { @@ -1438,7 +1439,29 @@ export class ViewerServer { private getOpenClawHome(): string { const home = process.env.HOME || process.env.USERPROFILE || ""; - return path.join(home, ".openclaw"); + return process.env.OPENCLAW_STATE_DIR || path.join(home, ".openclaw"); + } + + private handleCleanupPolluted(res: http.ServerResponse): void { + try { + const polluted = this.store.findPollutedUserChunks(); + let deleted = 0; + for (const { id, reason } of polluted) { + if (this.store.deleteChunk(id)) { + deleted++; + this.log.info(`Cleaned polluted chunk ${id}: ${reason}`); + } + } + const fixed = this.store.fixMixedUserChunks(); + this.log.info(`Cleanup: removed ${deleted} polluted, fixed ${fixed} mixed chunks`); + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ deleted, fixed, total: polluted.length })); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + this.log.error(`handleCleanupPolluted error: ${msg}`); + res.writeHead(500, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: msg })); + } } private handleCleanupPolluted(res: http.ServerResponse): void { diff --git a/apps/memos-local-openclaw/www/demo/index.html b/apps/memos-local-openclaw/www/demo/index.html new file mode 100644 index 000000000..ffc6627e1 --- /dev/null +++ b/apps/memos-local-openclaw/www/demo/index.html @@ -0,0 +1,670 @@ + + + + + +MemOS Local — 交互式演示 | Interactive Demo + + + + + + + + + +
+
+
+ + + +
+
+

沉浸体验 MemOS LocalExperience MemOS Local

+

交互式演示记忆导入、智能检索和 Viewer 管理的完整流程。所有数据均为模拟,无需安装即可体验。Interactive demo of memory import, smart retrieval, and Viewer management. All data is simulated — no installation required.

+
+
+ +
+
+
🦐 记忆导入🦐 Memory Import
+
🔍 智能检索🔍 Smart Retrieval
+
📊 Viewer 管理📊 Viewer Dashboard
+
+
+ + +
+
+ +
+
+
1
+
扫描记忆Scan
+
+
+
2
+
导入迁移Import
+
+
+
3
+
导入完成Complete
+
+
+ + +
+
+
Memory Viewer — Import
+
+
+
🦐
+

导入 OpenClaw 记忆Import OpenClaw Memories

+

将 OpenClaw 内置的记忆数据和对话历史迁移到智能记忆系统。Migrate OpenClaw's built-in memory data and conversation history to the intelligent memory system.

+
+
+
3
SQLite 文件SQLite Files
+
1,349
对话消息Messages
+
55
会话Sessions
+
配置就绪Config Ready
+
+
+ +
+
+
+
+ + +
+
+
Memory Viewer — Importing...
+
+
+
0
✓ 已导入✓ Stored
+
0
⏭ 跳过⏭ Skipped
+
0
🔀 合并🔀 Merged
+
0
✕ 错误✕ Errors
+
+
+
+
0%0 / 597
+
+
+
+
+
+ + +
+
+
Memory Viewer — Import Complete
+
+
+
+

导入完成Import Complete

+

共处理 597 条记忆:422 条导入,156 条智能去重跳过,19 条合并升级。Processed 597 memories: 422 imported, 156 deduped, 19 merged.

+
+
+
422
已导入Imported
+
156
智能跳过Smart Skip
+
19
合并升级Merged
+
0
错误Errors
+
+
+ + +
+
+
+
+ +
+
+ + + + + +
+
+ +
+
Memory Viewer — http://127.0.0.1:18799
+
+
+
📚 记忆📚 Memories
+
📋 任务📋 Tasks
+
🧠 技能🧠 Skills
+
📊 分析📊 Analytics
+
📝 日志📝 Logs
+
📥 导入📥 Import
+
⚙ 设置⚙ Settings
+
+ + +
+
+
597
记忆Memories
+
55
会话Sessions
+
422
嵌入Embeddings
+
6
天数Days
+
+
+
🔍搜索记忆(支持语义搜索)Search memories (supports semantic search)
+
+ All + User + Assistant +
+
+
+
USER帮我查一下阿里云ECS安全组怎么配置,需要开放6333端口给Qdrant使用How to configure Alibaba Cloud ECS security groups? Need to open port 6333 for Qdrant🦐03/04 10:41
+
ASST安全组配置需要在ECS控制台中设置入站和出站规则,包括端口范围、协议类型、授权对象等。具体步骤如下:1. 登录ECS控制台 → 网络与安全 → 安全组;2. 选择实例所在安全组 → 添加规则;3. 协议类型选 TCP,端口范围填 6333/6333,授权对象填你的客户端 IPSecurity group configuration requires setting inbound and outbound rules in ECS console, including port range, protocol type, and authorization target. Steps: 1. Login ECS console → Network & Security → Security Groups; 2. Select instance security group → Add Rule; 3. Protocol TCP, port 6333/6333, authorize your client IP🦐03/04 10:41
+
USER红烧肉怎么做?要那种入口即化的How to make braised pork? The melt-in-mouth kind🦐03/04 09:42
+
ASST经典红烧肉做法:五花肉切块冷水下锅焯水,热锅炒糖色至枣红色,加入五花肉翻炒上色,加生抽老抽料酒,小火慢炖1.5小时。入口即化的关键:小火慢炖、糖色不要炒过头、焯水后冰水激一下Classic braised pork recipe: cut pork belly into cubes, blanch in cold water, stir-fry sugar caramel to dark red, add pork belly and coat evenly, add soy sauce and cooking wine, simmer on low heat for 1.5 hours. Key to melt-in-mouth: low heat slow simmer, don't over-caramelize sugar, ice bath after blanching03/04 09:42
+
USER帮我整理一下我的工作经历,用于更新简历Help me organize my work experience for resume update03/05 09:07
+
+
1 - 5 / 597
+
+ + +
+
+
4总任务Total
+
3已完成Completed
+
1进行中Active
+
0跳过Skipped
+
+
阿里云ECS安全组设置与Qdrant集成Alibaba Cloud ECS Security Groups & Qdrant Integration
已完成Completed📄 98 条记忆📄 98 memories03/04

用户请求帮助配置阿里云ECS安全组以支持Qdrant向量数据库的集成部署,涉及端口开放、防火墙规则设置和安全最佳实践。User requested help configuring Alibaba Cloud ECS security groups for Qdrant vector database integration deployment, involving port opening, firewall rules, and security best practices.

+
红烧肉做法Braised Pork Recipe
已完成Completed📄 4 条记忆📄 4 memories03/04

用户询问红烧肉的详细做法,助手提供了从选材到烹饪的完整步骤,包括入口即化的关键技巧。User asked for detailed braised pork recipe. Assistant provided complete steps from ingredient selection to cooking, including key tips for melt-in-mouth texture.

+
工作经历整理Work Experience Summary
已完成Completed📄 8 条记忆📄 8 memories03/05

整理和结构化用户的工作经历信息,用于简历和职业规划。涵盖2018-2021阿里云高级工程师及近期AI Agent项目。Organized and structured user's work experience for resume and career planning. Covers 2018-2021 Alibaba Cloud senior engineer and recent AI Agent projects.

+
OpenClaw 插件与记忆管理OpenClaw Plugin & Memory Management
进行中Active📄 19 条记忆📄 19 memories03/05

用户探索 OpenClaw 的插件系统和 MemOS 记忆管理功能,包括安装配置、Viewer 使用和记忆迁移。User explored OpenClaw's plugin system and MemOS memory management features, including installation, Viewer usage, and memory migration.

+
+ + +
+
+
2总技能Total
+
+
🧠 memos-memory-guide
生效中Activev1质量: 8.5Quality: 8.5已安装Installed

Agent 记忆工具使用指南 — 指导 Agent 何时使用 memory_search、memory_timeline、task_summary、skill_get 等工具,自动优化召回策略。Agent memory tool usage guide — guides the Agent on when to use memory_search, memory_timeline, task_summary, skill_get tools, auto-optimizing recall strategy.

+
⚡ cloud-infrastructure-setup
生效中Activev2质量: 7.8Quality: 7.8已安装Installed

从多次云基础设施配置对话中提炼的技能 — 安全组配置、端口管理、服务部署的标准化流程与踩坑警告。Skill distilled from multiple cloud infrastructure conversations — standardized processes for security groups, port management, service deployment, and pitfall warnings.

+
+ + +
+
+
597
总记忆Total Memories
+
+47
今日写入Writes Today
+
55
会话Sessions
+
422
嵌入Embeddings
+
+
+

📊 每日记忆写入量📊 Memory Writes per Day

+
+
02/28
+
03/01
+
03/02
+
03/03
+
03/04
+
03/05
+
03/06
+
+
+
+
+

👤 按角色👤 By Role

+
+
user
269
+
assistant
311
+
system
17
+
+
+
+

📝 按类型📝 By Kind

+
+
paragraph
358
+
code_block
149
+
dialog
90
+
+
+
+
+ + +
+
+ All + auto_recall + memory_search + memory_add +
+
+
auto_recallquery: "帮我查一下阿里云ECS安全组" → 3 results (142ms)query: "Alibaba Cloud ECS security groups" → 3 results (142ms)03/04 10:41
+
memory_addsession: openclaw-0084da3f, chunks: 4, dedup: 1 skip (87ms)session: openclaw-0084da3f, chunks: 4, dedup: 1 skip (87ms)03/04 10:42
+
memory_searchquery: "红烧肉做法" → 2 results, score: 0.96-0.78 (95ms)query: "braised pork recipe" → 2 results, score: 0.96-0.78 (95ms)03/04 09:43
+
task_summarytask: "阿里云ECS安全组设置" → completed, 98 chunks (210ms)task: "ECS Security Groups" → completed, 98 chunks (210ms)03/04 11:00
+
+
+ + +
+
+
🦐
+

导入 OpenClaw 记忆Import OpenClaw Memories

+

将 OpenClaw 内置记忆迁移到智能记忆系统,支持断点续传和智能去重。Migrate OpenClaw built-in memories to the intelligent memory system. Supports resume and smart dedup.

+
+ 开始导入Start Import +
+
+
+ + +
+
+
Embedding
+
+ Provideropenai_compatible + Modelbge-m3 + Endpointhttps://your-api-endpoint/v1 + API Keysk-•••••• +
+
Summarizer
+
+ Provideropenai_compatible + Modelgpt-4o-mini + Endpointhttps://your-api-endpoint/v1 + API Keysk-•••••• +
+
+ Viewer Port18799 + Password•••• +
+
+
+
+
+ +
+
+ +
+ +
© 2026 MemTensor. MemOS Local OpenClaw Plugin — Interactive Demo
+ + + + diff --git a/apps/memos-local-openclaw/www/docs/index.html b/apps/memos-local-openclaw/www/docs/index.html new file mode 100644 index 000000000..3c8d401e2 --- /dev/null +++ b/apps/memos-local-openclaw/www/docs/index.html @@ -0,0 +1,552 @@ + + + + + +MemOS — OpenClaw 记忆插件文档 + + + + + + + + + +
+ + +
+ + + +
+ +
+
MemOS OpenClaw 插件MemOS OpenClaw Plugin
+

MemOS

+

+ OpenClaw 提供完全本地化的持久记忆、智能任务总结、技能自动进化和多智能体协同。npm 一键安装,支持分级模型配置。 + Fully local persistent memory, smart task summarization, auto skill evolution, and multi-agent collaboration for OpenClaw. One-command install, tiered model support. +

+
+ 完全本地化:数据存于本机 SQLite,零云依赖。Viewer 仅 127.0.0.1,密码保护。 + Fully local: Data in local SQLite, zero cloud dependency. Viewer 127.0.0.1 only, password-protected. +
+ +
+
💾

全量写入Full-Write

每次对话自动捕获,语义分片后持久化。Auto-captures every conversation, chunks semantically.

+

任务总结与技能进化Tasks & Skills

碎片对话归纳为结构化任务,再提炼为可复用技能并持续升级。Conversations organized into tasks, then distilled into skills that auto-upgrade.

+
🔍

混合检索Hybrid Search

FTS5 + 向量,RRF,MMR,时间衰减。FTS5 + vector, RRF, MMR, recency decay.

+
🧠

全量可视化Visualization

记忆/任务/技能/分析/日志/导入/设置 7 个管理页。7 pages: memories, tasks, skills, analytics, logs, import, settings.

+
💰

分级模型Tiered Models

Embedding/摘要/技能可独立配置不同模型。Each pipeline configurable with different models.

+
🤝

多智能体协同Multi-Agent

记忆隔离 + 公共记忆 + 技能共享,多 Agent 协同进化。Memory isolation + public memory + skill sharing for collective evolution.

+
🦐

原生记忆导入Native Memory Import

一键迁移 OpenClaw 内置记忆,智能去重、断点续传、实时进度。One-click migration from OpenClaw built-in memories with smart dedup, resume, and real-time progress.

+
🔗

LLM 智能降级LLM Fallback Chain

技能模型 → 摘要模型 → OpenClaw 原生模型三级自动降级,零手动干预。Skill model → summarizer → OpenClaw native model, auto-fallback with zero manual intervention.

+
✏️

任务/技能 CRUDTask & Skill CRUD

列表卡片直接编辑、删除、重试技能生成、切换可见性。Edit, delete, retry skill gen, toggle visibility — all from list cards.

+
+
+ +
+

系统架构Architecture

+

四条流水线:记忆写入 → 任务总结与技能进化(异步)→ 智能检索 → 协同共享。每个 Agent 拥有独立记忆空间,通过公共记忆和技能共享实现协同进化。Four pipelines: write → task & skill evolution (async) → retrieval → collaboration. Each agent has isolated memory; public memory and skill sharing enable collective evolution.

+ +
+
OpenClawagent_end
+
Capture
+
Ingestchunk→summary→embed→dedup
+
SQLite+FTS5
+
+
+
Task Processor异步 · 话题检测 → 摘要async · topic → summary
+
Skill Evolver异步 · 评估 → 生成/升级async · eval → create/up
+
+
+
before_agent_startauto-recall
+
RecallFTS+Vector
+
LLM filter
+
Inject context
+
+
+
Agentmemory_search
+
RRF→MMR→Decay
+
LLM filter
+
excerpts+chunkId/task_id
+
task_summary / skill_get / memory_timeline
+
+ +

数据流Data Flow

+

写入Write

+
    +
  1. agent_end → Capture → Chunk → LLM Summary → Embed → Dedup → Store
  2. +
  3. 异步:任务检测 → 任务摘要 → 技能评估 → 技能生成/升级Async: task detect → summary → skill eval → create/upgrade
  4. +
+

检索Read

+
    +
  1. 每轮自动:before_agent_start 用用户消息检索 → LLM 过滤相关 → 注入 system 上下文;无结果时提示 agent 自生成 query 调 memory_searchPer turn: before_agent_start searches with user message → LLM filters relevant → inject system context; if no hits, hint agent to call memory_search with self-generated query.
  2. +
  3. memory_search → FTS5+Vector → RRF → MMR → Decay → LLM filter → excerpts + chunkId/task_id(无 summary)
  4. +
  5. task_summary / skill_get(skillId|taskId) / memory_timeline(chunkId) / skill_install
  6. +
+
+ +
+

快速开始Quick Start

+
    +
  • Node.js ≥ 18
  • +
  • OpenClaw 已安装OpenClaw installed
  • +
  • Embedding / Summarizer API 可选,不配自动用本地模型Embedding / Summarizer APIs optional, falls back to local
  • +
+ +

Step 0:安装 C++ 编译工具(macOS / Linux 推荐)Step 0: Install C++ Build Tools (macOS / Linux recommended)

+

插件依赖 better-sqlite3 原生模块。macOS / Linux 用户建议先安装编译工具,可大幅提升安装成功率。Windows 用户使用 Node.js LTS 版本时通常有预编译文件,可直接跳到 Step 1。The plugin depends on better-sqlite3, a native C/C++ module. macOS / Linux users should install build tools first. Windows users with Node.js LTS usually have prebuilt binaries and can skip to Step 1.

+
# macOS
+xcode-select --install
+
+# Linux (Ubuntu / Debian)
+sudo apt install build-essential python3
+
+# Windows: 通常无需操作。如安装失败,安装 Visual Studio Build Tools:
+# https://visualstudio.microsoft.com/visual-cpp-build-tools/bash
+ +

Step 1:安装插件 & 启动Step 1: Install Plugin & Start

+
openclaw plugins install @memtensor/memos-local-openclaw-plugin
+openclaw gateway startbash
+ +
安装失败?最常见的问题是 better-sqlite3 原生模块编译失败。请确认已执行上方 Step 0,然后手动重建:cd ~/.openclaw/extensions/memos-local-openclaw-plugin && npm rebuild better-sqlite3。更多方案请查看 安装排查指南better-sqlite3 官方文档Install failed? The most common issue is better-sqlite3 compilation failure. Ensure Step 0 is done, then manually rebuild: cd ~/.openclaw/extensions/memos-local-openclaw-plugin && npm rebuild better-sqlite3. See the troubleshooting guide or official better-sqlite3 docs for more solutions.
+ +

升级Upgrade

+
openclaw plugins update memos-local-openclaw-plugin
+openclaw gateway stop && openclaw gateway startbash
+
升级自动完成依赖安装、旧版清理和原生模块编译,无需手动操作。如果 update 命令不可用,先删除旧目录再重新安装:rm -rf ~/.openclaw/extensions/memos-local-openclaw-plugin && openclaw plugins install @memtensor/memos-local-openclaw-plugin(记忆数据不受影响)。Upgrade automatically handles dependencies, legacy cleanup, and native module compilation. If update is unavailable, delete the old directory first: rm -rf ~/.openclaw/extensions/memos-local-openclaw-plugin && openclaw plugins install @memtensor/memos-local-openclaw-plugin (memory data is stored separately and won't be affected).
+ +

配置Configuration

+

两种方式:编辑 openclaw.json 或通过 Viewer 网页面板在线修改。支持分级模型。Two methods: edit openclaw.json or via Viewer web panel. Tiered models supported.

+
{
+  "plugins": {
+    "slots": { "memory": "memos-local-openclaw-plugin" },
+    "entries": { "memos-local-openclaw-plugin": {
+      "config": {
+        "embedding": {                           // lightweight
+          "provider": "openai_compatible",
+          "model": "bge-m3",
+          "endpoint": "https://your-api-endpoint/v1",
+          "apiKey": "sk-••••••"
+        },
+        "summarizer": {                          // mid-tier
+            "provider": "openai_compatible",
+          "model": "gpt-4o-mini",
+          "endpoint": "https://your-api-endpoint/v1",
+          "apiKey": "sk-••••••"
+        },
+        "skillEvolution": {
+          "summarizer": {                        // high-quality
+            "provider": "openai_compatible",
+            "model": "claude-4.6-opus",
+            "endpoint": "https://your-api-endpoint/v1",
+            "apiKey": "sk-••••••"
+          }
+        },
+        "recall": {                               // optional
+          "vectorSearchMaxChunks": 0   // 0=search all; set 200000–300000 only if slow on huge DB
+        },
+        "viewerPort": 18799
+      }
+    }}
+  }
+}json
+
安装后每次对话自动存入记忆。访问 http://127.0.0.1:18799 使用 Viewer。Every conversation auto-stored. Visit http://127.0.0.1:18799 for Viewer.
+
+ +
+

🦐 记忆迁移 — 再续前缘🦐 Memory Migration — Reconnect

+

将 OpenClaw 原生内置的记忆数据(SQLite 存储的对话历史)无缝迁移到 MemOS 的智能记忆系统。你和 AI 共同积累的每一段对话,都值得被记住。Seamlessly migrate OpenClaw's native built-in memory data (SQLite conversation history) to MemOS's intelligent memory system. Every conversation you've built with AI deserves to be remembered.

+ +
核心特性:一键导入 · 智能去重 · 断点续传 · 任务与技能生成 · 实时进度 · 🦐 标识导入来源Key Features: One-click import · Smart dedup · Resume anytime · Task & skill gen · Real-time progress · 🦐 source tagging
+ +

操作步骤Usage

+

方式一:通过 Viewer 网页面板(推荐)Method 1: Via Viewer Web Panel (Recommended)

+
    +
  1. 访问 http://127.0.0.1:18799,切换到 Import 页面。Visit http://127.0.0.1:18799, switch to the Import page.
  2. +
  3. 点击 扫描 OpenClaw 原生记忆,系统自动扫描 ~/.openclaw/ 下的 SQLite 数据库和 JSONL 日志。Click Scan OpenClaw Native Memories — the system auto-scans SQLite databases and JSONL logs under ~/.openclaw/.
  4. +
  5. 查看扫描结果(文件数、会话数、消息数),确认后点击 开始导入Review scan results (files, sessions, messages), then click Start Import.
  6. +
  7. 实时查看导入进度条、统计数据(已导入/跳过/合并/错误)和日志。Monitor real-time progress bar, stats (stored/skipped/merged/errors), and logs.
  8. +
+ +

方式二:通过 Agent 对话Method 2: Via Agent Chat

+

在与 OpenClaw 的对话中,直接让 AI 操作:In your conversation with OpenClaw, tell the AI:

+
// Example prompts
+"请帮我导入 OpenClaw 的原生记忆"
+"Import my OpenClaw native memories"text
+ +

方式三:通过 HTTP APIMethod 3: Via HTTP API

+
# 1. 扫描
+curl http://127.0.0.1:18799/api/migrate/scan
+
+# 2. 开始导入(SSE 流式进度)
+curl http://127.0.0.1:18799/api/migrate/start
+
+# 3. 停止导入
+curl -X POST http://127.0.0.1:18799/api/migrate/stopbash
+ +

后处理:任务与技能生成Post-Processing: Task & Skill Generation

+

导入完成后,可选择对导入的记忆进行后处理:After import, optionally post-process imported memories:

+
    +
  • 任务生成:自动检测会话中的任务边界,为每个会话生成结构化摘要(目标/步骤/结果)。Task generation: Auto-detect task boundaries per session, generate structured summaries (goal/steps/result).
  • +
  • 技能进化:从已完成的任务中提炼可复用技能,生成 SKILL.md 文件并安装到工作区。Skill evolution: Distill reusable skills from completed tasks, generate SKILL.md and install to workspace.
  • +
+

后处理在同一 Agent 内串行执行,不同 Agent 之间可并行(并发度可配置 1–8)。已处理过的会话自动跳过。支持选择只生成任务、只生成技能或两者同时执行。Post-processing runs serially within each agent, with parallel processing across agents (configurable concurrency 1–8). Already processed sessions are auto-skipped. Choose task-only, skill-only, or both.

+ +

断点续传Resume & Stop

+

导入和后处理均支持随时暂停:Both import and post-processing support pause/resume:

+
    +
  • 点击 停止 按钮后,进度自动保存。Click Stop, progress auto-saved.
  • +
  • 刷新页面后自动检测未完成的导入,恢复进度条显示。On page refresh, auto-detect incomplete imports and restore progress display.
  • +
  • 再次点击开始即从上次中断处继续,已处理的记忆自动跳过。Click start again to continue from where you left off — processed memories are auto-skipped.
  • +
  • 导入和后处理在后台运行,关闭 Viewer 页面不影响执行。Import and post-processing run in the background — closing the Viewer page won't interrupt them.
  • +
+ +
🦐 来源标识:所有通过迁移导入的记忆都带有 🦐 标识,在 Viewer 的记忆列表中可一眼区分原生导入和对话生成的记忆。🦐 Source Tag: All migrated memories are tagged with 🦐, making them visually distinguishable from conversation-generated memories in the Viewer.
+
+ +
+

模块Modules

+

Capture

+

过滤 system/self-tool,剥离 OpenClaw 元数据。保留 user/assistant/tool。Filter system/self-tool, strip metadata. Keep user/assistant/tool.

+

Ingest

+

异步队列:语义分片 → LLM 摘要 → 向量化 → 智能去重(Top-5 相似 + LLM 判 DUPLICATE/UPDATE/NEW,UPDATE 合并摘要并追加内容)→ 存储;演化块记录 merge_history。Async queue: chunk → summary → embed → smart dedup (Top-5 similar + LLM DUPLICATE/UPDATE/NEW; UPDATE merges summary and appends content) → store; evolved chunks track merge_history.

+

任务总结Task Summarization

+

异步逐轮检测任务边界:分组为用户回合 → 第一条直接分配 → 后续每条由 LLM 判断话题是否切换(强偏向 SAME,避免过度分割)→ 2h 超时强制切分 → 结构化摘要(目标/步骤/结果)。支持编辑、删除、重试技能生成。Async per-turn boundary detection: group into user turns → first turn assigned directly → each subsequent turn checked by LLM topic judge (strongly biased toward SAME to avoid over-splitting) → 2h timeout forces split → structured summary (goal/steps/result). Supports edit, delete, retry skill generation.

+

技能进化Skill Evolution

+

规则过滤 → LLM 评估(可重复/有价值的任务才生成技能)→ SKILL.md 生成(步骤/警告/脚本)/ 升级 → 质量评分 → 安装。LLM 使用三级降级链(技能模型 → 摘要模型 → OpenClaw 原生模型)。支持编辑、删除、设为公开/私有。Rule filter → LLM evaluate (only repeatable/valuable tasks generate skills) → SKILL.md (steps/warnings/scripts) / upgrade → score → install. LLM uses a 3-level fallback chain (skill model → summarizer → OpenClaw native model). Supports edit, delete, toggle visibility.

+

Recall

+

FTS5+Vector → RRF(k=60) → MMR(λ=0.7) → Decay(14d) → Normalize → Filter(≥0.45) → Top-K。自动关联 Task/Skill。FTS5+Vector → RRF(k=60) → MMR(λ=0.7) → Decay(14d) → Normalize → Filter(≥0.45) → Top-K. Auto-links Task/Skill.

+

Viewer

+

7 页:记忆 CRUD/搜索/演化标识、任务(对话气泡)、技能(版本/下载)、分析、日志(工具调用输入输出)、OpenClaw 原生记忆导入、在线配置。密码保护。7 pages: memory CRUD/search/evolution badges, tasks (chat bubbles), skills (versions/download), analytics, logs (tool call I/O), OpenClaw native memory import, online config. Password-protected.

+
+ +
+

检索算法Retrieval

+

RRF

+
\[ \text{RRF}(d) = \sum_i \frac{1}{k + \text{rank}_i(d) + 1} \]
+

MMR

+
\[ \text{MMR}(d) = \lambda \cdot \text{rel}(d) - (1-\lambda) \cdot \max \text{sim}(d, d_s) \]
+

时间衰减Recency

+
\[ \text{final} = \text{score} \times \bigl(0.3 + 0.7 \times 0.5^{t/14}\bigr) \]
+
+ +
+

API

+ +

query (required), maxResults (20), minScore (0.45), role. Returns excerpts(原文片段)+ chunkId / task_id,无 summary;经 LLM 相关性过滤。excerpts + chunkId/task_id, no summary; LLM relevance filter.

+

memory_get

+

获取记忆块完整原文。Get full original text of a memory chunk. chunkId, maxChars (optional).

+

memory_timeline

+

以 chunkId 为锚点的上下文邻居。Context neighbors by chunkId. chunkId, window (2).

+

task_summary

+

任务结构化摘要。Structured task summary. taskId or query.

+

skill_get / skill_install

+

skill_get 支持 skillId 或 taskId(按任务解析技能);skill_install 安装到工作区。skill_get accepts skillId or taskId; skill_install installs to workspace.

+

memory_write_public

+

写入公共记忆(owner="public"),所有 Agent 均可检索。Write public memory (owner="public"), discoverable by all agents. content (required), summary (optional).

+ +

搜索技能:FTS5 关键词 + 向量语义双通道,RRF 融合后经 LLM 判断相关性。Search skills via FTS5 + vector, RRF fusion, then LLM relevance judgment. query (required), scope ("mix" | "self" | "public", default "mix").

+

skill_publish / skill_unpublish

+

skill_publish 将技能设为公开,其他 Agent 可通过 skill_search 发现并安装。skill_unpublish 设为私有。skill_publish makes a skill public and discoverable via skill_search. skill_unpublish sets it private. skillId (required).

+

memory_viewer

+

返回 Viewer URL。Returns Viewer URL.

+

Viewer HTTP

+ + + + + + + + + + + + + + + + + + + +
MethodPath说明Description
GET/Memory Viewer HTML
POST/api/auth/*setup / login / reset / logout
GET/api/memories记忆列表(分页、过滤)Memory list (pagination, filters)
GET/api/search混合搜索(向量 minScore 0.64 + FTS5 降级)Hybrid search (vector minScore 0.64 + FTS5 fallback)
POST/PUT/DELETE/api/memory/:id记忆 CRUDMemory CRUD
GET/api/tasks任务列表(状态过滤)Task list (status filter)
GET/PUT/DELETE/api/task/:id任务详情/编辑/删除Task detail/edit/delete
POST/api/task/:id/retry-skill重试技能生成Retry skill generation
GET/api/skills技能列表Skill list
GET/PUT/DELETE/api/skill/:id技能详情/编辑/删除Skill detail/edit/delete
PUT/api/skill/:id/visibility设置公开/私有Set public/private
GET/api/skill/:id/download技能 ZIP 下载Download as ZIP
GET/api/stats, /api/metrics统计与分析Stats & metrics
GET/api/logs工具调用日志Tool call logs
GET/PUT/api/config在线配置Online configuration
GET/POST/api/migrate/*记忆导入(扫描/开始/停止/SSE 进度)Memory import (scan/start/stop/SSE)
POST/GET/api/migrate/postprocess/*后处理(任务/技能生成)Post-process (task/skill gen)
+
+ +
+

多智能体协同Multi-Agent Collaboration

+

MemOS 原生支持多 Agent 场景。每个 Agent 的记忆和任务通过 owner 字段隔离(格式 agent:{agentId}),检索时自动过滤为当前 Agent + public。MemOS natively supports multi-agent scenarios. Each agent's memories and tasks are isolated via an owner field (agent:{agentId}); retrieval automatically filters to current agent + public.

+
    +
  • 记忆隔离:Agent A 无法检索 Agent B 的私有记忆Memory Isolation: Agent A cannot retrieve Agent B's private memories
  • +
  • 公共记忆:通过 memory_write_public 写入 owner="public" 的记忆,所有 Agent 可检索Public Memory: Use memory_write_public to write owner="public" memories discoverable by all agents
  • +
  • 技能共享:通过 skill_publish 将技能设为公开,其他 Agent 可通过 skill_search 发现并安装Skill Sharing: Use skill_publish to make skills public; other agents discover and install via skill_search
  • +
  • 技能检索skill_search 支持 scope 参数(mix/self/public),FTS + 向量双通道 + RRF 融合 + LLM 相关性判断Skill Discovery: skill_search supports scope (mix/self/public), FTS + vector dual channel + RRF fusion + LLM relevance judgment
  • +
+
+ +
+

LLM 降级链LLM Fallback Chain

+

所有 LLM 调用(摘要、话题检测、去重、技能生成/升级)均使用三级自动降级机制:All LLM calls (summary, topic detection, dedup, skill generation/upgrade) use a 3-level automatic fallback chain:

+
+
skillSummarizer技能专用模型(可选)Skill-dedicated (optional)
+
summarizer通用摘要模型General summarizer
+
OpenClaw Native从 openclaw.json 读取Auto-detected from openclaw.json
+
+
    +
  • 每一级失败后自动尝试下一级,无需手动干预Each level auto-falls back to the next on failure, zero manual intervention
  • +
  • skillSummarizer 未配置时直接跳到 summarizerIf skillSummarizer is not configured, skips directly to summarizer
  • +
  • OpenClaw 原生模型从 ~/.openclaw/openclaw.jsonagents.defaults.model.primary 自动读取OpenClaw native model auto-detected from ~/.openclaw/openclaw.jsonagents.defaults.model.primary
  • +
  • 如果所有模型均失败,回退到规则方法(无 LLM)或跳过该步骤If all models fail, falls back to rule-based methods (no LLM) or skips the step
  • +
+
+ +
+

数据库Database

+

~/.openclaw/memos-local/memos.db, WAL. Tables: chunks (owner), chunks_fts, embeddings, tasks (owner), skills (owner, visibility), skill_versions, task_skills, skill_embeddings, skills_fts.

+
+ +
+

安全Security

+

Viewer 仅 127.0.0.1;密码 SHA-256;HttpOnly+SameSite Cookie;会话 24h;数据仅本地。127.0.0.1 only; SHA-256 password; HttpOnly+SameSite; 24h session; data stays local.

+
+ +
+

默认值Defaults

+ + + + + + + + + + + + + + + +
参数Parameter默认Default说明Description
maxResults6 (max 20)默认返回数Default result count
minScore (tool)0.45memory_search 最低分memory_search minimum
minScore (viewer)0.64Viewer 搜索向量阈值Viewer search vector threshold
rrfK60RRF 融合常数RRF fusion constant
mmrLambda0.7MMR 相关性 vs 多样性MMR relevance vs diversity
recencyHalfLife14d时间衰减半衰期Recency decay half-life
vectorSearchMaxChunks0 (all)0=搜索全部;大库可设 200k-300k0=search all; set 200k-300k for large DBs
dedup threshold0.75语义去重余弦相似度Semantic dedup cosine similarity
viewerPort18799Memory Viewer
taskIdle2h任务空闲超时Task idle timeout
topicJudgeWarmup1LLM 话题判断预热(用户消息数)LLM topic judge warm-up (user turns)
skillMinChunks6技能评估最小 chunk 数Min chunks for skill evaluation
importConcurrency1 (max 8)导入 Agent 并行度Import agent parallelism
+
+ +
+

MemOSMemOS MemOS — OpenClaw Plugin · Docs

+

首页Home · 安装排查指南Troubleshooting · npm · GitHub · MIT

+
+
+ + + + + + + + diff --git a/apps/memos-local-openclaw/www/docs/troubleshooting.html b/apps/memos-local-openclaw/www/docs/troubleshooting.html new file mode 100644 index 000000000..e48df69d8 --- /dev/null +++ b/apps/memos-local-openclaw/www/docs/troubleshooting.html @@ -0,0 +1,438 @@ + + + + + +MemOS Local — 安装排查指南 + + + + + + + +
+ +
+

MemOS Local — 安装排查指南

+

遇到安装问题?按以下步骤逐一排查

+

📦 better-sqlite3 官方排查文档  |  GitHub Issues

+
+ + + + +

1. 快速诊断命令

+ +

在终端依次运行以下命令,快速判断问题所在:

+ +
# 1) 插件目录是否存在
+ls ~/.openclaw/extensions/memos-local-openclaw-plugin/
+
+# 2) better-sqlite3 原生模块是否可用
+cd ~/.openclaw/extensions/memos-local-openclaw-plugin
+node -e "require('better-sqlite3'); console.log('✔ better-sqlite3 OK')"
+
+# 3) 核心依赖是否完整
+node -e "['@sinclair/typebox','uuid','posthog-node'].forEach(d=>{try{require.resolve(d);console.log('✔',d)}catch{console.log('✖',d)}})"
+
+# 4) 运行 postinstall 脚本查看完整诊断
+node scripts/postinstall.cjs
+
+# 5) 查看 gateway 日志中的插件相关信息
+grep -i "memos\|plugin.*error\|plugin.*fail" /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log
+ + +

2. 运行 postinstall 脚本

+ +

postinstall 脚本会自动检测并修复常见问题。进入插件目录后运行:

+ +
cd ~/.openclaw/extensions/memos-local-openclaw-plugin
+node scripts/postinstall.cjs
+ +

正常输出应该包含三个阶段,每个都显示

+ +
─── Phase 0: 检测核心依赖 / Check core dependencies ───
+  @sinclair/typebox 
+  uuid 
+  posthog-node 
+  @huggingface/transformers 
+ All core dependencies present.
+
+─── Phase 1: 清理旧版本插件 / Clean up legacy plugins ───
+ No legacy plugin directories found. Clean.
+
+─── Phase 2: 检查 better-sqlite3 原生模块 / Check native module ───
+ better-sqlite3 is ready.
+
+✔ Setup complete!
+ +
+ ⚠ 如果 Phase 0 失败 +

缺少依赖通常是网络问题。手动安装:

+
cd ~/.openclaw/extensions/memos-local-openclaw-plugin
+npm install --omit=dev
+
+ +
+ ⚠ 如果 Phase 2 失败 +

better-sqlite3 编译失败,参见下一节。

+
+ + +

3. better-sqlite3 编译失败

+ +

这是最常见的安装问题。better-sqlite3 是一个需要 C/C++ 编译的原生 Node.js 模块。如果以下步骤无法解决你的问题,请参考 better-sqlite3 官方排查文档 获取更多平台特定的解决方案。

+ +

错误表现

+
Error: Could not locate the bindings file. Tried:
+ → .../node_modules/better-sqlite3/build/better_sqlite3.node
+ → .../node_modules/better-sqlite3/build/Release/better_sqlite3.node
+ ...
+ +

解决步骤

+ +
+
+ 1 +
+

安装 C/C++ 编译工具

+
+
+
+ +
# macOS
+xcode-select --install
+
+# Ubuntu / Debian
+sudo apt install build-essential python3
+
+# Windows — 通常不需要!
+# better-sqlite3 对 Windows + Node.js LTS 提供预编译二进制文件,
+# 大部分情况下可直接安装成功。
+# 如果仍然失败,安装 Visual Studio Build Tools:
+# https://visualstudio.microsoft.com/visual-cpp-build-tools/
+# 安装时勾选 "C++ build tools" 工作负载
+ +
+
+ 2 +
+

重新编译 better-sqlite3

+
+
+
+ +
cd ~/.openclaw/extensions/memos-local-openclaw-plugin
+npm rebuild better-sqlite3
+ +
+
+ 3 +
+

验证是否成功

+
+
+
+ +
node -e "require('better-sqlite3'); console.log('✔ OK')"
+ +
+
+ 4 +
+

重启 gateway

+
+
+
+ +
openclaw gateway stop && openclaw gateway start
+ +
+ 💡 Node.js 版本说明 +

如果使用非 LTS 版本的 Node.js(如 v25.x),better-sqlite3 可能没有预编译的二进制文件,必须从源码编译。确保已安装上述编译工具。

+

推荐使用 Node.js LTS 版本(v18.x 或 v20.x),这些版本有预编译的二进制文件,通常不需要本地编译。

+
+ +
+ 💡 更多排查资源 +

如果上述方法均无法解决,请查看以下资源:

+ +
+ + +

4. Plugin ID Mismatch 警告

+ +

错误表现

+
warn plugin id mismatch (manifest uses "memos-local-openclaw-plugin",
+     entry hints "memos-lite-openclaw-plugin")
+ +

原因

+

旧版本插件(memos-lite-*)的残留目录或配置未清理。

+ +

解决方法

+
# 运行 postinstall 脚本自动清理(推荐)
+cd ~/.openclaw/extensions/memos-local-openclaw-plugin
+node scripts/postinstall.cjs
+
+# 或手动清理旧目录
+rm -rf ~/.openclaw/extensions/memos-lite
+rm -rf ~/.openclaw/extensions/memos-lite-openclaw-plugin
+ +

然后检查配置文件中是否有旧条目:

+
cat ~/.openclaw/openclaw.json | grep -i "memos-lite"
+ +

如果有,删除对应的配置条目,或直接运行 postinstall 脚本自动迁移。

+ + +

5. 插件加载失败 (register error)

+ +

错误表现

+
error [plugins] memos-local-openclaw-plugin failed during register:
+Error: Could not locate the bindings file.
+ +

解决方法

+

这几乎都是 better-sqlite3 的问题,按照第 3 节的步骤修复即可。

+ +

插件内置了自愈机制——启动时会自动尝试 npm rebuild better-sqlite3,但如果系统没有编译工具,自愈也会失败。

+ + +

6. Memory Viewer 页面报错

+ +

Scan failed: Cannot read properties of undefined

+

通常是新安装时数据库为空或 store 未初始化。升级到最新版本即可解决:

+
openclaw plugins update memos-local-openclaw-plugin
+ +

页面显示 undefined 或数据为空

+

尝试强制刷新浏览器缓存:Ctrl+Shift+R(macOS: Cmd+Shift+R

+ + +

7. 升级问题

+ +

升级命令(推荐)

+
openclaw plugins update memos-local-openclaw-plugin
+ +

升级过程会自动运行 postinstall 脚本,处理依赖安装、旧版清理和原生模块编译。

+ +

如果 update 不可用,重新安装

+
# 必须先删除旧目录,否则 install 会报 "plugin already exists"
+rm -rf ~/.openclaw/extensions/memos-local-openclaw-plugin
+openclaw plugins install @memtensor/memos-local-openclaw-plugin
+ +
+ 💡 为什么要先删除? +

OpenClaw 的 plugins install 命令检测到目标目录已存在时会直接拒绝安装,不会运行任何脚本。这是 OpenClaw 框架的安全机制,插件自身无法绕过。

+
+ +
+ ✔ 数据安全 +

升级不会删除已有的记忆数据。数据库位于 ~/.openclaw/memos-local/memos.db,独立于插件目录。

+
+ +

升级后 gateway 未加载新版本

+
openclaw gateway stop && openclaw gateway start
+ + +

8. 查看日志

+ +

Gateway 运行日志

+
# 查看当天完整日志
+cat /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log
+
+# 只看插件相关
+grep -i "memos" /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log
+
+# 只看错误
+grep -i "error\|fail\|warn" /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log | grep -i "memos\|plugin"
+
+# 实时追踪(debug 用)
+tail -f /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log | grep -i "memos"
+ +

重新启动并捕获完整启动日志

+
openclaw gateway stop
+openclaw gateway start 2>&1 | tee /tmp/gateway-debug.log
+

然后将 /tmp/gateway-debug.log 发给开发者排查。

+ +

postinstall 诊断日志

+
cd ~/.openclaw/extensions/memos-local-openclaw-plugin
+node scripts/postinstall.cjs 2>&1 | tee /tmp/postinstall-debug.log
+ + +

9. 完全重装

+ +

如果以上方法都无法解决,可以完全重装(不会丢失记忆数据):

+ +
# 1) 卸载
+openclaw plugins uninstall memos-local-openclaw-plugin
+
+# 2) 确认旧目录已删除
+rm -rf ~/.openclaw/extensions/memos-local-openclaw-plugin
+rm -rf ~/.openclaw/extensions/memos-lite
+rm -rf ~/.openclaw/extensions/memos-lite-openclaw-plugin
+
+# 3) 重新安装
+openclaw plugins install @memtensor/memos-local-openclaw-plugin
+
+# 4) 重启 gateway
+openclaw gateway stop && openclaw gateway start
+ +
+ ✔ 数据保留 +

记忆数据存储在 ~/.openclaw/memos-local/memos.db,不在插件目录内,重装不会影响。

+
+ + +

10. 常见问题

+ +
+

Q: 安装时一直卡在 "Installing plugin dependencies..." 不动

+

这通常是 better-sqlite3 正在编译。首次编译可能需要 30-60 秒,取决于网络和机器性能。如果超过 2 分钟,按 Ctrl+C 中断,然后手动运行:

+
cd ~/.openclaw/extensions/memos-local-openclaw-plugin
+npm install --omit=dev
+npm rebuild better-sqlite3
+
+ +
+

Q: macOS 提示 "xcrun: error: invalid active developer path"

+

需要安装 Xcode 命令行工具:

+
xcode-select --install
+

安装完成后重新运行 npm rebuild better-sqlite3

+
+ +
+

Q: 升级后 Memory Viewer 显示异常

+

浏览器缓存了旧版本页面。强制刷新:Ctrl+Shift+R(macOS: Cmd+Shift+R)。

+
+ +
+

Q: 我的数据在哪?安全吗?

+

所有记忆数据存储在 ~/.openclaw/memos-local/memos.db(SQLite 文件),独立于插件安装目录。升级、重装插件都不会影响数据。

+

建议定期备份:

+
cp ~/.openclaw/memos-local/memos.db ~/memos-backup-$(date +%Y%m%d).db
+
+ +
+

Q: 如何确认插件版本?

+
cat ~/.openclaw/extensions/memos-local-openclaw-plugin/package.json | grep version
+
+ +
+

Q: 任务摘要/技能生成/去重 LLM 调用失败

+

所有 LLM 调用使用三级自动降级链:skillSummarizersummarizer → OpenClaw 原生模型。

+
    +
  • 检查 gateway 日志中的 failedtrying next 信息
  • +
  • 确认 API Key 和 Endpoint 配置正确
  • +
  • 如果所有模型都失败,功能会降级为规则方法或跳过
  • +
  • 可通过 Viewer → Settings 在线修改模型配置,保存后立即生效
  • +
+
+ +
+

Q: 任务划分不准确(过度切分或不切分)

+

任务边界检测使用逐轮 LLM 话题判断:

+
    +
  • 确认 summarizer 模型已正确配置且可用
  • +
  • 更强的 LLM 模型(如 GPT-4、Claude)会有更好的话题判断效果
  • +
  • 如果判断效果不理想,可尝试配置 skillSummarizer 使用更强的模型
  • +
  • 查看 gateway 日志中的 Topic judge 日志确认 LLM 是否被正确调用
  • +
+
+ +
+

Q: duplicate plugin id detected 警告

+

同一个 plugin ID 被多个目录加载。检查是否有重复的插件目录:

+
ls ~/.openclaw/extensions/ | grep memos
+

只保留 memos-local-openclaw-plugin,删除其他的:

+
rm -rf ~/.openclaw/extensions/memos-local  # 如果存在
+
+ + + +
+ + diff --git a/apps/memos-local-openclaw/www/index.html b/apps/memos-local-openclaw/www/index.html new file mode 100644 index 000000000..37defcca4 --- /dev/null +++ b/apps/memos-local-openclaw/www/index.html @@ -0,0 +1,972 @@ + + + + + +MemOS — OpenClaw 记忆插件 | 本地化 · 智能进化 · 全量可视化 + + + + + + + + + +
+
+
+
+ + + + +
+
+
+ + + + + + + + + + + + +
+
+ + OpenClaw 本地插件 · MIT 开源OpenClaw Local Plugin · MIT +
+

+ 让你的 OpenClaw
越用越聪明
+ Give Your OpenClaw
Lasting Intelligence
+

+

+ 为 OpenClaw 注入持久记忆与自进化技能
完全本地化 全量可视化管理 分级模型极致省钱
+ Persistent memory and self-evolving skills for OpenClaw agents.
100% local storage, full visualization dashboard, and tiered models for cost efficiency.
+

+

把 MemOS 带进你的 OpenClawBring MemOS to your OpenClaw workflow

+ + +
+
+
macOS/Linux
+
+
# One liner, Works everywhere. Installs everything.
+
$curl -fsSL https://cdn.memtensor.com.cn/memos-local-openclaw/install.sh | bash
+
+
+
+
+
+ +
+ + +
+
+
+

没有记忆的 Agent,每次都从零开始Without Memory, Every Task Starts from Zero

+

MemOS 为 OpenClaw 注入持久记忆与自进化技能。MemOS equips OpenClaw with persistent memory and self-evolving skills.

+
+
+
💻

完全本地化Fully Local

记忆、任务、技能全存本机 SQLite,零云依赖。All data stored in local SQLite — zero cloud dependency, complete privacy.

+
🧠

全量可视化管理Full Visualization

内置管理面板,记忆 / 任务 / 技能完全透明可控。Built-in web dashboard — memories, tasks, and skills fully transparent and controllable.

+

任务总结与技能进化Task Summary & Skill Evolution

碎片对话自动归纳为结构化任务,再提炼为可复用技能并持续升级。从「记住」到「学会」,同一个坑不踩两次。Fragmented conversations auto-organized into structured tasks, then distilled into reusable skills that evolve over time. From "remembering" to "mastering" — never repeat the same mistake twice.

+
💰

分级模型 · 省钱Tiered Models

Embedding 轻量、摘要中等、技能高质量——按需分配,大幅省钱。Lightweight, mid-tier, and high-quality models layered by purpose — maximum performance at minimum cost.

+
🤝

多智能体协同Multi-Agent Collaboration

记忆隔离 + 公共记忆 + 技能共享。多个 Agent 各有私域记忆,又能共享知识与技能,协同进化。Memory isolation + public memory + skill sharing. Each agent has private memories while sharing knowledge and skills for collective evolution.

+
🦞

OpenClaw 原生记忆导入Native Memory Import

一键迁移 OpenClaw 内置记忆,智能去重、断点续传、实时进度。你过往的记忆不会丢失,再续前缘。One-click migration from OpenClaw built-in memories. Smart dedup, resume anytime, real-time progress. Your past memories, never lost.

+
+
+
+ +
+ + +
+
+
+

三大引擎,驱动 Agent 协同进化Three Engines That Drive Collaborative Evolution

+
+
+
+
+

任务总结与技能自进化Task Summary & Skill Evolution

+

碎片对话自动归组为结构化任务(目标 → 步骤 → 结果),再由 LLM 评估提炼为可复用技能。遇到相似场景时自动升级——更快、更准、更省 Token。从「能记住」到「会做」,同一个坑不踩两次。任务与技能支持编辑、删除、重试等完整管理。Fragmented conversations are auto-organized into structured tasks (goal → steps → result), then LLM evaluates and distills them into reusable skills. Skills auto-upgrade on similar scenarios — faster, more accurate, lower cost. From "remembering" to "mastering" — never repeat the same mistake. Full CRUD for tasks and skills.

+
逐轮话题检测Per-Turn Topic Detection结构化摘要Structured Summary自动评估Auto Evaluate版本管理VersioningLLM 降级链LLM Fallback
+
+
+
+
Task → Skill Evolution
+
Task: "部署 Nginx 反向代理"  completed
+Goal:  配置反向代理到 Node.js
+Steps: 1. nginx conf  2. upstream  3. SSL  4. reload
+Result: ✓ HTTPS 正常
+
+Evaluating: shouldGenerate=true  conf=0.85
+→ SKILL.md + scripts → quality 8.5/10
+✓ "nginx-proxy" v1 created
+
+// 再次执行时自动升级
+Upgrade: extend → added WebSocket
+✓ v2 (score: 9.0)
+
+
+
+
+
+

多智能体协同进化Multi-Agent Collaborative Evolution

+

每个 Agent 拥有独立的私域记忆,互不可见。但通过「公共记忆」和「技能共享」机制,Agent 之间能够共享决策、经验与能力。一个 Agent 学会的技能,可以发布为公共技能,其他 Agent 搜索并安装后即可复用。多智能体不再各自为战,而是协同进化、共同进步。Each agent has isolated private memory, invisible to others. But through public memory and skill sharing, agents can share decisions, experiences, and capabilities. Skills learned by one agent can be published for others to discover and install. Multi-agent systems no longer work in silos — they evolve collaboratively, growing together.

+
记忆隔离Memory Isolation公共记忆Public Memory技能共享Skill Sharing
+
+
+
+
Multi-Agent Collaboration
+
Agent Alpha:
+  memory_search("deploy config")
+  → sees own + public memories only
+  memory_write_public("shared deploy config")
+  skill_publish("nginx-proxy") ✓ now public
+
+Agent Beta:
+  skill_search("nginx deployment")
+  → Found: nginx-proxy (public)
+  skill_install("nginx-proxy") ✓ installed
+
+
+
+
+
+

全量记忆可视化管理Full Memory Visualization

+

内置 Web 管理面板——记忆、任务、技能、分析、日志、导入、设置共 7 页。任务以对话气泡还原,技能支持版本对比与下载,日志页可查看工具调用输入输出与耗时。Built-in dashboard — 7 pages: memories, tasks, skills, analytics, logs, import, and settings. Task details as chat bubbles. Logs show tool call I/O and duration.

+
+
+
+
127.0.0.1:18799
+
+
MemoriesTasksSkillsAnalyticsLogsImportSettings
+
+
总记忆Total
1,284
+
今日Today
+47
+
任务Tasks
12
+
技能Skills
8
+
+
+
user帮我配置 Nginx 反向代理到 3000 端口Set up Nginx proxy to port 30002m
+
asst好的,创建 nginx 配置文件并写入 upstream 配置。Creating nginx config file and writing upstream block.2m
+
user还需要加 SSL 证书Also add SSL cert5m
+
+
+
+
+
+
+
+
+ +
+ + +
+
+
+

从对话到记忆到技能的智能闭环The Intelligent Loop: Conversation → Memory → Skill

+
+
+
+ + + + + + + + + ① 记忆写入① Memory Write + 异步队列 · 智能去重(重复/更新/新增) · 更新时合并Async queue · Smart dedup (DUP/UP/NEW) · Merge history + Capture + Chunk + Summary + Embed + 智能去重Smart DedupTop-5·LLM DUP/UP/NEW + SQLite+FTS5 + + + + ② 任务总结② Task Summarization + 异步 · 检测边界 → 结构化摘要Async · Boundaries → Summary + 话题检测Topic + 质量过滤Filter + LLM 摘要LLM Summary + 标题生成Title + + + 异步触发Async + + + ③ 技能进化③ Skill Evolution + 异步 · 评估 → 生成/升级 → 安装Async · Evaluate → Create/Upgrade + 规则过滤Rules + LLM 评估Evaluate + 生成/升级Create/Up + 质量评分Score + + + 异步 · 任务完成后Async · After task + + + ④ 智能检索④ Smart Retrieval + 记忆 → 任务 → 技能 三层递进Memory → Task → Skill + Hybrid + RRF + MMR + Decay + Task + Skill + + + + + + + 🔄 进化闭环 — Agent 越用越强🔄 Evolution Loop — Agents Get Smarter + 💬对话自动沉淀Auto Capture + 📋碎片→结构化知识Fragments→Knowledge + 经验固化为技能Experience→Skills + 🚀技能持续进化Skills Evolve + + + 反馈闭环 · 下次执行自动调用已有技能Feedback loop · Auto-invoke next run + +
+ +
+

💡 为什么这套架构对 OpenClaw 至关重要💡 Why This Architecture Matters

+
+
📋
Task:碎片→知识Tasks: Fragments→Knowledge

多轮对话组织为完整知识单元,检索效率大幅提升。Multi-turn dialogues organized into reusable knowledge units.

+
Skill:记住→会做Skills: Remember→Do

实战操作指南,相似任务直接调用,跳过摸索。Battle-tested procedural guides, invoked automatically on similar tasks.

+
🔄
自动进化:越用越强Auto-Evolution

新经验触发 Skill 升级(refine/extend/fix)。New experiences trigger automatic skill upgrades (refine / extend / fix).

+
💰
分级模型:按需配算力Tiered Models

轻量/中等/高质量模型分层配置,极致省钱。Purpose-matched models for maximum cost efficiency.

+
+
+
+
+
+ +
+ + +
+
+
+

60 秒上手Up and Running in 60 Seconds

+

npm 一键安装,两种配置方式任选。One-command install. Two configuration methods.

+
+
+
+
+

1. 一键安装1. Install

+

macOS / Linux 用户建议先安装 C++ 编译工具(用于 better-sqlite3)。
遇到安装问题?查看排查指南 →
macOS / Linux users: install C++ build tools first (for better-sqlite3).
Install issues? See troubleshooting guide →

+
+
+
+
terminal
+
# Step 0: 安装编译工具 (macOS / Linux)
+xcode-select --install        # macOS
+# sudo apt install build-essential  # Linux
+
+# Step 1: 安装插件 & 启动
+curl -fsSL https://cdn.memtensor.com.cn/memos-local-openclaw/install.sh | bash
+
+
+
+
+
+

2. 配置2. Config

+

网页面板:http://127.0.0.1:18799 登录后点「设置」。或编辑 openclaw.jsonWeb panel: http://127.0.0.1:18799 → Settings. Or edit openclaw.json.

+
+
+
+ + +
+
+
+
127.0.0.1:18799
+
+
MemoriesTasksSkillsAnalyticsLogsSettings
+
+
Embedding
+
+ Provideropenai_compatible + Modelbge-m3 + Endpointhttps://your-api-endpoint/v1 + API Keysk-•••••• +
+
Summarizer
+
+ Provideropenai_compatible + Modelgpt-4o-mini + Endpointhttps://your-api-endpoint/v1 + API Keysk-•••••• +
+
Skill Evolution
+
+ Modelclaude-4.6-opus + Endpointhttps://your-api-endpoint/v1 +
+
+ Viewer Port18799 +
+
+
保存即生效Save to apply
+
+
+
+
+
+
{
+  "plugins": {
+    "slots": { "memory": "memos-local-openclaw-plugin" },
+    "entries": {
+      "memos-local-openclaw-plugin": {
+        "config": {
+          "embedding": {
+            "provider": "openai_compatible",
+            "model": "bge-m3",
+            "endpoint": "https://your-api-endpoint/v1",
+            "apiKey": "sk-••••••"
+          },
+          "summarizer": {
+            "provider": "openai_compatible",
+            "model": "gpt-4o-mini",
+            "endpoint": "https://your-api-endpoint/v1",
+            "apiKey": "sk-••••••"
+          },
+          "skillEvolution": {
+            "summarizer": {
+              "provider": "openai_compatible",
+              "model": "claude-4.6-opus",
+              "endpoint": "https://your-api-endpoint/v1",
+              "apiKey": "sk-••••••"
+            }
+          },
+          "viewerPort": 18799
+        }
+      }
+    }
+  }
+}
+
+
+
+
+
+
+
+ +
+ + +
+
+
+

适配你的技术栈Works with Your Preferred Stack

+

OpenAI 兼容 API 即插即用,无配置自动降级本地模型。Any OpenAI-compatible API works out of the box. Automatic fallback to local models when no API key is configured.

+
+
+
OpenAI
Anthropic
Gemini
Bedrock
Cohere
Voyage
Mistral
本地Local
+
+
+
+ + +
+
+

12 个智能工具12 Smart Tools

+
+
🧠

auto_recall

每轮自动回忆Auto recall each turn

+
🔍

memory_search

记忆检索Memory search

+
📄

memory_get

获取完整记忆Get full memory

+
📜

memory_timeline

上下文邻居Context neighbors

+
📢

memory_write_public

写入公共记忆Write public memory

+
📋

task_summary

任务摘要Task summary

+

skill_get

技能指南Skill guide

+
📦

skill_install

安装技能Install skill

+
🔎

skill_search

技能发现Skill discovery

+
🌍

skill_publish

公开技能Publish skill

+
🔒

skill_unpublish

取消公开Unpublish skill

+
🌐

memory_viewer

管理面板Dashboard

+
+
+
+ +
+ + +
+
+
+
+ 🦞 + OpenClaw 原生记忆导入OpenClaw Native Memory Import +
+

再续前缘 —
过往的记忆,不会丢失
Reconnect —
Your Past Memories, Never Lost

+

从 OpenClaw 原生 SQLite 和会话记录中无缝迁移,智能去重、自动摘要、技能生成一气呵成。你和 AI 共同积累的每一段对话,都值得被记住。Seamlessly migrate from OpenClaw's native SQLite and session logs. Smart deduplication, auto-summarization, and skill generation — all in one flow. Every conversation you've built with your AI deserves to be preserved.

+
+ +
+
+ 🚀 +

一键迁移One-Click Import

+

自动扫描 OpenClaw 原生记忆文件,一键启动导入,实时显示进度与统计。Automatically scans OpenClaw native memory files. Start import with one click and monitor real-time progress.

+
+
+ 🧬 +

智能去重Smart Dedup

+

向量相似度 + LLM 判断双重去重,相似内容自动合并,不留冗余。Vector similarity combined with LLM judgment for dual-layer deduplication. Similar content is automatically merged with zero redundancy.

+
+
+ ⏸️ +

断点续传Resume Anytime

+

支持随时暂停,刷新页面后自动恢复进度。后台持续运行,已处理的自动跳过。Pause anytime and auto-resume on page refresh. Runs in the background, automatically skipping already processed items.

+
+
+ +

任务与技能生成Task & Skill Gen

+

导入后可选生成任务摘要和技能进化,同一 Agent 内串行处理,不同 Agent 之间并行(可配置 1–8 并发度),支持暂停和断点续传。Optionally generate task summaries and evolve skills. Serial within each agent, parallel across agents (configurable 1–8 concurrency), with full pause and resume support.

+
+
+
+
+ +
+ + +
+
+
+

沉浸体验完整流程Experience the Complete Workflow

+

从记忆导入到智能检索再到可视化管理,一站式体验 MemOS 的核心能力。From memory import to smart retrieval to visual management — explore MemOS's core capabilities in an interactive demo.

+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
场景一Scene 1
+

🦞 记忆导入🦞 Memory Import

+

从 OpenClaw 原生格式无缝迁移,实时进度与智能去重。Seamlessly migrate from OpenClaw's native format with real-time progress and smart deduplication.

+
+
1扫描原生记忆文件Scan native memory files
+
2一键导入与去重One-click import & dedup
+
3生成任务与技能Generate tasks & skills
+
+ 开始体验 →Try it → +
+
+ + +
+
+
+
+
+
+
FTS
+
VEC
+
RRF
+
+
+
+
+
+
+
场景二Scene 2
+

🔍 智能检索🔍 Smart Retrieval

+

FTS5 全文 + 向量相似度 + RRF 融合 + MMR 重排,多策略混合召回。FTS5 full-text search, vector similarity, RRF fusion, and MMR reranking — multi-strategy hybrid recall for precise results.

+
+
1输入自然语言查询Enter natural language query
+
2多路混合检索融合Multi-path hybrid retrieval
+
3相关度排序展示Relevance-ranked results
+
+ 开始体验 →Try it → +
+
+ + +
+
+
+
+
+
Memories
+
Tasks
+
Skills
+
+
+
597
memories
+
55
sessions
+
+
+
+
+
+
+
+
+
场景三Scene 3
+

📊 Viewer 管理📊 Viewer Dashboard

+

七大管理页面:记忆浏览、任务摘要、技能进化、数据分析、日志追踪、记忆导入、在线配置。Seven management pages: memories, tasks, skills, analytics, logs, import, and settings.

+
+
1记忆 CRUD 管理Memory CRUD management
+
2任务与技能追踪Task & skill tracking
+
3数据洞察分析Data insights & analytics
+
+ 开始体验 →Try it → +
+
+
+
+
+ +
+ + +
+
+
+ + + + + + + + + + + + +
+

让你的 OpenClaw
越用越聪明
Give Your OpenClaw
Lasting Intelligence

+

完全本地化 · 全量可视化 · 任务与技能自进化 · 多智能体协同 · 记忆迁移100% local · Full dashboard · Task & skill evolution · Multi-agent collaboration · Memory migration

+ +
+
+ +
+
+
+
MemOS MemOS
+ +
+
© 2026 MemTensor. MemOS OpenClaw Plugin.
+
+
+ + + + + +