ai-helper-agent 1.3.4 → 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/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 (req.method === 'POST') {
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)),