openclaw-memory-alibaba-mysql 0.2.4 → 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 +35 -2
- package/db.ts +155 -181
- package/index.ts +44 -9
- package/package.json +1 -1
package/config.ts
CHANGED
|
@@ -24,8 +24,10 @@ export type LLMConfig = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export type MemoryConfig = {
|
|
27
|
-
|
|
28
|
-
|
|
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. */
|
|
@@ -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 =
|
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 {
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
70
|
-
|
|
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 LONGTEXT 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.
|
|
55
|
+
await conn.end();
|
|
90
56
|
}
|
|
91
57
|
}
|
|
92
58
|
|
|
93
|
-
private async
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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: "
|
|
232
|
+
action: "created",
|
|
227
233
|
entry: {
|
|
228
|
-
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
:
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
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
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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
|
-
|
|
755
|
-
|
|
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
|
});
|