Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 0 additions & 110 deletions apps/memos-local-openclaw/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { Type } from "@sinclair/typebox";
import * as fs from "fs";
import * as path from "path";
import { createRequire } from "node:module";
import { fileURLToPath } from "url";
import { buildContext } from "./src/config";
import type { HostModelsConfig } from "./src/openclaw-api";
Expand Down Expand Up @@ -206,115 +205,6 @@ const memosLocalPlugin = {
}

const moduleDir = path.dirname(fileURLToPath(import.meta.url));
const localRequire = createRequire(import.meta.url);
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";

function detectPluginDir(startDir: string): string {
let cur = startDir;
for (let i = 0; i < 6; i++) {
const pkg = path.join(cur, "package.json");
if (fs.existsSync(pkg)) return cur;
const parent = path.dirname(cur);
if (parent === cur) break;
cur = parent;
}
return startDir;
}

const pluginDir = detectPluginDir(moduleDir);

function runNpm(args: string[]) {
const { spawnSync } = localRequire("child_process") as typeof import("node:child_process");
return spawnSync(npmCmd, args, {
cwd: pluginDir,
stdio: "pipe",
shell: false,
timeout: 120_000,
});
}

let sqliteReady = false;

function trySqliteLoad(): boolean {
try {
const resolved = localRequire.resolve("better-sqlite3", { paths: [pluginDir] });
const resolvedReal = fs.existsSync(resolved) ? fs.realpathSync.native(resolved) : resolved;
if (!isPathInside(pluginDir, resolvedReal)) {
api.logger.warn(`memos-local: better-sqlite3 resolved outside plugin dir: ${resolved}`);
return false;
}
localRequire(resolvedReal);
return true;
} catch {
return false;
}
}

sqliteReady = trySqliteLoad();

if (!sqliteReady) {
api.logger.warn(`memos-local: better-sqlite3 not found in ${pluginDir}, attempting auto-rebuild ...`);

try {
const rebuildResult = runNpm(["rebuild", "better-sqlite3"]);

const stdout = rebuildResult.stdout?.toString() || "";
const stderr = rebuildResult.stderr?.toString() || "";
if (stdout) api.logger.info(`memos-local: rebuild stdout: ${stdout.slice(0, 500)}`);
if (stderr) api.logger.warn(`memos-local: rebuild stderr: ${stderr.slice(0, 500)}`);

if (rebuildResult.status === 0) {
Object.keys(localRequire.cache)
.filter(k => k.includes("better-sqlite3") || k.includes("better_sqlite3"))
.forEach(k => delete localRequire.cache[k]);
sqliteReady = trySqliteLoad();
if (sqliteReady) {
api.logger.info("memos-local: better-sqlite3 auto-rebuild succeeded!");
} else {
api.logger.warn("memos-local: rebuild exited 0 but module still not loadable from plugin dir");
}
} else {
api.logger.warn(`memos-local: rebuild exited with code ${rebuildResult.status}`);
}
} catch (rebuildErr) {
api.logger.warn(`memos-local: auto-rebuild error: ${rebuildErr}`);
}

if (!sqliteReady) {
const nodeVer = process.version;
const nodeMajor = parseInt(process.versions?.node?.split(".")[0] ?? "0", 10);
const isNode25Plus = nodeMajor >= 25;
const lines = [
"",
"╔══════════════════════════════════════════════════════════════╗",
"║ MemOS Local Memory — better-sqlite3 native module missing ║",
"╠══════════════════════════════════════════════════════════════╣",
"║ ║",
"║ Auto-rebuild failed (Node " + nodeVer + "). Run manually: ║",
"║ ║",
`║ cd ${pluginDir}`,
"║ npm rebuild better-sqlite3 ║",
"║ openclaw gateway stop && openclaw gateway start ║",
"║ ║",
"║ If rebuild fails, install build tools first: ║",
"║ macOS: xcode-select --install ║",
"║ Linux: sudo apt install build-essential python3 ║",
];
if (isNode25Plus) {
lines.push("║ ║");
lines.push("║ Node 25+ has no prebuild: build tools required, or use ║");
lines.push("║ Node LTS (20/22): nvm install 22 && nvm use 22 ║");
}
lines.push("║ ║");
lines.push("╚══════════════════════════════════════════════════════════════╝");
lines.push("");
api.logger.warn(lines.join("\n"));
throw new Error(
`better-sqlite3 native module not found (Node ${nodeVer}). Auto-rebuild failed. Fix: install build tools, then cd ${pluginDir} && npm rebuild better-sqlite3. Or use Node LTS (20/22).`
);
}
}

let pluginCfg = (api.pluginConfig ?? {}) as Record<string, unknown>;
const stateDir = process.env.OPENCLAW_STATE_DIR || api.resolvePath("~/.openclaw");

Expand Down
14 changes: 14 additions & 0 deletions apps/memos-local-openclaw/tests/startup-registration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { describe, expect, it } from "vitest";
import fs from "node:fs";
import path from "node:path";

describe("startup registration", () => {
it("does not run npm rebuild during plugin registration", () => {
const source = fs.readFileSync(path.resolve(__dirname, "../index.ts"), "utf-8");
const registrationPrelude = source.slice(0, source.indexOf("const ctx = buildContext"));

expect(registrationPrelude).not.toContain("spawnSync");
expect(registrationPrelude).not.toContain("npm rebuild better-sqlite3");
expect(registrationPrelude).not.toContain("rebuild\", \"better-sqlite3");
});
});