Skip to content
Closed
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
20 changes: 19 additions & 1 deletion cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ export function registerMemoryCLI(program: Command, context: CLIContext): void {
.option("--dry-run", "Show what would be re-embedded without writing")
.option("--skip-existing", "Skip entries whose id already exists in the target DB")
.option("--force", "Allow using the same source-db as the target dbPath (DANGEROUS)")
.option("--storage-options <json>", "Storage options for LanceDB connection (JSON string, e.g. '{\"aws_access_key_id\":\"...\"}')")
.action(async (options) => {
try {
if (!context.embedder) {
Expand All @@ -526,6 +527,23 @@ export function registerMemoryCLI(program: Command, context: CLIContext): void {
const skipExisting = options.skipExisting === true;
const force = options.force === true;

let storageOptions: Record<string, string> = {};
if (options.storageOptions) {
try {
const parsed = JSON.parse(options.storageOptions as string);
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
for (const [key, value] of Object.entries(parsed)) {
if (typeof value === "string") {
storageOptions[key] = value;
}
}
}
} catch {
console.error("Invalid JSON in --storage-options");
process.exit(1);
}
}

// Safety: prevent accidental in-place re-embedding
let sourceReal = sourceDbPath;
let targetReal = context.store.dbPath;
Expand All @@ -542,7 +560,7 @@ export function registerMemoryCLI(program: Command, context: CLIContext): void {
}

const lancedb = await loadLanceDB();
const db = await lancedb.connect(sourceDbPath);
const db = await lancedb.connect(sourceDbPath, { storageOptions });
const table = await db.openTable("memories");

let query = table
Expand Down
49 changes: 38 additions & 11 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ interface PluginConfig {
dedupeErrorSignals?: boolean;
};
mdMirror?: { enabled?: boolean; dir?: string };
storageOptions?: Record<string, string>;
}

type ReflectionThinkLevel = "off" | "minimal" | "low" | "medium" | "high";
Expand Down Expand Up @@ -1585,17 +1586,26 @@ const memoryLanceDBProPlugin = {
// Parse and validate configuration
const config = parsePluginConfig(api.pluginConfig);

const resolvedDbPath = api.resolvePath(config.dbPath || getDefaultDbPath());
const dbPath = config.dbPath || getDefaultDbPath();
const isCloud = /^[a-z][a-z0-9+.-]*:\/\//i.test(dbPath.trim());

// Pre-flight: validate storage path (symlink resolution, mkdir, write check).
// Runs synchronously and logs warnings; does NOT block gateway startup.
try {
validateStoragePath(resolvedDbPath);
} catch (err) {
api.logger.warn(
`memory-lancedb-pro: storage path issue — ${String(err)}\n` +
` The plugin will still attempt to start, but writes may fail.`,
);
let resolvedDbPath: string;
if (isCloud) {
resolvedDbPath = dbPath;
} else {
// Local path: resolve and validate
resolvedDbPath = api.resolvePath(dbPath);

// Pre-flight: validate storage path (symlink resolution, mkdir, write check).
// Runs synchronously and logs warnings; does NOT block gateway startup.
try {
validateStoragePath(resolvedDbPath);
} catch (err) {
api.logger.warn(
`memory-lancedb-pro: storage path issue — ${String(err)}\n` +
` The plugin will still attempt to start, but writes may fail.`,
);
}
}

const vectorDim = getVectorDimensions(
Expand All @@ -1604,7 +1614,7 @@ const memoryLanceDBProPlugin = {
);

// Initialize core components
const store = new MemoryStore({ dbPath: resolvedDbPath, vectorDim });
const store = new MemoryStore({ dbPath: resolvedDbPath, vectorDim, storageOptions: config.storageOptions });
const embedder = createEmbedder({
provider: "openai-compatible",
apiKey: config.embedding.apiKey,
Expand Down Expand Up @@ -3330,6 +3340,23 @@ export function parsePluginConfig(value: unknown): PluginConfig {
: undefined,
}
: undefined,
storageOptions:
typeof cfg.storageOptions === "object" && cfg.storageOptions !== null && !Array.isArray(cfg.storageOptions)
? (() => {
const opts = cfg.storageOptions as Record<string, unknown>;
const resolved: Record<string, string> = {};
for (const [key, value] of Object.entries(opts)) {
if (typeof value !== "string") {
throw new Error(
`storageOptions[${key}] is invalid: expected string, got ${typeof value}`
);
}
// Resolve environment variables in value
resolved[key] = resolveEnvVars(value);
}
return resolved;
})()
: undefined,
};
}

Expand Down
13 changes: 13 additions & 0 deletions openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,13 @@
"description": "Fallback directory for Markdown mirror files when agent workspace is unknown"
}
}
},
"storageOptions": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Additional storage options for LanceDB connection configuration"
}
},
"required": [
Expand Down Expand Up @@ -780,6 +787,12 @@
"help": "Define which scopes each agent can access",
"advanced": true
},
"storageOptions": {
"label": "Storage Options",
"help": "Additional storage options for LanceDB connection configuration (e.g., cloud provider credentials)",
"advanced": true,
"sensitive": true
},
"enableManagementTools": {
"label": "Management Tools",
"help": "Enable management/debug tools such as memory_list, memory_stats, and governance-oriented self-improvement review/extract actions.",
Expand Down
10 changes: 8 additions & 2 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,11 @@ export class MemoryStore {
private initPromise: Promise<void> | null = null;
private ftsIndexCreated = false;
private updateQueue: Promise<void> = Promise.resolve();
private readonly storageOptions: Record<string, string>;

constructor(private readonly config: StoreConfig) {}
constructor(private readonly config: StoreConfig) {
this.storageOptions = config.storageOptions || {};
}

get dbPath(): string {
return this.config.dbPath;
Expand All @@ -208,7 +211,10 @@ export class MemoryStore {

let db: LanceDB.Connection;
try {
db = await lancedb.connect(this.config.dbPath);
const connectOpts = Object.keys(this.storageOptions).length > 0
? { storageOptions: this.storageOptions }
: undefined;
db = await lancedb.connect(this.config.dbPath, connectOpts);
} catch (err: any) {
const code = err.code || "";
const message = err.message || String(err);
Expand Down
Loading
Loading