ai-helper-agent 1.3.3 → 1.4.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/README.md +119 -5
- package/bin/agent.js +16 -8
- package/package.json +3 -3
- package/src/auth.js +26 -0
- package/src/config.js +21 -7
- package/src/mcp/client.js +244 -0
- package/src/mcp/mcp-config.js +124 -0
- package/src/mcp/registry.js +165 -0
- package/src/mcp/transport.js +100 -0
- package/src/security.js +58 -5
- package/src/server.js +324 -2
- package/src/skill/executor.js +369 -0
- package/src/skill/loader.js +547 -0
- package/src/skill/markdown-loader.js +411 -0
- package/src/skill/registry.js +310 -0
- package/src/skill/template.js +96 -0
package/src/server.js
CHANGED
|
@@ -12,6 +12,34 @@ import { checkPath, checkCommand } from './security.js';
|
|
|
12
12
|
import { executeCommand, executeCommandSync, addWsClient, killProcess, getRunningProcesses } from './executor.js';
|
|
13
13
|
import { setConsoleOutput, logAuth, logFs, logExec, logSecurity, logSystem, logError, queryLogs, getLogDates } from './logger.js';
|
|
14
14
|
import { initSearchTools, getSearchToolsAvailable, searchFiles, searchContent } from './search.js';
|
|
15
|
+
import {
|
|
16
|
+
initializeMcpRegistry,
|
|
17
|
+
shutdownMcpRegistry,
|
|
18
|
+
getMcpServersStatus,
|
|
19
|
+
getMcpTools,
|
|
20
|
+
callMcpTool,
|
|
21
|
+
connectMcpServer,
|
|
22
|
+
disconnectMcpServer,
|
|
23
|
+
loadMcpConfig,
|
|
24
|
+
addMcpServer,
|
|
25
|
+
removeMcpServer,
|
|
26
|
+
toggleMcpServer
|
|
27
|
+
} from './mcp/registry.js';
|
|
28
|
+
import {
|
|
29
|
+
initializeSkillRegistry,
|
|
30
|
+
getSkillList,
|
|
31
|
+
getSkill,
|
|
32
|
+
toggleSkill,
|
|
33
|
+
runSkill,
|
|
34
|
+
importSkill,
|
|
35
|
+
removeSkill,
|
|
36
|
+
reloadSkills,
|
|
37
|
+
getAgentSkillPrompts,
|
|
38
|
+
getAgentSkillPrompt,
|
|
39
|
+
getSkillsDir
|
|
40
|
+
} from './skill/registry.js';
|
|
41
|
+
import { getSkillExecutionStatus } from './skill/executor.js';
|
|
42
|
+
import { saveMarkdownSkill, importMarkdownSkillFromZip, importMarkdownSkillFromUrl } from './skill/loader.js';
|
|
15
43
|
|
|
16
44
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
45
|
const AGENT_VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')).version;
|
|
@@ -196,7 +224,7 @@ export function startServer() {
|
|
|
196
224
|
});
|
|
197
225
|
}
|
|
198
226
|
|
|
199
|
-
if (
|
|
227
|
+
if (['POST', 'PUT', 'DELETE'].includes(req.method)) {
|
|
200
228
|
let body;
|
|
201
229
|
try { body = await parseBody(req); }
|
|
202
230
|
catch (err) { return jsonResponse(res, 400, { success: false, error: err.message }); }
|
|
@@ -411,6 +439,184 @@ export function startServer() {
|
|
|
411
439
|
logExec('stopped', { execId: body.execId, success: stopped });
|
|
412
440
|
return jsonResponse(res, 200, { success: stopped, execId: body.execId });
|
|
413
441
|
}
|
|
442
|
+
|
|
443
|
+
// === Skill 操作(需要 body) ===
|
|
444
|
+
|
|
445
|
+
// 执行 Skill
|
|
446
|
+
if (pathname === '/api/skill/run') {
|
|
447
|
+
if (!body.name) return jsonResponse(res, 400, { success: false, error: '缺少 name 参数' });
|
|
448
|
+
const result = await runSkill(body.name, body.params || {});
|
|
449
|
+
logSystem('skill_run', { skillName: body.name, success: result.success });
|
|
450
|
+
return jsonResponse(res, result.success ? 200 : 400, result);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 导入 Skill
|
|
454
|
+
if (pathname === '/api/skill/import') {
|
|
455
|
+
if (!body.name || !body.steps) {
|
|
456
|
+
return jsonResponse(res, 400, { success: false, error: '缺少必要参数: name, steps' });
|
|
457
|
+
}
|
|
458
|
+
const result = await importSkill(body);
|
|
459
|
+
logSystem('skill_import', { skillName: body.name });
|
|
460
|
+
return jsonResponse(res, result.success ? 200 : 400, result);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// 删除 Skill
|
|
464
|
+
if (pathname === '/api/skill/delete') {
|
|
465
|
+
if (!body.name) return jsonResponse(res, 400, { success: false, error: '缺少 name 参数' });
|
|
466
|
+
const result = await removeSkill(body.name);
|
|
467
|
+
logSystem('skill_remove', { skillName: body.name });
|
|
468
|
+
return jsonResponse(res, result.success ? 200 : 400, result);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// 切换 Skill 启用/停用
|
|
472
|
+
if (pathname === '/api/skill/toggle') {
|
|
473
|
+
if (!body.name) return jsonResponse(res, 400, { success: false, error: '缺少 name 参数' });
|
|
474
|
+
const result = toggleSkill(body.name);
|
|
475
|
+
logSystem('skill_toggle', { skillName: body.name, enabled: result.enabled });
|
|
476
|
+
return jsonResponse(res, result.success ? 200 : 400, result);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// 重新加载 Skill
|
|
480
|
+
if (pathname === '/api/skill/reload') {
|
|
481
|
+
const count = await reloadSkills();
|
|
482
|
+
logSystem('skill_reload', { count });
|
|
483
|
+
return jsonResponse(res, 200, { success: true, count });
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// === Agent Skill Markdown 管理 ===
|
|
487
|
+
|
|
488
|
+
// 创建/更新 Agent Skill Markdown
|
|
489
|
+
if (pathname === '/api/skill/save-markdown') {
|
|
490
|
+
if (!body.name) {
|
|
491
|
+
return jsonResponse(res, 400, { success: false, error: '缺少 name 参数' });
|
|
492
|
+
}
|
|
493
|
+
if (!body.markdown && !body.prompt) {
|
|
494
|
+
return jsonResponse(res, 400, { success: false, error: '缺少 markdown/prompt 参数' });
|
|
495
|
+
}
|
|
496
|
+
// 检查是否为不可编辑的内置技能
|
|
497
|
+
const { skills: skillsMap } = await import('./skill/registry.js');
|
|
498
|
+
const existingSkill = skillsMap.get(body.name);
|
|
499
|
+
if (existingSkill?.builtin && existingSkill?.editable === false) {
|
|
500
|
+
return jsonResponse(res, 403, { success: false, error: `"${body.name}" 是内置技能,不可编辑` });
|
|
501
|
+
}
|
|
502
|
+
const skillDef = {
|
|
503
|
+
type: 'agent',
|
|
504
|
+
name: body.name,
|
|
505
|
+
description: body.description || '',
|
|
506
|
+
version: body.version || '1.0',
|
|
507
|
+
enabled: body.enabled !== false,
|
|
508
|
+
fullPrompt: body.markdown || body.prompt || '',
|
|
509
|
+
prompt: body.markdown || body.prompt || ''
|
|
510
|
+
};
|
|
511
|
+
const result = saveMarkdownSkill(getSkillsDir(), skillDef);
|
|
512
|
+
if (result.success) {
|
|
513
|
+
// 重新注册
|
|
514
|
+
const { loadMarkdownSkill } = await import('./skill/markdown-loader.js');
|
|
515
|
+
const skill = loadMarkdownSkill(result.dirPath);
|
|
516
|
+
if (skill) {
|
|
517
|
+
skillsMap.set(skill.name, skill);
|
|
518
|
+
}
|
|
519
|
+
logSystem('skill_save_markdown', { skillName: body.name });
|
|
520
|
+
}
|
|
521
|
+
return jsonResponse(res, result.success ? 200 : 400, result);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// 从 Zip 导入 Agent Skill(base64 编码的 zip 内容)
|
|
525
|
+
if (pathname === '/api/skill/import-zip') {
|
|
526
|
+
if (!body.zipData) {
|
|
527
|
+
return jsonResponse(res, 400, { success: false, error: '缺少 zipData 参数(base64 编码的 zip 内容)' });
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
const zipBuffer = Buffer.from(body.zipData, 'base64');
|
|
531
|
+
const result = await importMarkdownSkillFromZip(getSkillsDir(), zipBuffer, body.name);
|
|
532
|
+
if (result.success && result.skill) {
|
|
533
|
+
const { skills } = await import('./skill/registry.js');
|
|
534
|
+
skills.set(result.skill.name, result.skill);
|
|
535
|
+
logSystem('skill_import_zip', { skillName: result.skill.name });
|
|
536
|
+
}
|
|
537
|
+
return jsonResponse(res, result.success ? 200 : 400, result);
|
|
538
|
+
} catch (err) {
|
|
539
|
+
return jsonResponse(res, 400, { success: false, error: `Zip 导入失败: ${err.message}` });
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// 从 URL 导入 Agent Skill
|
|
544
|
+
if (pathname === '/api/skill/import-url') {
|
|
545
|
+
if (!body.url) {
|
|
546
|
+
return jsonResponse(res, 400, { success: false, error: '缺少 url 参数' });
|
|
547
|
+
}
|
|
548
|
+
try {
|
|
549
|
+
const result = await importMarkdownSkillFromUrl(getSkillsDir(), body.url);
|
|
550
|
+
if (result.success && result.skill) {
|
|
551
|
+
const { skills } = await import('./skill/registry.js');
|
|
552
|
+
skills.set(result.skill.name, result.skill);
|
|
553
|
+
logSystem('skill_import_url', { skillName: result.skill.name, url: body.url });
|
|
554
|
+
}
|
|
555
|
+
return jsonResponse(res, result.success ? 200 : 400, result);
|
|
556
|
+
} catch (err) {
|
|
557
|
+
return jsonResponse(res, 400, { success: false, error: `URL 导入失败: ${err.message}` });
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// === MCP 操作(需要 body) ===
|
|
562
|
+
|
|
563
|
+
// 添加 MCP 服务器
|
|
564
|
+
if (pathname === '/api/mcp/servers' && req.method === 'POST') {
|
|
565
|
+
if (!body.id || !body.command) {
|
|
566
|
+
return jsonResponse(res, 400, { success: false, error: '缺少必要参数: id, command' });
|
|
567
|
+
}
|
|
568
|
+
const result = await addMcpServer(body);
|
|
569
|
+
logSystem('mcp_server_add', { serverId: body.id });
|
|
570
|
+
return jsonResponse(res, result.success ? 200 : 400, result);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// 删除 MCP 服务器
|
|
574
|
+
if (pathname === '/api/mcp/servers' && req.method === 'DELETE') {
|
|
575
|
+
if (!body.id) return jsonResponse(res, 400, { success: false, error: '缺少 id 参数' });
|
|
576
|
+
const result = await removeMcpServer(body.id);
|
|
577
|
+
logSystem('mcp_server_remove', { serverId: body.id });
|
|
578
|
+
return jsonResponse(res, result.success ? 200 : 400, result);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// MCP 连接
|
|
582
|
+
if (pathname === '/api/mcp/servers/connect') {
|
|
583
|
+
if (!body.id) return jsonResponse(res, 400, { success: false, error: '缺少 id 参数' });
|
|
584
|
+
const result = await connectMcpServer(body.id);
|
|
585
|
+
logSystem('mcp_server_connect', { serverId: body.id, success: result.success });
|
|
586
|
+
return jsonResponse(res, result.success ? 200 : 500, result);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// MCP 断开
|
|
590
|
+
if (pathname === '/api/mcp/servers/disconnect') {
|
|
591
|
+
if (!body.id) return jsonResponse(res, 400, { success: false, error: '缺少 id 参数' });
|
|
592
|
+
const result = await disconnectMcpServer(body.id);
|
|
593
|
+
logSystem('mcp_server_disconnect', { serverId: body.id });
|
|
594
|
+
return jsonResponse(res, 200, result);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// MCP 启用/禁用切换
|
|
598
|
+
if (pathname === '/api/mcp/servers/toggle') {
|
|
599
|
+
if (body.id === undefined || body.enabled === undefined) {
|
|
600
|
+
return jsonResponse(res, 400, { success: false, error: '缺少参数: id, enabled' });
|
|
601
|
+
}
|
|
602
|
+
// 禁用时先断开连接,确保工具立即可用/不可用
|
|
603
|
+
if (!body.enabled) {
|
|
604
|
+
await disconnectMcpServer(body.id);
|
|
605
|
+
}
|
|
606
|
+
const result = await toggleMcpServer(body.id, body.enabled);
|
|
607
|
+
logSystem('mcp_server_toggle', { serverId: body.id, enabled: body.enabled });
|
|
608
|
+
return jsonResponse(res, result.success ? 200 : 400, result);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// MCP 工具调用
|
|
612
|
+
if (pathname === '/api/mcp/call') {
|
|
613
|
+
if (!body.serverId || !body.toolName) {
|
|
614
|
+
return jsonResponse(res, 400, { success: false, error: '缺少参数: serverId, toolName' });
|
|
615
|
+
}
|
|
616
|
+
const result = await callMcpTool(body.serverId, body.toolName, body.args || {});
|
|
617
|
+
logSystem('mcp_tool_call', { serverId: body.serverId, toolName: body.toolName, success: result.success });
|
|
618
|
+
return jsonResponse(res, result.success ? 200 : 500, result);
|
|
619
|
+
}
|
|
414
620
|
}
|
|
415
621
|
|
|
416
622
|
// 运行中进程列表(需认证)
|
|
@@ -434,6 +640,104 @@ export function startServer() {
|
|
|
434
640
|
return jsonResponse(res, 200, { success: true, dates: getLogDates() });
|
|
435
641
|
}
|
|
436
642
|
|
|
643
|
+
// ========== Skill 管理接口(仅 GET 路由) ==========
|
|
644
|
+
|
|
645
|
+
// Skill 列表
|
|
646
|
+
if (req.method === 'GET' && pathname === '/api/skill/list') {
|
|
647
|
+
return jsonResponse(res, 200, getSkillList());
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Skill 详情
|
|
651
|
+
if (req.method === 'GET' && pathname === '/api/skill/detail') {
|
|
652
|
+
const name = url.searchParams.get('name');
|
|
653
|
+
if (!name) return jsonResponse(res, 400, { success: false, error: '缺少 name 参数' });
|
|
654
|
+
return jsonResponse(res, 200, getSkill(name));
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Skill 执行状态查询
|
|
658
|
+
if (req.method === 'GET' && pathname === '/api/skill/status') {
|
|
659
|
+
const execId = url.searchParams.get('execId');
|
|
660
|
+
if (!execId) return jsonResponse(res, 400, { success: false, error: '缺少 execId 参数' });
|
|
661
|
+
return jsonResponse(res, 200, getSkillExecutionStatus(execId));
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Agent Skill Prompts(用于 AI System Prompt 注入)
|
|
665
|
+
if (req.method === 'GET' && pathname === '/api/skill/agent-prompts') {
|
|
666
|
+
const prompts = getAgentSkillPrompts();
|
|
667
|
+
return jsonResponse(res, 200, { success: true, prompts });
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// 按需加载单个 Agent Skill 的完整内容
|
|
671
|
+
if (req.method === 'GET' && pathname === '/api/skill/agent-prompt') {
|
|
672
|
+
const skillName = url.searchParams.get('name');
|
|
673
|
+
if (!skillName) return jsonResponse(res, 400, { success: false, error: '缺少 name 参数' });
|
|
674
|
+
const result = getAgentSkillPrompt(skillName);
|
|
675
|
+
return jsonResponse(res, result.success ? 200 : 404, result);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Agent Skill 的 SKILL.md 内容
|
|
679
|
+
if (req.method === 'GET' && pathname === '/api/skill/markdown') {
|
|
680
|
+
const name = url.searchParams.get('name');
|
|
681
|
+
if (!name) return jsonResponse(res, 400, { success: false, error: '缺少 name 参数' });
|
|
682
|
+
const result = getSkill(name);
|
|
683
|
+
if (!result.success) return jsonResponse(res, 404, result);
|
|
684
|
+
const skill = result.skill;
|
|
685
|
+
if (skill.type !== 'agent') {
|
|
686
|
+
return jsonResponse(res, 400, { success: false, error: '该 Skill 不是 Agent 类型' });
|
|
687
|
+
}
|
|
688
|
+
return jsonResponse(res, 200, {
|
|
689
|
+
success: true,
|
|
690
|
+
name: skill.name,
|
|
691
|
+
markdown: skill.fullPrompt || skill.prompt || '',
|
|
692
|
+
frontmatter: {
|
|
693
|
+
name: skill.name,
|
|
694
|
+
description: skill.description,
|
|
695
|
+
version: skill.version,
|
|
696
|
+
enabled: skill.enabled
|
|
697
|
+
},
|
|
698
|
+
resources: skill.resources || [],
|
|
699
|
+
dirPath: skill.dirPath
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Skill 资源文件内容
|
|
704
|
+
if (req.method === 'GET' && pathname === '/api/skill/resource') {
|
|
705
|
+
const name = url.searchParams.get('name');
|
|
706
|
+
const resource = url.searchParams.get('resource');
|
|
707
|
+
if (!name || !resource) {
|
|
708
|
+
return jsonResponse(res, 400, { success: false, error: '缺少 name 或 resource 参数' });
|
|
709
|
+
}
|
|
710
|
+
const result = getSkill(name);
|
|
711
|
+
if (!result.success) return jsonResponse(res, 404, result);
|
|
712
|
+
const skill = result.skill;
|
|
713
|
+
if (skill.type !== 'agent') {
|
|
714
|
+
return jsonResponse(res, 400, { success: false, error: '该 Skill 不是 Agent 类型' });
|
|
715
|
+
}
|
|
716
|
+
const resInfo = skill.resources?.find(r => r.name === resource);
|
|
717
|
+
if (!resInfo) {
|
|
718
|
+
return jsonResponse(res, 404, { success: false, error: `资源 "${resource}" 不存在` });
|
|
719
|
+
}
|
|
720
|
+
try {
|
|
721
|
+
const content = readFileSync(resInfo.path, 'utf-8');
|
|
722
|
+
return jsonResponse(res, 200, { success: true, name: resource, content, size: resInfo.size });
|
|
723
|
+
} catch (err) {
|
|
724
|
+
return jsonResponse(res, 500, { success: false, error: `读取资源失败: ${err.message}` });
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// ========== MCP 管理接口(仅 GET 路由) ==========
|
|
729
|
+
|
|
730
|
+
// MCP Servers 列表
|
|
731
|
+
if (req.method === 'GET' && pathname === '/api/mcp/servers') {
|
|
732
|
+
return jsonResponse(res, 200, getMcpServersStatus());
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// MCP 工具列表
|
|
736
|
+
if (req.method === 'GET' && pathname === '/api/mcp/tools') {
|
|
737
|
+
const serverId = url.searchParams.get('serverId') || undefined;
|
|
738
|
+
return jsonResponse(res, 200, getMcpTools(serverId));
|
|
739
|
+
}
|
|
740
|
+
|
|
437
741
|
// 404
|
|
438
742
|
jsonResponse(res, 404, { success: false, error: '未知的 API 路径' });
|
|
439
743
|
}
|
|
@@ -489,10 +793,25 @@ export function startServer() {
|
|
|
489
793
|
}
|
|
490
794
|
});
|
|
491
795
|
|
|
492
|
-
server.listen(port, host, () => {
|
|
796
|
+
server.listen(port, host, async () => {
|
|
493
797
|
console.log(`[Agent] HTTP 服务已启动: http://${host}:${port}`);
|
|
494
798
|
console.log(`[Agent] WebSocket 服务已启动: ws://${host}:${port}`);
|
|
495
799
|
startPairCodeRotation();
|
|
800
|
+
|
|
801
|
+
// 初始化 MCP 注册表(异步,不阻塞请求)
|
|
802
|
+
try {
|
|
803
|
+
await initializeMcpRegistry();
|
|
804
|
+
} catch (err) {
|
|
805
|
+
console.error('[Agent] MCP 注册表初始化失败:', err.message);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// 初始化 Skill 注册表
|
|
809
|
+
try {
|
|
810
|
+
const skillCount = initializeSkillRegistry();
|
|
811
|
+
console.log(`[Agent] Skill 注册表已初始化: ${skillCount} 个 Skill`);
|
|
812
|
+
} catch (err) {
|
|
813
|
+
console.error('[Agent] Skill 注册表初始化失败:', err.message);
|
|
814
|
+
}
|
|
496
815
|
});
|
|
497
816
|
|
|
498
817
|
// 优雅关闭(异步 + 防并发)
|
|
@@ -507,6 +826,9 @@ export function startServer() {
|
|
|
507
826
|
killProcess(entry.execId);
|
|
508
827
|
}
|
|
509
828
|
|
|
829
|
+
// 关闭所有 MCP 连接
|
|
830
|
+
try { shutdownMcpRegistry(); } catch {}
|
|
831
|
+
|
|
510
832
|
// 等待 server 和 wss 关闭
|
|
511
833
|
await Promise.all([
|
|
512
834
|
new Promise(resolve => server.close(resolve)),
|