yuangs 5.57.0 → 5.58.0
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/agent/AgentRuntime.js +68 -1
- package/dist/agent/AgentRuntime.js.map +1 -1
- package/dist/agent/errorTracker.d.ts +110 -0
- package/dist/agent/errorTracker.js +216 -0
- package/dist/agent/errorTracker.js.map +1 -0
- package/dist/agent/executor.d.ts +84 -1
- package/dist/agent/executor.js +656 -11
- package/dist/agent/executor.js.map +1 -1
- package/dist/agent/protocolV2_2.js +45 -21
- package/dist/agent/protocolV2_2.js.map +1 -1
- package/dist/agent/state.d.ts +2 -0
- package/dist/agent/toolCapability.d.ts +81 -0
- package/dist/agent/toolCapability.js +471 -0
- package/dist/agent/toolCapability.js.map +1 -0
- package/package.json +1 -1
package/dist/agent/executor.js
CHANGED
|
@@ -8,43 +8,138 @@ const child_process_1 = require("child_process");
|
|
|
8
8
|
const util_1 = require("util");
|
|
9
9
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const toolCapability_1 = require("./toolCapability");
|
|
12
|
+
const CapabilityLevel_1 = require("../core/capability/CapabilityLevel");
|
|
11
13
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
14
|
+
/**
|
|
15
|
+
* 增强的工具执行器
|
|
16
|
+
* 支持丰富的原子工具操作
|
|
17
|
+
* 集成能力感知的工具调用
|
|
18
|
+
*/
|
|
12
19
|
class ToolExecutor {
|
|
13
20
|
static MAX_OUTPUT_LENGTH = 2000; // Maximum output length in characters
|
|
21
|
+
static READ_POSITIONS = new Map(); // 记录文件读取位置用于 continue_reading
|
|
22
|
+
static currentCapabilityLevel = CapabilityLevel_1.CapabilityLevel.STRUCTURAL; // 当前模型的能力等级(可配置)
|
|
14
23
|
/**
|
|
15
|
-
*
|
|
24
|
+
* 设置当前能力等级
|
|
16
25
|
*/
|
|
17
|
-
static
|
|
26
|
+
static setCapabilityLevel(level) {
|
|
27
|
+
this.currentCapabilityLevel = level;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 获取当前能力等级
|
|
31
|
+
*/
|
|
32
|
+
static getCapabilityLevel() {
|
|
33
|
+
return this.currentCapabilityLevel;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 检查工具是否可以被当前能力等级执行
|
|
37
|
+
*/
|
|
38
|
+
static checkToolCapability(toolName) {
|
|
39
|
+
const required = (0, toolCapability_1.getToolCapabilityRequirement)(toolName);
|
|
40
|
+
if (required === null) {
|
|
41
|
+
return { allowed: false }; // 工具不存在
|
|
42
|
+
}
|
|
43
|
+
const allowed = (0, toolCapability_1.canExecuteTool)(toolName, this.currentCapabilityLevel);
|
|
44
|
+
return { allowed, required, current: this.currentCapabilityLevel };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 获取指定能力等级下可用的工具列表
|
|
48
|
+
*/
|
|
49
|
+
static getAvailableTools() {
|
|
50
|
+
const tools = [];
|
|
51
|
+
for (const [name] of Object.entries(toolCapability_1.TOOL_CAPABILITY_MAP)) {
|
|
52
|
+
if ((0, toolCapability_1.canExecuteTool)(name, this.currentCapabilityLevel)) {
|
|
53
|
+
tools.push(name);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return tools;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 获取能力等级的可读名称
|
|
60
|
+
*/
|
|
61
|
+
static getCapabilityName(level) {
|
|
62
|
+
switch (level) {
|
|
63
|
+
case CapabilityLevel_1.CapabilityLevel.SEMANTIC: return 'SEMANTIC (极致语义)';
|
|
64
|
+
case CapabilityLevel_1.CapabilityLevel.STRUCTURAL: return 'STRUCTURAL (结构分析)';
|
|
65
|
+
case CapabilityLevel_1.CapabilityLevel.LINE: return 'LINE (行级分析)';
|
|
66
|
+
case CapabilityLevel_1.CapabilityLevel.TEXT: return 'TEXT (文本处理)';
|
|
67
|
+
case CapabilityLevel_1.CapabilityLevel.NONE: return 'NONE (无智能要求)';
|
|
68
|
+
default: return 'UNKNOWN';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 智能截断输出
|
|
73
|
+
* 当输出过长时,返回特殊标记和继续读取的提示
|
|
74
|
+
*/
|
|
75
|
+
static maybeTruncate(output, toolName, filePath) {
|
|
18
76
|
if (output.length <= this.MAX_OUTPUT_LENGTH) {
|
|
19
77
|
return output;
|
|
20
78
|
}
|
|
21
79
|
const truncated = output.slice(0, this.MAX_OUTPUT_LENGTH);
|
|
22
|
-
const
|
|
80
|
+
const remaining = output.length - this.MAX_OUTPUT_LENGTH;
|
|
81
|
+
// 根据工具类型提供不同的建议
|
|
82
|
+
let suggestion = `
|
|
23
83
|
|
|
24
84
|
[⚠️ OUTPUT TRUNCATED]
|
|
25
|
-
|
|
85
|
+
输出被截断,还有 ${remaining} 个字符未显示。
|
|
26
86
|
|
|
27
|
-
|
|
87
|
+
`;
|
|
88
|
+
if (toolName === 'read_file' && filePath) {
|
|
89
|
+
// 记录读取位置
|
|
90
|
+
this.READ_POSITIONS.set(filePath, this.MAX_OUTPUT_LENGTH);
|
|
91
|
+
suggestion += `
|
|
92
|
+
**建议操作**:
|
|
93
|
+
1. 使用 \`read_file_lines\` 工具读取特定行范围:
|
|
94
|
+
{ "tool_name": "read_file_lines", "parameters": { "path": "${filePath}", "start_line": 1, "end_line": 100 } }
|
|
95
|
+
|
|
96
|
+
2. 使用 \`continue_reading\` 工具继续读取:
|
|
97
|
+
{ "tool_name": "continue_reading", "parameters": { "path": "${filePath}" } }
|
|
98
|
+
|
|
99
|
+
3. 使用 \`search_in_files\` 工具搜索关键词:
|
|
100
|
+
{ "tool_name": "search_in_files", "parameters": { "pattern": "关键词", "path": "${filePath}" } }
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
else if (toolName === 'shell_cmd') {
|
|
104
|
+
suggestion += `
|
|
105
|
+
**建议操作**:
|
|
106
|
+
1. 使用 \`head\` 查看前几行:
|
|
28
107
|
head -n 50 filename
|
|
29
108
|
|
|
30
|
-
2.
|
|
109
|
+
2. 使用 \`tail\` 查看后几行:
|
|
31
110
|
tail -n 50 filename
|
|
32
111
|
|
|
33
|
-
3.
|
|
112
|
+
3. 使用 \`grep\` 过滤内容:
|
|
34
113
|
grep "keyword" filename
|
|
35
114
|
|
|
36
|
-
4.
|
|
115
|
+
4. 将输出重定向到文件再读取:
|
|
116
|
+
command > output.txt && read_file output.txt
|
|
37
117
|
`;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
suggestion += `
|
|
121
|
+
**建议操作**:
|
|
122
|
+
1. 检查输出是否已经包含所需信息
|
|
123
|
+
2. 使用更精确的搜索参数
|
|
124
|
+
3. 将结果分批处理
|
|
125
|
+
`;
|
|
126
|
+
}
|
|
38
127
|
return truncated + suggestion;
|
|
39
128
|
}
|
|
40
129
|
static async execute(action) {
|
|
41
130
|
const { type, payload } = action;
|
|
42
131
|
try {
|
|
43
132
|
const result = await this.executeAction(type, payload);
|
|
44
|
-
|
|
133
|
+
// 智能截断,提取文件路径用于继续读取
|
|
134
|
+
let filePath;
|
|
135
|
+
if (type === 'tool_call' && payload?.tool_name === 'read_file') {
|
|
136
|
+
filePath = payload?.parameters?.path;
|
|
137
|
+
}
|
|
138
|
+
const truncated = this.maybeTruncate(result.output, type, filePath);
|
|
45
139
|
return {
|
|
46
140
|
...result,
|
|
47
|
-
output: truncated
|
|
141
|
+
output: truncated,
|
|
142
|
+
needsContinue: result.output.length > this.MAX_OUTPUT_LENGTH
|
|
48
143
|
};
|
|
49
144
|
}
|
|
50
145
|
catch (error) {
|
|
@@ -79,13 +174,59 @@ The output was too long (${output.length} chars). Here are some ways to get what
|
|
|
79
174
|
}
|
|
80
175
|
static async executeTool(payload) {
|
|
81
176
|
const toolName = payload.tool_name;
|
|
177
|
+
// 能力检查:验证工具是否可以被当前能力等级执行
|
|
178
|
+
const capabilityCheck = this.checkToolCapability(toolName);
|
|
179
|
+
if (!capabilityCheck.allowed) {
|
|
180
|
+
if (capabilityCheck.required === undefined) {
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
error: `Unknown tool: ${toolName}`,
|
|
184
|
+
output: ''
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
// 工具存在但当前能力等级不足
|
|
188
|
+
const currentLevel = capabilityCheck.current ?? this.currentCapabilityLevel;
|
|
189
|
+
return {
|
|
190
|
+
success: false,
|
|
191
|
+
error: `Tool "${toolName}" requires ${this.getCapabilityName(capabilityCheck.required)} capability, current is ${this.getCapabilityName(currentLevel)}`,
|
|
192
|
+
output: ''
|
|
193
|
+
};
|
|
194
|
+
}
|
|
82
195
|
switch (toolName) {
|
|
196
|
+
// ===== 基础文件操作 =====
|
|
83
197
|
case 'read_file':
|
|
84
198
|
return await this.toolReadFile(payload.parameters);
|
|
199
|
+
case 'read_file_lines':
|
|
200
|
+
return await this.toolReadFileLines(payload.parameters);
|
|
85
201
|
case 'write_file':
|
|
86
202
|
return await this.toolWriteFile(payload.parameters);
|
|
203
|
+
case 'append_file':
|
|
204
|
+
return await this.toolAppendFile(payload.parameters);
|
|
205
|
+
// ===== 文件搜索和列表 =====
|
|
87
206
|
case 'list_files':
|
|
88
207
|
return await this.toolListFiles(payload.parameters);
|
|
208
|
+
case 'list_directory_tree':
|
|
209
|
+
return await this.toolListDirectoryTree(payload.parameters);
|
|
210
|
+
case 'search_in_files':
|
|
211
|
+
return await this.toolSearchInFiles(payload.parameters);
|
|
212
|
+
// ===== 代码分析工具 =====
|
|
213
|
+
case 'search_symbol':
|
|
214
|
+
return await this.toolSearchSymbol(payload.parameters);
|
|
215
|
+
case 'analyze_dependencies':
|
|
216
|
+
return await this.toolAnalyzeDependencies(payload.parameters);
|
|
217
|
+
// ===== Git 操作 =====
|
|
218
|
+
case 'git_status':
|
|
219
|
+
return await this.toolGitStatus(payload.parameters);
|
|
220
|
+
case 'git_diff':
|
|
221
|
+
return await this.toolGitDiff(payload.parameters);
|
|
222
|
+
case 'git_log':
|
|
223
|
+
return await this.toolGitLog(payload.parameters);
|
|
224
|
+
// ===== 上下文管理工具 =====
|
|
225
|
+
case 'continue_reading':
|
|
226
|
+
return await this.toolContinueReading(payload.parameters);
|
|
227
|
+
case 'file_info':
|
|
228
|
+
return await this.toolFileInfo(payload.parameters);
|
|
229
|
+
// ===== 已弃用/未实现 =====
|
|
89
230
|
case 'web_search':
|
|
90
231
|
return {
|
|
91
232
|
success: false,
|
|
@@ -200,7 +341,7 @@ The output was too long (${output.length} chars). Here are some ways to get what
|
|
|
200
341
|
try {
|
|
201
342
|
const tempFile = path_1.default.join(process.cwd(), '.yuangs_temp.patch');
|
|
202
343
|
await promises_1.default.writeFile(tempFile, diff, 'utf-8');
|
|
203
|
-
|
|
344
|
+
await execAsync(`git apply --check ${tempFile}`, {
|
|
204
345
|
cwd: process.cwd()
|
|
205
346
|
});
|
|
206
347
|
const { stdout: applyOutput } = await execAsync(`git apply ${tempFile}`, {
|
|
@@ -221,6 +362,510 @@ The output was too long (${output.length} chars). Here are some ways to get what
|
|
|
221
362
|
};
|
|
222
363
|
}
|
|
223
364
|
}
|
|
365
|
+
// ===== 新增工具方法实现 =====
|
|
366
|
+
/**
|
|
367
|
+
* 读取文件的指定行范围
|
|
368
|
+
*/
|
|
369
|
+
static async toolReadFileLines(params) {
|
|
370
|
+
const filePath = params.path;
|
|
371
|
+
const startLine = params.start_line || 1;
|
|
372
|
+
const endLine = params.end_line;
|
|
373
|
+
const encoding = params.encoding || 'utf-8';
|
|
374
|
+
try {
|
|
375
|
+
const content = await promises_1.default.readFile(filePath, encoding);
|
|
376
|
+
const lines = String(content).split('\n');
|
|
377
|
+
const startIndex = Math.max(0, startLine - 1);
|
|
378
|
+
const endIndex = endLine ? Math.min(lines.length, endLine) : lines.length;
|
|
379
|
+
if (startIndex >= lines.length) {
|
|
380
|
+
return {
|
|
381
|
+
success: false,
|
|
382
|
+
error: `起始行号 ${startLine} 超出文件范围(文件共 ${lines.length} 行)`,
|
|
383
|
+
output: ''
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
const selectedLines = lines.slice(startIndex, endIndex);
|
|
387
|
+
const result = selectedLines
|
|
388
|
+
.map((line, idx) => `${startIndex + idx + 1}: ${line}`)
|
|
389
|
+
.join('\n');
|
|
390
|
+
return {
|
|
391
|
+
success: true,
|
|
392
|
+
output: result,
|
|
393
|
+
artifacts: [filePath]
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
return {
|
|
398
|
+
success: false,
|
|
399
|
+
error: error.message,
|
|
400
|
+
output: ''
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* 向文件末尾追加内容
|
|
406
|
+
*/
|
|
407
|
+
static async toolAppendFile(params) {
|
|
408
|
+
const filePath = params.path;
|
|
409
|
+
const content = params.content;
|
|
410
|
+
const encoding = params.encoding || 'utf-8';
|
|
411
|
+
try {
|
|
412
|
+
await promises_1.default.mkdir(path_1.default.dirname(filePath), { recursive: true });
|
|
413
|
+
await promises_1.default.appendFile(filePath, content, encoding);
|
|
414
|
+
return {
|
|
415
|
+
success: true,
|
|
416
|
+
output: `Successfully appended to ${filePath}`,
|
|
417
|
+
artifacts: [filePath]
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
return {
|
|
422
|
+
success: false,
|
|
423
|
+
error: error.message,
|
|
424
|
+
output: ''
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* 生成目录结构的树形展示
|
|
430
|
+
*/
|
|
431
|
+
static async toolListDirectoryTree(params) {
|
|
432
|
+
const dirPath = params.path || '.';
|
|
433
|
+
const maxDepth = params.max_depth || 3;
|
|
434
|
+
const includeFiles = params.include_files !== false;
|
|
435
|
+
const excludePatterns = params.exclude_patterns || ['node_modules', '.git', 'dist', 'build'];
|
|
436
|
+
try {
|
|
437
|
+
const tree = await this.buildDirectoryTree(dirPath, maxDepth, includeFiles, excludePatterns, 0);
|
|
438
|
+
return {
|
|
439
|
+
success: true,
|
|
440
|
+
output: tree,
|
|
441
|
+
artifacts: []
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
return {
|
|
446
|
+
success: false,
|
|
447
|
+
error: error.message,
|
|
448
|
+
output: ''
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
static async buildDirectoryTree(dirPath, maxDepth, includeFiles, excludePatterns, currentDepth) {
|
|
453
|
+
if (currentDepth >= maxDepth)
|
|
454
|
+
return '';
|
|
455
|
+
const entries = await promises_1.default.readdir(dirPath, { withFileTypes: true });
|
|
456
|
+
const lines = [];
|
|
457
|
+
const isLast = (index) => index === entries.length - 1;
|
|
458
|
+
for (let i = 0; i < entries.length; i++) {
|
|
459
|
+
const entry = entries[i];
|
|
460
|
+
const fullName = entry.name;
|
|
461
|
+
// 跳过排除的目录/文件
|
|
462
|
+
if (excludePatterns.some(pattern => fullName.includes(pattern))) {
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const prefix = currentDepth === 0 ? '' : '│ '.repeat(currentDepth);
|
|
466
|
+
const connector = isLast(i) ? '└── ' : '├── ';
|
|
467
|
+
if (entry.isDirectory()) {
|
|
468
|
+
lines.push(`${prefix}${connector}${fullName}/`);
|
|
469
|
+
const subTree = await this.buildDirectoryTree(path_1.default.join(dirPath, fullName), maxDepth, includeFiles, excludePatterns, currentDepth + 1);
|
|
470
|
+
if (subTree) {
|
|
471
|
+
lines.push(subTree);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
else if (includeFiles) {
|
|
475
|
+
lines.push(`${prefix}${connector}${fullName}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return lines.join('\n');
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* 在文件中搜索指定内容(类似 grep)
|
|
482
|
+
*/
|
|
483
|
+
static async toolSearchInFiles(params) {
|
|
484
|
+
const pattern = params.pattern;
|
|
485
|
+
const searchPath = params.path || '.';
|
|
486
|
+
const filePattern = params.file_pattern;
|
|
487
|
+
const ignoreCase = params.ignore_case || false;
|
|
488
|
+
const contextLines = params.context_lines || 0;
|
|
489
|
+
const maxResults = params.max_results || 50;
|
|
490
|
+
try {
|
|
491
|
+
let grepCmd = 'grep';
|
|
492
|
+
// 构建 grep 命令
|
|
493
|
+
if (ignoreCase)
|
|
494
|
+
grepCmd += ' -i';
|
|
495
|
+
grepCmd += ' -r';
|
|
496
|
+
if (contextLines > 0)
|
|
497
|
+
grepCmd += ` -C ${contextLines}`;
|
|
498
|
+
grepCmd += ` -n`; // 显示行号
|
|
499
|
+
// 转义 pattern
|
|
500
|
+
const escapedPattern = pattern.replace(/'/g, "'\\''");
|
|
501
|
+
grepCmd += ` -- '${escapedPattern}'`;
|
|
502
|
+
// 添加路径
|
|
503
|
+
grepCmd += ` ${searchPath}`;
|
|
504
|
+
// 执行搜索
|
|
505
|
+
const { stdout } = await execAsync(grepCmd, {
|
|
506
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
507
|
+
cwd: process.cwd()
|
|
508
|
+
});
|
|
509
|
+
// 解析结果并限制数量
|
|
510
|
+
const lines = String(stdout).split('\n').filter(line => line.trim());
|
|
511
|
+
const limitedLines = lines.slice(0, maxResults);
|
|
512
|
+
// 如果指定了文件模式,进行过滤
|
|
513
|
+
let filteredLines = limitedLines;
|
|
514
|
+
if (filePattern) {
|
|
515
|
+
const regex = new RegExp(filePattern.replace(/\*/g, '.*'));
|
|
516
|
+
filteredLines = limitedLines.filter((line) => {
|
|
517
|
+
const match = line.match(/^([^:]+):/);
|
|
518
|
+
return match && regex.test(match[1]);
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
success: true,
|
|
523
|
+
output: filteredLines.join('\n'),
|
|
524
|
+
artifacts: []
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
// grep 没有找到结果时会返回错误码
|
|
529
|
+
if (error.code === 1) {
|
|
530
|
+
return {
|
|
531
|
+
success: true,
|
|
532
|
+
output: '未找到匹配结果',
|
|
533
|
+
artifacts: []
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
success: false,
|
|
538
|
+
error: error.message,
|
|
539
|
+
output: ''
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* 搜索代码符号(函数、类、变量等)
|
|
545
|
+
* 使用 grep 进行简化搜索
|
|
546
|
+
*/
|
|
547
|
+
static async toolSearchSymbol(params) {
|
|
548
|
+
const symbol = params.symbol;
|
|
549
|
+
const symbolType = params.symbol_type;
|
|
550
|
+
const searchPath = params.path || '.';
|
|
551
|
+
const filePattern = params.file_pattern;
|
|
552
|
+
try {
|
|
553
|
+
let pattern = symbol;
|
|
554
|
+
// 根据符号类型构建搜索模式
|
|
555
|
+
switch (symbolType) {
|
|
556
|
+
case 'function':
|
|
557
|
+
pattern = `function\\s+${symbol}|${symbol}\\s*[:=]\\s*function|const\\s+${symbol}\\s*=`;
|
|
558
|
+
break;
|
|
559
|
+
case 'class':
|
|
560
|
+
pattern = `class\\s+${symbol}`;
|
|
561
|
+
break;
|
|
562
|
+
case 'interface':
|
|
563
|
+
pattern = `interface\\s+${symbol}`;
|
|
564
|
+
break;
|
|
565
|
+
default:
|
|
566
|
+
pattern = symbol;
|
|
567
|
+
}
|
|
568
|
+
let grepCmd = `grep -rn --color=never -E "${pattern}" ${searchPath}`;
|
|
569
|
+
if (filePattern) {
|
|
570
|
+
grepCmd += ` --include="${filePattern}"`;
|
|
571
|
+
}
|
|
572
|
+
const { stdout } = await execAsync(grepCmd, {
|
|
573
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
574
|
+
cwd: process.cwd()
|
|
575
|
+
});
|
|
576
|
+
if (!stdout.trim()) {
|
|
577
|
+
return {
|
|
578
|
+
success: true,
|
|
579
|
+
output: `未找到符号 "${symbol}"`,
|
|
580
|
+
artifacts: []
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
success: true,
|
|
585
|
+
output: stdout,
|
|
586
|
+
artifacts: []
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
if (error.code === 1) {
|
|
591
|
+
return {
|
|
592
|
+
success: true,
|
|
593
|
+
output: `未找到符号 "${symbol}"`,
|
|
594
|
+
artifacts: []
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
success: false,
|
|
599
|
+
error: error.message,
|
|
600
|
+
output: ''
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* 分析文件的依赖关系(import/require)
|
|
606
|
+
*/
|
|
607
|
+
static async toolAnalyzeDependencies(params) {
|
|
608
|
+
const targetPath = params.path;
|
|
609
|
+
// recursive 参数保留用于未来扩展
|
|
610
|
+
params.recursive;
|
|
611
|
+
try {
|
|
612
|
+
const stat = await promises_1.default.stat(targetPath);
|
|
613
|
+
let files = [];
|
|
614
|
+
if (stat.isFile()) {
|
|
615
|
+
files = [targetPath];
|
|
616
|
+
}
|
|
617
|
+
else if (stat.isDirectory()) {
|
|
618
|
+
// 获取目录下所有文件
|
|
619
|
+
const allFiles = await this.getFiles(targetPath, true);
|
|
620
|
+
files = allFiles
|
|
621
|
+
.filter(f => f.type === 'file')
|
|
622
|
+
.filter(f => /\.(ts|js|tsx|jsx|vue|svelte)$/.test(f.path))
|
|
623
|
+
.map(f => f.path);
|
|
624
|
+
}
|
|
625
|
+
const dependencies = {};
|
|
626
|
+
for (const file of files) {
|
|
627
|
+
const content = await promises_1.default.readFile(file, 'utf-8');
|
|
628
|
+
const deps = [];
|
|
629
|
+
// 匹配 ES6 imports
|
|
630
|
+
const importRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
631
|
+
let match;
|
|
632
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
633
|
+
deps.push(match[1]);
|
|
634
|
+
}
|
|
635
|
+
// 匹配 CommonJS requires
|
|
636
|
+
const requireRegex = /require\(['"]([^'"]+)['"]\)/g;
|
|
637
|
+
while ((match = requireRegex.exec(content)) !== null) {
|
|
638
|
+
deps.push(match[1]);
|
|
639
|
+
}
|
|
640
|
+
// 匹配动态 imports
|
|
641
|
+
const dynamicImportRegex = /import\(['"]([^'"]+)['"]\)/g;
|
|
642
|
+
while ((match = dynamicImportRegex.exec(content)) !== null) {
|
|
643
|
+
deps.push(match[1]);
|
|
644
|
+
}
|
|
645
|
+
if (deps.length > 0) {
|
|
646
|
+
dependencies[file] = [...new Set(deps)]; // 去重
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
// 格式化输出
|
|
650
|
+
let output = '依赖关系分析结果:\n\n';
|
|
651
|
+
for (const [file, deps] of Object.entries(dependencies)) {
|
|
652
|
+
output += `${file}:\n`;
|
|
653
|
+
for (const dep of deps) {
|
|
654
|
+
output += ` - ${dep}\n`;
|
|
655
|
+
}
|
|
656
|
+
output += '\n';
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
success: true,
|
|
660
|
+
output: output || '未找到依赖关系',
|
|
661
|
+
artifacts: []
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
catch (error) {
|
|
665
|
+
return {
|
|
666
|
+
success: false,
|
|
667
|
+
error: error.message,
|
|
668
|
+
output: ''
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Git 状态
|
|
674
|
+
*/
|
|
675
|
+
static async toolGitStatus(params) {
|
|
676
|
+
const repoPath = params.path || '.';
|
|
677
|
+
try {
|
|
678
|
+
const { stdout } = await execAsync(`git -C ${repoPath} status --porcelain -b`, {
|
|
679
|
+
cwd: process.cwd()
|
|
680
|
+
});
|
|
681
|
+
const { stdout: branchInfo } = await execAsync(`git -C ${repoPath} branch --show-current`, {
|
|
682
|
+
cwd: process.cwd()
|
|
683
|
+
});
|
|
684
|
+
let output = `当前分支: ${branchInfo.trim()}\n\n`;
|
|
685
|
+
if (!stdout.trim()) {
|
|
686
|
+
output += '工作区干净,没有未提交的更改';
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
output += '未提交的更改:\n';
|
|
690
|
+
output += stdout;
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
success: true,
|
|
694
|
+
output,
|
|
695
|
+
artifacts: []
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
catch (error) {
|
|
699
|
+
return {
|
|
700
|
+
success: false,
|
|
701
|
+
error: error.message,
|
|
702
|
+
output: '不是 Git 仓库或 Git 命令执行失败'
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Git 差异
|
|
708
|
+
*/
|
|
709
|
+
static async toolGitDiff(params) {
|
|
710
|
+
const filePath = params.file;
|
|
711
|
+
const cached = params.cached || false;
|
|
712
|
+
const lines = params.lines;
|
|
713
|
+
try {
|
|
714
|
+
let cmd = 'git diff';
|
|
715
|
+
if (cached)
|
|
716
|
+
cmd += ' --cached';
|
|
717
|
+
if (filePath)
|
|
718
|
+
cmd += ` -- ${filePath}`;
|
|
719
|
+
if (lines)
|
|
720
|
+
cmd += ` | head -n ${lines}`;
|
|
721
|
+
const { stdout } = await execAsync(cmd, {
|
|
722
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
723
|
+
cwd: process.cwd()
|
|
724
|
+
});
|
|
725
|
+
if (!stdout.trim()) {
|
|
726
|
+
return {
|
|
727
|
+
success: true,
|
|
728
|
+
output: filePath ? `文件 ${filePath} 没有更改` : '没有更改',
|
|
729
|
+
artifacts: []
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
return {
|
|
733
|
+
success: true,
|
|
734
|
+
output: stdout,
|
|
735
|
+
artifacts: []
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
catch (error) {
|
|
739
|
+
return {
|
|
740
|
+
success: false,
|
|
741
|
+
error: error.message,
|
|
742
|
+
output: ''
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Git 日志
|
|
748
|
+
*/
|
|
749
|
+
static async toolGitLog(params) {
|
|
750
|
+
const maxCount = params.max_count || 10;
|
|
751
|
+
const filePath = params.file;
|
|
752
|
+
const oneline = params.oneline !== false;
|
|
753
|
+
try {
|
|
754
|
+
let cmd = `git log -n ${maxCount}`;
|
|
755
|
+
if (oneline)
|
|
756
|
+
cmd += ' --oneline';
|
|
757
|
+
if (filePath)
|
|
758
|
+
cmd += ` -- ${filePath}`;
|
|
759
|
+
const { stdout } = await execAsync(cmd, {
|
|
760
|
+
cwd: process.cwd()
|
|
761
|
+
});
|
|
762
|
+
return {
|
|
763
|
+
success: true,
|
|
764
|
+
output: stdout || '没有提交历史',
|
|
765
|
+
artifacts: []
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
return {
|
|
770
|
+
success: false,
|
|
771
|
+
error: error.message,
|
|
772
|
+
output: ''
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* 继续读取之前被截断的文件内容
|
|
778
|
+
*/
|
|
779
|
+
static async toolContinueReading(params) {
|
|
780
|
+
const filePath = params.path;
|
|
781
|
+
const fromPosition = params.from_position;
|
|
782
|
+
const length = params.length || this.MAX_OUTPUT_LENGTH;
|
|
783
|
+
try {
|
|
784
|
+
let startPos = fromPosition;
|
|
785
|
+
// 如果没有指定位置,尝试从记录中获取
|
|
786
|
+
if (startPos === undefined) {
|
|
787
|
+
startPos = this.READ_POSITIONS.get(filePath) || this.MAX_OUTPUT_LENGTH;
|
|
788
|
+
}
|
|
789
|
+
const content = await promises_1.default.readFile(filePath, 'utf-8');
|
|
790
|
+
if (startPos >= content.length) {
|
|
791
|
+
return {
|
|
792
|
+
success: true,
|
|
793
|
+
output: '[EOF] 已到达文件末尾',
|
|
794
|
+
artifacts: []
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
const endPos = Math.min(startPos + length, content.length);
|
|
798
|
+
const result = content.slice(startPos, endPos);
|
|
799
|
+
// 更新读取位置
|
|
800
|
+
this.READ_POSITIONS.set(filePath, endPos);
|
|
801
|
+
const prefix = `[从位置 ${startPos} 读取,共 ${result.length} 字符]\n\n`;
|
|
802
|
+
const remaining = content.length - endPos;
|
|
803
|
+
let suffix = '';
|
|
804
|
+
if (remaining > 0) {
|
|
805
|
+
suffix = `\n\n[还有 ${remaining} 字符未读取,使用 continue_reading 继续]`;
|
|
806
|
+
this.READ_POSITIONS.set(filePath, endPos);
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
this.READ_POSITIONS.delete(filePath); // 到达末尾,清除记录
|
|
810
|
+
}
|
|
811
|
+
return {
|
|
812
|
+
success: true,
|
|
813
|
+
output: prefix + result + suffix,
|
|
814
|
+
readPosition: endPos,
|
|
815
|
+
artifacts: [filePath]
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
catch (error) {
|
|
819
|
+
return {
|
|
820
|
+
success: false,
|
|
821
|
+
error: error.message,
|
|
822
|
+
output: ''
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* 获取文件元信息
|
|
828
|
+
*/
|
|
829
|
+
static async toolFileInfo(params) {
|
|
830
|
+
const filePath = params.path;
|
|
831
|
+
try {
|
|
832
|
+
const stat = await promises_1.default.stat(filePath);
|
|
833
|
+
const info = {
|
|
834
|
+
path: filePath,
|
|
835
|
+
type: stat.isDirectory() ? 'directory' : 'file',
|
|
836
|
+
size: stat.size,
|
|
837
|
+
sizeHuman: this.formatBytes(stat.size),
|
|
838
|
+
modified: stat.mtime.toISOString(),
|
|
839
|
+
created: stat.birthtime.toISOString(),
|
|
840
|
+
permissions: stat.mode.toString(8),
|
|
841
|
+
isReadable: !!(stat.mode & parseInt('0400', 8)), // 检查读权限
|
|
842
|
+
isWritable: !!(stat.mode & parseInt('0200', 8)) // 检查写权限
|
|
843
|
+
};
|
|
844
|
+
return {
|
|
845
|
+
success: true,
|
|
846
|
+
output: JSON.stringify(info, null, 2),
|
|
847
|
+
artifacts: []
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
return {
|
|
852
|
+
success: false,
|
|
853
|
+
error: error.message,
|
|
854
|
+
output: ''
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* 格式化字节数
|
|
860
|
+
*/
|
|
861
|
+
static formatBytes(bytes) {
|
|
862
|
+
if (bytes === 0)
|
|
863
|
+
return '0 B';
|
|
864
|
+
const k = 1024;
|
|
865
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
866
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
867
|
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
868
|
+
}
|
|
224
869
|
}
|
|
225
870
|
exports.ToolExecutor = ToolExecutor;
|
|
226
871
|
//# sourceMappingURL=executor.js.map
|