xiaozuoassistant 0.2.57 → 0.2.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/core/brain.js +65 -1
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import { config } from '../config/loader.js';
|
|
|
2
2
|
import { skillRegistry } from '../skills/registry.js';
|
|
3
3
|
import { SYSTEM_PROMPT } from '../config/prompts.js';
|
|
4
4
|
import { createOpenAIClient } from '../llm/openai.js';
|
|
5
|
+
import path from 'path';
|
|
5
6
|
export class Brain {
|
|
6
7
|
constructor() {
|
|
7
8
|
this.openai = createOpenAIClient(config.llm);
|
|
@@ -54,6 +55,12 @@ export class Brain {
|
|
|
54
55
|
config.workspace ||
|
|
55
56
|
'').trim();
|
|
56
57
|
const localRuntimeContext = `\n\n[Local Runtime]\nYou are running on the user's local machine (not a cloud-only assistant).\nYou can use built-in tools to list/read/write files within the current session workspace.\nWorkspace: ${effectiveWorkspace || '(not set)'}\nRules:\n- When asked to list/read/write files, use the provided tools (fs_list_directory / fs_read_file / fs_write_file) instead of asking the user to run shell commands.\n- Never claim you are unable to access the local filesystem in general.\n- If a user provides a path outside the current workspace, explain that access is limited to the workspace for safety, and ask the user to switch the session workspace or move files into the workspace.\n- For files sent from Feishu, check workspace/downloads/<botName>/ first.\n`.trimEnd();
|
|
58
|
+
// Deterministic fast-path: for explicit file read/list requests with absolute path,
|
|
59
|
+
// execute filesystem tools directly instead of relying on model tool selection.
|
|
60
|
+
const directFsResult = await this.tryDirectFsIntent(newMessage, context, effectiveWorkspace);
|
|
61
|
+
if (directFsResult) {
|
|
62
|
+
return directFsResult;
|
|
63
|
+
}
|
|
57
64
|
// Convert history messages to the format expected by OpenAI
|
|
58
65
|
// Strategy: Keep last N messages to avoid context window overflow
|
|
59
66
|
// TODO: A better strategy would be token-based truncation.
|
|
@@ -155,8 +162,21 @@ export class Brain {
|
|
|
155
162
|
const fsSuccess = fsToolOutcomes.some(x => x.success);
|
|
156
163
|
if (!fsSuccess) {
|
|
157
164
|
const lastFsError = [...fsToolOutcomes].reverse().find(x => !x.success)?.error;
|
|
165
|
+
const attemptedFsTools = fsToolOutcomes.map(x => x.name);
|
|
166
|
+
const absPathMatch = newMessage.match(/\/[^\s"'`]+/);
|
|
167
|
+
const requestedPath = absPathMatch ? absPathMatch[0] : '';
|
|
168
|
+
const resolvedWorkspace = effectiveWorkspace ? path.resolve(effectiveWorkspace) : '';
|
|
169
|
+
const resolvedRequested = requestedPath ? path.resolve(requestedPath) : '';
|
|
170
|
+
let workspaceCheck = '';
|
|
171
|
+
if (resolvedWorkspace && resolvedRequested) {
|
|
172
|
+
const rel = path.relative(resolvedWorkspace, resolvedRequested);
|
|
173
|
+
const outside = Boolean(rel) && (rel.startsWith('..') || path.isAbsolute(rel));
|
|
174
|
+
workspaceCheck = outside
|
|
175
|
+
? `路径检查:目标路径在 workspace 之外(${resolvedRequested} 不在 ${resolvedWorkspace} 内)。`
|
|
176
|
+
: `路径检查:目标路径位于 workspace 内。`;
|
|
177
|
+
}
|
|
158
178
|
const ws = effectiveWorkspace || '(not set)';
|
|
159
|
-
return `未能实际读取文件:文件工具未成功执行。\n当前会话 workspace:${ws}\n${
|
|
179
|
+
return `未能实际读取文件:文件工具未成功执行。\n当前会话 workspace:${ws}\n请求路径:${requestedPath || '(未识别到明确路径)'}\n${workspaceCheck || '路径检查:无法判定。'}\n${attemptedFsTools.length > 0 ? `已触发工具:${attemptedFsTools.join(', ')}` : '未触发任何 fs_* 文件工具调用(模型未选择工具)。'}\n${lastFsError ? `工具错误:${lastFsError}` : '原因:未拿到可用的文件工具执行结果。请重试,或缩短指令为“读取 <绝对路径>”。'}`;
|
|
160
180
|
}
|
|
161
181
|
}
|
|
162
182
|
if (process.env.DEBUG)
|
|
@@ -288,5 +308,49 @@ Format: [{"title": "...", "content": "..."}]` },
|
|
|
288
308
|
}
|
|
289
309
|
throw lastError;
|
|
290
310
|
}
|
|
311
|
+
async tryDirectFsIntent(newMessage, context, effectiveWorkspace) {
|
|
312
|
+
const absPathMatch = newMessage.match(/\/[^\s"'`]+/);
|
|
313
|
+
if (!absPathMatch)
|
|
314
|
+
return null;
|
|
315
|
+
const requestedPath = absPathMatch[0];
|
|
316
|
+
const asksRead = /(读取|读一下|打开|查看|read|cat)/i.test(newMessage);
|
|
317
|
+
const asksList = /(列出|目录|list|ls)/i.test(newMessage);
|
|
318
|
+
if (!asksRead && !asksList)
|
|
319
|
+
return null;
|
|
320
|
+
const ws = (effectiveWorkspace || '').trim();
|
|
321
|
+
const resolvedWorkspace = ws ? path.resolve(ws) : '';
|
|
322
|
+
const resolvedRequested = path.resolve(requestedPath);
|
|
323
|
+
if (resolvedWorkspace) {
|
|
324
|
+
const rel = path.relative(resolvedWorkspace, resolvedRequested);
|
|
325
|
+
const outside = Boolean(rel) && (rel.startsWith('..') || path.isAbsolute(rel));
|
|
326
|
+
if (outside) {
|
|
327
|
+
return `未能实际读取文件:目标路径不在当前会话 workspace 内。\n当前会话 workspace:${resolvedWorkspace}\n请求路径:${resolvedRequested}\n路径检查:目标路径在 workspace 之外。`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (asksRead) {
|
|
331
|
+
const skill = skillRegistry.getSkill('fs_read_file');
|
|
332
|
+
if (!skill) {
|
|
333
|
+
return '未能实际读取文件:fs_read_file 工具不可用。';
|
|
334
|
+
}
|
|
335
|
+
const result = await skill.execute({ path: requestedPath }, context);
|
|
336
|
+
if (typeof result === 'string' && /^error/i.test(result.trim())) {
|
|
337
|
+
return `未能实际读取文件:文件工具执行失败。\n当前会话 workspace:${resolvedWorkspace || '(not set)'}\n请求路径:${resolvedRequested}\n工具错误:${result}`;
|
|
338
|
+
}
|
|
339
|
+
return `已读取 \`${resolvedRequested}\` 内容如下:\n\n${String(result)}`;
|
|
340
|
+
}
|
|
341
|
+
const skill = skillRegistry.getSkill('fs_list_directory');
|
|
342
|
+
if (!skill) {
|
|
343
|
+
return '未能列出目录:fs_list_directory 工具不可用。';
|
|
344
|
+
}
|
|
345
|
+
const result = await skill.execute({ path: requestedPath }, context);
|
|
346
|
+
if (typeof result === 'string' && /^error/i.test(result.trim())) {
|
|
347
|
+
return `未能列出目录:文件工具执行失败。\n当前会话 workspace:${resolvedWorkspace || '(not set)'}\n请求路径:${resolvedRequested}\n工具错误:${result}`;
|
|
348
|
+
}
|
|
349
|
+
if (Array.isArray(result)) {
|
|
350
|
+
const lines = result.map((item) => `${item.type === 'directory' ? '📁' : '📄'} ${item.name}`);
|
|
351
|
+
return `目录 \`${resolvedRequested}\` 列表:\n${lines.join('\n')}`;
|
|
352
|
+
}
|
|
353
|
+
return `目录 \`${resolvedRequested}\` 列表结果:\n${JSON.stringify(result, null, 2)}`;
|
|
354
|
+
}
|
|
291
355
|
}
|
|
292
356
|
export const brain = Brain.getInstance();
|