openclaw-memory-alibaba-mysql 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/config.ts CHANGED
@@ -24,8 +24,10 @@ export type LLMConfig = {
24
24
  };
25
25
 
26
26
  export type MemoryConfig = {
27
- mysql: MysqlConnectionConfig;
28
- embedding: EmbeddingConfig;
27
+ /** Omitted when plugin is loaded without DB config (e.g. npm install); required at runtime for memory ops. */
28
+ mysql?: MysqlConnectionConfig;
29
+ /** Omitted when plugin is loaded without DB config; required at runtime for memory ops. */
30
+ embedding?: EmbeddingConfig;
29
31
  /** When true, use LLM to decide insert vs update among top-10 similar memories; requires llm config. Default false. */
30
32
  memory_duplication_conflict_process: boolean;
31
33
  /** Required when memory_duplication_conflict_process is true. */
@@ -59,7 +61,7 @@ export type { MemoryCategory };
59
61
  const DEFAULT_MODEL = "text-embedding-v3";
60
62
  const DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
61
63
  const DEFAULT_TABLE_NAME = "openclaw_memories";
62
- export const DEFAULT_CAPTURE_MAX_CHARS = 500;
64
+ export const DEFAULT_CAPTURE_MAX_CHARS = 50000;
63
65
 
64
66
  const EMBEDDING_DIMENSIONS: Record<string, number> = {
65
67
  "text-embedding-v3": 1024,
@@ -204,6 +206,37 @@ export const memoryConfigSchema = {
204
206
  "memory config",
205
207
  );
206
208
 
209
+ // --- When DB config is missing, return minimal config without throwing (e.g. npm install) ---
210
+ if (!cfg.mysql || typeof cfg.mysql !== "object" || Array.isArray(cfg.mysql) ||
211
+ !cfg.embedding || typeof cfg.embedding !== "object" || Array.isArray(cfg.embedding)) {
212
+ const tableName =
213
+ typeof cfg.tableName === "string" && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(cfg.tableName)
214
+ ? cfg.tableName
215
+ : DEFAULT_TABLE_NAME;
216
+ const capChars =
217
+ typeof cfg.captureMaxChars === "number" && cfg.captureMaxChars >= 100 && cfg.captureMaxChars <= 100_000
218
+ ? Math.floor(cfg.captureMaxChars)
219
+ : DEFAULT_CAPTURE_MAX_CHARS;
220
+ return {
221
+ mysql: undefined,
222
+ embedding: undefined,
223
+ memory_duplication_conflict_process: false,
224
+ llm: undefined,
225
+ similarityThresholdUserMemory: 0.95,
226
+ similarityThresholdSelfImproving: 0.92,
227
+ enableFullContextMemory: false,
228
+ enableSelfImprovingMemory: false,
229
+ memoryExtractionMethod: "llm",
230
+ autoRecall: cfg.autoRecall !== false,
231
+ autoCapture: cfg.autoCapture !== false,
232
+ captureMaxChars: capChars,
233
+ enableMemoryDecay: false,
234
+ memoryDecayHalfLifeDays: 30,
235
+ memoryDecayStrategy: "exponential",
236
+ tableName,
237
+ };
238
+ }
239
+
207
240
  // --- LLM requirement ---
208
241
  const memory_duplication_conflict_process = cfg.memory_duplication_conflict_process === true;
209
242
  const rawMethod =
@@ -242,8 +275,8 @@ export const memoryConfigSchema = {
242
275
 
243
276
  const captureMaxChars =
244
277
  typeof cfg.captureMaxChars === "number" ? Math.floor(cfg.captureMaxChars) : undefined;
245
- if (typeof captureMaxChars === "number" && (captureMaxChars < 100 || captureMaxChars > 10_000)) {
246
- throw new Error("captureMaxChars must be between 100 and 10000");
278
+ if (typeof captureMaxChars === "number" && (captureMaxChars < 100 || captureMaxChars > 100_000)) {
279
+ throw new Error("captureMaxChars must be between 100 and 100000");
247
280
  }
248
281
 
249
282
  // --- Memory decay: only enableMemoryDecay is configurable; half-life and strategy use defaults ---
package/db.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import mysql from "mysql2/promise";
3
- import type { Pool } from "mysql2/promise";
3
+ import type { Connection } from "mysql2/promise";
4
4
  import type { MemoryCategory } from "./config.js";
5
5
  import { USER_MEMORY_FACT } from "./categories.js";
6
6
  import type { MysqlConnectionConfig } from "./config.js";
@@ -29,8 +29,6 @@ function stripFourByteUtf8(text: string): string {
29
29
  }
30
30
 
31
31
  export class MemoryDB {
32
- private pool: Pool | null = null;
33
- private initPromise: Promise<void> | null = null;
34
32
  private vectorIndexCreated = false;
35
33
 
36
34
  constructor(
@@ -39,58 +37,48 @@ export class MemoryDB {
39
37
  private readonly vectorDim: number,
40
38
  ) {}
41
39
 
42
- private async ensureInitialized(): Promise<void> {
43
- if (this.pool) {
44
- return;
45
- }
46
- if (this.initPromise) {
47
- return this.initPromise;
48
- }
49
- this.initPromise = this.doInitialize();
50
- return this.initPromise;
51
- }
52
-
53
- private async doInitialize(): Promise<void> {
54
- this.pool = mysql.createPool({
40
+ /** Short-lived connection: create, ensure table, run fn, close. */
41
+ private async withConnection<T>(fn: (conn: Connection) => Promise<T>): Promise<T> {
42
+ const conn = await mysql.createConnection({
55
43
  host: this.mysqlConfig.host,
56
44
  port: this.mysqlConfig.port,
57
45
  user: this.mysqlConfig.user,
58
46
  password: this.mysqlConfig.password,
59
47
  database: this.mysqlConfig.database,
60
48
  ssl: this.mysqlConfig.ssl ? {} : undefined,
61
- connectionLimit: 5,
62
- waitForConnections: true,
63
- queueLimit: 0,
64
49
  });
65
-
66
- const conn = await this.pool.getConnection();
67
50
  try {
68
51
  await conn.query("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED");
69
- await conn.query(`
70
- CREATE TABLE IF NOT EXISTS \`${this.tableName}\` (
71
- id VARCHAR(36) NOT NULL PRIMARY KEY,
72
- agent_id VARCHAR(128) NOT NULL,
73
- user_id VARCHAR(128) NULL DEFAULT NULL,
74
- session_id VARCHAR(128) NULL DEFAULT NULL,
75
- text TEXT NOT NULL,
76
- embedding VECTOR(${this.vectorDim}) NOT NULL,
77
- importance FLOAT DEFAULT 0,
78
- category VARCHAR(64) DEFAULT '${USER_MEMORY_FACT}',
79
- created_at BIGINT NOT NULL,
80
- is_deleted TINYINT NOT NULL DEFAULT 0,
81
- INDEX idx_agent_id (agent_id),
82
- INDEX idx_agent_session_category (agent_id, session_id, category)
83
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
84
- `);
85
- await this.ensureIsDeletedColumn(conn);
86
- await this.ensureSessionCategoryIndex(conn);
87
- await this.tryCreateVectorIndex(conn);
52
+ await this.ensureTable(conn);
53
+ return await fn(conn);
88
54
  } finally {
89
- conn.release();
55
+ await conn.end();
90
56
  }
91
57
  }
92
58
 
93
- private async ensureIsDeletedColumn(conn: mysql.PoolConnection): Promise<void> {
59
+ private async ensureTable(conn: Connection): Promise<void> {
60
+ await conn.query(`
61
+ CREATE TABLE IF NOT EXISTS \`${this.tableName}\` (
62
+ id VARCHAR(36) NOT NULL PRIMARY KEY,
63
+ agent_id VARCHAR(128) NOT NULL,
64
+ user_id VARCHAR(128) NULL DEFAULT NULL,
65
+ session_id VARCHAR(128) NULL DEFAULT NULL,
66
+ text LONGTEXT NOT NULL,
67
+ embedding VECTOR(${this.vectorDim}) NOT NULL,
68
+ importance FLOAT DEFAULT 0,
69
+ category VARCHAR(64) DEFAULT '${USER_MEMORY_FACT}',
70
+ created_at BIGINT NOT NULL,
71
+ is_deleted TINYINT NOT NULL DEFAULT 0,
72
+ INDEX idx_agent_id (agent_id),
73
+ INDEX idx_agent_session_category (agent_id, session_id, category)
74
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
75
+ `);
76
+ await this.ensureIsDeletedColumn(conn);
77
+ await this.ensureSessionCategoryIndex(conn);
78
+ await this.tryCreateVectorIndex(conn);
79
+ }
80
+
81
+ private async ensureIsDeletedColumn(conn: Connection): Promise<void> {
94
82
  const [rows] = await conn.query(
95
83
  `SELECT COLUMN_NAME FROM information_schema.COLUMNS
96
84
  WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = 'is_deleted'`,
@@ -102,7 +90,7 @@ export class MemoryDB {
102
90
  );
103
91
  }
104
92
 
105
- private async ensureSessionCategoryIndex(conn: mysql.PoolConnection): Promise<void> {
93
+ private async ensureSessionCategoryIndex(conn: Connection): Promise<void> {
106
94
  const [rows] = await conn.query(
107
95
  `SELECT COUNT(1) AS cnt FROM information_schema.statistics
108
96
  WHERE table_schema = DATABASE() AND table_name = ? AND index_name = 'idx_agent_session_category'`,
@@ -114,7 +102,7 @@ export class MemoryDB {
114
102
  );
115
103
  }
116
104
 
117
- private async tryCreateVectorIndex(conn: mysql.PoolConnection): Promise<void> {
105
+ private async tryCreateVectorIndex(conn: Connection): Promise<void> {
118
106
  if (this.vectorIndexCreated) {
119
107
  return;
120
108
  }
@@ -151,38 +139,33 @@ export class MemoryDB {
151
139
  sessionId?: string | null;
152
140
  },
153
141
  ): Promise<MemoryEntry> {
154
- await this.ensureInitialized();
142
+ return this.withConnection(async (conn) => {
143
+ const id = randomUUID();
144
+ const createdAt = Date.now();
145
+ const vectorStr = JSON.stringify(entry.vector);
146
+ const userId = entry.userId ?? null;
147
+ const sessionId = entry.sessionId ?? null;
148
+ const textSafe = stripFourByteUtf8(entry.text);
155
149
 
156
- const id = randomUUID();
157
- const createdAt = Date.now();
158
- const vectorStr = JSON.stringify(entry.vector);
159
- const userId = entry.userId ?? null;
160
- const sessionId = entry.sessionId ?? null;
161
- const textSafe = stripFourByteUtf8(entry.text);
162
-
163
- await this.pool!.query(
164
- `INSERT INTO \`${this.tableName}\` (id, agent_id, user_id, session_id, text, embedding, importance, category, created_at, is_deleted)
165
- VALUES (?, ?, ?, ?, ?, VEC_FROMTEXT(?), ?, ?, ?, 0)`,
166
- [id, agentId, userId, sessionId, textSafe, vectorStr, entry.importance, entry.category, createdAt],
167
- );
150
+ await conn.query(
151
+ `INSERT INTO \`${this.tableName}\` (id, agent_id, user_id, session_id, text, embedding, importance, category, created_at, is_deleted)
152
+ VALUES (?, ?, ?, ?, ?, VEC_FROMTEXT(?), ?, ?, ?, 0)`,
153
+ [id, agentId, userId, sessionId, textSafe, vectorStr, entry.importance, entry.category, createdAt],
154
+ );
168
155
 
169
- if (!this.vectorIndexCreated) {
170
- const conn = await this.pool!.getConnection();
171
- try {
156
+ if (!this.vectorIndexCreated) {
172
157
  await this.tryCreateVectorIndex(conn);
173
- } finally {
174
- conn.release();
175
158
  }
176
- }
177
159
 
178
- return {
179
- id,
180
- agentId,
181
- text: textSafe,
182
- importance: entry.importance,
183
- category: entry.category,
184
- createdAt,
185
- };
160
+ return {
161
+ id,
162
+ agentId,
163
+ text: textSafe,
164
+ importance: entry.importance,
165
+ category: entry.category,
166
+ createdAt,
167
+ };
168
+ });
186
169
  }
187
170
 
188
171
  /**
@@ -200,32 +183,55 @@ export class MemoryDB {
200
183
  userId?: string | null;
201
184
  },
202
185
  ): Promise<{ action: "created" | "updated"; entry: MemoryEntry }> {
203
- await this.ensureInitialized();
186
+ return this.withConnection(async (conn) => {
187
+ const textSafe = stripFourByteUtf8(entry.text);
188
+ const vectorStr = JSON.stringify(entry.vector);
189
+ const userId = entry.userId ?? null;
190
+ const createdAt = Date.now();
191
+
192
+ const [existingRows] = await conn.query(
193
+ `SELECT id FROM \`${this.tableName}\`
194
+ WHERE agent_id = ? AND category = ? AND is_deleted = 0
195
+ AND ((? IS NULL AND session_id IS NULL) OR (session_id = ?))
196
+ LIMIT 1`,
197
+ [agentId, entry.category, sessionId, sessionId],
198
+ );
199
+ const existing = (existingRows as Array<{ id: string }>)[0];
200
+
201
+ if (existing) {
202
+ await conn.query(
203
+ `UPDATE \`${this.tableName}\` SET text = ?, embedding = VEC_FROMTEXT(?), importance = ?, created_at = ?, user_id = ?
204
+ WHERE id = ? AND agent_id = ?`,
205
+ [textSafe, vectorStr, entry.importance, createdAt, userId, existing.id, agentId],
206
+ );
207
+ return {
208
+ action: "updated",
209
+ entry: {
210
+ id: existing.id,
211
+ agentId,
212
+ text: textSafe,
213
+ importance: entry.importance,
214
+ category: entry.category,
215
+ createdAt,
216
+ },
217
+ };
218
+ }
204
219
 
205
- const textSafe = stripFourByteUtf8(entry.text);
206
- const vectorStr = JSON.stringify(entry.vector);
207
- const userId = entry.userId ?? null;
208
- const createdAt = Date.now();
220
+ const id = randomUUID();
221
+ await conn.query(
222
+ `INSERT INTO \`${this.tableName}\` (id, agent_id, user_id, session_id, text, embedding, importance, category, created_at, is_deleted)
223
+ VALUES (?, ?, ?, ?, ?, VEC_FROMTEXT(?), ?, ?, ?, 0)`,
224
+ [id, agentId, userId, sessionId, textSafe, vectorStr, entry.importance, entry.category, createdAt],
225
+ );
209
226
 
210
- const [existingRows] = await this.pool!.query(
211
- `SELECT id FROM \`${this.tableName}\`
212
- WHERE agent_id = ? AND category = ? AND is_deleted = 0
213
- AND ((? IS NULL AND session_id IS NULL) OR (session_id = ?))
214
- LIMIT 1`,
215
- [agentId, entry.category, sessionId, sessionId],
216
- );
217
- const existing = (existingRows as Array<{ id: string }>)[0];
227
+ if (!this.vectorIndexCreated) {
228
+ await this.tryCreateVectorIndex(conn);
229
+ }
218
230
 
219
- if (existing) {
220
- await this.pool!.query(
221
- `UPDATE \`${this.tableName}\` SET text = ?, embedding = VEC_FROMTEXT(?), importance = ?, created_at = ?, user_id = ?
222
- WHERE id = ? AND agent_id = ?`,
223
- [textSafe, vectorStr, entry.importance, createdAt, userId, existing.id, agentId],
224
- );
225
231
  return {
226
- action: "updated",
232
+ action: "created",
227
233
  entry: {
228
- id: existing.id,
234
+ id,
229
235
  agentId,
230
236
  text: textSafe,
231
237
  importance: entry.importance,
@@ -233,35 +239,7 @@ export class MemoryDB {
233
239
  createdAt,
234
240
  },
235
241
  };
236
- }
237
-
238
- const id = randomUUID();
239
- await this.pool!.query(
240
- `INSERT INTO \`${this.tableName}\` (id, agent_id, user_id, session_id, text, embedding, importance, category, created_at, is_deleted)
241
- VALUES (?, ?, ?, ?, ?, VEC_FROMTEXT(?), ?, ?, ?, 0)`,
242
- [id, agentId, userId, sessionId, textSafe, vectorStr, entry.importance, entry.category, createdAt],
243
- );
244
-
245
- if (!this.vectorIndexCreated) {
246
- const conn = await this.pool!.getConnection();
247
- try {
248
- await this.tryCreateVectorIndex(conn);
249
- } finally {
250
- conn.release();
251
- }
252
- }
253
-
254
- return {
255
- action: "created",
256
- entry: {
257
- id,
258
- agentId,
259
- text: textSafe,
260
- importance: entry.importance,
261
- category: entry.category,
262
- createdAt,
263
- },
264
- };
242
+ });
265
243
  }
266
244
 
267
245
  /**
@@ -275,51 +253,50 @@ export class MemoryDB {
275
253
  minScore = 0.5,
276
254
  categories?: MemoryCategory[],
277
255
  ): Promise<MemorySearchResult[]> {
278
- await this.ensureInitialized();
279
-
280
- const vectorStr = JSON.stringify(vector);
281
- const hasCategoryFilter = Array.isArray(categories) && categories.length > 0;
282
- const placeholders = hasCategoryFilter ? categories!.map(() => "?").join(", ") : "";
283
- const whereClause = hasCategoryFilter
284
- ? `WHERE agent_id = ? AND (is_deleted = 0 OR is_deleted IS NULL) AND category IN (${placeholders})`
285
- : `WHERE agent_id = ? AND (is_deleted = 0 OR is_deleted IS NULL)`;
286
- const args: unknown[] = hasCategoryFilter
287
- ? [vectorStr, agentId, ...categories!, limit]
288
- : [vectorStr, agentId, limit];
256
+ return this.withConnection(async (conn) => {
257
+ const vectorStr = JSON.stringify(vector);
258
+ const hasCategoryFilter = Array.isArray(categories) && categories.length > 0;
259
+ const placeholders = hasCategoryFilter ? categories!.map(() => "?").join(", ") : "";
260
+ const whereClause = hasCategoryFilter
261
+ ? `WHERE agent_id = ? AND (is_deleted = 0 OR is_deleted IS NULL) AND category IN (${placeholders})`
262
+ : `WHERE agent_id = ? AND (is_deleted = 0 OR is_deleted IS NULL)`;
263
+ const args: unknown[] = hasCategoryFilter
264
+ ? [vectorStr, agentId, ...categories!, limit]
265
+ : [vectorStr, agentId, limit];
289
266
 
290
- const [rows] = await this.pool!.query(
291
- `SELECT id, text, importance, category, created_at, is_deleted,
292
- VEC_DISTANCE_COSINE(embedding, VEC_FROMTEXT(?)) AS distance
293
- FROM \`${this.tableName}\`
294
- ${whereClause}
295
- ORDER BY distance ASC
296
- LIMIT ?`,
297
- args,
298
- );
267
+ const [rows] = await conn.query(
268
+ `SELECT id, text, importance, category, created_at, is_deleted,
269
+ VEC_DISTANCE_COSINE(embedding, VEC_FROMTEXT(?)) AS distance
270
+ FROM \`${this.tableName}\`
271
+ ${whereClause}
272
+ ORDER BY distance ASC
273
+ LIMIT ?`,
274
+ args,
275
+ );
299
276
 
300
- const results: MemorySearchResult[] = [];
301
- for (const row of rows as Array<Record<string, unknown>>) {
302
- const distance = Number(row.distance) || 0;
303
- const score = 1 - distance;
304
- if (score < minScore) {
305
- continue;
277
+ const results: MemorySearchResult[] = [];
278
+ for (const row of rows as Array<Record<string, unknown>>) {
279
+ const distance = Number(row.distance) || 0;
280
+ const score = 1 - distance;
281
+ if (score < minScore) {
282
+ continue;
283
+ }
284
+ const isDeleted = row.is_deleted;
285
+ results.push({
286
+ entry: {
287
+ id: row.id as string,
288
+ agentId,
289
+ text: row.text as string,
290
+ importance: Number(row.importance) || 0,
291
+ category: (row.category as MemoryCategory) || USER_MEMORY_FACT,
292
+ createdAt: Number(row.created_at) || 0,
293
+ isDeleted: isDeleted !== undefined && isDeleted !== null ? Number(isDeleted) : undefined,
294
+ },
295
+ score,
296
+ });
306
297
  }
307
- const isDeleted = row.is_deleted;
308
- results.push({
309
- entry: {
310
- id: row.id as string,
311
- agentId,
312
- text: row.text as string,
313
- importance: Number(row.importance) || 0,
314
- category: (row.category as MemoryCategory) || USER_MEMORY_FACT,
315
- createdAt: Number(row.created_at) || 0,
316
- isDeleted: isDeleted !== undefined && isDeleted !== null ? Number(isDeleted) : undefined,
317
- },
318
- score,
319
- });
320
- }
321
-
322
- return results;
298
+ return results;
299
+ });
323
300
  }
324
301
 
325
302
  /** Soft-delete: set is_deleted = 1. Returns true if a row was updated. */
@@ -327,32 +304,29 @@ export class MemoryDB {
327
304
  if (!UUID_RE.test(id)) {
328
305
  throw new Error(`Invalid memory ID format: ${id}`);
329
306
  }
330
- await this.ensureInitialized();
331
- const [result] = await this.pool!.query(
332
- `UPDATE \`${this.tableName}\` SET is_deleted = 1 WHERE id = ? AND agent_id = ?`,
333
- [id, agentId],
334
- );
335
- return ((result as mysql.ResultSetHeader).affectedRows ?? 0) > 0;
307
+ return this.withConnection(async (conn) => {
308
+ const [result] = await conn.query(
309
+ `UPDATE \`${this.tableName}\` SET is_deleted = 1 WHERE id = ? AND agent_id = ?`,
310
+ [id, agentId],
311
+ );
312
+ return ((result as mysql.ResultSetHeader).affectedRows ?? 0) > 0;
313
+ });
336
314
  }
337
315
 
338
316
  async delete(agentId: string, id: string): Promise<boolean> {
339
317
  if (!UUID_RE.test(id)) {
340
318
  throw new Error(`Invalid memory ID format: ${id}`);
341
319
  }
342
- await this.ensureInitialized();
343
-
344
- const [result] = await this.pool!.query(
345
- `DELETE FROM \`${this.tableName}\` WHERE id = ? AND agent_id = ?`,
346
- [id, agentId],
347
- );
348
- return ((result as mysql.ResultSetHeader).affectedRows ?? 0) > 0;
320
+ return this.withConnection(async (conn) => {
321
+ const [result] = await conn.query(
322
+ `DELETE FROM \`${this.tableName}\` WHERE id = ? AND agent_id = ?`,
323
+ [id, agentId],
324
+ );
325
+ return ((result as mysql.ResultSetHeader).affectedRows ?? 0) > 0;
326
+ });
349
327
  }
350
328
 
351
329
  async close(): Promise<void> {
352
- if (this.pool) {
353
- this.pool.end();
354
- this.pool = null;
355
- this.initPromise = null;
356
- }
330
+ this.vectorIndexCreated = false;
357
331
  }
358
332
  }
package/index.ts CHANGED
@@ -745,15 +745,24 @@ const memoryPlugin = {
745
745
 
746
746
  register(api: OpenClawPluginApi) {
747
747
  const cfg = memoryConfigSchema.parse(api.pluginConfig);
748
- const { model, dimensions, apiKey, baseUrl } = cfg.embedding;
749
-
750
- const vectorDim = vectorDimsForModel(model, dimensions);
751
- const db = new MemoryDB(cfg.mysql, cfg.tableName, vectorDim);
752
- const embeddings = new Embeddings(apiKey, model, baseUrl, vectorDim);
748
+ let db: MemoryDB | null = null;
749
+ let embeddings: Embeddings | null = null;
750
+ if (cfg.mysql && cfg.embedding) {
751
+ const { model, dimensions, apiKey, baseUrl } = cfg.embedding;
752
+ const vectorDim = vectorDimsForModel(model, dimensions);
753
+ db = new MemoryDB(cfg.mysql, cfg.tableName, vectorDim);
754
+ embeddings = new Embeddings(apiKey, model, baseUrl, vectorDim);
755
+ api.logger.info(
756
+ `openclaw-memory-alibaba-mysql: registered (host: ${cfg.mysql.host}, table: ${cfg.tableName})`,
757
+ );
758
+ } else {
759
+ api.logger.info(
760
+ "openclaw-memory-alibaba-mysql: registered without mysql/embedding config (memory ops no-op until configured)",
761
+ );
762
+ }
753
763
 
754
- api.logger.info(
755
- `openclaw-memory-alibaba-mysql: registered (host: ${cfg.mysql.host}, table: ${cfg.tableName})`,
756
- );
764
+ const getDbAndEmbeddings = (): { db: MemoryDB; embeddings: Embeddings } | null =>
765
+ db && embeddings ? { db, embeddings } : null;
757
766
 
758
767
  // --- Tools: memory_recall, memory_store, memory_forget ---
759
768
 
@@ -768,6 +777,14 @@ const memoryPlugin = {
768
777
  limit: Type.Optional(Type.Number({ description: "Max results (default: 5)" })),
769
778
  }),
770
779
  async execute(_toolCallId, params) {
780
+ const out = getDbAndEmbeddings();
781
+ if (!out) {
782
+ return {
783
+ content: [{ type: "text", text: "Memory plugin: configure mysql and embedding in plugin config to use memory." }],
784
+ details: { error: "not_configured" },
785
+ };
786
+ }
787
+ const { db, embeddings } = out;
771
788
  const { query, limit = RECALL_LIMIT_USER_DEFAULT } = params as { query: string; limit?: number };
772
789
  const agentId = ctx.agentId ?? "default";
773
790
  const vector = await embeddings.embed(query);
@@ -844,6 +861,14 @@ const memoryPlugin = {
844
861
  ),
845
862
  }),
846
863
  async execute(_toolCallId, params) {
864
+ const out = getDbAndEmbeddings();
865
+ if (!out) {
866
+ return {
867
+ content: [{ type: "text", text: "Memory plugin: configure mysql and embedding in plugin config to use memory." }],
868
+ details: { error: "not_configured" },
869
+ };
870
+ }
871
+ const { db, embeddings } = out;
847
872
  const {
848
873
  text,
849
874
  importance = DEFAULT_IMPORTANCE,
@@ -893,6 +918,14 @@ const memoryPlugin = {
893
918
  memoryId: Type.Optional(Type.String({ description: "Specific memory ID" })),
894
919
  }),
895
920
  async execute(_toolCallId, params) {
921
+ const out = getDbAndEmbeddings();
922
+ if (!out) {
923
+ return {
924
+ content: [{ type: "text", text: "Memory plugin: configure mysql and embedding in plugin config to use memory." }],
925
+ details: { error: "not_configured" },
926
+ };
927
+ }
928
+ const { db, embeddings } = out;
896
929
  const { query, memoryId } = params as { query?: string; memoryId?: string };
897
930
  const agentId = ctx.agentId ?? "default";
898
931
 
@@ -961,6 +994,7 @@ const memoryPlugin = {
961
994
 
962
995
  if (cfg.autoRecall) {
963
996
  api.on("before_agent_start", async (event, ctx) => {
997
+ if (!db || !embeddings) return;
964
998
  if (!event.prompt || event.prompt.length < 5) return;
965
999
 
966
1000
  try {
@@ -993,6 +1027,7 @@ const memoryPlugin = {
993
1027
 
994
1028
  if (cfg.autoCapture) {
995
1029
  api.on("agent_end", async (event, ctx) => {
1030
+ if (!db || !embeddings) return;
996
1031
  if (!event.success || !event.messages || event.messages.length === 0) return;
997
1032
 
998
1033
  try {
@@ -1042,7 +1077,7 @@ const memoryPlugin = {
1042
1077
  );
1043
1078
  },
1044
1079
  stop: async () => {
1045
- await db.close();
1080
+ if (db) await db.close();
1046
1081
  api.logger.info("openclaw-memory-alibaba-mysql: stopped");
1047
1082
  },
1048
1083
  });
@@ -66,7 +66,7 @@
66
66
  },
67
67
  "autoRecall": { "type": "boolean", "default": true },
68
68
  "autoCapture": { "type": "boolean", "default": true },
69
- "captureMaxChars": { "type": "number", "default": 500 },
69
+ "captureMaxChars": { "type": "number", "default": 50000 },
70
70
  "enableMemoryDecay": {
71
71
  "type": "boolean",
72
72
  "default": false,
@@ -176,7 +176,7 @@
176
176
  "captureMaxChars": {
177
177
  "label": "Capture Max Chars",
178
178
  "placeholder": "500",
179
- "help": "Max length per captured memory (100–10000)"
179
+ "help": "Max length per captured memory (100–100000)"
180
180
  },
181
181
  "enableMemoryDecay": {
182
182
  "label": "Enable Memory Decay",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-memory-alibaba-mysql",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "OpenClaw memory plugin using Alibaba Cloud RDS MySQL vector storage",
5
5
  "type": "module",
6
6
  "license": "MIT",