@t0ken.ai/memoryx-openclaw-plugin 1.1.18 → 2.0.0

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/README.md CHANGED
@@ -48,6 +48,20 @@ openclaw plugins install @t0ken.ai/memoryx-openclaw-plugin
48
48
  openclaw gateway restart
49
49
  ```
50
50
 
51
+ ## Update
52
+
53
+ ```bash
54
+ openclaw plugins update @t0ken.ai/memoryx-openclaw-plugin
55
+ openclaw gateway restart
56
+ ```
57
+
58
+ Or update all plugins:
59
+
60
+ ```bash
61
+ openclaw plugins update --all
62
+ openclaw gateway restart
63
+ ```
64
+
51
65
  ## Configuration
52
66
 
53
67
  Edit `~/.openclaw/openclaw.json`:
package/dist/index.d.ts CHANGED
@@ -18,7 +18,9 @@
18
18
  * 3. Use dynamic import() for better-sqlite3: await import("better-sqlite3")
19
19
  * 4. Use setImmediate() for deferred operations
20
20
  *
21
- * See: https://github.com/openclaw/openclaw (memory-lancedb plugin pattern)
21
+ * This version uses @t0ken.ai/memoryx-sdk with conversation preset:
22
+ * - maxTokens: 30000 (flush when reaching token limit)
23
+ * - intervalMs: 300000 (flush after 5 minutes idle)
22
24
  */
23
25
  interface PluginConfig {
24
26
  apiBaseUrl?: string;
@@ -40,44 +42,11 @@ interface RecallResult {
40
42
  remainingQuota: number;
41
43
  upgradeHint?: string;
42
44
  }
43
- declare class ConversationManager {
44
- private currentConversationId;
45
- private conversationCreatedAt;
46
- private lastActivityAt;
47
- private readonly ROUND_THRESHOLD;
48
- private readonly TIMEOUT_MS;
49
- constructor();
50
- private generateId;
51
- getConversationId(): string;
52
- addMessage(role: string, content: string): Promise<boolean>;
53
- shouldFlush(): Promise<boolean>;
54
- startNewConversation(): void;
55
- getStatus(): Promise<{
56
- messageCount: number;
57
- conversationId: string;
58
- rounds: number;
59
- }>;
60
- }
61
45
  declare class MemoryXPlugin {
62
- private config;
63
- private conversationManager;
64
- private flushTimer;
65
- private sendQueueTimer;
66
- private readonly FLUSH_CHECK_INTERVAL;
67
- private readonly SEND_QUEUE_INTERVAL;
68
- private readonly MAX_RETRY_COUNT;
69
46
  private pluginConfig;
70
47
  private initialized;
71
48
  constructor(pluginConfig?: PluginConfig);
72
- init(): void;
73
- private get apiBase();
74
- private loadConfig;
75
- private saveConfig;
76
- private autoRegister;
77
- private getMachineFingerprint;
78
- private startFlushTimer;
79
- private startSendQueueTimer;
80
- private processSendQueue;
49
+ init(): Promise<void>;
81
50
  onMessage(role: string, content: string): Promise<boolean>;
82
51
  recall(query: string, limit?: number): Promise<RecallResult>;
83
52
  endConversation(): Promise<void>;
@@ -85,18 +54,19 @@ declare class MemoryXPlugin {
85
54
  store(content: string): Promise<{
86
55
  success: boolean;
87
56
  task_id?: string;
88
- duplicate?: boolean;
89
- existing?: string;
90
57
  }>;
91
58
  list(limit?: number): Promise<any[]>;
92
59
  getStatus(): Promise<{
93
60
  initialized: boolean;
94
61
  hasApiKey: boolean;
95
- conversationStatus: {
96
- messageCount: number;
97
- conversationId: string;
98
- rounds: number;
99
- };
62
+ queueStats: any;
63
+ }>;
64
+ getAccountInfo(): Promise<{
65
+ apiKey: string | null;
66
+ projectId: string;
67
+ userId: string | null;
68
+ apiBaseUrl: string;
69
+ initialized: boolean;
100
70
  }>;
101
71
  }
102
72
  declare const _default: {
@@ -107,5 +77,5 @@ declare const _default: {
107
77
  register(api: any, pluginConfig?: PluginConfig): void;
108
78
  };
109
79
  export default _default;
110
- export { MemoryXPlugin, ConversationManager };
80
+ export { MemoryXPlugin };
111
81
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAqDH,UAAU,YAAY;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAiBD,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,eAAe,EAAE,KAAK,CAAC;QACnB,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;AA0JD,cAAM,mBAAmB;IACrB,OAAO,CAAC,qBAAqB,CAAc;IAC3C,OAAO,CAAC,qBAAqB,CAAyC;IACtE,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;IAIrB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiB3D,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAkBrC,oBAAoB,IAAI,IAAI;IAMtB,SAAS,IAAI,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAQ/F;AAED,cAAM,aAAa;IACf,OAAO,CAAC,MAAM,CAMZ;IAEF,OAAO,CAAC,mBAAmB,CAAkD;IAC7E,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IAC5C,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;IAmBZ,OAAO,KAAK,OAAO,GAElB;YAEa,UAAU;YAWV,UAAU;YAIV,YAAY;IA+B1B,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,mBAAmB;YAMb,gBAAgB;IAiEjB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA2B1D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,OAAO,CAAC,YAAY,CAAC;IA6D/D,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAKhC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA6B1C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAqC/G,IAAI,CAAC,KAAK,GAAE,MAAW,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAuCxC,SAAS,IAAI,OAAO,CAAC;QAC9B,WAAW,EAAE,OAAO,CAAC;QACrB,SAAS,EAAE,OAAO,CAAC;QACnB,kBAAkB,EAAE;YAAE,YAAY,EAAE,MAAM,CAAC;YAAC,cAAc,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KACvF,CAAC;CAQL;;;;;;kBAUiB,GAAG,iBAAiB,YAAY,GAAG,IAAI;;AANzD,wBAuUE;AAEF,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAkCH,UAAU,YAAY;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;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,eAAe,EAAE,KAAK,CAAC;QACnB,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;AAmCD,cAAM,aAAa;IACf,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,WAAW,CAAkB;gBAEzB,YAAY,CAAC,EAAE,YAAY;IAIjC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAad,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiC1D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,OAAO,CAAC,YAAY,CAAC;IA6B/D,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAUhC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAc1C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAcvE,IAAI,CAAC,KAAK,GAAE,MAAW,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAiBxC,SAAS,IAAI,OAAO,CAAC;QAC9B,WAAW,EAAE,OAAO,CAAC;QACrB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,EAAE,GAAG,CAAC;KACnB,CAAC;IAoBW,cAAc,IAAI,OAAO,CAAC;QACnC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,OAAO,CAAC;KACxB,CAAC;CAuBL;;;;;;kBAUiB,GAAG,iBAAiB,YAAY,GAAG,IAAI;;AANzD,wBA0XE;AAEF,OAAO,EAAE,aAAa,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -18,32 +18,17 @@
18
18
  * 3. Use dynamic import() for better-sqlite3: await import("better-sqlite3")
19
19
  * 4. Use setImmediate() for deferred operations
20
20
  *
21
- * See: https://github.com/openclaw/openclaw (memory-lancedb plugin pattern)
21
+ * This version uses @t0ken.ai/memoryx-sdk with conversation preset:
22
+ * - maxTokens: 30000 (flush when reaching token limit)
23
+ * - intervalMs: 300000 (flush after 5 minutes idle)
22
24
  */
23
25
  import * as fs from "fs";
24
26
  import * as path from "path";
25
27
  import * as os from "os";
26
- import * as crypto from "crypto";
27
- import { getEncoding } from "js-tiktoken";
28
28
  const DEFAULT_API_BASE = "https://t0ken.ai/api";
29
29
  const PLUGIN_DIR = path.join(os.homedir(), ".openclaw", "extensions", "memoryx-openclaw-plugin");
30
30
  let logStream = null;
31
31
  let logStreamReady = false;
32
- let tokenizer = null;
33
- function getTokenizer() {
34
- if (!tokenizer) {
35
- tokenizer = getEncoding("cl100k_base");
36
- }
37
- return tokenizer;
38
- }
39
- function countTokens(text) {
40
- try {
41
- return getTokenizer().encode(text).length;
42
- }
43
- catch {
44
- return Math.ceil(text.length / 4);
45
- }
46
- }
47
32
  function ensureDir() {
48
33
  if (!fs.existsSync(PLUGIN_DIR)) {
49
34
  fs.mkdirSync(PLUGIN_DIR, { recursive: true });
@@ -65,359 +50,58 @@ function log(message) {
65
50
  }
66
51
  });
67
52
  }
68
- let dbPromise = null;
69
- let isSending = false;
70
- async function getDb() {
71
- if (dbPromise)
72
- return dbPromise;
73
- dbPromise = (async () => {
74
- ensureDir();
75
- const Database = (await import("better-sqlite3")).default;
76
- const dbPath = path.join(PLUGIN_DIR, "memoryx.db");
77
- const db = new Database(dbPath);
78
- db.exec(`
79
- CREATE TABLE IF NOT EXISTS config (
80
- key TEXT PRIMARY KEY,
81
- value TEXT
82
- );
83
-
84
- CREATE TABLE IF NOT EXISTS send_queue (
85
- id INTEGER PRIMARY KEY AUTOINCREMENT,
86
- conversation_id TEXT NOT NULL,
87
- conversation_created_at INTEGER NOT NULL,
88
- role TEXT NOT NULL,
89
- content TEXT NOT NULL,
90
- timestamp INTEGER NOT NULL,
91
- retry_count INTEGER DEFAULT 0
92
- );
93
-
94
- CREATE INDEX IF NOT EXISTS idx_send_queue_created ON send_queue(conversation_created_at);
95
- CREATE INDEX IF NOT EXISTS idx_send_queue_conversation ON send_queue(conversation_id);
96
- `);
97
- return db;
53
+ // Lazy-loaded SDK instance
54
+ let sdkInstance = null;
55
+ let sdkInitPromise = null;
56
+ async function getSDK(pluginConfig) {
57
+ if (sdkInstance)
58
+ return sdkInstance;
59
+ if (sdkInitPromise)
60
+ return sdkInitPromise;
61
+ sdkInitPromise = (async () => {
62
+ // Dynamic import SDK
63
+ const { MemoryXSDK } = await import("@t0ken.ai/memoryx-sdk");
64
+ // Use conversation preset: maxTokens: 30000, intervalMs: 300000 (5 min)
65
+ sdkInstance = new MemoryXSDK({
66
+ preset: 'conversation',
67
+ apiUrl: pluginConfig?.apiBaseUrl || DEFAULT_API_BASE,
68
+ autoRegister: true,
69
+ agentType: 'openclaw',
70
+ storageDir: PLUGIN_DIR
71
+ });
72
+ // Set debug mode
73
+ const { setDebug } = await import("@t0ken.ai/memoryx-sdk");
74
+ setDebug(true);
75
+ log("SDK initialized with conversation preset (30k tokens / 5min idle)");
76
+ return sdkInstance;
98
77
  })();
99
- return dbPromise;
100
- }
101
- class SQLiteStorage {
102
- static async loadConfig() {
103
- try {
104
- const db = await getDb();
105
- const row = db.prepare("SELECT value FROM config WHERE key = 'config'").get();
106
- if (row) {
107
- return JSON.parse(row.value);
108
- }
109
- }
110
- catch (e) {
111
- log(`Failed to load config: ${e}`);
112
- }
113
- return null;
114
- }
115
- static async saveConfig(config) {
116
- try {
117
- const db = await getDb();
118
- db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES ('config', ?)").run(JSON.stringify(config));
119
- }
120
- catch (e) {
121
- log(`Failed to save config: ${e}`);
122
- }
123
- }
124
- static async addConversationToQueue(conversationId, conversationCreatedAt, messages) {
125
- const db = await getDb();
126
- const stmt = db.prepare(`
127
- INSERT INTO send_queue (conversation_id, conversation_created_at, role, content, timestamp)
128
- VALUES (?, ?, ?, ?, ?)
129
- `);
130
- for (const msg of messages) {
131
- stmt.run(conversationId, conversationCreatedAt, msg.role, msg.content, msg.timestamp);
132
- }
133
- }
134
- static async getNextConversation() {
135
- const db = await getDb();
136
- const row = db.prepare(`
137
- SELECT conversation_id, MIN(conversation_created_at) as conversation_created_at
138
- FROM send_queue
139
- GROUP BY conversation_id
140
- ORDER BY MIN(conversation_created_at) ASC
141
- LIMIT 1
142
- `).get();
143
- if (!row)
144
- return null;
145
- const messages = db.prepare(`
146
- SELECT id, conversation_id, conversation_created_at, role, content, timestamp, retry_count
147
- FROM send_queue
148
- WHERE conversation_id = ?
149
- ORDER BY id ASC
150
- `).all(row.conversation_id);
151
- return {
152
- conversationId: row.conversation_id,
153
- conversationCreatedAt: row.conversation_created_at,
154
- messages
155
- };
156
- }
157
- static async deleteConversation(conversationId) {
158
- const db = await getDb();
159
- db.prepare("DELETE FROM send_queue WHERE conversation_id = ?").run(conversationId);
160
- }
161
- static async incrementRetryByConversation(conversationId) {
162
- const db = await getDb();
163
- db.prepare("UPDATE send_queue SET retry_count = retry_count + 1 WHERE conversation_id = ?").run(conversationId);
164
- }
165
- static async getQueueStats(conversationId) {
166
- const db = await getDb();
167
- const rows = db.prepare(`
168
- SELECT role FROM send_queue
169
- WHERE conversation_id = ?
170
- ORDER BY id ASC
171
- `).all(conversationId);
172
- let rounds = 0;
173
- let lastRole = "";
174
- for (const row of rows) {
175
- if (row.role === "assistant" && lastRole === "user") {
176
- rounds++;
177
- }
178
- lastRole = row.role;
179
- }
180
- return { count: rows.length, rounds };
181
- }
182
- static async clearOldConversations(maxAge = 7 * 24 * 60 * 60) {
183
- const db = await getDb();
184
- const cutoff = Math.floor(Date.now() / 1000) - maxAge;
185
- db.prepare("DELETE FROM send_queue WHERE conversation_created_at < ?").run(cutoff);
186
- }
187
- static async getQueueLength() {
188
- const db = await getDb();
189
- const row = db.prepare("SELECT COUNT(DISTINCT conversation_id) as count FROM send_queue").get();
190
- return row?.count || 0;
191
- }
192
- }
193
- class ConversationManager {
194
- currentConversationId = "";
195
- conversationCreatedAt = Math.floor(Date.now() / 1000);
196
- lastActivityAt = Date.now();
197
- ROUND_THRESHOLD = 2;
198
- TIMEOUT_MS = 30 * 60 * 1000;
199
- constructor() {
200
- this.currentConversationId = this.generateId();
201
- }
202
- generateId() {
203
- return `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
204
- }
205
- getConversationId() {
206
- return this.currentConversationId;
207
- }
208
- async addMessage(role, content) {
209
- if (!content || content.length < 2) {
210
- return false;
211
- }
212
- const db = await getDb();
213
- db.prepare(`
214
- INSERT INTO send_queue (conversation_id, conversation_created_at, role, content, timestamp)
215
- VALUES (?, ?, ?, ?, ?)
216
- `).run(this.currentConversationId, this.conversationCreatedAt, role, content, Date.now());
217
- this.lastActivityAt = Date.now();
218
- const stats = await SQLiteStorage.getQueueStats(this.currentConversationId);
219
- return stats.rounds >= this.ROUND_THRESHOLD;
220
- }
221
- async shouldFlush() {
222
- const stats = await SQLiteStorage.getQueueStats(this.currentConversationId);
223
- if (stats.count === 0) {
224
- return false;
225
- }
226
- if (stats.rounds >= this.ROUND_THRESHOLD) {
227
- return true;
228
- }
229
- const elapsed = Date.now() - this.lastActivityAt;
230
- if (elapsed > this.TIMEOUT_MS) {
231
- return true;
232
- }
233
- return false;
234
- }
235
- startNewConversation() {
236
- this.currentConversationId = this.generateId();
237
- this.conversationCreatedAt = Math.floor(Date.now() / 1000);
238
- this.lastActivityAt = Date.now();
239
- }
240
- async getStatus() {
241
- const stats = await SQLiteStorage.getQueueStats(this.currentConversationId);
242
- return {
243
- messageCount: stats.count,
244
- conversationId: this.currentConversationId,
245
- rounds: stats.rounds
246
- };
247
- }
78
+ return sdkInitPromise;
248
79
  }
