fix: adding skills config and updating the plugin the config (#4958)

This commit is contained in:
Kartik
2026-04-29 22:19:40 +05:30
committed by GitHub
parent 594b4e65d6
commit ebb9bb2b15
20 changed files with 585 additions and 176 deletions
+32 -5
View File
@@ -43,6 +43,7 @@ import {
readPluginAuth,
writePluginAuth,
writePluginConfigField,
enableSkillsConfig,
OPENCLAW_CONFIG_FILE,
} from "./config-file.ts";
import { jsonOut, jsonErr, redactSecrets } from "./json-helpers.ts";
@@ -50,6 +51,7 @@ import {
LLM_PROVIDERS, EMBEDDER_PROVIDERS, VECTOR_PROVIDERS,
buildOssLlmConfig, buildOssEmbedderConfig, buildOssVectorConfig,
validateOssFlags, checkQdrantConnectivity, checkOllamaConnectivity, checkPgConnectivity,
collectionNameForDims,
} from "./oss-wizard.ts";
// ============================================================================
@@ -213,10 +215,11 @@ function saveLoginConfig(
const userId = resolveUserId(userIdFlag, existingAuth.userId);
writePluginAuth({ apiKey, userId, mode: "platform", ...(userEmail && { userEmail }) });
enableSkillsConfig(userId);
if (!silent) {
console.log(` Configuration saved to ${OPENCLAW_CONFIG_FILE}`);
console.log(` Mode: platform`);
console.log(` Mode: platform (skills enabled)`);
console.log(` User ID: ${userId}`);
}
}
@@ -226,10 +229,11 @@ function saveOssConfig(userIdFlag?: string, silent?: boolean): void {
const userId = resolveUserId(userIdFlag, existingAuth.userId);
writePluginAuth({ apiKey: "", userId, mode: "open-source" });
enableSkillsConfig(userId);
if (!silent) {
console.log(` Configuration saved to ${OPENCLAW_CONFIG_FILE}`);
console.log(` Mode: open-source`);
console.log(` Mode: open-source (skills enabled)`);
console.log(` User ID: ${userId}`);
}
}
@@ -350,6 +354,17 @@ async function runOssWizardInteractive(
}
const vecCfg = buildOssVectorConfig(vecDef.id, vecInput as any);
// Warn if switching embedder dimensions — old collection will have wrong vector size
const existingVecCfg = existingAuth as any;
const oldDims = existingVecCfg?.oss?.vectorStore?.config?.dimension as number | undefined;
if (oldDims && dims && oldDims !== dims) {
console.log(`\n ⚠ Dimension change detected: ${oldDims} → ${dims}`);
console.log(` Old collection had ${oldDims}-dim vectors. New embedder produces ${dims}-dim vectors.`);
console.log(` A new collection "${collectionNameForDims(dims)}" will be created.`);
console.log(` Old memories in the previous collection will NOT be accessible with the new embedder.\n`);
}
writePluginConfigField(["oss", "vectorStore"], vecCfg);
// === Step 4: User ID ===
@@ -370,6 +385,8 @@ async function runOssWizardInteractive(
console.log(` LLM: ${llmDef.id} (${llmCfg.config.model})`);
console.log(` Embedder: ${embDef.id} (${embCfg.config.model})`);
console.log(` Vector: ${vecDef.id} (${vecDef.id === "qdrant" ? vecCfg.config.url : vecCfg.config.host})`);
console.log(` Dims: ${dims ?? "unknown"}`);
console.log(` Collection:${dims ? " " + collectionNameForDims(dims) : " (default)"}`);
console.log(` User ID: ${userIdValue}`);
console.log("");
console.log(" Run: openclaw gateway restart");
@@ -539,6 +556,15 @@ export function registerCliCommands(
}
}
// Warn on dimension change
const prevAuth = readPluginAuth() as any;
const prevDims = prevAuth?.oss?.vectorStore?.config?.dimension as number | undefined;
const newDims = dims;
let dimWarning: string | undefined;
if (prevDims && newDims && prevDims !== newDims) {
dimWarning = `Dimension change: ${prevDims} → ${newDims}. New collection "${collectionNameForDims(newDims)}" will be used. Old memories not accessible with new embedder.`;
}
writePluginConfigField(["oss", "llm"], llmCfg);
writePluginConfigField(["oss", "embedder"], { provider: embCfg.provider, config: embCfg.config });
writePluginConfigField(["oss", "vectorStore"], vecCfg);
@@ -550,9 +576,10 @@ export function registerCliCommands(
mode: "open-source",
config: {
llm: { provider: llmCfg.provider, model: llmCfg.config.model },
embedder: { provider: embCfg.provider, model: embCfg.config.model },
vectorStore: { provider: vecCfg.provider, ...(vecId === "qdrant" ? { url: vecCfg.config.url } : { host: vecCfg.config.host }) },
embedder: { provider: embCfg.provider, model: embCfg.config.model, dims: newDims },
vectorStore: { provider: vecCfg.provider, ...(vecId === "qdrant" ? { url: vecCfg.config.url } : { host: vecCfg.config.host }), collectionName: newDims ? collectionNameForDims(newDims) : undefined },
},
...(dimWarning && { warning: dimWarning }),
userId: resolveUserId(opts.userId, existingAuth.userId),
message: "Open-source mode configured. Restart the gateway: openclaw gateway restart",
};
@@ -889,7 +916,7 @@ export function registerCliCommands(
runId?: string,
): SearchOptions => {
const base = buildSearchOptions(userIdOverride, lim, runId);
base.threshold = 0.3;
base.threshold = 0.1;
return base;
};
+78 -78
View File
@@ -19,7 +19,6 @@ export const OPENCLAW_CONFIG_FILE = join(OPENCLAW_CONFIG_DIR, "openclaw.json");
export const DEFAULT_BASE_URL = "https://api.mem0.ai";
const PLUGIN_ID = "openclaw-mem0";
const NPM_PACKAGE = "@mem0/openclaw-mem0";
// ============================================================================
// Types
@@ -76,11 +75,44 @@ function readFullConfig(): Record<string, unknown> {
}
}
/** Write the full ~/.openclaw/openclaw.json (preserves all non-plugin config) */
/**
* Write the full ~/.openclaw/openclaw.json.
*
* Re-reads the file immediately before writing and deep-merges the
* `plugins` section so that fields written by other processes (e.g.
* OpenClaw gateway adding `installs`, `slots`) are not clobbered.
*/
function writeFullConfig(config: Record<string, unknown>): void {
if (!exists(OPENCLAW_CONFIG_DIR)) {
mkdirp(OPENCLAW_CONFIG_DIR, 0o700);
}
if (exists(OPENCLAW_CONFIG_FILE)) {
try {
const diskText = readText(OPENCLAW_CONFIG_FILE);
if (diskText.trim()) {
const disk = JSON.parse(diskText) as Record<string, unknown>;
const diskPlugins = disk.plugins as Record<string, unknown> | undefined;
const ourPlugins = config.plugins as Record<string, unknown> | undefined;
if (diskPlugins && ourPlugins) {
const OPENCLAW_MANAGED = ["installs", "slots"];
for (const key of OPENCLAW_MANAGED) {
if (key in diskPlugins) {
ourPlugins[key] = diskPlugins[key];
}
}
for (const key of Object.keys(diskPlugins)) {
if (!(key in ourPlugins)) {
ourPlugins[key] = diskPlugins[key];
}
}
}
}
} catch {
// disk unreadable — write our version as-is
}
}
writeText(
OPENCLAW_CONFIG_FILE,
JSON.stringify(config, null, 2),
@@ -122,82 +154,6 @@ export function writePluginAuth(auth: PluginAuthConfig): void {
writeFullConfig(full);
}
/**
* Ensure the plugin has a valid install record and is in plugins.allow.
*
* OpenClaw's `plugins update` command requires a `plugins.installs.<id>`
* record with `source: "npm"` and `spec` to know how to update. Without
* this, `openclaw plugins update` prints "No install record" and skips.
*
* Similarly, if `plugins.allow` exists as an array, the plugin ID must
* be in it or OpenClaw treats the plugin as untrusted.
*
* This is safe to call multiple times — it only writes missing fields.
*/
export function ensureInstallRecord(): void {
try {
const full = readFullConfig() as any;
const entry = full?.plugins?.entries?.[PLUGIN_ID];
const record = full?.plugins?.installs?.[PLUGIN_ID];
const allow = full?.plugins?.allow;
const specPinned = record?.spec && /\d+\.\d+\.\d+/.test(record.spec);
if (
entry?.enabled === true &&
record?.source &&
record?.spec &&
!specPinned &&
Array.isArray(allow) &&
allow.includes(PLUGIN_ID)
) {
return;
}
ensurePluginStructure(full);
let changed = false;
// Ensure install record exists for `openclaw plugins update` support
if (!full.plugins.installs) full.plugins.installs = {};
if (!full.plugins.installs[PLUGIN_ID]) {
full.plugins.installs[PLUGIN_ID] = {
source: "npm",
spec: `${NPM_PACKAGE}@latest`,
resolvedName: NPM_PACKAGE,
installedAt: new Date().toISOString(),
};
changed = true;
} else {
const record = full.plugins.installs[PLUGIN_ID];
if (!record.source) {
record.source = "npm";
changed = true;
}
if (!record.spec || /\d+\.\d+\.\d+/.test(record.spec)) {
record.spec = record.source === "clawhub"
? `clawhub:${NPM_PACKAGE}`
: `${NPM_PACKAGE}@latest`;
changed = true;
}
if (!record.resolvedName) {
record.resolvedName = NPM_PACKAGE;
changed = true;
}
}
if (!Array.isArray(full.plugins.allow)) {
full.plugins.allow = [PLUGIN_ID];
changed = true;
} else if (!full.plugins.allow.includes(PLUGIN_ID)) {
full.plugins.allow.push(PLUGIN_ID);
changed = true;
}
if (changed) writeFullConfig(full);
} catch {
// Best-effort — don't break plugin loading if config is unreadable
}
}
/** Ensure the nested plugin entry structure exists in the config object. */
function ensurePluginStructure(full: any): void {
@@ -231,6 +187,50 @@ export function writePluginConfigField(
writeFullConfig(full);
}
/**
* Default skills configuration — matches configure.py output.
* Enables triage, recall (with reranking), and dream consolidation.
*/
const DEFAULT_SKILLS_CONFIG = {
triage: { enabled: true },
recall: {
enabled: true,
tokenBudget: 1500,
rerank: true,
keywordSearch: true,
identityAlwaysInclude: true,
},
dream: { enabled: true },
domain: "companion",
};
/**
* Enable skills-mode config after onboarding.
*
* Sets skills config on the plugin entry, tools.profile = "full",
* and disables the built-in session-memory hook to avoid conflicts.
* Preserves any existing skills config if already set.
*/
export function enableSkillsConfig(userId: string): void {
const full = readFullConfig() as any;
ensurePluginStructure(full);
const cfg = full.plugins.entries[PLUGIN_ID].config;
if (!cfg.skills) {
cfg.skills = { ...DEFAULT_SKILLS_CONFIG };
}
if (!full.tools) full.tools = {};
full.tools.profile = "full";
if (!full.hooks) full.hooks = {};
if (!full.hooks.internal) full.hooks.internal = {};
if (!full.hooks.internal.entries) full.hooks.internal.entries = {};
full.hooks.internal.entries["session-memory"] = { enabled: false };
writeFullConfig(full);
}
/** Get the configured base URL from openclaw.json or default */
export function getBaseUrl(): string {
const auth = readPluginAuth();
+14 -2
View File
@@ -56,8 +56,15 @@ export const KNOWN_EMBEDDER_DIMS: Record<string, number> = {
"text-embedding-3-large": 3072,
"text-embedding-ada-002": 1536,
"nomic-embed-text": 768,
"mxbai-embed-large": 1024,
"all-minilm": 384,
"snowflake-arctic-embed": 1024,
};
export function collectionNameForDims(dims: number): string {
return `mem0_${dims}d`;
}
// ============================================================================
// Config builders
// ============================================================================
@@ -105,7 +112,8 @@ export function buildOssEmbedderConfig(
config.url = input.url || def.defaultUrl;
}
const dims = KNOWN_EMBEDDER_DIMS[model] ?? undefined;
const dims = KNOWN_EMBEDDER_DIMS[model] ?? def.defaultDims;
if (dims) config.embeddingDims = dims;
return { provider: providerId, config, dims };
}
@@ -138,7 +146,11 @@ export function buildOssVectorConfig(
config.dbname = input.dbname || "postgres";
}
if (input.dims) config.dimension = input.dims;
if (input.dims) {
config.dimension = input.dims;
config.embeddingModelDims = input.dims;
config.collectionName = collectionNameForDims(input.dims);
}
return { provider: providerId, config };
}