@t0ken.ai/memoryx-openclaw-plugin 1.1.9 → 1.1.11

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/dist/index.d.ts CHANGED
@@ -1,14 +1,3 @@
1
- /**
2
- * MemoryX Realtime Plugin for OpenClaw
3
- *
4
- * Features:
5
- * - ConversationBuffer with token counting
6
- * - Batch upload to /conversations/flush
7
- * - Auto-register and quota handling
8
- * - Sensitive data filtered on server
9
- * - Configurable API base URL
10
- * - Precise token counting with tiktoken
11
- */
12
1
  interface PluginConfig {
13
2
  apiBaseUrl?: string;
14
3
  }
@@ -40,6 +29,7 @@ declare class ConversationBuffer {
40
29
  private readonly TIMEOUT_MS;
41
30
  constructor();
42
31
  private generateId;
32
+ getConversationId(): string;
43
33
  addMessage(role: string, content: string): boolean;
44
34
  shouldFlush(): boolean;
45
35
  flush(): {
@@ -59,15 +49,22 @@ declare class MemoryXPlugin {
59
49
  private config;
60
50
  private buffer;
61
51
  private flushTimer;
52
+ private pendingRetryTimer;
62
53
  private readonly FLUSH_CHECK_INTERVAL;
54
+ private readonly PENDING_RETRY_INTERVAL;
55
+ private readonly MAX_RETRY_COUNT;
63
56
  private pluginConfig;
57
+ private initialized;
64
58
  constructor(pluginConfig?: PluginConfig);
59
+ init(): void;
65
60
  private get apiBase();
66
61
  private loadConfig;
67
62
  private saveConfig;
68
63
  private autoRegister;
69
64
  private getMachineFingerprint;
70
65
  private startFlushTimer;
66
+ private startPendingRetryTimer;
67
+ private retryPendingMessages;
71
68
  private flushConversation;
72
69
  onMessage(role: string, content: string): Promise<boolean>;
73
70
  recall(query: string, limit?: number): Promise<RecallResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA+CH,UAAU,YAAY;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAUD,UAAU,OAAO;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,YAAY;IAClB,QAAQ,EAAE,KAAK,CAAC;QACZ,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AA0BD,cAAM,kBAAkB;IACpB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,cAAc,CAAsB;IAE5C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAK;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkB;;IAM7C,OAAO,CAAC,UAAU;IAIlB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAuBlD,WAAW,IAAI,OAAO;IAiBtB,KAAK,IAAI;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE;IAgBzD,UAAU,IAAI;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE,GAAG,IAAI;IAOrE,SAAS,IAAI;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE;CAMhE;AAED,cAAM,aAAa;IACf,OAAO,CAAC,MAAM,CAMZ;IAEF,OAAO,CAAC,MAAM,CAAgD;IAC9D,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,YAAY,CAA6B;gBAErC,YAAY,CAAC,EAAE,YAAY;IA0BvC,OAAO,KAAK,OAAO,GAElB;IAED,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,UAAU;YAIJ,YAAY;IAgC1B,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,eAAe;YAQT,iBAAiB;IAqClB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAyB1D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,OAAO,CAAC,YAAY,CAAC;IAoD/D,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtC,SAAS,IAAI;QAChB,WAAW,EAAE,OAAO,CAAC;QACrB,SAAS,EAAE,OAAO,CAAC;QACnB,YAAY,EAAE;YAAE,YAAY,EAAE,MAAM,CAAC;YAAC,cAAc,EAAE,MAAM,CAAA;SAAE,CAAA;KACjE;CAOJ;;;;;;kBAUiB,GAAG,iBAAiB,YAAY,GAAG,IAAI;;AANzD,wBA0EE;AAEF,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkCA,UAAU,YAAY;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAUD,UAAU,OAAO;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,YAAY;IAClB,QAAQ,EAAE,KAAK,CAAC;QACZ,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAyJD,cAAM,kBAAkB;IACpB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,cAAc,CAAsB;IAE5C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAK;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkB;;IAM7C,OAAO,CAAC,UAAU;IAIlB,iBAAiB,IAAI,MAAM;IAI3B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAuBlD,WAAW,IAAI,OAAO;IAiBtB,KAAK,IAAI;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE;IAgBzD,UAAU,IAAI;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE,GAAG,IAAI;IAOrE,SAAS,IAAI;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE;CAMhE;AAED,cAAM,aAAa;IACf,OAAO,CAAC,MAAM,CAMZ;IAEF,OAAO,CAAC,MAAM,CAAgD;IAC9D,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAS;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAK;IACrC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAkB;gBAEzB,YAAY,CAAC,EAAE,YAAY;IAQvC,IAAI,IAAI,IAAI;IAkBZ,OAAO,KAAK,OAAO,GAElB;YAEa,UAAU;YAWV,UAAU;YAIV,YAAY;IAgC1B,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,sBAAsB;YAMhB,oBAAoB;YAmDpB,iBAAiB;IAsDlB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA6B1D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,OAAO,CAAC,YAAY,CAAC;IAsD/D,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtC,SAAS,IAAI;QAChB,WAAW,EAAE,OAAO,CAAC;QACrB,SAAS,EAAE,OAAO,CAAC;QACnB,YAAY,EAAE;YAAE,YAAY,EAAE,MAAM,CAAC;YAAC,cAAc,EAAE,MAAM,CAAA;SAAE,CAAA;KACjE;CAOJ;;;;;;kBAUiB,GAAG,iBAAiB,YAAY,GAAG,IAAI;;AANzD,wBAmEE;AAEF,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -1,79 +1,158 @@
1
- /**
2
- * MemoryX Realtime Plugin for OpenClaw
3
- *
4
- * Features:
5
- * - ConversationBuffer with token counting
6
- * - Batch upload to /conversations/flush
7
- * - Auto-register and quota handling
8
- * - Sensitive data filtered on server
9
- * - Configurable API base URL
10
- * - Precise token counting with tiktoken
11
- */
12
1
  import * as fs from "fs";
13
2
  import * as path from "path";
14
3
  import * as os from "os";
15
4
  import * as crypto from "crypto";
16
5
  const DEFAULT_API_BASE = "https://t0ken.ai/api";
17
- let logPath = "";
6
+ const PLUGIN_DIR = path.join(os.homedir(), ".openclaw", "extensions", "memoryx-openclaw-plugin");
18
7
  let logStream = null;
19
- let configPath = "";
20
- function getLogPath() {
21
- if (logPath)
22
- return logPath;
23
- logPath = path.join(os.homedir(), ".openclaw", "extensions", "memoryx-openclaw-plugin", "plugin.log");
24
- const dir = path.dirname(logPath);
25
- if (!fs.existsSync(dir)) {
26
- fs.mkdirSync(dir, { recursive: true });
27
- }
28
- return logPath;
29
- }
30
- function getConfigPath() {
31
- if (configPath)
32
- return configPath;
33
- configPath = path.join(os.homedir(), ".openclaw", "extensions", "memoryx-openclaw-plugin", "config.json");
34
- const dir = path.dirname(configPath);
35
- if (!fs.existsSync(dir)) {
36
- fs.mkdirSync(dir, { recursive: true });
37
- }
38
- return configPath;
8
+ let logStreamReady = false;
9
+ function ensureDir() {
10
+ if (!fs.existsSync(PLUGIN_DIR)) {
11
+ fs.mkdirSync(PLUGIN_DIR, { recursive: true });
12
+ }
39
13
  }
40
14
  function log(message) {
41
- const timestamp = new Date().toISOString();
42
- const line = `[${timestamp}] ${message}\n`;
43
- try {
44
- if (!logStream) {
45
- logStream = fs.createWriteStream(getLogPath(), { flags: "a" });
46
- }
47
- logStream.write(line);
48
- }
49
- catch (e) {
50
- console.error("[MemoryX] Log write failed:", e);
51
- }
52
15
  console.log(`[MemoryX] ${message}`);
16
+ setImmediate(() => {
17
+ try {
18
+ if (!logStreamReady) {
19
+ ensureDir();
20
+ logStream = fs.createWriteStream(path.join(PLUGIN_DIR, "plugin.log"), { flags: "a" });
21
+ logStreamReady = true;
22
+ }
23
+ logStream?.write(`[${new Date().toISOString()}] ${message}\n`);
24
+ }
25
+ catch (e) {
26
+ // ignore
27
+ }
28
+ });
29
+ }
30
+ let dbPromise = null;
31
+ async function getDb() {
32
+ if (dbPromise)
33
+ return dbPromise;
34
+ dbPromise = (async () => {
35
+ ensureDir();
36
+ const Database = (await import("better-sqlite3")).default;
37
+ const dbPath = path.join(PLUGIN_DIR, "memoryx.db");
38
+ const db = new Database(dbPath);
39
+ db.exec(`
40
+ CREATE TABLE IF NOT EXISTS config (
41
+ key TEXT PRIMARY KEY,
42
+ value TEXT
43
+ );
44
+
45
+ CREATE TABLE IF NOT EXISTS pending_messages (
46
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
47
+ conversation_id TEXT NOT NULL,
48
+ role TEXT NOT NULL,
49
+ content TEXT NOT NULL,
50
+ timestamp INTEGER NOT NULL,
51
+ retry_count INTEGER DEFAULT 0,
52
+ created_at INTEGER DEFAULT (strftime('%s', 'now'))
53
+ );
54
+
55
+ CREATE INDEX IF NOT EXISTS idx_pending_conversation ON pending_messages(conversation_id);
56
+ CREATE INDEX IF NOT EXISTS idx_pending_created ON pending_messages(created_at);
57
+
58
+ CREATE TABLE IF NOT EXISTS temp_messages (
59
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
60
+ conversation_id TEXT NOT NULL,
61
+ role TEXT NOT NULL,
62
+ content TEXT NOT NULL,
63
+ timestamp INTEGER NOT NULL,
64
+ created_at INTEGER DEFAULT (strftime('%s', 'now'))
65
+ );
66
+
67
+ CREATE INDEX IF NOT EXISTS idx_temp_conversation ON temp_messages(conversation_id);
68
+ `);
69
+ return db;
70
+ })();
71
+ return dbPromise;
53
72
  }
54
- class JsonStorage {
55
- static load() {
73
+ class SQLiteStorage {
74
+ static async loadConfig() {
56
75
  try {
57
- const filePath = getConfigPath();
58
- if (fs.existsSync(filePath)) {
59
- const data = fs.readFileSync(filePath, "utf-8");
60
- return JSON.parse(data);
76
+ const db = await getDb();
77
+ const row = db.prepare("SELECT value FROM config WHERE key = 'config'").get();
78
+ if (row) {
79
+ return JSON.parse(row.value);
61
80
  }
62
81
  }
63
82
  catch (e) {
64
- console.warn("[MemoryX] Failed to load config:", e);
83
+ log(`Failed to load config: ${e}`);
65
84
  }
66
85
  return null;
67
86
  }
68
- static save(config) {
87
+ static async saveConfig(config) {
69
88
  try {
70
- const filePath = getConfigPath();
71
- fs.writeFileSync(filePath, JSON.stringify(config, null, 2), "utf-8");
89
+ const db = await getDb();
90
+ db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES ('config', ?)").run(JSON.stringify(config));
72
91
  }
73
92
  catch (e) {
74
- console.warn("[MemoryX] Failed to save config:", e);
93
+ log(`Failed to save config: ${e}`);
75
94
  }
76
95
  }
96
+ static async addPendingMessage(conversationId, role, content) {
97
+ const db = await getDb();
98
+ const result = db.prepare(`
99
+ INSERT INTO pending_messages (conversation_id, role, content, timestamp)
100
+ VALUES (?, ?, ?, ?)
101
+ `).run(conversationId, role, content, Date.now());
102
+ return result.lastInsertRowid;
103
+ }
104
+ static async getPendingMessages(limit = 100) {
105
+ const db = await getDb();
106
+ return db.prepare(`
107
+ SELECT * FROM pending_messages
108
+ ORDER BY created_at ASC
109
+ LIMIT ?
110
+ `).all(limit);
111
+ }
112
+ static async deletePendingMessage(id) {
113
+ const db = await getDb();
114
+ db.prepare("DELETE FROM pending_messages WHERE id = ?").run(id);
115
+ }
116
+ static async incrementRetryCount(id) {
117
+ const db = await getDb();
118
+ db.prepare("UPDATE pending_messages SET retry_count = retry_count + 1 WHERE id = ?").run(id);
119
+ }
120
+ static async clearOldPendingMessages(maxAge = 7 * 24 * 60 * 60) {
121
+ const db = await getDb();
122
+ const cutoff = Math.floor(Date.now() / 1000) - maxAge;
123
+ db.prepare("DELETE FROM pending_messages WHERE created_at < ?").run(cutoff);
124
+ }
125
+ static async addTempMessage(conversationId, role, content) {
126
+ const db = await getDb();
127
+ const result = db.prepare(`
128
+ INSERT INTO temp_messages (conversation_id, role, content, timestamp)
129
+ VALUES (?, ?, ?, ?)
130
+ `).run(conversationId, role, content, Date.now());
131
+ return result.lastInsertRowid;
132
+ }
133
+ static async getTempMessages(conversationId) {
134
+ const db = await getDb();
135
+ const rows = db.prepare(`
136
+ SELECT role, content, timestamp FROM temp_messages
137
+ WHERE conversation_id = ?
138
+ ORDER BY timestamp ASC
139
+ `).all(conversationId);
140
+ return rows.map((r) => ({
141
+ role: r.role,
142
+ content: r.content,
143
+ tokens: 0,
144
+ timestamp: r.timestamp
145
+ }));
146
+ }
147
+ static async clearTempMessages(conversationId) {
148
+ const db = await getDb();
149
+ db.prepare("DELETE FROM temp_messages WHERE conversation_id = ?").run(conversationId);
150
+ }
151
+ static async clearOldTempMessages(maxAge = 24 * 60 * 60) {
152
+ const db = await getDb();
153
+ const cutoff = Math.floor(Date.now() / 1000) - maxAge;
154
+ db.prepare("DELETE FROM temp_messages WHERE created_at < ?").run(cutoff);
155
+ }
77
156
  }
78
157
  class ConversationBuffer {
79
158
  messages = [];
@@ -90,6 +169,9 @@ class ConversationBuffer {
90
169
  generateId() {
91
170
  return `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
92
171
  }
172
+ getConversationId() {
173
+ return this.conversationId;
174
+ }
93
175
  addMessage(role, content) {
94
176
  if (!content || content.length < 2) {
95
177
  return false;
@@ -157,39 +239,40 @@ class MemoryXPlugin {
157
239
  };
158
240
  buffer = new ConversationBuffer();
159
241
  flushTimer = null;
242
+ pendingRetryTimer = null;
160
243
  FLUSH_CHECK_INTERVAL = 30000;
244
+ PENDING_RETRY_INTERVAL = 60000;
245
+ MAX_RETRY_COUNT = 5;
161
246
  pluginConfig = null;
247
+ initialized = false;
162
248
  constructor(pluginConfig) {
163
- log("Constructor started");
164
249
  this.pluginConfig = pluginConfig || null;
165
250
  if (pluginConfig?.apiBaseUrl) {
166
251
  this.config.apiBaseUrl = pluginConfig.apiBaseUrl;
167
- log(`API Base URL set to: ${pluginConfig.apiBaseUrl}`);
168
252
  }
169
253
  this.config.initialized = true;
170
- log("Constructor finished, scheduling async init");
171
- setImmediate(() => {
172
- log("Async init started");
173
- try {
174
- this.loadConfig();
175
- log(`Config loaded, apiKey: ${this.config.apiKey ? 'present' : 'missing'}`);
176
- this.startFlushTimer();
177
- log("Flush timer started");
178
- if (!this.config.apiKey) {
179
- log("Starting auto-register");
180
- this.autoRegister().catch(e => log(`Auto-register failed: ${e}`));
181
- }
182
- }
183
- catch (e) {
184
- log(`Async init error: ${e}`);
254
+ }
255
+ init() {
256
+ if (this.initialized)
257
+ return;
258
+ this.initialized = true;
259
+ log("Async init started");
260
+ this.loadConfig().then(() => {
261
+ log(`Config loaded, apiKey: ${this.config.apiKey ? 'present' : 'missing'}`);
262
+ this.startFlushTimer();
263
+ this.startPendingRetryTimer();
264
+ this.retryPendingMessages();
265
+ if (!this.config.apiKey) {
266
+ log("Starting auto-register");
267
+ this.autoRegister().catch(e => log(`Auto-register failed: ${e}`));
185
268
  }
186
- });
269
+ }).catch(e => log(`Init failed: ${e}`));
187
270
  }
188
271
  get apiBase() {
189
272
  return this.config.apiBaseUrl || DEFAULT_API_BASE;
190
273
  }
191
- loadConfig() {
192
- const stored = JsonStorage.load();
274
+ async loadConfig() {
275
+ const stored = await SQLiteStorage.loadConfig();
193
276
  if (stored) {
194
277
  this.config = {
195
278
  ...this.config,
@@ -198,8 +281,8 @@ class MemoryXPlugin {
198
281
  };
199
282
  }
200
283
  }
201
- saveConfig() {
202
- JsonStorage.save(this.config);
284
+ async saveConfig() {
285
+ await SQLiteStorage.saveConfig(this.config);
203
286
  }
204
287
  async autoRegister() {
205
288
  try {
@@ -222,11 +305,11 @@ class MemoryXPlugin {
222
305
  this.config.apiKey = data.api_key;
223
306
  this.config.projectId = String(data.project_id);
224
307
  this.config.userId = data.agent_id;
225
- this.saveConfig();
226
- console.log("[MemoryX] Auto-registered successfully");
308
+ await this.saveConfig();
309
+ log("Auto-registered successfully");
227
310
  }
228
311
  catch (e) {
229
- console.error("[MemoryX] Auto-register failed:", e);
312
+ log(`Auto-register failed: ${e}`);
230
313
  }
231
314
  }
232
315
  getMachineFingerprint() {
@@ -247,8 +330,70 @@ class MemoryXPlugin {
247
330
  }
248
331
  }, this.FLUSH_CHECK_INTERVAL);
249
332
  }
333
+ startPendingRetryTimer() {
334
+ this.pendingRetryTimer = setInterval(() => {
335
+ this.retryPendingMessages();
336
+ }, this.PENDING_RETRY_INTERVAL);
337
+ }
338
+ async retryPendingMessages() {
339
+ if (!this.config.apiKey)
340
+ return;
341
+ try {
342
+ const pending = await SQLiteStorage.getPendingMessages(50);
343
+ if (pending.length === 0)
344
+ return;
345
+ log(`Retrying ${pending.length} pending messages`);
346
+ for (const msg of pending) {
347
+ if (msg.retry_count >= this.MAX_RETRY_COUNT) {
348
+ log(`Deleting message ${msg.id}: max retries exceeded`);
349
+ await SQLiteStorage.deletePendingMessage(msg.id);
350
+ continue;
351
+ }
352
+ try {
353
+ const response = await fetch(`${this.apiBase}/v1/conversations/flush`, {
354
+ method: "POST",
355
+ headers: {
356
+ "Content-Type": "application/json",
357
+ "X-API-Key": this.config.apiKey
358
+ },
359
+ body: JSON.stringify({
360
+ conversation_id: msg.conversation_id,
361
+ messages: [{
362
+ role: msg.role,
363
+ content: msg.content,
364
+ timestamp: msg.timestamp,
365
+ tokens: 0
366
+ }]
367
+ })
368
+ });
369
+ if (response.ok) {
370
+ await SQLiteStorage.deletePendingMessage(msg.id);
371
+ log(`Sent pending message ${msg.id}`);
372
+ }
373
+ else {
374
+ await SQLiteStorage.incrementRetryCount(msg.id);
375
+ log(`Failed to send pending message ${msg.id}: ${response.status}`);
376
+ }
377
+ }
378
+ catch (e) {
379
+ await SQLiteStorage.incrementRetryCount(msg.id);
380
+ log(`Error sending pending message ${msg.id}: ${e}`);
381
+ }
382
+ }
383
+ }
384
+ catch (e) {
385
+ log(`Retry pending messages failed: ${e}`);
386
+ }
387
+ }
250
388
  async flushConversation() {
251
389
  if (!this.config.apiKey) {
390
+ const data = this.buffer.forceFlush();
391
+ if (data) {
392
+ for (const msg of data.messages) {
393
+ await SQLiteStorage.addPendingMessage(data.conversation_id, msg.role, msg.content);
394
+ }
395
+ log(`Cached ${data.messages.length} messages (no API key)`);
396
+ }
252
397
  return;
253
398
  }
254
399
  const data = this.buffer.forceFlush();
@@ -267,22 +412,31 @@ class MemoryXPlugin {
267
412
  if (!response.ok) {
268
413
  const errorData = await response.json().catch(() => ({}));
269
414
  if (response.status === 402) {
270
- console.warn("[MemoryX] Quota exceeded:", errorData.detail);
415
+ log(`Quota exceeded: ${errorData.detail}`);
271
416
  }
272
417
  else {
273
- console.error("[MemoryX] Flush failed:", errorData);
418
+ log(`Flush failed: ${JSON.stringify(errorData)}`);
419
+ }
420
+ for (const msg of data.messages) {
421
+ await SQLiteStorage.addPendingMessage(data.conversation_id, msg.role, msg.content);
274
422
  }
423
+ log(`Cached ${data.messages.length} messages (API error)`);
275
424
  }
276
425
  else {
277
426
  const result = await response.json();
278
- console.log(`[MemoryX] Flushed ${data.messages.length} messages, extracted ${result.extracted_count} memories`);
427
+ log(`Flushed ${data.messages.length} messages, extracted ${result.extracted_count} memories`);
279
428
  }
280
429
  }
281
430
  catch (e) {
282
- console.error("[MemoryX] Flush error:", e);
431
+ log(`Flush error: ${e}`);
432
+ for (const msg of data.messages) {
433
+ await SQLiteStorage.addPendingMessage(data.conversation_id, msg.role, msg.content);
434
+ }
435
+ log(`Cached ${data.messages.length} messages (network error)`);
283
436
  }
284
437
  }
285
438
  async onMessage(role, content) {
439
+ this.init();
286
440
  if (!content || content.length < 2) {
287
441
  return false;
288
442
  }
@@ -295,6 +449,7 @@ class MemoryXPlugin {
295
449
  return false;
296
450
  }
297
451
  }
452
+ await SQLiteStorage.addTempMessage(this.buffer.getConversationId(), role, content);
298
453
  const shouldFlush = this.buffer.addMessage(role, content);
299
454
  if (shouldFlush) {
300
455
  await this.flushConversation();
@@ -302,6 +457,7 @@ class MemoryXPlugin {
302
457
  return true;
303
458
  }
304
459
  async recall(query, limit = 5) {
460
+ this.init();
305
461
  if (!this.config.apiKey || !query || query.length < 2) {
306
462
  return { memories: [], isLimited: false, remainingQuota: 0 };
307
463
  }
@@ -343,13 +499,14 @@ class MemoryXPlugin {
343
499
  };
344
500
  }
345
501
  catch (e) {
346
- console.error("[MemoryX] Recall failed:", e);
502
+ log(`Recall failed: ${e}`);
347
503
  return { memories: [], isLimited: false, remainingQuota: 0 };
348
504
  }
349
505
  }
350
506
  async endConversation() {
351
507
  await this.flushConversation();
352
- console.log("[MemoryX] Conversation ended, buffer flushed");
508
+ await SQLiteStorage.clearTempMessages(this.buffer.getConversationId());
509
+ log("Conversation ended, buffer flushed");
353
510
  }
354
511
  getStatus() {
355
512
  return {
@@ -362,23 +519,17 @@ class MemoryXPlugin {
362
519
  let plugin;
363
520
  export default {
364
521
  id: "memoryx-openclaw-plugin",
365
- name: "MemoryX Real-time Plugin",
366
- version: "1.1.4",
522
+ name: "MemoryX Realtime Plugin",
523
+ version: "1.2.0",
367
524
  description: "Real-time memory capture and recall for OpenClaw",
368
525
  register(api, pluginConfig) {
369
- log("=== REGISTER CALLED ===");
370
526
  api.logger.info("[MemoryX] Plugin registering...");
371
527
  if (pluginConfig?.apiBaseUrl) {
372
528
  api.logger.info(`[MemoryX] API Base: \`${pluginConfig.apiBaseUrl}\``);
373
529
  }
374
- api.logger.info(`[MemoryX] Config: ${getConfigPath()}`);
375
- log(`Config path: ${getConfigPath()}`);
376
- log(`Log file: ${getLogPath()}`);
377
- log("Creating plugin instance");
378
530
  plugin = new MemoryXPlugin(pluginConfig);
379
- log("Plugin instance created");
380
531
  api.on("message_received", async (event, ctx) => {
381
- const { content, from, timestamp } = event;
532
+ const { content } = event;
382
533
  if (content && plugin) {
383
534
  await plugin.onMessage("user", content);
384
535
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t0ken.ai/memoryx-openclaw-plugin",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "description": "MemoryX real-time memory capture and recall plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "author": "MemoryX Team",
@@ -31,8 +31,11 @@
31
31
  ]
32
32
  },
33
33
  "devDependencies": {
34
+ "@types/better-sqlite3": "^7.6.0",
34
35
  "@types/node": "^20.0.0",
35
36
  "typescript": "^5.0.0"
36
37
  },
37
- "dependencies": {}
38
+ "dependencies": {
39
+ "better-sqlite3": "^11.0.0"
40
+ }
38
41
  }