xiaozuoassistant 0.2.1 → 0.2.3
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/package.json +1 -1
- package/dist/client/assets/browser-ponyfill-DyBlALcJ.js +0 -2
- package/dist/client/assets/index-Ck4FlWN3.css +0 -1
- package/dist/client/assets/index-CuwVd97X.js +0 -201
- package/dist/client/favicon.svg +0 -4
- package/dist/client/index.html +0 -14
- package/dist/client/locales/en/translation.json +0 -110
- package/dist/client/locales/zh/translation.json +0 -112
- package/dist/server/agents/office.js +0 -23
- package/dist/server/app.js +0 -50
- package/dist/server/channels/base-channel.js +0 -23
- package/dist/server/channels/create-channels.js +0 -18
- package/dist/server/channels/dingtalk.js +0 -83
- package/dist/server/channels/feishu.js +0 -108
- package/dist/server/channels/telegram.js +0 -53
- package/dist/server/channels/terminal.js +0 -49
- package/dist/server/channels/web.js +0 -66
- package/dist/server/channels/wechat.js +0 -107
- package/dist/server/config/loader.js +0 -96
- package/dist/server/config/paths.js +0 -24
- package/dist/server/config/prompts.js +0 -12
- package/dist/server/core/agents/manager.js +0 -27
- package/dist/server/core/agents/runtime.js +0 -92
- package/dist/server/core/brain.js +0 -255
- package/dist/server/core/event-bus.js +0 -24
- package/dist/server/core/logger.js +0 -71
- package/dist/server/core/memories/manager.js +0 -238
- package/dist/server/core/memories/short-term.js +0 -512
- package/dist/server/core/memories/structured.js +0 -357
- package/dist/server/core/memories/vector.js +0 -137
- package/dist/server/core/memory.js +0 -2
- package/dist/server/core/plugin-manager.js +0 -128
- package/dist/server/core/plugin.js +0 -1
- package/dist/server/core/scheduler.js +0 -85
- package/dist/server/core/task-queue.js +0 -104
- package/dist/server/core/types.js +0 -1
- package/dist/server/index.js +0 -860
- package/dist/server/llm/openai.js +0 -23
- package/dist/server/plugins/core-skills/src/create-agent.js +0 -58
- package/dist/server/plugins/core-skills/src/delegate.js +0 -39
- package/dist/server/plugins/core-skills/src/file-system.js +0 -142
- package/dist/server/plugins/core-skills/src/index.js +0 -26
- package/dist/server/plugins/core-skills/src/list-agents.js +0 -24
- package/dist/server/plugins/core-skills/src/search.js +0 -31
- package/dist/server/plugins/core-skills/src/system-time.js +0 -27
- package/dist/server/plugins/office-skills/src/index.js +0 -19
- package/dist/server/plugins/office-skills/src/office-excel.js +0 -84
- package/dist/server/plugins/office-skills/src/office-ppt.js +0 -58
- package/dist/server/plugins/office-skills/src/office-word.js +0 -90
- package/dist/server/routes/auth.js +0 -28
- package/dist/server/server/create-http.js +0 -22
- package/dist/server/server.js +0 -29
- package/dist/server/skills/base-skill.js +0 -20
- package/dist/server/skills/registry.js +0 -52
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
import Database from 'better-sqlite3';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import fs from 'fs-extra';
|
|
4
|
-
const DB_DIR = path.resolve(process.cwd(), 'data/sqlite');
|
|
5
|
-
const DB_FILE = path.join(DB_DIR, 'memories.db');
|
|
6
|
-
export class StructuredMemory {
|
|
7
|
-
constructor() {
|
|
8
|
-
fs.ensureDirSync(DB_DIR);
|
|
9
|
-
this.db = new Database(DB_FILE);
|
|
10
|
-
this.initTables();
|
|
11
|
-
}
|
|
12
|
-
static getInstance() {
|
|
13
|
-
if (!StructuredMemory.instance) {
|
|
14
|
-
StructuredMemory.instance = new StructuredMemory();
|
|
15
|
-
}
|
|
16
|
-
return StructuredMemory.instance;
|
|
17
|
-
}
|
|
18
|
-
initTables() {
|
|
19
|
-
try {
|
|
20
|
-
// Facts table: key-value or simple structured data
|
|
21
|
-
this.db.prepare(`
|
|
22
|
-
CREATE TABLE IF NOT EXISTS facts (
|
|
23
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
24
|
-
category TEXT NOT NULL,
|
|
25
|
-
key TEXT NOT NULL,
|
|
26
|
-
value TEXT NOT NULL,
|
|
27
|
-
confidence REAL DEFAULT 1.0,
|
|
28
|
-
source TEXT,
|
|
29
|
-
timestamp INTEGER
|
|
30
|
-
)
|
|
31
|
-
`).run();
|
|
32
|
-
// User Profile: specialized facts (multi-user)
|
|
33
|
-
// V3: Simplified profile (name, email only)
|
|
34
|
-
this.db.prepare(`
|
|
35
|
-
CREATE TABLE IF NOT EXISTS user_profile_v3 (
|
|
36
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
37
|
-
user_id TEXT NOT NULL,
|
|
38
|
-
name TEXT,
|
|
39
|
-
email TEXT,
|
|
40
|
-
updated_at INTEGER,
|
|
41
|
-
UNIQUE(user_id)
|
|
42
|
-
)
|
|
43
|
-
`).run();
|
|
44
|
-
this.migrateUserProfileV3();
|
|
45
|
-
// Graph nodes (Entities)
|
|
46
|
-
this.db.prepare(`
|
|
47
|
-
CREATE TABLE IF NOT EXISTS entities (
|
|
48
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
49
|
-
name TEXT UNIQUE NOT NULL,
|
|
50
|
-
type TEXT NOT NULL,
|
|
51
|
-
metadata TEXT
|
|
52
|
-
)
|
|
53
|
-
`).run();
|
|
54
|
-
// Graph edges (Relations)
|
|
55
|
-
this.db.prepare(`
|
|
56
|
-
CREATE TABLE IF NOT EXISTS relations (
|
|
57
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
58
|
-
source_id INTEGER,
|
|
59
|
-
target_id INTEGER,
|
|
60
|
-
relation TEXT NOT NULL,
|
|
61
|
-
weight REAL DEFAULT 1.0,
|
|
62
|
-
FOREIGN KEY(source_id) REFERENCES entities(id),
|
|
63
|
-
FOREIGN KEY(target_id) REFERENCES entities(id)
|
|
64
|
-
)
|
|
65
|
-
`).run();
|
|
66
|
-
// Projects
|
|
67
|
-
this.db.prepare(`
|
|
68
|
-
CREATE TABLE IF NOT EXISTS projects (
|
|
69
|
-
id TEXT PRIMARY KEY,
|
|
70
|
-
name TEXT NOT NULL,
|
|
71
|
-
description TEXT,
|
|
72
|
-
departments TEXT,
|
|
73
|
-
members TEXT,
|
|
74
|
-
directory TEXT,
|
|
75
|
-
created_at INTEGER,
|
|
76
|
-
updated_at INTEGER
|
|
77
|
-
)
|
|
78
|
-
`).run();
|
|
79
|
-
// Notebooks
|
|
80
|
-
this.db.prepare(`
|
|
81
|
-
CREATE TABLE IF NOT EXISTS notebooks (
|
|
82
|
-
id TEXT PRIMARY KEY,
|
|
83
|
-
name TEXT NOT NULL,
|
|
84
|
-
description TEXT,
|
|
85
|
-
keywords TEXT,
|
|
86
|
-
created_at INTEGER,
|
|
87
|
-
updated_at INTEGER
|
|
88
|
-
)
|
|
89
|
-
`).run();
|
|
90
|
-
// Notes
|
|
91
|
-
this.db.prepare(`
|
|
92
|
-
CREATE TABLE IF NOT EXISTS notes (
|
|
93
|
-
id TEXT PRIMARY KEY,
|
|
94
|
-
notebook_id TEXT NOT NULL,
|
|
95
|
-
title TEXT NOT NULL,
|
|
96
|
-
content TEXT,
|
|
97
|
-
is_todo INTEGER DEFAULT 0,
|
|
98
|
-
done INTEGER DEFAULT 0,
|
|
99
|
-
created_at INTEGER,
|
|
100
|
-
updated_at INTEGER,
|
|
101
|
-
FOREIGN KEY(notebook_id) REFERENCES notebooks(id) ON DELETE CASCADE
|
|
102
|
-
)
|
|
103
|
-
`).run();
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
console.error('[StructuredMemory] initTables failed:', error);
|
|
107
|
-
throw error; // Rethrow to stop startup if DB is critical
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
migrateUserProfileV3() {
|
|
111
|
-
try {
|
|
112
|
-
const columns = this.db.prepare('PRAGMA table_info(user_profile)').all();
|
|
113
|
-
// If old table doesn't exist, we are good (new install will use v3)
|
|
114
|
-
// But wait, we created v3 table above.
|
|
115
|
-
// We need to check if we need to migrate from v1/v2 to v3.
|
|
116
|
-
// Strategy:
|
|
117
|
-
// 1. Check if 'user_profile' table exists.
|
|
118
|
-
// 2. If it does, read data, transform, insert into v3, drop old table.
|
|
119
|
-
const tableExists = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='user_profile'").get();
|
|
120
|
-
if (!tableExists)
|
|
121
|
-
return;
|
|
122
|
-
console.log('[StructuredMemory] Migrating user profile to V3...');
|
|
123
|
-
const rows = this.db.prepare('SELECT * FROM user_profile').all();
|
|
124
|
-
// Group by user_id
|
|
125
|
-
const users = {};
|
|
126
|
-
for (const row of rows) {
|
|
127
|
-
const uid = row.user_id || 'default';
|
|
128
|
-
if (!users[uid])
|
|
129
|
-
users[uid] = {};
|
|
130
|
-
if (row.key === 'name')
|
|
131
|
-
users[uid].name = row.value;
|
|
132
|
-
if (row.key === 'email')
|
|
133
|
-
users[uid].email = row.value;
|
|
134
|
-
}
|
|
135
|
-
const ins = this.db.prepare('INSERT OR REPLACE INTO user_profile_v3 (user_id, name, email, updated_at) VALUES (?, ?, ?, ?)');
|
|
136
|
-
const tx = this.db.transaction(() => {
|
|
137
|
-
for (const [uid, data] of Object.entries(users)) {
|
|
138
|
-
ins.run(uid, data.name || null, data.email || null, Date.now());
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
tx();
|
|
142
|
-
this.db.prepare('DROP TABLE user_profile').run();
|
|
143
|
-
this.db.prepare('DROP TABLE IF EXISTS user_profile_v2').run(); // Clean up intermediate v2 if any
|
|
144
|
-
}
|
|
145
|
-
catch (e) {
|
|
146
|
-
console.error('[StructuredMemory] Migration to V3 failed:', e);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
addFact(category, key, value, source = 'user') {
|
|
150
|
-
const stmt = this.db.prepare(`
|
|
151
|
-
INSERT INTO facts (category, key, value, source, timestamp)
|
|
152
|
-
VALUES (?, ?, ?, ?, ?)
|
|
153
|
-
`);
|
|
154
|
-
stmt.run(category, key, value, source, Date.now());
|
|
155
|
-
}
|
|
156
|
-
getFacts(category) {
|
|
157
|
-
if (category) {
|
|
158
|
-
return this.db.prepare('SELECT * FROM facts WHERE category = ?').all(category);
|
|
159
|
-
}
|
|
160
|
-
return this.db.prepare('SELECT * FROM facts').all();
|
|
161
|
-
}
|
|
162
|
-
updateUserProfile(userId, key, value) {
|
|
163
|
-
const uid = userId || 'default';
|
|
164
|
-
const now = Date.now();
|
|
165
|
-
// V3: Column based
|
|
166
|
-
if (key !== 'name' && key !== 'email') {
|
|
167
|
-
console.warn(`[StructuredMemory] Unknown profile key: ${key}`);
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
// Use dynamic SQL for specific column update
|
|
171
|
-
this.db.prepare(`
|
|
172
|
-
INSERT INTO user_profile_v3 (user_id, ${key}, updated_at)
|
|
173
|
-
VALUES (?, ?, ?)
|
|
174
|
-
ON CONFLICT(user_id) DO UPDATE SET ${key} = excluded.${key}, updated_at = excluded.updated_at
|
|
175
|
-
`).run(uid, value, now);
|
|
176
|
-
}
|
|
177
|
-
getUserProfile(userId) {
|
|
178
|
-
const row = this.db.prepare('SELECT * FROM user_profile_v3 WHERE user_id = ?').get(userId || 'default');
|
|
179
|
-
if (!row)
|
|
180
|
-
return {};
|
|
181
|
-
// Map back to object for compatibility
|
|
182
|
-
return {
|
|
183
|
-
name: row.name || '',
|
|
184
|
-
email: row.email || ''
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
// Simple Graph Operations
|
|
188
|
-
addEntity(name, type, metadata = {}) {
|
|
189
|
-
try {
|
|
190
|
-
this.db.prepare('INSERT INTO entities (name, type, metadata) VALUES (?, ?, ?)')
|
|
191
|
-
.run(name, type, JSON.stringify(metadata));
|
|
192
|
-
}
|
|
193
|
-
catch (e) {
|
|
194
|
-
// Ignore unique constraint violation, maybe update?
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
addRelation(sourceName, targetName, relation) {
|
|
198
|
-
const source = this.db.prepare('SELECT id FROM entities WHERE name = ?').get(sourceName);
|
|
199
|
-
const target = this.db.prepare('SELECT id FROM entities WHERE name = ?').get(targetName);
|
|
200
|
-
if (source && target) {
|
|
201
|
-
this.db.prepare('INSERT INTO relations (source_id, target_id, relation) VALUES (?, ?, ?)')
|
|
202
|
-
.run(source.id, target.id, relation);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
// --- Projects ---
|
|
206
|
-
createProject(id, name, description, departments, members, directory) {
|
|
207
|
-
const now = Date.now();
|
|
208
|
-
this.db.prepare(`
|
|
209
|
-
INSERT INTO projects (id, name, description, departments, members, directory, created_at, updated_at)
|
|
210
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
211
|
-
`).run(id, name, description || null, departments || null, members || null, directory || null, now, now);
|
|
212
|
-
}
|
|
213
|
-
getProject(id) {
|
|
214
|
-
const row = this.db.prepare('SELECT * FROM projects WHERE id = ?').get(id);
|
|
215
|
-
if (!row)
|
|
216
|
-
return null;
|
|
217
|
-
return {
|
|
218
|
-
id: row.id,
|
|
219
|
-
name: row.name,
|
|
220
|
-
description: row.description,
|
|
221
|
-
departments: row.departments,
|
|
222
|
-
members: row.members,
|
|
223
|
-
directory: row.directory,
|
|
224
|
-
createdAt: row.created_at,
|
|
225
|
-
updatedAt: row.updated_at
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
updateProject(id, patch) {
|
|
229
|
-
const current = this.getProject(id);
|
|
230
|
-
if (!current)
|
|
231
|
-
throw new Error('Project not found');
|
|
232
|
-
const next = { ...current, ...patch };
|
|
233
|
-
const now = Date.now();
|
|
234
|
-
this.db.prepare(`
|
|
235
|
-
UPDATE projects SET name = ?, description = ?, departments = ?, members = ?, directory = ?, updated_at = ?
|
|
236
|
-
WHERE id = ?
|
|
237
|
-
`).run(next.name, next.description || null, next.departments || null, next.members || null, next.directory || null, now, id);
|
|
238
|
-
}
|
|
239
|
-
deleteProject(id) {
|
|
240
|
-
this.db.prepare('DELETE FROM projects WHERE id = ?').run(id);
|
|
241
|
-
}
|
|
242
|
-
listProjects() {
|
|
243
|
-
const rows = this.db.prepare('SELECT * FROM projects ORDER BY updated_at DESC').all();
|
|
244
|
-
return rows.map(row => ({
|
|
245
|
-
id: row.id,
|
|
246
|
-
name: row.name,
|
|
247
|
-
description: row.description,
|
|
248
|
-
departments: row.departments,
|
|
249
|
-
members: row.members,
|
|
250
|
-
directory: row.directory,
|
|
251
|
-
createdAt: row.created_at,
|
|
252
|
-
updatedAt: row.updated_at
|
|
253
|
-
}));
|
|
254
|
-
}
|
|
255
|
-
// --- Notebooks ---
|
|
256
|
-
createNotebook(id, name, description, keywords) {
|
|
257
|
-
const now = Date.now();
|
|
258
|
-
this.db.prepare(`
|
|
259
|
-
INSERT INTO notebooks (id, name, description, keywords, created_at, updated_at)
|
|
260
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
261
|
-
`).run(id, name, description || null, keywords || null, now, now);
|
|
262
|
-
}
|
|
263
|
-
getNotebook(id) {
|
|
264
|
-
const row = this.db.prepare('SELECT * FROM notebooks WHERE id = ?').get(id);
|
|
265
|
-
if (!row)
|
|
266
|
-
return null;
|
|
267
|
-
return {
|
|
268
|
-
id: row.id,
|
|
269
|
-
name: row.name,
|
|
270
|
-
description: row.description,
|
|
271
|
-
keywords: row.keywords,
|
|
272
|
-
createdAt: row.created_at,
|
|
273
|
-
updatedAt: row.updated_at
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
updateNotebook(id, patch) {
|
|
277
|
-
const current = this.getNotebook(id);
|
|
278
|
-
if (!current)
|
|
279
|
-
throw new Error('Notebook not found');
|
|
280
|
-
const next = { ...current, ...patch };
|
|
281
|
-
const now = Date.now();
|
|
282
|
-
this.db.prepare(`
|
|
283
|
-
UPDATE notebooks SET name = ?, description = ?, keywords = ?, updated_at = ?
|
|
284
|
-
WHERE id = ?
|
|
285
|
-
`).run(next.name, next.description || null, next.keywords || null, now, id);
|
|
286
|
-
}
|
|
287
|
-
deleteNotebook(id) {
|
|
288
|
-
this.db.prepare('DELETE FROM notebooks WHERE id = ?').run(id);
|
|
289
|
-
}
|
|
290
|
-
listNotebooks() {
|
|
291
|
-
const rows = this.db.prepare('SELECT * FROM notebooks ORDER BY updated_at DESC').all();
|
|
292
|
-
return rows.map(row => ({
|
|
293
|
-
id: row.id,
|
|
294
|
-
name: row.name,
|
|
295
|
-
description: row.description,
|
|
296
|
-
keywords: row.keywords,
|
|
297
|
-
createdAt: row.created_at,
|
|
298
|
-
updatedAt: row.updated_at
|
|
299
|
-
}));
|
|
300
|
-
}
|
|
301
|
-
// --- Notes ---
|
|
302
|
-
createNote(id, notebookId, title, content, isTodo = false, done = false) {
|
|
303
|
-
const now = Date.now();
|
|
304
|
-
this.db.prepare(`
|
|
305
|
-
INSERT INTO notes (id, notebook_id, title, content, is_todo, done, created_at, updated_at)
|
|
306
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
307
|
-
`).run(id, notebookId, title, content || null, isTodo ? 1 : 0, done ? 1 : 0, now, now);
|
|
308
|
-
}
|
|
309
|
-
getNote(id) {
|
|
310
|
-
const row = this.db.prepare('SELECT * FROM notes WHERE id = ?').get(id);
|
|
311
|
-
if (!row)
|
|
312
|
-
return null;
|
|
313
|
-
return {
|
|
314
|
-
id: row.id,
|
|
315
|
-
notebookId: row.notebook_id,
|
|
316
|
-
title: row.title,
|
|
317
|
-
content: row.content,
|
|
318
|
-
isTodo: !!row.is_todo,
|
|
319
|
-
done: !!row.done,
|
|
320
|
-
createdAt: row.created_at,
|
|
321
|
-
updatedAt: row.updated_at
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
updateNote(id, patch) {
|
|
325
|
-
const current = this.getNote(id);
|
|
326
|
-
if (!current)
|
|
327
|
-
throw new Error('Note not found');
|
|
328
|
-
const next = { ...current, ...patch };
|
|
329
|
-
const now = Date.now();
|
|
330
|
-
this.db.prepare(`
|
|
331
|
-
UPDATE notes SET title = ?, content = ?, is_todo = ?, done = ?, updated_at = ?
|
|
332
|
-
WHERE id = ?
|
|
333
|
-
`).run(next.title, next.content || null, next.isTodo ? 1 : 0, next.done ? 1 : 0, now, id);
|
|
334
|
-
}
|
|
335
|
-
deleteNote(id) {
|
|
336
|
-
this.db.prepare('DELETE FROM notes WHERE id = ?').run(id);
|
|
337
|
-
}
|
|
338
|
-
listNotes(notebookId) {
|
|
339
|
-
let rows;
|
|
340
|
-
if (notebookId) {
|
|
341
|
-
rows = this.db.prepare('SELECT * FROM notes WHERE notebook_id = ? ORDER BY created_at DESC').all(notebookId);
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
rows = this.db.prepare('SELECT * FROM notes ORDER BY created_at DESC').all();
|
|
345
|
-
}
|
|
346
|
-
return rows.map(row => ({
|
|
347
|
-
id: row.id,
|
|
348
|
-
notebookId: row.notebook_id,
|
|
349
|
-
title: row.title,
|
|
350
|
-
content: row.content,
|
|
351
|
-
isTodo: !!row.is_todo,
|
|
352
|
-
done: !!row.done,
|
|
353
|
-
createdAt: row.created_at,
|
|
354
|
-
updatedAt: row.updated_at
|
|
355
|
-
}));
|
|
356
|
-
}
|
|
357
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import * as lancedb from '@lancedb/lancedb';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import fs from 'fs-extra';
|
|
4
|
-
import { config } from '../../config/loader.js';
|
|
5
|
-
import { createOpenAIClient } from '../../llm/openai.js';
|
|
6
|
-
const VECTOR_DB_DIR = path.resolve(process.cwd(), 'data/lancedb');
|
|
7
|
-
export class VectorMemory {
|
|
8
|
-
constructor() {
|
|
9
|
-
this.openai = null;
|
|
10
|
-
this.isInitialized = false;
|
|
11
|
-
fs.ensureDirSync(VECTOR_DB_DIR);
|
|
12
|
-
this.updateClient();
|
|
13
|
-
this.initDB();
|
|
14
|
-
}
|
|
15
|
-
updateClient() {
|
|
16
|
-
this.openai = config.llm.apiKey ? createOpenAIClient(config.llm) : null;
|
|
17
|
-
}
|
|
18
|
-
static getInstance() {
|
|
19
|
-
if (!VectorMemory.instance) {
|
|
20
|
-
VectorMemory.instance = new VectorMemory();
|
|
21
|
-
}
|
|
22
|
-
return VectorMemory.instance;
|
|
23
|
-
}
|
|
24
|
-
async initDB() {
|
|
25
|
-
try {
|
|
26
|
-
this.db = await lancedb.connect(VECTOR_DB_DIR);
|
|
27
|
-
// Ensure table exists
|
|
28
|
-
const tableNames = await this.db.tableNames();
|
|
29
|
-
if (!tableNames.includes('memories')) {
|
|
30
|
-
// Create table with dummy data to define schema, then delete it
|
|
31
|
-
// LanceDB schema inference is based on data
|
|
32
|
-
const dummyData = [{
|
|
33
|
-
id: 'init',
|
|
34
|
-
text: 'init',
|
|
35
|
-
vector: Array(1536).fill(0), // OpenAI embedding dimension
|
|
36
|
-
metadata: { type: 'recent', timestamp: Date.now(), sessionId: 'init', tags: '' }
|
|
37
|
-
}];
|
|
38
|
-
this.table = await this.db.createTable('memories', dummyData);
|
|
39
|
-
// await this.table.delete('id = "init"'); // Older lancedb syntax might differ
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
this.table = await this.db.openTable('memories');
|
|
43
|
-
}
|
|
44
|
-
this.isInitialized = true;
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
console.error('Failed to init LanceDB:', error);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
async getEmbedding(text) {
|
|
51
|
-
if (!this.openai) {
|
|
52
|
-
console.warn('OpenAI client not initialized (missing API Key), skipping embedding.');
|
|
53
|
-
return Array(1536).fill(0);
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
// Use OpenAI compatible embedding endpoint
|
|
57
|
-
// Note: Some compatible providers might use different models/dimensions.
|
|
58
|
-
// Default to text-embedding-ada-002 or similar standard.
|
|
59
|
-
const model = config.llm.embeddingModel || 'text-embedding-ada-002';
|
|
60
|
-
const response = await this.openai.embeddings.create({
|
|
61
|
-
model: model,
|
|
62
|
-
input: text,
|
|
63
|
-
});
|
|
64
|
-
return response.data[0].embedding;
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
console.error('Embedding failed:', error);
|
|
68
|
-
// Fallback: return zero vector or throw?
|
|
69
|
-
// For MVP robustness, return zeros if embedding fails (search won't work but app won't crash)
|
|
70
|
-
return Array(1536).fill(0);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
async addMemory(text, type, metadata = {}) {
|
|
74
|
-
if (!this.isInitialized)
|
|
75
|
-
await this.initDB();
|
|
76
|
-
if (!this.table)
|
|
77
|
-
return; // DB failed to init
|
|
78
|
-
const vector = await this.getEmbedding(text);
|
|
79
|
-
const memory = {
|
|
80
|
-
id: Math.random().toString(36).substring(7),
|
|
81
|
-
text,
|
|
82
|
-
vector,
|
|
83
|
-
metadata: {
|
|
84
|
-
type: type,
|
|
85
|
-
timestamp: Date.now(),
|
|
86
|
-
...metadata
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
try {
|
|
90
|
-
await this.table.add([memory]);
|
|
91
|
-
}
|
|
92
|
-
catch (e) {
|
|
93
|
-
console.error('Failed to add memory to vector db:', e);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async search(query, type, limit = 5) {
|
|
97
|
-
if (!this.isInitialized)
|
|
98
|
-
await this.initDB();
|
|
99
|
-
if (!this.table)
|
|
100
|
-
return [];
|
|
101
|
-
const vector = await this.getEmbedding(query);
|
|
102
|
-
// Check if table supports search
|
|
103
|
-
if (this.table.search) {
|
|
104
|
-
let search = this.table.search(vector).limit(limit);
|
|
105
|
-
if (type) {
|
|
106
|
-
try {
|
|
107
|
-
// Using simpler filter to avoid Dictionary type issues
|
|
108
|
-
// search = search.where(`metadata.type = '${type}'`);
|
|
109
|
-
// Skip filtering for now to avoid lancedb issues
|
|
110
|
-
}
|
|
111
|
-
catch (e) {
|
|
112
|
-
console.warn('Filter not supported in this LanceDB version/mode, filtering manually');
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
try {
|
|
116
|
-
const results = await search.execute();
|
|
117
|
-
return results.map((r) => ({
|
|
118
|
-
id: r.id,
|
|
119
|
-
text: r.text,
|
|
120
|
-
vector: r.vector,
|
|
121
|
-
metadata: r.metadata
|
|
122
|
-
}));
|
|
123
|
-
}
|
|
124
|
-
catch (e) {
|
|
125
|
-
console.error('Vector search failed:', e);
|
|
126
|
-
return [];
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return [];
|
|
130
|
-
}
|
|
131
|
-
async getMemoriesOlderThan(maxAgeDays) {
|
|
132
|
-
return []; // Disable for now to avoid build errors with older lancedb types
|
|
133
|
-
}
|
|
134
|
-
async pruneOldMemories(maxAgeDays) {
|
|
135
|
-
return 0; // Disable for now
|
|
136
|
-
}
|
|
137
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { skillRegistry } from '../skills/registry.js';
|
|
4
|
-
export class PluginManager {
|
|
5
|
-
constructor(pluginDir) {
|
|
6
|
-
this.plugins = new Map();
|
|
7
|
-
this.channels = [];
|
|
8
|
-
this.pluginDir = pluginDir;
|
|
9
|
-
}
|
|
10
|
-
getChannels() {
|
|
11
|
-
return this.channels;
|
|
12
|
-
}
|
|
13
|
-
createContext(pluginName) {
|
|
14
|
-
return {
|
|
15
|
-
registerSkill: (skill) => {
|
|
16
|
-
console.log(`[PluginManager] Plugin '${pluginName}' registered skill: ${skill.name}`);
|
|
17
|
-
// Inject plugin name into skill for tracking if needed
|
|
18
|
-
skill.pluginName = pluginName;
|
|
19
|
-
skillRegistry.register(skill);
|
|
20
|
-
},
|
|
21
|
-
registerChannel: (channel) => {
|
|
22
|
-
console.log(`[PluginManager] Plugin '${pluginName}' registered channel: ${channel.name}`);
|
|
23
|
-
this.channels.push(channel);
|
|
24
|
-
// Note: Channels registered after app start might need manual starting if the app is already running.
|
|
25
|
-
// For simplicity in this MVP, we assume plugins are loaded at startup.
|
|
26
|
-
},
|
|
27
|
-
logger: {
|
|
28
|
-
info: (msg) => console.log(`[Plugin:${pluginName}] ${msg}`),
|
|
29
|
-
warn: (msg) => console.warn(`[Plugin:${pluginName}] ${msg}`),
|
|
30
|
-
error: (msg) => console.error(`[Plugin:${pluginName}] ${msg}`),
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
async loadPlugins() {
|
|
35
|
-
try {
|
|
36
|
-
// 1. Load built-in plugins (migrated skills) from local src/plugins
|
|
37
|
-
const internalPluginDir = path.resolve(__dirname, '../plugins');
|
|
38
|
-
await this.scanAndLoad(internalPluginDir);
|
|
39
|
-
// 2. Load user plugins from configured plugin directory
|
|
40
|
-
// Only if it's different from internal directory to avoid duplicates
|
|
41
|
-
if (path.resolve(this.pluginDir) !== internalPluginDir) {
|
|
42
|
-
await this.scanAndLoad(this.pluginDir);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
console.error('[PluginManager] Error loading plugins:', error);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
async scanAndLoad(dir) {
|
|
50
|
-
try {
|
|
51
|
-
await fs.access(dir);
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return; // Directory doesn't exist, skip
|
|
55
|
-
}
|
|
56
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
57
|
-
for (const entry of entries) {
|
|
58
|
-
if (entry.isDirectory()) {
|
|
59
|
-
await this.loadPluginFromDir(path.join(dir, entry.name));
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
getPlugins() {
|
|
64
|
-
return Array.from(this.plugins.values()).map(p => ({
|
|
65
|
-
name: p.metadata.name,
|
|
66
|
-
version: p.metadata.version,
|
|
67
|
-
description: p.metadata.description,
|
|
68
|
-
author: p.metadata.author,
|
|
69
|
-
enabled: true // Todo: Implement enable/disable persistence
|
|
70
|
-
}));
|
|
71
|
-
}
|
|
72
|
-
async loadPluginFromDir(dirPath) {
|
|
73
|
-
try {
|
|
74
|
-
// Check for package.json or index.js/ts
|
|
75
|
-
const pkgPath = path.join(dirPath, 'package.json');
|
|
76
|
-
const indexPath = path.join(dirPath, 'index.js'); // Assuming compiled JS for now, or ts-node handling
|
|
77
|
-
let entryPoint = indexPath;
|
|
78
|
-
// Try to read package.json main field
|
|
79
|
-
try {
|
|
80
|
-
const pkgContent = await fs.readFile(pkgPath, 'utf-8');
|
|
81
|
-
const pkg = JSON.parse(pkgContent);
|
|
82
|
-
if (pkg.main) {
|
|
83
|
-
entryPoint = path.join(dirPath, pkg.main);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
catch (e) {
|
|
87
|
-
// No package.json, assume index.js
|
|
88
|
-
}
|
|
89
|
-
// Dynamic import
|
|
90
|
-
// Note: In a real-world scenario, we might need to handle CJS/ESM compatibility carefully.
|
|
91
|
-
// Here we assume the plugin exports a default object implementing the Plugin interface.
|
|
92
|
-
// Ensure we are importing a file that exists
|
|
93
|
-
try {
|
|
94
|
-
await fs.access(entryPoint);
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
// Try .ts if .js doesn't exist (for dev mode with tsx/ts-node)
|
|
98
|
-
if (entryPoint.endsWith('.js')) {
|
|
99
|
-
const tsEntryPoint = entryPoint.replace(/\.js$/, '.ts');
|
|
100
|
-
try {
|
|
101
|
-
await fs.access(tsEntryPoint);
|
|
102
|
-
entryPoint = tsEntryPoint;
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
// console.warn(`[PluginManager] Could not find entry point for plugin at ${dirPath}`);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
const pluginModule = await import(entryPoint);
|
|
114
|
-
const plugin = pluginModule.default || pluginModule;
|
|
115
|
-
if (!plugin.metadata || !plugin.onLoad) {
|
|
116
|
-
console.warn(`[PluginManager] Invalid plugin structure in ${dirPath}`);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
console.log(`[PluginManager] Loading plugin: ${plugin.metadata.name} (${plugin.metadata.version})`);
|
|
120
|
-
const context = this.createContext(plugin.metadata.name);
|
|
121
|
-
await plugin.onLoad(context);
|
|
122
|
-
this.plugins.set(plugin.metadata.name, plugin);
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
console.error(`[PluginManager] Failed to load plugin from ${dirPath}:`, error);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|