@t0ken.ai/memoryx-openclaw-plugin 1.1.19 → 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/dist/index.d.ts +6 -43
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +122 -483
- package/package.json +6 -5
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
|
-
*
|
|
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,12 @@ 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
|
-
|
|
96
|
-
messageCount: number;
|
|
97
|
-
conversationId: string;
|
|
98
|
-
rounds: number;
|
|
99
|
-
};
|
|
62
|
+
queueStats: any;
|
|
100
63
|
}>;
|
|
101
64
|
getAccountInfo(): Promise<{
|
|
102
65
|
apiKey: string | null;
|
|
@@ -114,5 +77,5 @@ declare const _default: {
|
|
|
114
77
|
register(api: any, pluginConfig?: PluginConfig): void;
|
|
115
78
|
};
|
|
116
79
|
export default _default;
|
|
117
|
-
export { MemoryXPlugin
|
|
80
|
+
export { MemoryXPlugin };
|
|
118
81
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
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
|
-
*
|
|
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
|
-
|
|
69
|
-
let
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
method: "POST",
|
|
310
|
-
headers: { "Content-Type": "application/json" },
|
|
311
|
-
body: JSON.stringify({
|
|
312
|
-
machine_fingerprint: fingerprint,
|
|
313
|
-
agent_type: "openclaw",
|
|
314
|
-
agent_name: os.hostname(),
|
|
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(`
|
|
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
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
|
443
|
-
|
|
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: (
|
|
135
|
+
memories: (result.data || []).map((m) => ({
|
|
470
136
|
id: m.id,
|
|
471
|
-
content: m.
|
|
137
|
+
content: m.content || m.memory,
|
|
472
138
|
category: m.category || "other",
|
|
473
139
|
score: m.score || 0.5
|
|
474
140
|
})),
|
|
475
|
-
relatedMemories: (
|
|
141
|
+
relatedMemories: (result.related_memories || []).map((m) => ({
|
|
476
142
|
id: m.id,
|
|
477
|
-
content: m.
|
|
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:
|
|
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
|
-
|
|
492
|
-
|
|
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
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
|
560
|
-
|
|
561
|
-
|
|
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.
|
|
199
|
+
content: m.content || m.memory,
|
|
579
200
|
category: m.category || "other"
|
|
580
201
|
}));
|
|
581
202
|
}
|
|
@@ -585,30 +206,54 @@ class MemoryXPlugin {
|
|
|
585
206
|
}
|
|
586
207
|
}
|
|
587
208
|
async getStatus() {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
+
}
|
|
594
226
|
}
|
|
595
227
|
async getAccountInfo() {
|
|
596
|
-
this.init();
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
+
}
|
|
604
249
|
}
|
|
605
250
|
}
|
|
606
251
|
let plugin;
|
|
607
252
|
export default {
|
|
608
253
|
id: "memoryx-openclaw-plugin",
|
|
609
254
|
name: "MemoryX Realtime Plugin",
|
|
610
|
-
version: "
|
|
611
|
-
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)",
|
|
612
257
|
register(api, pluginConfig) {
|
|
613
258
|
api.logger.info("[MemoryX] Plugin registering...");
|
|
614
259
|
if (pluginConfig?.apiBaseUrl) {
|
|
@@ -770,12 +415,6 @@ export default {
|
|
|
770
415
|
details: { action: "stored", task_id: result.task_id }
|
|
771
416
|
};
|
|
772
417
|
}
|
|
773
|
-
else if (result.duplicate) {
|
|
774
|
-
return {
|
|
775
|
-
content: [{ type: "text", text: `Similar memory already exists: "${result.existing}"` }],
|
|
776
|
-
details: { action: "duplicate" }
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
418
|
else {
|
|
780
419
|
return {
|
|
781
420
|
content: [{ type: "text", text: "Failed to store memory. Please try again." }],
|
|
@@ -936,7 +575,7 @@ export default {
|
|
|
936
575
|
await plugin.endConversation();
|
|
937
576
|
}
|
|
938
577
|
});
|
|
939
|
-
api.logger.info("[MemoryX] Plugin registered successfully");
|
|
578
|
+
api.logger.info("[MemoryX] Plugin registered successfully (v2.0.0 with SDK)");
|
|
940
579
|
}
|
|
941
580
|
};
|
|
942
|
-
export { MemoryXPlugin
|
|
581
|
+
export { MemoryXPlugin };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@t0ken.ai/memoryx-openclaw-plugin",
|
|
3
|
-
"version": "
|
|
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
|
-
"
|
|
40
|
-
"js-tiktoken": "^1.0.21"
|
|
41
|
+
"@t0ken.ai/memoryx-sdk": "^1.0.0"
|
|
41
42
|
}
|
|
42
43
|
}
|