xiaozuoassistant 0.1.41

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 (56) hide show
  1. package/README.md +90 -0
  2. package/bin/cli.js +429 -0
  3. package/config.json +36 -0
  4. package/dist/client/assets/browser-ponyfill-DNlTAU2D.js +2 -0
  5. package/dist/client/assets/index-BPATxdcV.js +153 -0
  6. package/dist/client/assets/index-CgL5gMVL.css +1 -0
  7. package/dist/client/favicon.svg +4 -0
  8. package/dist/client/index.html +354 -0
  9. package/dist/client/locales/en/translation.json +77 -0
  10. package/dist/client/locales/zh/translation.json +77 -0
  11. package/dist/server/agents/office.js +23 -0
  12. package/dist/server/app.js +50 -0
  13. package/dist/server/channels/base-channel.js +13 -0
  14. package/dist/server/channels/create-channels.js +18 -0
  15. package/dist/server/channels/dingtalk.js +83 -0
  16. package/dist/server/channels/feishu.js +95 -0
  17. package/dist/server/channels/telegram.js +53 -0
  18. package/dist/server/channels/terminal.js +49 -0
  19. package/dist/server/channels/web.js +45 -0
  20. package/dist/server/channels/wechat.js +107 -0
  21. package/dist/server/config/loader.js +73 -0
  22. package/dist/server/config/prompts.js +12 -0
  23. package/dist/server/core/agents/manager.js +22 -0
  24. package/dist/server/core/agents/runtime.js +85 -0
  25. package/dist/server/core/brain.js +131 -0
  26. package/dist/server/core/event-bus.js +24 -0
  27. package/dist/server/core/logger.js +71 -0
  28. package/dist/server/core/memories/manager.js +115 -0
  29. package/dist/server/core/memories/short-term.js +128 -0
  30. package/dist/server/core/memories/structured.js +109 -0
  31. package/dist/server/core/memories/vector.js +138 -0
  32. package/dist/server/core/memory.js +2 -0
  33. package/dist/server/core/plugin-manager.js +112 -0
  34. package/dist/server/core/plugin.js +1 -0
  35. package/dist/server/core/scheduler.js +24 -0
  36. package/dist/server/core/types.js +1 -0
  37. package/dist/server/index.js +318 -0
  38. package/dist/server/llm/openai.js +23 -0
  39. package/dist/server/routes/auth.js +28 -0
  40. package/dist/server/server/create-http.js +17 -0
  41. package/dist/server/server.js +29 -0
  42. package/dist/server/skills/base-skill.js +16 -0
  43. package/dist/server/skills/create-agent.js +58 -0
  44. package/dist/server/skills/delegate.js +39 -0
  45. package/dist/server/skills/file-system.js +137 -0
  46. package/dist/server/skills/list-agents.js +24 -0
  47. package/dist/server/skills/office-excel.js +84 -0
  48. package/dist/server/skills/office-ppt.js +58 -0
  49. package/dist/server/skills/office-word.js +90 -0
  50. package/dist/server/skills/registry.js +27 -0
  51. package/dist/server/skills/search.js +31 -0
  52. package/dist/server/skills/system-time.js +27 -0
  53. package/package.json +116 -0
  54. package/public/favicon.svg +4 -0
  55. package/public/locales/en/translation.json +77 -0
  56. package/public/locales/zh/translation.json +77 -0
