xiaozuoassistant 0.1.44 → 0.1.45

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.
@@ -70,28 +70,69 @@ app.get('/api/health', (req, res) => {
70
70
  });
71
71
  // 获取所有会话
72
72
  app.get('/api/sessions', async (req, res) => {
73
- const sessions = await memory.listSessions();
74
- res.json(sessions);
73
+ try {
74
+ const sessions = await memory.listSessions();
75
+ res.json(sessions);
76
+ }
77
+ catch (e) {
78
+ res.status(500).json({ error: e.message });
79
+ }
75
80
  });
76
81
  // 获取会话详情
77
82
  app.get('/api/sessions/:id', async (req, res) => {
78
- const session = await memory.getSession(req.params.id);
79
- if (session) {
83
+ try {
84
+ const session = await memory.getSession(req.params.id);
85
+ if (!session) {
86
+ res.status(404).json({ error: 'Session not found' });
87
+ return;
88
+ }
80
89
  res.json(session);
81
90
  }
82
- else {
83
- res.status(404).json({ error: 'Session not found' });
91
+ catch (e) {
92
+ res.status(500).json({ error: e.message });
84
93
  }
85
94
  });
86
95
  // 创建新会话
87
96
  app.post('/api/sessions', async (req, res) => {
88
- const session = await memory.createSession();
89
- res.json(session);
97
+ try {
98
+ const { alias, workspace } = req.body || {};
99
+ const session = await memory.createSession({ alias, workspace });
100
+ res.json(session.meta);
101
+ }
102
+ catch (e) {
103
+ res.status(500).json({ error: e.message });
104
+ }
105
+ });
106
+ // 更新会话元信息
107
+ app.patch('/api/sessions/:id', async (req, res) => {
108
+ try {
109
+ const { alias, workspace } = req.body || {};
110
+ const meta = await memory.updateSessionMeta(req.params.id, { alias, workspace });
111
+ res.json(meta);
112
+ }
113
+ catch (e) {
114
+ const msg = String(e?.message || 'Unknown error');
115
+ if (msg.toLowerCase().includes('not found')) {
116
+ res.status(404).json({ error: 'Session not found' });
117
+ return;
118
+ }
119
+ res.status(500).json({ error: msg });
120
+ }
90
121
  });
91
122
  // 删除会话
92
123
  app.delete('/api/sessions/:id', async (req, res) => {
93
- await memory.deleteSession(req.params.id);
94
- res.json({ status: 'success', message: 'Session deleted' });
124
+ try {
125
+ await memory.deleteSession(req.params.id);
126
+ res.json({ status: 'success', message: 'Session deleted' });
127
+ }
128
+ catch (e) {
129
+ const msg = String(e?.message || 'Unknown error');
130
+ if (msg.toLowerCase().includes('not found')) {
131
+ res.status(404).json({ error: 'Session not found' });
132
+ return;
133
+ }
134
+ res.status(500).json({ error: msg });
135
+ }
95
136
  });
96
137
  // 文件选择接口 (使用系统默认文件对话框可能需要Electron,这里作为Web服务,我们提供目录列表供选择)
97
138
  // 简单的目录列举接口
@@ -246,13 +287,12 @@ eventBus.onEvent('message', async (event) => {
246
287
  const content = payload.content;
247
288
  try {
248
289
  // 1. 获取会话上下文
249
- let session = await memory.getSession(sessionId);
290
+ const session = await memory.getSession(sessionId);
250
291
  if (!session) {
251
- session = await memory.createSession();
252
- // 如果是新创建的会话,可能需要通知前端更新 ID(略复杂,暂且假设前端已获取 ID)
292
+ throw new Error('Session not found. Please create or select a session first.');
253
293
  }
254
294
  // 2. 记录用户消息
255
- await memory.addMessage(sessionId, { role: 'user', content });
295
+ await memory.addMessage(sessionId, { role: 'user', content, timestamp: Date.now() });
256
296
  // 3. Brain 处理
257
297
  // Use MemoryManager to retrieve full context (Short-term + Recent + Structured)
258
298
  const context = await memory.getRelevantContext(content, sessionId);
@@ -262,7 +302,11 @@ eventBus.onEvent('message', async (event) => {
262
302
  // Or simpler: We just pass session.messages as before, but Brain needs to know about the context.
263
303
  // Let's change how we call brain.processMessage.
264
304
  // Construct a temporary history with enhanced context for the LLM
305
+ const sessionAlias = session.meta.alias ? `Session Alias: ${session.meta.alias}` : '';
306
+ const workspaceLine = session.meta.workspace ? `Current Workspace: ${session.meta.workspace}` : '';
265
307
  const enhancedSystemPrompt = `You are xiaozuoAssistant, a helpful AI assistant.
308
+ ${sessionAlias}
309
+ ${workspaceLine}
266
310
  Here is some relevant context from your memory:
267
311
  ${context}`;
268
312
  // We need to inject this into the Brain.
@@ -270,9 +314,19 @@ ${context}`;
270
314
  // We can modify Brain to accept a system prompt override.
271
315
  // Or we can just create a new method in Brain or pass it as an argument.
272
316
  // 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);
317
+ const ctx = {
318
+ sessionId,
319
+ history: session.messages,
320
+ channel: channelName,
321
+ session: session.meta,
322
+ metadata: {
323
+ workspace: session.meta.workspace,
324
+ alias: session.meta.alias
325
+ }
326
+ };
327
+ const responseContent = await brain.processMessage(session.messages, content, enhancedSystemPrompt, ctx);
274
328
  // 4. 记录 AI 响应
275
- await memory.addMessage(sessionId, { role: 'assistant', content: responseContent });
329
+ await memory.addMessage(sessionId, { role: 'assistant', content: responseContent, timestamp: Date.now() });
276
330
  // 5. 发送响应事件
277
331
  eventBus.emitEvent({
278
332
  type: 'response',
@@ -33,7 +33,7 @@ export class CreateAgentSkill extends BaseSkill {
33
33
  required: ['name', 'description', 'systemPrompt']
34
34
  };
35
35
  }
36
- async execute(args) {
36
+ async execute(args, _ctx) {
37
37
  if (agentManager.getAgent(args.name)) {
38
38
  return { error: `Agent '${args.name}' already exists.` };
39
39
  }
@@ -21,7 +21,7 @@ export class DelegateSkill extends BaseSkill {
21
21
  required: ['agentName', 'task']
22
22
  };
23
23
  }
24
- async execute(args) {
24
+ async execute(args, _ctx) {
25
25
  const agent = agentManager.getAgent(args.agentName);
26
26
  if (!agent) {
27
27
  return { error: `Agent '${args.agentName}' not found. Available agents: ${agentManager.getAllAgents().map(a => a.name).join(', ')}` };
@@ -1,6 +1,7 @@
1
1
  import { BaseSkill } from './base-skill.js';
2
2
  import fs from 'fs/promises';
3
3
  import path from 'path';
4
+ import { resolvePathWithinWorkspace, resolveSessionWorkspace } from '../config/paths.js';
4
5
  export class ListDirectorySkill extends BaseSkill {
5
6
  constructor() {
6
7
  super(...arguments);
@@ -11,14 +12,15 @@ export class ListDirectorySkill extends BaseSkill {
11
12
  properties: {
12
13
  path: {
13
14
  type: 'string',
14
- description: 'The absolute path to the directory to list. Defaults to current working directory.'
15
+ description: 'Absolute path, or relative to the current session workspace. Defaults to the session workspace.'
15
16
  }
16
17
  },
17
18
  required: []
18
19
  };
19
20
  }
20
- async execute(params) {
21
- const dirPath = params.path ? path.resolve(params.path) : process.cwd();
21
+ async execute(params, ctx) {
22
+ const workspace = resolveSessionWorkspace(ctx);
23
+ const dirPath = params.path ? resolvePathWithinWorkspace(workspace, String(params.path)) : workspace;
22
24
  try {
23
25
  const files = await fs.readdir(dirPath, { withFileTypes: true });
24
26
  return files.map(file => ({
@@ -41,7 +43,7 @@ export class ReadFileSkill extends BaseSkill {
41
43
  properties: {
42
44
  path: {
43
45
  type: 'string',
44
- description: 'The absolute path to the file to read'
46
+ description: 'Absolute path, or relative to the current session workspace.'
45
47
  },
46
48
  encoding: {
47
49
  type: 'string',
@@ -52,8 +54,9 @@ export class ReadFileSkill extends BaseSkill {
52
54
  required: ['path']
53
55
  };
54
56
  }
55
- async execute(params) {
56
- const filePath = path.resolve(params.path);
57
+ async execute(params, ctx) {
58
+ const workspace = resolveSessionWorkspace(ctx);
59
+ const filePath = resolvePathWithinWorkspace(workspace, String(params.path));
57
60
  const encoding = params.encoding || 'utf-8';
58
61
  try {
59
62
  const content = await fs.readFile(filePath, { encoding: encoding });
@@ -74,7 +77,7 @@ export class WriteFileSkill extends BaseSkill {
74
77
  properties: {
75
78
  path: {
76
79
  type: 'string',
77
- description: 'The absolute path to the file to write'
80
+ description: 'Absolute path, or relative to the current session workspace.'
78
81
  },
79
82
  content: {
80
83
  type: 'string',
@@ -84,8 +87,9 @@ export class WriteFileSkill extends BaseSkill {
84
87
  required: ['path', 'content']
85
88
  };
86
89
  }
87
- async execute(params) {
88
- const filePath = path.resolve(params.path);
90
+ async execute(params, ctx) {
91
+ const workspace = resolveSessionWorkspace(ctx);
92
+ const filePath = resolvePathWithinWorkspace(workspace, String(params.path));
89
93
  try {
90
94
  // Ensure directory exists
91
95
  await fs.mkdir(path.dirname(filePath), { recursive: true });
@@ -107,7 +111,7 @@ export class DeleteFileSkill extends BaseSkill {
107
111
  properties: {
108
112
  path: {
109
113
  type: 'string',
110
- description: 'The absolute path to the file or directory to delete'
114
+ description: 'Absolute path, or relative to the current session workspace.'
111
115
  },
112
116
  recursive: {
113
117
  type: 'boolean',
@@ -117,8 +121,9 @@ export class DeleteFileSkill extends BaseSkill {
117
121
  required: ['path']
118
122
  };
119
123
  }
120
- async execute(params) {
121
- const filePath = path.resolve(params.path);
124
+ async execute(params, ctx) {
125
+ const workspace = resolveSessionWorkspace(ctx);
126
+ const filePath = resolvePathWithinWorkspace(workspace, String(params.path));
122
127
  const recursive = params.recursive || false;
123
128
  try {
124
129
  const stats = await fs.stat(filePath);
@@ -11,7 +11,7 @@ export class ListAgentsSkill extends BaseSkill {
11
11
  required: []
12
12
  };
13
13
  }
14
- async execute() {
14
+ async execute(_params, _ctx) {
15
15
  const agents = agentManager.getAllAgents();
16
16
  return {
17
17
  agents: agents.map(a => ({
@@ -21,7 +21,7 @@ export class ReadExcelSkill extends BaseSkill {
21
21
  required: ['file_path']
22
22
  };
23
23
  }
24
- async execute(args) {
24
+ async execute(args, _ctx) {
25
25
  try {
26
26
  if (!fs.existsSync(args.file_path)) {
27
27
  return { error: `File not found: ${args.file_path}` };
@@ -68,7 +68,7 @@ export class CreateExcelSkill extends BaseSkill {
68
68
  required: ['file_path', 'data']
69
69
  };
70
70
  }
71
- async execute(args) {
71
+ async execute(args, _ctx) {
72
72
  try {
73
73
  const workbook = xlsx.utils.book_new();
74
74
  const sheet = xlsx.utils.json_to_sheet(args.data);
@@ -31,7 +31,7 @@ export class CreatePptxSkill extends BaseSkill {
31
31
  required: ['file_path', 'slides']
32
32
  };
33
33
  }
34
- async execute(args) {
34
+ async execute(args, _ctx) {
35
35
  try {
36
36
  // Handle different import styles (ESM/CJS interop)
37
37
  const PptxGenJS = pptxgen.default || pptxgen;
@@ -18,7 +18,7 @@ export class ReadWordSkill extends BaseSkill {
18
18
  required: ['file_path']
19
19
  };
20
20
  }
21
- async execute(args) {
21
+ async execute(args, _ctx) {
22
22
  try {
23
23
  if (!fs.existsSync(args.file_path)) {
24
24
  return { error: `File not found: ${args.file_path}` };
@@ -55,7 +55,7 @@ export class CreateWordSkill extends BaseSkill {
55
55
  required: ['file_path', 'content']
56
56
  };
57
57
  }
58
- async execute(args) {
58
+ async execute(args, _ctx) {
59
59
  return new Promise((resolve, reject) => {
60
60
  try {
61
61
  const docx = officegen('docx');
@@ -15,7 +15,7 @@ export class SearchSkill extends BaseSkill {
15
15
  required: ['query']
16
16
  };
17
17
  }
18
- async execute(params) {
18
+ async execute(params, _ctx) {
19
19
  const { query } = params;
20
20
  // 这里可以集成真实的搜索 API,如 Google Search API 或 SerpApi
21
21
  // 为了简化,我们返回一个模拟结果
@@ -16,7 +16,7 @@ export class SystemTimeSkill extends BaseSkill {
16
16
  required: []
17
17
  };
18
18
  }
19
- async execute(params) {
19
+ async execute(params, _ctx) {
20
20
  const format = params.format || 'iso';
21
21
  const now = new Date();
22
22
  if (format === 'locale') {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "xiaozuoassistant",
3
3
  "private": false,
4
4
  "description": "Your personal, locally-hosted AI assistant for office productivity.",
5
- "version": "0.1.44",
5
+ "version": "0.1.45",
6
6
  "author": "mantle.lau",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -16,11 +16,20 @@
16
16
  "title": "xiaozuoAssistant",
17
17
  "newChat": "New chat",
18
18
  "recent": "Recent",
19
- "defaultSession": "Default Session",
20
- "newConversation": "New Conversation",
21
19
  "settings": "Settings",
20
+ "editSession": "Edit session",
22
21
  "deleteConfirm": "Are you sure you want to delete this session?"
23
22
  },
23
+ "sessionMeta": {
24
+ "title": "Session Settings",
25
+ "aliasLabel": "Alias (optional)",
26
+ "aliasPlaceholder": "e.g., Project A / Requirements",
27
+ "aliasHint": "Alias is shown in the list and header. Empty falls back to session ID.",
28
+ "workspaceLabel": "Workspace",
29
+ "workspaceHint": "File tools default to this workspace and are restricted to it.",
30
+ "selectWorkspace": "Select workspace",
31
+ "selectCurrentFolder": "Select current folder"
32
+ },
24
33
  "settings": {
25
34
  "title": "Settings",
26
35
  "tabs": {
@@ -16,11 +16,20 @@
16
16
  "title": "xiaozuoAssistant",
17
17
  "newChat": "新对话",
18
18
  "recent": "最近",
19
- "defaultSession": "默认会话",
20
- "newConversation": "新会话",
21
19
  "settings": "设置",
20
+ "editSession": "编辑会话",
22
21
  "deleteConfirm": "确定要删除这个会话吗?"
23
22
  },
23
+ "sessionMeta": {
24
+ "title": "会话设置",
25
+ "aliasLabel": "会话别名(可选)",
26
+ "aliasPlaceholder": "例如:项目A / 需求讨论",
27
+ "aliasHint": "列表与聊天头部将优先展示别名。留空将回退显示会话 ID。",
28
+ "workspaceLabel": "Workspace(工作目录)",
29
+ "workspaceHint": "文件相关工具默认以该目录为工作区,并限制访问范围。",
30
+ "selectWorkspace": "选择工作目录",
31
+ "selectCurrentFolder": "选择当前目录"
32
+ },
24
33
  "settings": {
25
34
  "title": "设置",
26
35
  "tabs": {