xiaozuoassistant 0.2.7 → 0.2.9

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.
Files changed (54) hide show
  1. package/dist/client/assets/browser-ponyfill-9G8TIH9Q.js +2 -0
  2. package/dist/client/assets/index-BFCq_nf5.js +201 -0
  3. package/dist/client/assets/index-u0lXmgyZ.css +1 -0
  4. package/dist/client/favicon.svg +4 -0
  5. package/dist/client/index.html +14 -0
  6. package/dist/client/locales/en/translation.json +110 -0
  7. package/dist/client/locales/zh/translation.json +112 -0
  8. package/dist/server/agents/office.js +23 -0
  9. package/dist/server/app.js +50 -0
  10. package/dist/server/channels/base-channel.js +23 -0
  11. package/dist/server/channels/create-channels.js +18 -0
  12. package/dist/server/channels/dingtalk.js +83 -0
  13. package/dist/server/channels/feishu.js +108 -0
  14. package/dist/server/channels/telegram.js +53 -0
  15. package/dist/server/channels/terminal.js +49 -0
  16. package/dist/server/channels/web.js +66 -0
  17. package/dist/server/channels/wechat.js +107 -0
  18. package/dist/server/config/loader.js +96 -0
  19. package/dist/server/config/paths.js +24 -0
  20. package/dist/server/config/prompts.js +12 -0
  21. package/dist/server/core/agents/manager.js +27 -0
  22. package/dist/server/core/agents/runtime.js +92 -0
  23. package/dist/server/core/brain.js +255 -0
  24. package/dist/server/core/event-bus.js +24 -0
  25. package/dist/server/core/logger.js +71 -0
  26. package/dist/server/core/memories/manager.js +238 -0
  27. package/dist/server/core/memories/short-term.js +512 -0
  28. package/dist/server/core/memories/structured.js +357 -0
  29. package/dist/server/core/memories/vector.js +137 -0
  30. package/dist/server/core/memory.js +2 -0
  31. package/dist/server/core/plugin-manager.js +128 -0
  32. package/dist/server/core/plugin.js +1 -0
  33. package/dist/server/core/scheduler.js +85 -0
  34. package/dist/server/core/task-queue.js +104 -0
  35. package/dist/server/core/types.js +1 -0
  36. package/dist/server/index.js +862 -0
  37. package/dist/server/llm/openai.js +23 -0
  38. package/dist/server/plugins/core-skills/src/create-agent.js +58 -0
  39. package/dist/server/plugins/core-skills/src/delegate.js +39 -0
  40. package/dist/server/plugins/core-skills/src/file-system.js +142 -0
  41. package/dist/server/plugins/core-skills/src/index.js +26 -0
  42. package/dist/server/plugins/core-skills/src/list-agents.js +24 -0
  43. package/dist/server/plugins/core-skills/src/search.js +31 -0
  44. package/dist/server/plugins/core-skills/src/system-time.js +27 -0
  45. package/dist/server/plugins/office-skills/src/index.js +19 -0
  46. package/dist/server/plugins/office-skills/src/office-excel.js +84 -0
  47. package/dist/server/plugins/office-skills/src/office-ppt.js +58 -0
  48. package/dist/server/plugins/office-skills/src/office-word.js +90 -0
  49. package/dist/server/routes/auth.js +28 -0
  50. package/dist/server/server/create-http.js +22 -0
  51. package/dist/server/server.js +29 -0
  52. package/dist/server/skills/base-skill.js +20 -0
  53. package/dist/server/skills/registry.js +52 -0
  54. package/package.json +1 -1
@@ -0,0 +1,357 @@
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
+ }
@@ -0,0 +1,137 @@
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
+ }
@@ -0,0 +1,2 @@
1
+ import { memoryManager } from './memories/manager.js';
2
+ export const memory = memoryManager;
@@ -0,0 +1,128 @@
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
+ }
@@ -0,0 +1 @@
1
+ export {};