@@ -0,0 +1,138 @@
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
+ // For DashScope/Qwen, we might need 'text-embedding-v1' or similar if 'text-embedding-v3-small' fails.
60
+ // We'll try to use a more standard one or catch the error.
61
+ const response = await this.openai.embeddings.create({
62
+ model: 'text-embedding-v1', // Common alias in compatible APIs, or use config if available
63
+ input: text,
64
+ });
65
+ return response.data[0].embedding;
66
+ }
67
+ catch (error) {
68
+ console.error('Embedding failed:', error);
69
+ // Fallback: return zero vector or throw?
70
+ // For MVP robustness, return zeros if embedding fails (search won't work but app won't crash)
71
+ return Array(1536).fill(0);
72
+ }
73
+ }
74
+ async addMemory(text, type, metadata = {}) {
75
+ if (!this.isInitialized)
76
+ await this.initDB();
77
+ if (!this.table)
78
+ return; // DB failed to init
79
+ const vector = await this.getEmbedding(text);
80
+ const memory = {
81
+ id: Math.random().toString(36).substring(7),
82
+ text,
83
+ vector,
84
+ metadata: {
85
+ type,
86
+ timestamp: Date.now(),
87
+ ...metadata
88
+ }
89
+ };
90
+ try {
91
+ await this.table.add([memory]);
92
+ }
93
+ catch (e) {
94
+ console.error('Failed to add memory to vector db:', e);
95
+ }
96
+ }
97
+ async search(query, type, limit = 5) {
98
+ if (!this.isInitialized)
99
+ await this.initDB();
100
+ if (!this.table)
101
+ return [];
102
+ const vector = await this.getEmbedding(query);
103
+ // Check if table supports search
104
+ if (this.table.search) {
105
+ let search = this.table.search(vector).limit(limit);
106
+ if (type) {
107
+ try {
108
+ // Using simpler filter to avoid Dictionary type issues
109
+ // search = search.where(`metadata.type = '${type}'`);
110
+ // Skip filtering for now to avoid lancedb issues
111
+ }
112
+ catch (e) {
113
+ console.warn('Filter not supported in this LanceDB version/mode, filtering manually');
114
+ }
115
+ }
116
+ try {
117
+ const results = await search.execute();
118
+ return results.map((r) => ({
119
+ id: r.id,
120
+ text: r.text,
121
+ vector: r.vector,
122
+ metadata: r.metadata
123
+ }));
124
+ }
125
+ catch (e) {
126
+ console.error('Vector search failed:', e);
127
+ return [];
128
+ }
129
+ }
130
+ return [];
131
+ }
132
+ async getMemoriesOlderThan(maxAgeDays) {
133
+ return []; // Disable for now to avoid build errors with older lancedb types
134
+ }
135
+ async pruneOldMemories(maxAgeDays) {
136
+ return 0; // Disable for now
137
+ }
138
+ }
@@ -0,0 +1,2 @@
1
+ import { memoryManager } from './memories/manager.js';
2
+ export const memory = memoryManager;
@@ -0,0 +1,112 @@
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
+ // Ensure plugin directory exists
37
+ try {
38
+ await fs.access(this.pluginDir);
39
+ }
40
+ catch {
41
+ console.log(`[PluginManager] Plugin directory ${this.pluginDir} does not exist. Creating...`);
42
+ await fs.mkdir(this.pluginDir, { recursive: true });
43
+ return;
44
+ }
45
+ const entries = await fs.readdir(this.pluginDir, { withFileTypes: true });
46
+ for (const entry of entries) {
47
+ if (entry.isDirectory()) {
48
+ await this.loadPluginFromDir(path.join(this.pluginDir, entry.name));
49
+ }
50
+ }
51
+ }
52
+ catch (error) {
53
+ console.error('[PluginManager] Error loading plugins:', error);
54
+ }
55
+ }
56
+ async loadPluginFromDir(dirPath) {
57
+ try {
58
+ // Check for package.json or index.js/ts
59
+ const pkgPath = path.join(dirPath, 'package.json');
60
+ const indexPath = path.join(dirPath, 'index.js'); // Assuming compiled JS for now, or ts-node handling
61
+ let entryPoint = indexPath;
62
+ // Try to read package.json main field
63
+ try {
64
+ const pkgContent = await fs.readFile(pkgPath, 'utf-8');
65
+ const pkg = JSON.parse(pkgContent);
66
+ if (pkg.main) {
67
+ entryPoint = path.join(dirPath, pkg.main);
68
+ }
69
+ }
70
+ catch (e) {
71
+ // No package.json, assume index.js
72
+ }
73
+ // Dynamic import
74
+ // Note: In a real-world scenario, we might need to handle CJS/ESM compatibility carefully.
75
+ // Here we assume the plugin exports a default object implementing the Plugin interface.
76
+ // Ensure we are importing a file that exists
77
+ try {
78
+ await fs.access(entryPoint);
79
+ }
80
+ catch {
81
+ // Try .ts if .js doesn't exist (for dev mode with tsx/ts-node)
82
+ if (entryPoint.endsWith('.js')) {
83
+ const tsEntryPoint = entryPoint.replace(/\.js$/, '.ts');
84
+ try {
85
+ await fs.access(tsEntryPoint);
86
+ entryPoint = tsEntryPoint;
87
+ }
88
+ catch {
89
+ // console.warn(`[PluginManager] Could not find entry point for plugin at ${dirPath}`);
90
+ return;
91
+ }
92
+ }
93
+ else {
94
+ return;
95
+ }
96
+ }
97
+ const pluginModule = await import(entryPoint);
98
+ const plugin = pluginModule.default || pluginModule;
99
+ if (!plugin.metadata || !plugin.onLoad) {
100
+ console.warn(`[PluginManager] Invalid plugin structure in ${dirPath}`);
101
+ return;
102
+ }
103
+ console.log(`[PluginManager] Loading plugin: ${plugin.metadata.name} (${plugin.metadata.version})`);
104
+ const context = this.createContext(plugin.metadata.name);
105
+ await plugin.onLoad(context);
106
+ this.plugins.set(plugin.metadata.name, plugin);
107
+ }
108
+ catch (error) {
109
+ console.error(`[PluginManager] Failed to load plugin from ${dirPath}:`, error);
110
+ }
111
+ }
112
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import cron from 'node-cron';
2
+ import { memoryManager } from './memories/manager.js';
3
+ import { config } from '../config/loader.js';
4
+ let cronTask = null;
5
+ export const initScheduler = () => {
6
+ // Stop existing task if any
7
+ if (cronTask) {
8
+ cronTask.stop();
9
+ cronTask = null;
10
+ }
11
+ // Default: Every day at midnight
12
+ const cronSchedule = config.scheduler?.memoryMaintenanceCron || '0 0 * * *';
13
+ console.log(`[Scheduler] Initializing memory maintenance job with schedule: "${cronSchedule}"`);
14
+ // Only run immediately on initial startup, not every restart (optional)
15
+ // Or we can assume restart means config changed, so maybe we don't need to run immediately?
16
+ // Let's keep it simple: only schedule the cron job. Immediate run can be manual via API.
17
+ cronTask = cron.schedule(cronSchedule, () => {
18
+ memoryManager.runMaintenance();
19
+ });
20
+ };
21
+ export const runMaintenanceNow = () => {
22
+ console.log('[Scheduler] Manually triggering maintenance...');
23
+ memoryManager.runMaintenance();
24
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,318 @@
1
+ import { overrideConsole } from './core/logger.js'; // Import logger override
2
+ import express from 'express';
3
+ // Initialize logger override immediately to capture all logs
4
+ overrideConsole();
5
+ import { config, saveConfig } from './config/loader.js';
6
+ import { eventBus } from './core/event-bus.js';
7
+ import { brain } from './core/brain.js';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import fs from 'fs'; // Ensure fs is imported for sync operations like existsSync
11
+ import fsPromises from 'fs/promises'; // For async file operations if needed
12
+ import { memory } from './core/memory.js';
13
+ import { skillRegistry } from './skills/registry.js';
14
+ import { SystemTimeSkill } from './skills/system-time.js';
15
+ import { SearchSkill } from './skills/search.js';
16
+ import { ListDirectorySkill, ReadFileSkill, WriteFileSkill, DeleteFileSkill } from './skills/file-system.js';
17
+ import { PluginManager } from './core/plugin-manager.js';
18
+ import { initScheduler, runMaintenanceNow } from './core/scheduler.js';
19
+ import { agentManager } from './core/agents/manager.js';
20
+ import { createOfficeAgent } from './agents/office.js';
21
+ import { DelegateSkill } from './skills/delegate.js';
22
+ import { createChannels } from './channels/create-channels.js';
23
+ import { createHttpStack } from './server/create-http.js';
24
+ // 解决 ES Module 中没有 __dirname 的问题
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = path.dirname(__filename);
27
+ const { app, httpServer, io } = createHttpStack();
28
+ // Request logging middleware
29
+ app.use((req, res, next) => {
30
+ console.log(`[HTTP] ${req.method} ${req.url}`);
31
+ next();
32
+ });
33
+ // 静态文件服务
34
+ // 在生产环境中(dist/server/index.js),客户端构建文件通常位于 ../../dist/client 或 ../client
35
+ // 我们假设构建结构如下:
36
+ // dist/
37
+ // server/
38
+ // index.js
39
+ // client/
40
+ // index.html
41
+ // assets/
42
+ //
43
+ // 这样在 server/index.js 中,客户端路径应该是 path.join(__dirname, '../client')
44
+ const clientBuildPath = path.join(__dirname, '../client');
45
+ console.log(`[Server] Serving static files from: ${clientBuildPath}`);
46
+ // 或者在开发模式下,可能是不同的路径,但开发模式通常用 Vite 代理,所以这里主要关注生产环境。
47
+ app.use(express.static(clientBuildPath));
48
+ // API 路由
49
+ // ... existing routes ...
50
+ // 前端路由回退处理 (SPA 支持)
51
+ // 必须放在所有 API 路由之后
52
+ app.get('*', (req, res, next) => {
53
+ // 如果是 API 请求,跳过
54
+ if (req.path.startsWith('/api')) {
55
+ return next();
56
+ }
57
+ const indexPath = path.join(clientBuildPath, 'index.html');
58
+ if (fs.existsSync(indexPath)) { // 这里需要确保 fs 已导入,或者用 fs.access
59
+ res.sendFile(indexPath);
60
+ }
61
+ else {
62
+ // 如果找不到 index.html(例如开发模式只启动了后端),则返回简单的提示
63
+ res.send('xiaozuoAssistant backend is running. Frontend static files not found in ' + clientBuildPath);
64
+ }
65
+ });
66
+ // ... rest of the file ...
67
+ // API 路由
68
+ app.get('/api/health', (req, res) => {
69
+ res.json({ status: 'ok', message: 'xiaozuoAssistant backend is running' });
70
+ });
71
+ // 获取所有会话
72
+ app.get('/api/sessions', async (req, res) => {
73
+ const sessions = await memory.listSessions();
74
+ res.json(sessions);
75
+ });
76
+ // 获取会话详情
77
+ app.get('/api/sessions/:id', async (req, res) => {
78
+ const session = await memory.getSession(req.params.id);
79
+ if (session) {
80
+ res.json(session);
81
+ }
82
+ else {
83
+ res.status(404).json({ error: 'Session not found' });
84
+ }
85
+ });
86
+ // 创建新会话
87
+ app.post('/api/sessions', async (req, res) => {
88
+ const session = await memory.createSession();
89
+ res.json(session);
90
+ });
91
+ // 删除会话
92
+ app.delete('/api/sessions/:id', async (req, res) => {
93
+ await memory.deleteSession(req.params.id);
94
+ res.json({ status: 'success', message: 'Session deleted' });
95
+ });
96
+ // 文件选择接口 (使用系统默认文件对话框可能需要Electron,这里作为Web服务,我们提供目录列表供选择)
97
+ // 简单的目录列举接口
98
+ app.post('/api/fs/list', async (req, res) => {
99
+ try {
100
+ const dirPath = req.body.path || process.cwd();
101
+ // Use fsPromises for async operations which returns proper types for withFileTypes
102
+ const items = await fsPromises.readdir(dirPath, { withFileTypes: true });
103
+ const result = items.map(item => ({
104
+ name: item.name,
105
+ isDirectory: item.isDirectory(),
106
+ path: path.join(dirPath, item.name)
107
+ })).filter(item => item.isDirectory && !item.name.startsWith('.')); // 仅列出目录,忽略隐藏目录
108
+ res.json({
109
+ current: dirPath,
110
+ parent: path.dirname(dirPath),
111
+ items: result
112
+ });
113
+ }
114
+ catch (e) {
115
+ res.status(500).json({ error: e.message });
116
+ }
117
+ });
118
+ // 获取当前配置
119
+ app.get('/api/config', async (req, res) => {
120
+ res.json({
121
+ apiKey: config.llm.apiKey,
122
+ baseURL: config.llm.baseURL,
123
+ model: config.llm.model,
124
+ temperature: config.llm.temperature,
125
+ systemPrompt: config.systemPrompt,
126
+ memoryMaintenanceCron: config.scheduler?.memoryMaintenanceCron,
127
+ workspace: config.workspace
128
+ });
129
+ });
130
+ // 更新配置并写入 config.json
131
+ app.post('/api/config', async (req, res) => {
132
+ try {
133
+ const { apiKey, baseURL, model, temperature, systemPrompt, memoryMaintenanceCron, workspace } = req.body;
134
+ // 更新配置对象
135
+ const newConfig = { ...config };
136
+ if (apiKey !== undefined)
137
+ newConfig.llm.apiKey = apiKey;
138
+ if (baseURL !== undefined)
139
+ newConfig.llm.baseURL = baseURL;
140
+ if (model !== undefined)
141
+ newConfig.llm.model = model;
142
+ if (temperature !== undefined)
143
+ newConfig.llm.temperature = parseFloat(temperature);
144
+ if (systemPrompt !== undefined)
145
+ newConfig.systemPrompt = systemPrompt;
146
+ if (workspace !== undefined)
147
+ newConfig.workspace = workspace;
148
+ // Handle System Prompt update logic
149
+ // We only update systemPrompt automatically if workspace changed OR it's a new config
150
+ // But if user manually edited systemPrompt in the same request, we should respect that?
151
+ // The current frontend sends all fields.
152
+ // If workspace changed, we append/update it.
153
+ // Simplification: Always ensure the workspace is in the system prompt if configured.
154
+ if (newConfig.workspace) {
155
+ const workspaceLine = `Current Workspace: ${newConfig.workspace}`;
156
+ // If system prompt doesn't have it, append it.
157
+ if (!newConfig.systemPrompt) {
158
+ newConfig.systemPrompt = workspaceLine;
159
+ }
160
+ else if (!newConfig.systemPrompt.includes('Current Workspace:')) {
161
+ newConfig.systemPrompt += `\n${workspaceLine}`;
162
+ }
163
+ else {
164
+ // It has it, let's update it to match new workspace
165
+ // We use a regex to replace the line
166
+ newConfig.systemPrompt = newConfig.systemPrompt.replace(/Current Workspace: .*/, workspaceLine);
167
+ }
168
+ }
169
+ let restartScheduler = false;
170
+ if (memoryMaintenanceCron !== undefined) {
171
+ if (!newConfig.scheduler)
172
+ newConfig.scheduler = {};
173
+ if (newConfig.scheduler.memoryMaintenanceCron !== memoryMaintenanceCron) {
174
+ newConfig.scheduler.memoryMaintenanceCron = memoryMaintenanceCron;
175
+ restartScheduler = true;
176
+ }
177
+ }
178
+ // 保存到文件
179
+ saveConfig(newConfig);
180
+ // 如果 LLM 配置变了,更新 Brain
181
+ brain.updateClient();
182
+ // 如果定时任务配置变了,重启 Scheduler
183
+ if (restartScheduler) {
184
+ initScheduler();
185
+ }
186
+ res.json({ status: 'success', message: 'Config updated and saved to config.json' });
187
+ }
188
+ catch (error) {
189
+ res.status(500).json({ status: 'error', message: error.message });
190
+ }
191
+ });
192
+ // 手动触发记忆维护
193
+ app.post('/api/scheduler/run', async (req, res) => {
194
+ try {
195
+ runMaintenanceNow();
196
+ res.json({ status: 'success', message: 'Memory maintenance triggered.' });
197
+ }
198
+ catch (e) {
199
+ res.status(500).json({ status: 'error', message: e.message });
200
+ }
201
+ });
202
+ import { ReadWordSkill, CreateWordSkill } from './skills/office-word.js';
203
+ import { ReadExcelSkill, CreateExcelSkill } from './skills/office-excel.js';
204
+ import { CreatePptxSkill } from './skills/office-ppt.js';
205
+ import { CreateAgentSkill } from './skills/create-agent.js';
206
+ import { ListAgentsSkill } from './skills/list-agents.js';
207
+ // 初始化 Agents
208
+ agentManager.registerAgent(createOfficeAgent());
209
+ // 注册技能
210
+ skillRegistry.register(new CreateAgentSkill()); // 允许动态创建 Agent
211
+ skillRegistry.register(new ListAgentsSkill()); // 允许列出所有 Agent
212
+ skillRegistry.register(new DelegateSkill()); // 允许主 Brain 委派任务
213
+ skillRegistry.register(new SystemTimeSkill());
214
+ skillRegistry.register(new SearchSkill());
215
+ skillRegistry.register(new ListDirectorySkill());
216
+ skillRegistry.register(new ReadFileSkill());
217
+ skillRegistry.register(new WriteFileSkill());
218
+ skillRegistry.register(new DeleteFileSkill());
219
+ skillRegistry.register(new ReadWordSkill());
220
+ skillRegistry.register(new CreateWordSkill());
221
+ skillRegistry.register(new ReadExcelSkill());
222
+ skillRegistry.register(new CreateExcelSkill());
223
+ skillRegistry.register(new CreatePptxSkill());
224
+ // 初始化插件管理器
225
+ const pluginManager = new PluginManager(path.resolve(process.cwd(), 'plugins'));
226
+ await pluginManager.loadPlugins();
227
+ // 初始化通道
228
+ const channels = createChannels({ app, io, config, pluginManager });
229
+ // 启动通道并绑定事件
230
+ channels.forEach(channel => {
231
+ channel.start();
232
+ // 监听通道消息 -> 发送到 EventBus
233
+ channel.onMessage((sessionId, message) => {
234
+ eventBus.emitEvent({
235
+ type: 'message',
236
+ payload: { content: message },
237
+ channel: channel.name,
238
+ sessionId: sessionId,
239
+ timestamp: Date.now()
240
+ });
241
+ });
242
+ });
243
+ // 核心逻辑:监听 EventBus 消息 -> Brain 处理 -> 发回 EventBus
244
+ eventBus.onEvent('message', async (event) => {
245
+ const { sessionId, payload, channel: channelName } = event;
246
+ const content = payload.content;
247
+ try {
248
+ // 1. 获取会话上下文
249
+ let session = await memory.getSession(sessionId);
250
+ if (!session) {
251
+ session = await memory.createSession();
252
+ // 如果是新创建的会话,可能需要通知前端更新 ID(略复杂,暂且假设前端已获取 ID)
253
+ }
254
+ // 2. 记录用户消息
255
+ await memory.addMessage(sessionId, { role: 'user', content });
256
+ // 3. Brain 处理
257
+ // Use MemoryManager to retrieve full context (Short-term + Recent + Structured)
258
+ const context = await memory.getRelevantContext(content, sessionId);
259
+ // We pass the raw messages to processMessage for now, but Brain should ideally use the enhanced context.
260
+ // Let's modify Brain.processMessage to accept an optional context string or just prepend it.
261
+ // For this MVP, we will prepend the context to the system prompt or as a new system message.
262
+ // Or simpler: We just pass session.messages as before, but Brain needs to know about the context.
263
+ // Let's change how we call brain.processMessage.
264
+ // Construct a temporary history with enhanced context for the LLM
265
+ const enhancedSystemPrompt = `You are xiaozuoAssistant, a helpful AI assistant.
266
+ Here is some relevant context from your memory:
267
+ ${context}`;
268
+ // We need to inject this into the Brain.
269
+ // Since Brain.processMessage takes history, let's just pass the history but we need to tell Brain to use this system prompt.
270
+ // We can modify Brain to accept a system prompt override.
271
+ // Or we can just create a new method in Brain or pass it as an argument.
272
+ // Let's update Brain.processMessage signature in next step. For now, let's just pass the history.
273
+ const responseContent = await brain.processMessage(session.messages, content, enhancedSystemPrompt);
274
+ // 4. 记录 AI 响应
275
+ await memory.addMessage(sessionId, { role: 'assistant', content: responseContent });
276
+ // 5. 发送响应事件
277
+ eventBus.emitEvent({
278
+ type: 'response',
279
+ payload: { content: responseContent },
280
+ channel: channelName,
281
+ sessionId: sessionId,
282
+ timestamp: Date.now()
283
+ });
284
+ }
285
+ catch (error) {
286
+ console.error('Error processing message:', error);
287
+ eventBus.emitEvent({
288
+ type: 'error',
289
+ payload: { error: error.message },
290
+ channel: channelName,
291
+ sessionId: sessionId,
292
+ timestamp: Date.now()
293
+ });
294
+ }
295
+ });
296
+ // 监听 EventBus 响应 -> 发送给通道
297
+ eventBus.onEvent('response', (event) => {
298
+ const { sessionId, payload, channel: channelName } = event;
299
+ const channel = channels.find(c => c.name === channelName);
300
+ if (channel) {
301
+ channel.send(sessionId, payload.content);
302
+ }
303
+ });
304
+ eventBus.onEvent('error', (event) => {
305
+ const { sessionId, payload, channel: channelName } = event;
306
+ const channel = channels.find(c => c.name === channelName);
307
+ if (channel) {
308
+ channel.send(sessionId, `Error: ${payload.error}`);
309
+ }
310
+ });
311
+ // 启动记忆维护任务(自动清理过期记忆)
312
+ initScheduler();
313
+ // 启动服务器
314
+ const PORT = config.server.port;
315
+ httpServer.listen(PORT, () => {
316
+ console.log(`Server running on port ${PORT}`);
317
+ console.log(`Socket.IO server running`);
318
+ });
@@ -0,0 +1,23 @@
1
+ import OpenAI from 'openai';
2
+ export function resolveBaseURL(config) {
3
+ switch (config.provider) {
4
+ case 'deepseek':
5
+ return 'https://api.deepseek.com';
6
+ case 'minimax':
7
+ return 'https://api.minimax.chat/v1';
8
+ case 'doubao':
9
+ return 'https://ark.cn-beijing.volces.com/api/v3';
10
+ case 'qwen':
11
+ return 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1';
12
+ case 'custom':
13
+ case 'openai':
14
+ default:
15
+ return config.baseURL;
16
+ }
17
+ }
18
+ export function createOpenAIClient(config) {
19
+ return new OpenAI({
20
+ apiKey: config.apiKey || '',
21
+ baseURL: resolveBaseURL(config)
22
+ });
23
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * This is a user authentication API route demo.
3
+ * Handle user registration, login, token management, etc.
4
+ */
5
+ import { Router } from 'express';
6
+ const router = Router();
7
+ /**
8
+ * User Login
9
+ * POST /api/auth/register
10
+ */
11
+ router.post('/register', async (req, res) => {
12
+ // TODO: Implement register logic
13
+ });
14
+ /**
15
+ * User Login
16
+ * POST /api/auth/login
17
+ */
18
+ router.post('/login', async (req, res) => {
19
+ // TODO: Implement login logic
20
+ });
21
+ /**
22
+ * User Logout
23
+ * POST /api/auth/logout
24
+ */
25
+ router.post('/logout', async (req, res) => {
26
+ // TODO: Implement logout logic
27
+ });
28
+ export default router;
@@ -0,0 +1,17 @@
1
+ import express from 'express';
2
+ import { createServer } from 'http';
3
+ import { Server } from 'socket.io';
4
+ import cors from 'cors';
5
+ export function createHttpStack() {
6
+ const app = express();
7
+ const httpServer = createServer(app);
8
+ const io = new Server(httpServer, {
9
+ cors: {
10
+ origin: '*',
11
+ methods: ['GET', 'POST']
12
+ }
13
+ });
14
+ app.use(cors());
15
+ app.use(express.json());
16
+ return { app, httpServer, io };
17
+ }