ai-helper-agent 1.12.1 → 1.12.3
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/package.json +1 -1
- package/src/executor.js +3 -5
- package/src/search.js +2 -2
- package/src/server.js +3 -1
- package/src/skill/executor.js +64 -39
- package/src/skill/loader.js +7 -3
- package/src/skill/markdown-loader.js +2 -2
- package/src/skill/registry.js +2 -2
- package/src/skill/skill-creator-seed.js +10 -8
package/package.json
CHANGED
package/src/executor.js
CHANGED
|
@@ -370,10 +370,8 @@ function killProcess(execId) {
|
|
|
370
370
|
|
|
371
371
|
if (isWin) {
|
|
372
372
|
// Windows: /T 递归终止整个进程树
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
exec(`taskkill /F /T /PID ${pid}`, () => {});
|
|
376
|
-
}
|
|
373
|
+
spawn('taskkill', ['/T', '/PID', String(pid)], { windowsHide: true }).on('error', () => {
|
|
374
|
+
spawn('taskkill', ['/F', '/T', '/PID', String(pid)], { windowsHide: true });
|
|
377
375
|
});
|
|
378
376
|
} else {
|
|
379
377
|
// Unix: 负 PID 表示杀整个进程组(detached + 未调用 setsid 时有效)
|
|
@@ -399,7 +397,7 @@ function killProcess(execId) {
|
|
|
399
397
|
try {
|
|
400
398
|
if (entry.process.exitCode === null) {
|
|
401
399
|
if (isWin) {
|
|
402
|
-
|
|
400
|
+
spawn('taskkill', ['/F', '/T', '/PID', String(pid)], { windowsHide: true });
|
|
403
401
|
} else {
|
|
404
402
|
try { process.kill(-pid, 'SIGKILL'); }
|
|
405
403
|
catch { try { entry.process.kill('SIGKILL'); } catch {} }
|
package/src/search.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
3
|
import { readdir, stat, readFile } from 'fs/promises';
|
|
4
4
|
import { statSync } from 'fs';
|
|
5
|
-
import { join, basename, extname, resolve } from 'path';
|
|
5
|
+
import { join, basename, extname, resolve, isAbsolute } from 'path';
|
|
6
6
|
import { checkPath } from './security.js';
|
|
7
7
|
|
|
8
8
|
// 缓存检测结果,避免每次启动重复检测
|
|
@@ -112,7 +112,7 @@ function searchWithFd(rootPath, filePattern, recursive, maxResults) {
|
|
|
112
112
|
const output = Buffer.concat(stdout).toString('utf-8').trim();
|
|
113
113
|
const lines = output ? output.split('\n').filter(Boolean) : [];
|
|
114
114
|
const results = lines.map(p => {
|
|
115
|
-
const absPath = join(rootPath, p);
|
|
115
|
+
const absPath = isAbsolute(p) ? p : join(rootPath, p);
|
|
116
116
|
try {
|
|
117
117
|
const s = statSync(absPath);
|
|
118
118
|
return { path: absPath, name: basename(p), type: s.isDirectory() ? 'directory' : 'file', size: s.size, mtime: s.mtimeMs };
|
package/src/server.js
CHANGED
|
@@ -420,6 +420,7 @@ export function startServer() {
|
|
|
420
420
|
version: AGENT_VERSION,
|
|
421
421
|
running: true,
|
|
422
422
|
platform: PLATFORM_INFO.platform,
|
|
423
|
+
platformName: PLATFORM_INFO.platformName,
|
|
423
424
|
arch: PLATFORM_INFO.arch,
|
|
424
425
|
nodeVersion: PLATFORM_INFO.nodeVersion,
|
|
425
426
|
searchTools: getSearchToolsAvailable()
|
|
@@ -537,7 +538,8 @@ export function startServer() {
|
|
|
537
538
|
const result = await new Promise((resolvePromise) => {
|
|
538
539
|
const npm = spawn('npm', ['install', '-g', 'ai-helper-agent@latest', '--no-audit', '--no-fund'], {
|
|
539
540
|
shell: true,
|
|
540
|
-
env: { ...process.env }
|
|
541
|
+
env: { ...process.env },
|
|
542
|
+
windowsHide: true
|
|
541
543
|
});
|
|
542
544
|
npm.stdout.on('data', d => npmOutput.push(d.toString().trim()));
|
|
543
545
|
npm.stderr.on('data', d => npmOutput.push(d.toString().trim()));
|
package/src/skill/executor.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// skill/executor.js - Skill 执行引擎
|
|
2
2
|
// 解析 Skill 定义的步骤 DAG,按拓扑排序执行,支持并行
|
|
3
3
|
import { render } from './template.js';
|
|
4
|
-
import { readFile, writeFile, readdir, stat } from 'fs/promises';
|
|
4
|
+
import { readFile, writeFile, readdir, stat, unlink } from 'fs/promises';
|
|
5
5
|
import { checkPath, checkCommand } from '../security.js';
|
|
6
|
-
import { join } from 'path';
|
|
6
|
+
import { join, basename } from 'path';
|
|
7
7
|
|
|
8
8
|
// 执行上下文(当前活跃的执行任务)
|
|
9
9
|
const activeExecutions = new Map(); // execId → { skill, status, steps, results }
|
|
@@ -47,32 +47,53 @@ async function executeToolCall(toolName, args) {
|
|
|
47
47
|
// 映射 Skill 步骤中的 tool 到实际 Agent API
|
|
48
48
|
try {
|
|
49
49
|
switch (toolName) {
|
|
50
|
-
case '
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
case 'agent_file':
|
|
51
|
+
case 'agent_read_file':
|
|
52
|
+
case 'agent_write_file':
|
|
53
|
+
case 'agent_list_dir':
|
|
54
|
+
case 'agent_delete_file':
|
|
55
|
+
case 'agent_download_file': {
|
|
56
|
+
const action = args.action || (
|
|
57
|
+
toolName === 'agent_read_file' ? 'read' :
|
|
58
|
+
toolName === 'agent_write_file' ? 'write' :
|
|
59
|
+
toolName === 'agent_list_dir' ? 'list' :
|
|
60
|
+
toolName === 'agent_delete_file' ? 'delete' :
|
|
61
|
+
toolName === 'agent_download_file' ? 'download' :
|
|
62
|
+
(args.content ? 'write' : 'read')
|
|
63
|
+
);
|
|
58
64
|
const check = await checkPath(args.path);
|
|
59
65
|
if (!check.allowed) return { success: false, error: check.reason };
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
switch (action) {
|
|
67
|
+
case 'read': {
|
|
68
|
+
const content = await readFile(check.resolved, 'utf-8');
|
|
69
|
+
return { success: true, content, size: content.length, path: check.resolved };
|
|
70
|
+
}
|
|
71
|
+
case 'write': {
|
|
72
|
+
await writeFile(check.resolved, String(args.content || ''), 'utf-8');
|
|
73
|
+
return { success: true, message: `文件已写入: ${check.resolved}` };
|
|
74
|
+
}
|
|
75
|
+
case 'list': {
|
|
76
|
+
const names = await readdir(check.resolved);
|
|
77
|
+
const entries = await Promise.all(names.map(async (name) => {
|
|
78
|
+
const s = await stat(join(check.resolved, name));
|
|
79
|
+
return { name, type: s.isDirectory() ? 'directory' : 'file', size: s.size };
|
|
80
|
+
}));
|
|
81
|
+
return { success: true, entries, path: check.resolved };
|
|
82
|
+
}
|
|
83
|
+
case 'delete': {
|
|
84
|
+
await unlink(check.resolved);
|
|
85
|
+
return { success: true, message: `已删除: ${check.resolved}` };
|
|
86
|
+
}
|
|
87
|
+
case 'download': {
|
|
88
|
+
const content = await readFile(check.resolved);
|
|
89
|
+
return { success: true, content, path: check.resolved, filename: basename(check.resolved) };
|
|
90
|
+
}
|
|
91
|
+
default:
|
|
92
|
+
return { success: false, error: `未知 action: ${action}` };
|
|
93
|
+
}
|
|
73
94
|
}
|
|
74
95
|
|
|
75
|
-
case '
|
|
96
|
+
case 'agent_exec': {
|
|
76
97
|
const cmdCheck = checkCommand(args.command, false);
|
|
77
98
|
if (cmdCheck.level === 'deny') {
|
|
78
99
|
return { success: false, error: cmdCheck.reason };
|
|
@@ -87,7 +108,8 @@ async function executeToolCall(toolName, args) {
|
|
|
87
108
|
cwd: args.cwd || process.cwd(),
|
|
88
109
|
encoding: 'utf-8',
|
|
89
110
|
timeout: 30000,
|
|
90
|
-
maxBuffer: 10 * 1024 * 1024
|
|
111
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
112
|
+
windowsHide: true
|
|
91
113
|
}, (error, stdout, stderr) => {
|
|
92
114
|
if (error) {
|
|
93
115
|
resolve({ success: false, error: error.message, stdout, stderr });
|
|
@@ -98,8 +120,23 @@ async function executeToolCall(toolName, args) {
|
|
|
98
120
|
});
|
|
99
121
|
}
|
|
100
122
|
|
|
101
|
-
case '
|
|
102
|
-
|
|
123
|
+
case 'agent_search':
|
|
124
|
+
case 'agent_search_files':
|
|
125
|
+
case 'agent_search_content': {
|
|
126
|
+
const searchType = args.searchType || (
|
|
127
|
+
toolName === 'agent_search_content' ? 'content' : 'file'
|
|
128
|
+
);
|
|
129
|
+
const { searchFiles, searchContent } = await import('../search.js');
|
|
130
|
+
if (searchType === 'content') {
|
|
131
|
+
return await searchContent(
|
|
132
|
+
args.path || '.',
|
|
133
|
+
args.pattern,
|
|
134
|
+
args.filePattern || null,
|
|
135
|
+
args.caseSensitive || false,
|
|
136
|
+
Math.min(args.maxResults || 100, 5000),
|
|
137
|
+
args.contextLines || 2
|
|
138
|
+
);
|
|
139
|
+
}
|
|
103
140
|
return await searchFiles(
|
|
104
141
|
args.path || '.',
|
|
105
142
|
args.pattern || '*',
|
|
@@ -108,18 +145,6 @@ async function executeToolCall(toolName, args) {
|
|
|
108
145
|
);
|
|
109
146
|
}
|
|
110
147
|
|
|
111
|
-
case 'agent_search_content': {
|
|
112
|
-
const { searchContent } = await import('../search.js');
|
|
113
|
-
return await searchContent(
|
|
114
|
-
args.path || '.',
|
|
115
|
-
args.pattern,
|
|
116
|
-
args.filePattern || null,
|
|
117
|
-
args.caseSensitive || false,
|
|
118
|
-
Math.min(args.maxResults || 100, 5000),
|
|
119
|
-
args.contextLines || 2
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
148
|
default:
|
|
124
149
|
return { success: false, error: `未知工具: ${toolName}` };
|
|
125
150
|
}
|
package/src/skill/loader.js
CHANGED
|
@@ -256,19 +256,23 @@ export function deleteSkillFile(name, type) {
|
|
|
256
256
|
/**
|
|
257
257
|
* 种子 skill-creator 内置技能到文件系统
|
|
258
258
|
* 首次运行时将 skill-creator 的 SKILL.md 写入 ~/.ai-helper-agent/skills/skill-creator/
|
|
259
|
-
*
|
|
259
|
+
* 当种子版本号高于已安装版本时自动更新
|
|
260
260
|
* @returns {boolean} 是否执行了种子写入
|
|
261
261
|
*/
|
|
262
262
|
export function seedSkillCreator() {
|
|
263
263
|
const skillDir = join(SKILLS_DIR, SKILL_CREATOR_DIR);
|
|
264
264
|
const skillMdPath = join(skillDir, 'SKILL.md');
|
|
265
265
|
|
|
266
|
+
// 从种子内容中提取目标版本号
|
|
267
|
+
const seedVersionMatch = SKILL_CREATOR_SEED_MD.match(/^version:\s*([\d.]+)/m);
|
|
268
|
+
const seedVersion = seedVersionMatch ? seedVersionMatch[1] : '0.0.0';
|
|
269
|
+
|
|
266
270
|
if (existsSync(skillMdPath)) {
|
|
267
271
|
try {
|
|
268
272
|
const content = readFileSync(skillMdPath, 'utf-8');
|
|
269
273
|
const versionMatch = content.match(/^version:\s*([\d.]+)/m);
|
|
270
|
-
const
|
|
271
|
-
if (
|
|
274
|
+
const installedVersion = versionMatch ? versionMatch[1] : '0.0.0';
|
|
275
|
+
if (installedVersion === seedVersion) {
|
|
272
276
|
return false;
|
|
273
277
|
}
|
|
274
278
|
} catch { /* 读取失败则覆盖 */ }
|
|
@@ -360,7 +360,7 @@ export async function importMarkdownSkillFromZip(skillsDir, zipBuffer, skillName
|
|
|
360
360
|
writeFileSync(tmpZip, zipBuffer);
|
|
361
361
|
|
|
362
362
|
// 解压到临时目录(execFile 数组参数,不经 shell,杜绝命令注入)
|
|
363
|
-
await execFileAsync('unzip', ['-o', tmpZip, '-d', tmpDir], { timeout: 30000 });
|
|
363
|
+
await execFileAsync('unzip', ['-o', tmpZip, '-d', tmpDir], { timeout: 30000, windowsHide: true });
|
|
364
364
|
|
|
365
365
|
// 查找 SKILL.md
|
|
366
366
|
const entries = readdirSync(tmpDir);
|
|
@@ -414,7 +414,7 @@ export async function importMarkdownSkillFromZip(skillsDir, zipBuffer, skillName
|
|
|
414
414
|
}
|
|
415
415
|
|
|
416
416
|
// 复制(execFile 数组参数,不经 shell)
|
|
417
|
-
await execFileAsync('cp', ['-r', skillDir + '/.', destDir], { timeout: 30000 });
|
|
417
|
+
await execFileAsync('cp', ['-r', skillDir + '/.', destDir], { timeout: 30000, windowsHide: true });
|
|
418
418
|
|
|
419
419
|
// 重新加载
|
|
420
420
|
const skill = loadMarkdownSkill(destDir);
|
package/src/skill/registry.js
CHANGED
|
@@ -194,7 +194,7 @@ export async function runSkill(name, params = {}, onStepUpdate) {
|
|
|
194
194
|
/**
|
|
195
195
|
* 获取所有启用的 Agent Skill 的轻量列表
|
|
196
196
|
* 仅包含名称和描述,用于注入到 AI System Prompt 中。
|
|
197
|
-
* AI 需要使用
|
|
197
|
+
* AI 需要使用 agent_skill 工具按需加载完整内容。
|
|
198
198
|
* @returns {string} - 格式化的技能列表文本
|
|
199
199
|
*/
|
|
200
200
|
export function getAgentSkillPrompts() {
|
|
@@ -210,7 +210,7 @@ export function getAgentSkillPrompts() {
|
|
|
210
210
|
const parts = [];
|
|
211
211
|
parts.push('## 可用技能 (Skills)');
|
|
212
212
|
parts.push('');
|
|
213
|
-
parts.push('以下是可用的 Agent Skill(AI 能力扩展)列表。当用户需求与某个技能高度匹配时,使用 `
|
|
213
|
+
parts.push('以下是可用的 Agent Skill(AI 能力扩展)列表。当用户需求与某个技能高度匹配时,使用 `agent_skill` 工具(action: load)加载该技能的完整说明,然后根据说明自主完成任务。');
|
|
214
214
|
parts.push('');
|
|
215
215
|
parts.push('**使用策略**:');
|
|
216
216
|
parts.push('- 仅在用户需求明确匹配技能描述时才加载,不要为一般性的编程/技术问题加载技能');
|
|
@@ -8,7 +8,7 @@ export const SKILL_CREATOR_SEED_MD = `---
|
|
|
8
8
|
name: skill-creator
|
|
9
9
|
description: "创建新技能的元技能。当用户要求创建技能、沉淀技能、新增技能、或任何创建/添加技能的意图时,必须立即调用此技能。"
|
|
10
10
|
enabled: true
|
|
11
|
-
version: 1.1
|
|
11
|
+
version: 1.2.1
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# Skill Creator
|
|
@@ -99,18 +99,20 @@ enabled: true
|
|
|
99
99
|
- 示例(正例和反例)
|
|
100
100
|
- 注意事项(容易踩的坑)
|
|
101
101
|
|
|
102
|
+
> 💡 **多轮对话沉淀技巧**:当对话涉及多轮工具调用(ReAct 循环)时,先调用 \`exec_log\` 工具(scope: full_session)提取完整的执行日志。从**成功路径**中提炼经过验证的有效操作步骤,从**失败教训**中提炼注意事项和避坑指南,从**反思建议**中提炼优化方案。这能确保沉淀的技能内容覆盖完整的操作过程,而不仅仅是最终结果。
|
|
103
|
+
|
|
102
104
|
4. **创建技能目录**:
|
|
103
|
-
- 使用 \`
|
|
105
|
+
- 使用 \`agent_exec\` 创建技能子目录。
|
|
104
106
|
- **macOS/Linux**:\`mkdir -p ~/.ai-helper-agent/skills/<skill-name>\`
|
|
105
107
|
- **Windows (cmd)**:\`mkdir %USERPROFILE%\\.ai-helper-agent\\skills\\<skill-name>\`
|
|
106
108
|
- **Windows (PowerShell)**:\`New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\\.ai-helper-agent\\skills\\<skill-name>"\`
|
|
107
|
-
- 如果 \`mkdir\` 报错,尝试不带参数或使用 \`
|
|
109
|
+
- 如果 \`mkdir\` 报错,尝试不带参数或使用 \`agent_file\`(action: write)在目标路径写入一个临时文件,让系统自动创建父目录。
|
|
108
110
|
|
|
109
|
-
5. **写入 SKILL.md**:使用 \`
|
|
111
|
+
5. **写入 SKILL.md**:使用 \`agent_file\`(action: write)工具将内容写入目标文件。
|
|
110
112
|
|
|
111
|
-
> ⚠️ **关键限制**:\`
|
|
113
|
+
> ⚠️ **关键限制**:\`agent_file\` 不接受 \`~\` 或相对路径,**必须传入绝对路径**。
|
|
112
114
|
|
|
113
|
-
**推荐做法**:先用 \`
|
|
115
|
+
**推荐做法**:先用 \`agent_exec\` 获取用户主目录的绝对路径,再拼接完整路径。
|
|
114
116
|
|
|
115
117
|
6. **告知用户**:创建完成后,告知用户技能已就绪,说明触发关键词。建议用户在工具箱页面点击「重新加载」使技能生效。
|
|
116
118
|
|
|
@@ -119,10 +121,10 @@ enabled: true
|
|
|
119
121
|
当用户要求更新/完善已有技能时:
|
|
120
122
|
|
|
121
123
|
1. **确认技能名称**:与用户确认要更新的技能名称
|
|
122
|
-
2. **读取现有内容**:使用 \`
|
|
124
|
+
2. **读取现有内容**:使用 \`agent_file\`(action: read)读取目标 SKILL.md(路径规则同创建流程,需使用绝对路径,如 \`/Users/<你的用户名>/.ai-helper-agent/skills/<skill-name>/SKILL.md\`)
|
|
123
125
|
3. **分析更新需求**:结合当前对话上下文,识别需要更新的部分
|
|
124
126
|
4. **合并内容**:保留原技能的核心结构,将新内容融入适当位置
|
|
125
|
-
5. **覆盖写入**:使用 \`
|
|
127
|
+
5. **覆盖写入**:使用 \`agent_file\`(action: write)覆盖写入 SKILL.md(路径规则同上,必须使用绝对路径)
|
|
126
128
|
6. **告知用户**:告知更新内容,建议重新加载
|
|
127
129
|
|
|
128
130
|
## 更新注意事项
|