249
80
  class MemoryXPlugin {
250
- config = {
251
- apiKey: null,
252
- projectId: "default",
253
- userId: null,
254
- initialized: false,
255
- apiBaseUrl: DEFAULT_API_BASE
256
- };
257
- conversationManager = new ConversationManager();
258
- flushTimer = null;
259
- sendQueueTimer = null;
260
- FLUSH_CHECK_INTERVAL = 30000;
261
- SEND_QUEUE_INTERVAL = 5000;
262
- MAX_RETRY_COUNT = 5;
263
- pluginConfig = null;
81
+ pluginConfig;
264
82
  initialized = false;
265
83
  constructor(pluginConfig) {
266
- this.pluginConfig = pluginConfig || null;
267
- if (pluginConfig?.apiBaseUrl) {
268
- this.config.apiBaseUrl = pluginConfig.apiBaseUrl;
269
- }
270
- this.config.initialized = true;
84
+ this.pluginConfig = pluginConfig;
271
85
  }
272
- init() {
86
+ async init() {
273
87
  if (this.initialized)
274
88
  return;
275
89
  this.initialized = true;
276
90
  log("Async init started");
277
- this.loadConfig().then(() => {
278
- log(`Config loaded, apiKey: ${this.config.apiKey ? 'present' : 'missing'}`);
279
- this.startFlushTimer();
280
- this.startSendQueueTimer();
281
- SQLiteStorage.clearOldConversations().catch(() => { });
282
- this.processSendQueue();
283
- if (!this.config.apiKey) {
284
- log("Starting auto-register");
285
- this.autoRegister().catch(e => log(`Auto-register failed: ${e}`));
286
- }
287
- }).catch(e => log(`Init failed: ${e}`));
288
- }
289
- get apiBase() {
290
- return this.config.apiBaseUrl || DEFAULT_API_BASE;
291
- }
292
- async loadConfig() {
293
- const stored = await SQLiteStorage.loadConfig();
294
- if (stored) {
295
- this.config = {
296
- ...this.config,
297
- ...stored,
298
- apiBaseUrl: this.pluginConfig?.apiBaseUrl || stored.apiBaseUrl || this.config.apiBaseUrl
299
- };
300
- }
301
- }
302
- async saveConfig() {
303
- await SQLiteStorage.saveConfig(this.config);
304
- }
305
- async autoRegister() {
306
91
  try {
307
- const fingerprint = this.getMachineFingerprint();
308
- const response = await fetch(`${this.apiBase}/agents/auto-register`, {
309
- method: "POST",
310
- headers: { "Content-Type": "application/json" },
311
- body: JSON.stringify({
312
- machine_fingerprint: fingerprint,
313
- agent_type: "openclaw",
314
- agent_name: "openclaw-agent",
315
- platform: os.platform(),
316
- platform_version: os.release()
317
- })
318
- });
319
- if (!response.ok) {
320
- throw new Error(`Auto-register failed: ${response.status}`);
321
- }
322
- const data = await response.json();
323
- this.config.apiKey = data.api_key;
324
- this.config.projectId = String(data.project_id);
325
- this.config.userId = data.agent_id;
326
- await this.saveConfig();
327
- log("Auto-registered successfully");
92
+ await getSDK(this.pluginConfig);
93
+ log("SDK ready");
328
94
  }
329
95
  catch (e) {
330
- log(`Auto-register failed: ${e}`);
96
+ log(`Init failed: ${e}`);
331
97
  }
332
98
  }
333
- getMachineFingerprint() {
334
- const components = [
335
- os.hostname(),
336
- os.platform(),
337
- os.arch(),
338
- os.cpus()[0]?.model || "unknown",
339
- os.totalmem()
340
- ];
341
- const raw = components.join("|");
342
- return crypto.createHash("sha256").update(raw).digest("hex").slice(0, 32);
343
- }
344
- startFlushTimer() {
345
- this.flushTimer = setInterval(async () => {
346
- if (await this.conversationManager.shouldFlush()) {
347
- this.conversationManager.startNewConversation();
348
- }
349
- }, this.FLUSH_CHECK_INTERVAL);
350
- }
351
- startSendQueueTimer() {
352
- this.sendQueueTimer = setInterval(() => {
353
- this.processSendQueue();
354
- }, this.SEND_QUEUE_INTERVAL);
355
- }
356
- async processSendQueue() {
357
- if (isSending)
358
- return;
359
- if (!this.config.apiKey)
360
- return;
361
- isSending = true;
362
- try {
363
- await SQLiteStorage.clearOldConversations();
364
- const conversation = await SQLiteStorage.getNextConversation();
365
- if (!conversation) {
366
- isSending = false;
367
- return;
368
- }
369
- const { conversationId, messages } = conversation;
370
- const maxRetry = Math.max(...messages.map(m => m.retry_count));
371
- if (maxRetry >= this.MAX_RETRY_COUNT) {
372
- log(`Deleting conversation ${conversationId}: max retries exceeded`);
373
- await SQLiteStorage.deleteConversation(conversationId);
374
- isSending = false;
375
- return;
376
- }
377
- log(`Sending conversation ${conversationId} (${messages.length} messages)`);
378
- try {
379
- const response = await fetch(`${this.apiBase}/v1/conversations/flush`, {
380
- method: "POST",
381
- headers: {
382
- "Content-Type": "application/json",
383
- "X-API-Key": this.config.apiKey
384
- },
385
- body: JSON.stringify({
386
- conversation_id: conversationId,
387
- messages: messages.map(m => ({
388
- role: m.role,
389
- content: m.content,
390
- timestamp: m.timestamp,
391
- tokens: countTokens(m.content)
392
- }))
393
- })
394
- });
395
- if (response.ok) {
396
- await SQLiteStorage.deleteConversation(conversationId);
397
- const result = await response.json();
398
- log(`Sent conversation ${conversationId}, extracted ${result.extracted_count || 0} memories`);
399
- }
400
- else {
401
- await SQLiteStorage.incrementRetryByConversation(conversationId);
402
- const errorData = await response.json().catch(() => ({}));
403
- log(`Failed to send conversation ${conversationId}: ${response.status} ${JSON.stringify(errorData)}`);
404
- }
405
- }
406
- catch (e) {
407
- await SQLiteStorage.incrementRetryByConversation(conversationId);
408
- log(`Error sending conversation ${conversationId}: ${e}`);
409
- }
410
- }
411
- catch (e) {
412
- log(`Process send queue failed: ${e}`);
413
- }
414
- isSending = false;
415
- }
416
99
  async onMessage(role, content) {
417
- this.init();
100
+ await this.init();
418
101
  if (!content || content.length < 2) {
419
102
  return false;
420
103
  }
104
+ // Skip short messages
421
105
  const skipPatterns = [
422
106
  /^[好的ok谢谢嗯啊哈哈你好hihello拜拜再见]{1,5}$/i,
423
107
  /^[??!!。,,\s]{1,10}$/
@@ -427,59 +111,41 @@ class MemoryXPlugin {
427
111
  return false;
428
112
  }
429
113
  }
430
- const shouldFlush = await this.conversationManager.addMessage(role, content);
431
- if (shouldFlush) {
432
- this.conversationManager.startNewConversation();
114
+ try {
115
+ const sdk = await getSDK(this.pluginConfig);
116
+ if (role === 'user') {
117
+ await sdk.addUserMessage(content);
118
+ }
119
+ else {
120
+ await sdk.addAssistantMessage(content);
121
+ }
122
+ return true;
123
+ }
124
+ catch (e) {
125
+ log(`onMessage failed: ${e}`);
126
+ return false;
433
127
  }
434
- return true;
435
128
  }
436
129
  async recall(query, limit = 5) {
437
- this.init();
438
- if (!this.config.apiKey || !query || query.length < 2) {
439
- return { memories: [], relatedMemories: [], isLimited: false, remainingQuota: 0 };
440
- }
130
+ await this.init();
441
131
  try {
442
- const response = await fetch(`${this.apiBase}/v1/memories/search`, {
443
- method: "POST",
444
- headers: {
445
- "Content-Type": "application/json",
446
- "X-API-Key": this.config.apiKey
447
- },
448
- body: JSON.stringify({
449
- query,
450
- project_id: this.config.projectId,
451
- limit
452
- })
453
- });
454
- if (!response.ok) {
455
- const errorData = await response.json().catch(() => ({}));
456
- if (response.status === 402 || response.status === 429) {
457
- return {
458
- memories: [],
459
- relatedMemories: [],
460
- isLimited: true,
461
- remainingQuota: 0,
462
- upgradeHint: errorData.detail || "云端查询配额已用尽,请升级到付费版"
463
- };
464
- }
465
- throw new Error(`Search failed: ${response.status}`);
466
- }
467
- const data = await response.json();
132
+ const sdk = await getSDK(this.pluginConfig);
133
+ const result = await sdk.search(query, limit);
468
134
  return {
469
- memories: (data.data || []).map((m) => ({
135
+ memories: (result.data || []).map((m) => ({
470
136
  id: m.id,
471
- content: m.memory || m.content,
137
+ content: m.content || m.memory,
472
138
  category: m.category || "other",
473
139
  score: m.score || 0.5
474
140
  })),
475
- relatedMemories: (data.related_memories || []).map((m) => ({
141
+ relatedMemories: (result.related_memories || []).map((m) => ({
476
142
  id: m.id,
477
- content: m.memory || m.content,
143
+ content: m.content || m.memory,
478
144
  category: m.category || "other",
479
145
  score: m.score || 0
480
146
  })),
481
147
  isLimited: false,
482
- remainingQuota: data.remaining_quota ?? -1
148
+ remainingQuota: result.remaining_quota ?? -1
483
149
  };
484
150
  }
485
151
  catch (e) {
@@ -488,28 +154,22 @@ class MemoryXPlugin {
488
154
  }
489
155
  }
490
156
  async endConversation() {
491
- this.conversationManager.startNewConversation();
492
- log("Conversation ended, starting new conversation");
157
+ try {
158
+ const sdk = await getSDK(this.pluginConfig);
159
+ sdk.startNewConversation();
160
+ log("Conversation ended, starting new conversation");
161
+ }
162
+ catch (e) {
163
+ log(`End conversation failed: ${e}`);
164
+ }
493
165
  }
494
166
  async forget(memoryId) {
495
- this.init();
496
- if (!this.config.apiKey) {
497
- log("Forget failed: no API key");
498
- return false;
499
- }
167
+ await this.init();
500
168
  try {
501
- const response = await fetch(`${this.apiBase}/v1/memories/${memoryId}`, {
502
- method: "DELETE",
503
- headers: {
504
- "X-API-Key": this.config.apiKey
505
- }
506
- });
507
- if (response.ok) {
508
- log(`Forgot memory ${memoryId}`);
509
- return true;
510
- }
511
- log(`Forget failed: ${response.status}`);
512
- return false;
169
+ const sdk = await getSDK(this.pluginConfig);
170
+ await sdk.delete(memoryId);
171
+ log(`Forgot memory ${memoryId}`);
172
+ return true;
513
173
  }
514
174
  catch (e) {
515
175
  log(`Forget failed: ${e}`);
@@ -517,32 +177,12 @@ class MemoryXPlugin {
517
177
  }
518
178
  }
519
179
  async store(content) {
520
- this.init();
521
- if (!this.config.apiKey) {
522
- log("Store failed: no API key");
523
- return { success: false };
524
- }
180
+ await this.init();
525
181
  try {
526
- const response = await fetch(`${this.apiBase}/v1/memories`, {
527
- method: "POST",
528
- headers: {
529
- "Content-Type": "application/json",
530
- "X-API-Key": this.config.apiKey
531
- },
532
- body: JSON.stringify({
533
- content,
534
- project_id: this.config.projectId,
535
- metadata: { source: "function_call" }
536
- })
537
- });
538
- if (!response.ok) {
539
- const errorData = await response.json().catch(() => ({}));
540
- log(`Store failed: ${response.status} ${JSON.stringify(errorData)}`);
541
- return { success: false };
542
- }
543
- const data = await response.json();
544
- log(`Stored memory, task_id: ${data.task_id}`);
545
- return { success: true, task_id: data.task_id };
182
+ const sdk = await getSDK(this.pluginConfig);
183
+ const result = await sdk.addMemory(content);
184
+ log(`Stored memory, result: ${JSON.stringify(result)}`);
185
+ return { success: true, task_id: result?.task_id };
546
186
  }
547
187
  catch (e) {
548
188
  log(`Store failed: ${e}`);
@@ -550,32 +190,13 @@ class MemoryXPlugin {
550
190
  }
551
191
  }
552
192
  async list(limit = 10) {
553
- this.init();
554
- if (!this.config.apiKey) {
555
- log("List failed: no API key");
556
- return [];
557
- }
193
+ await this.init();
558
194
  try {
559
- const response = await fetch(`${this.apiBase}/v1/memories/search`, {
560
- method: "POST",
561
- headers: {
562
- "Content-Type": "application/json",
563
- "X-API-Key": this.config.apiKey
564
- },
565
- body: JSON.stringify({
566
- query: "*",
567
- project_id: this.config.projectId,
568
- limit
569
- })
570
- });
571
- if (!response.ok) {
572
- log(`List failed: ${response.status}`);
573
- return [];
574
- }
575
- const data = await response.json();
576
- return (data.data || []).map((m) => ({
195
+ const sdk = await getSDK(this.pluginConfig);
196
+ const result = await sdk.list(limit, 0);
197
+ return (result.data || result.memories || []).map((m) => ({
577
198
  id: m.id,
578
- content: m.memory || m.content,
199
+ content: m.content || m.memory,
579
200
  category: m.category || "other"
580
201
  }));
581
202
  }
@@ -585,20 +206,54 @@ class MemoryXPlugin {
585
206
  }
586
207
  }
587
208
  async getStatus() {
588
- const status = await this.conversationManager.getStatus();
589
- return {
590
- initialized: this.config.initialized,
591
- hasApiKey: !!this.config.apiKey,
592
- conversationStatus: status
593
- };
209
+ try {
210
+ const sdk = await getSDK(this.pluginConfig);
211
+ const accountInfo = await sdk.getAccountInfo();
212
+ const queueStats = await sdk.getQueueStats();
213
+ return {
214
+ initialized: true,
215
+ hasApiKey: !!accountInfo?.apiKey,
216
+ queueStats
217
+ };
218
+ }
219
+ catch (e) {
220
+ return {
221
+ initialized: false,
222
+ hasApiKey: false,
223
+ queueStats: null
224
+ };
225
+ }
226
+ }
227
+ async getAccountInfo() {
228
+ await this.init();
229
+ try {
230
+ const sdk = await getSDK(this.pluginConfig);
231
+ const info = await sdk.getAccountInfo();
232
+ return {
233
+ apiKey: info?.apiKey || null,
234
+ projectId: info?.projectId || "default",
235
+ userId: info?.userId || null,
236
+ apiBaseUrl: info?.apiBaseUrl || DEFAULT_API_BASE,
237
+ initialized: true
238
+ };
239
+ }
240
+ catch (e) {
241
+ return {
242
+ apiKey: null,
243
+ projectId: "default",
244
+ userId: null,
245
+ apiBaseUrl: DEFAULT_API_BASE,
246
+ initialized: false
247
+ };
248
+ }
594
249
  }
595
250
  }
596
251
  let plugin;
597
252
  export default {
598
253
  id: "memoryx-openclaw-plugin",
599
254
  name: "MemoryX Realtime Plugin",
600
- version: "1.1.18",
601
- description: "Real-time memory capture and recall for OpenClaw",
255
+ version: "2.0.0",
256
+ description: "Real-time memory capture and recall for OpenClaw (powered by @t0ken.ai/memoryx-sdk)",
602
257
  register(api, pluginConfig) {
603
258
  api.logger.info("[MemoryX] Plugin registering...");
604
259
  if (pluginConfig?.apiBaseUrl) {
@@ -760,12 +415,6 @@ export default {
760
415
  details: { action: "stored", task_id: result.task_id }
761
416
  };
762
417
  }
763
- else if (result.duplicate) {
764
- return {
765
- content: [{ type: "text", text: `Similar memory already exists: "${result.existing}"` }],
766
- details: { action: "duplicate" }
767
- };
768
- }
769
418
  else {
770
419
  return {
771
420
  content: [{ type: "text", text: "Failed to store memory. Please try again." }],
@@ -827,6 +476,55 @@ export default {
827
476
  }
828
477
  }
829
478
  }, { name: "memoryx_list" });
479
+ api.registerTool({
480
+ name: "memoryx_account_info",
481
+ label: "MemoryX Account Info",
482
+ description: "Get MemoryX account information including API Key, Project ID, User ID, and API Base URL. Use when user asks about their MemoryX account, API key, project settings, or account status. Returns all stored account configuration from local database.",
483
+ parameters: {
484
+ type: "object",
485
+ properties: {}
486
+ },
487
+ async execute(_toolCallId, params) {
488
+ if (!plugin) {
489
+ return {
490
+ content: [{ type: "text", text: "MemoryX plugin not initialized." }],
491
+ details: { error: "not_initialized" }
492
+ };
493
+ }
494
+ try {
495
+ const accountInfo = await plugin.getAccountInfo();
496
+ if (!accountInfo) {
497
+ return {
498
+ content: [{ type: "text", text: "No account information found. The plugin may not be registered yet." }],
499
+ details: { error: "no_account" }
500
+ };
501
+ }
502
+ const lines = [
503
+ "MemoryX Account Information:",
504
+ `API Key: ${accountInfo.apiKey || 'Not set'}`,
505
+ `Project ID: ${accountInfo.projectId || 'default'}`,
506
+ `User ID: ${accountInfo.userId || 'Not set'}`,
507
+ `API Base URL: ${accountInfo.apiBaseUrl || DEFAULT_API_BASE}`,
508
+ `Initialized: ${accountInfo.initialized ? 'Yes' : 'No'}`
509
+ ];
510
+ return {
511
+ content: [{ type: "text", text: lines.join("\n") }],
512
+ details: {
513
+ apiKey: accountInfo.apiKey,
514
+ projectId: accountInfo.projectId,
515
+ userId: accountInfo.userId,
516
+ apiBaseUrl: accountInfo.apiBaseUrl
517
+ }
518
+ };
519
+ }
520
+ catch (error) {
521
+ return {
522
+ content: [{ type: "text", text: `Failed to get account info: ${error.message}` }],
523
+ details: { error: error.message }
524
+ };
525
+ }
526
+ }
527
+ }, { name: "memoryx_account_info" });
830
528
  api.on("message_received", async (event, ctx) => {
831
529
  const { content } = event;
832
530
  if (content && plugin) {
@@ -877,7 +575,7 @@ export default {
877
575
  await plugin.endConversation();
878
576
  }
879
577
  });
880
- api.logger.info("[MemoryX] Plugin registered successfully");
578
+ api.logger.info("[MemoryX] Plugin registered successfully (v2.0.0 with SDK)");
881
579
  }
882
580
  };
883
- export { MemoryXPlugin, ConversationManager };
581
+ export { MemoryXPlugin };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@t0ken.ai/memoryx-openclaw-plugin",
3
- "version": "1.1.18",
4
- "description": "MemoryX real-time memory capture and recall plugin for OpenClaw",
3
+ "version": "2.0.0",
4
+ "description": "MemoryX real-time memory capture and recall plugin for OpenClaw (powered by @t0ken.ai/memoryx-sdk)",
5
5
  "type": "module",
6
6
  "author": "MemoryX Team",
7
7
  "license": "MIT",
@@ -31,12 +31,13 @@
31
31
  ]
32
32
  },
33
33
  "devDependencies": {
34
- "@types/better-sqlite3": "^7.6.0",
35
34
  "@types/node": "^20.0.0",
36
35
  "typescript": "^5.0.0"
37
36
  },
37
+ "peerDependencies": {
38
+ "better-sqlite3": "^11.0.0"
39
+ },
38
40
  "dependencies": {
39
- "better-sqlite3": "^11.0.0",
40
- "js-tiktoken": "^1.0.21"
41
+ "@t0ken.ai/memoryx-sdk": "^1.0.0"
41
42
  }
42
43
  }