nkds

导航

 

基于MonkeyCode二次开发:定制企业专属AI助手的完整指南(2026实战)

"MonkeyCode开源版已经非常强大,但每个企业都有自己独特的需求。通过二次开发,你可以打造一个完全贴合你业务场景的专属AI编程助手。"


一、为什么需要二次开发?

1.1 开源版的边界

┌─────────────────────────────────────────────────────┐
│           MonkeyCode开源版 vs 企业需求                │
│                                                      │
│  ✅ 开源版已具备的能力:                              │
│  ├── SDD规范驱动开发(完整功能)                      │
│  ├── MonkeyScan安全扫描(完整功能)                   │
│  ├── Agent代码生成(完整功能)                        │
│  ├── MCP协议支持(完整功能)                          │
│  ├── Issue→PR Pipeline(完整功能)                    │
│  └── 多语言/多框架支持                                │
│                                                      │
│  ❌ 企业可能需要的额外能力(需二次开发):              │
│  ├── 与内部系统深度集成(ERP/OA/CMDB)               │
│  ├── 自定义编码规范(公司特有的代码风格)             │
│  ├── 行业专用规则引擎(金融/医疗/政务等)             │
│  ├── 定制化UI/交互(嵌入现有管理后台)               │
│  ├── 高级权限控制(细粒度到字段级别)                 │
│  ├── 审计合规报告(满足特定监管要求)                 │
│  └── 与自研LLM的对接(私有模型微调后集成)            │
│                                                      │
│  💡 核心观点:                                       │
│  开源版是"通用型工具",                              │
│  二次开发的目标是"行业解决方案"。                     │
└─────────────────────────────────────────────────────┘

1.2 二次开发的典型场景

# 典型的二次开发需求矩阵

scenarios:
  # 场景1: 深度系统集成
  - name: "ERP数据联动"
    description: "让Agent能查询和修改ERP中的业务数据"
    complexity: medium
    approach: "开发自定义MCP Server"
    
  - name: "OA审批流集成"
    description: "代码变更自动触发OA审批流程"
    complexity: medium
    approach: "扩展Pipeline + Webhook回调"
    
  - name: "CMDB资产关联"
    description: "扫描结果自动关联到CMDB资产库"
    complexity: high
    approach: "自定义Scanner规则 + API对接"

  # 场景2: 行业定制
  - name: "金融行业编码规范"
    description: "符合银监会/证监会要求的代码规范检查"
    complexity: high
    approach: "自定义SDD模板 + MonkeyScan规则集"
    
  - name: "医疗行业HIPAA合规"
    description: "满足美国HIPAA法案的安全要求"
    complexity: very_high
    approach: "全面安全规则重写 + 审计增强"
    
  - name: "政务等保三级"
    description: "满足等保三级的所有技术要求"
    complexity: high
    approach: "等保规则集 + 合规报告生成器"

  # 场景3: UI/UX定制
  - name: "嵌入现有管理后台"
    description: "将MonkeyCode作为模块嵌入公司内部平台"
    complexity: medium
    approach: "前端组件封装 + API网关"
    
  - name: "多租户SaaS化"
    description: "为多个子公司提供独立的MonkeyCode实例"
    complexity: very_high
    approach: "架构改造 + 租户隔离 + 统一管控"

  # 场景4: AI能力定制
  - name: "接入自研大模型"
    description: "使用公司微调后的私有LLM替代默认模型"
    complexity: medium
    approach: "LlmProvider实现 + 模型路由"
    
  - name: "领域知识注入"
    description: "让Agent理解公司特有的业务术语和上下文"
    complexity: high
    approach: "RAG系统 + 知识库 + Prompt工程"

二、二次开发架构总览

2.1 MonkeyCode可扩展性架构图

MonkeyCode 二次开发扩展点全景:

┌─────────────────────────────────────────────────────────┐
│                    用户界面层 (UI Layer)                  │
│                                                           │
│   Web UI (React)          │  CLI (Commander.js)         │
│   VSCode Extension        │  IDE Plugin (JetBrains)     │
│   [可替换/可嵌入]          │  [可扩展]                     │
├───────────────────────────┼─────────────────────────────┤
│                    API网关层 (Gateway)                     │
│                                                           │
│   REST API                 │  GraphQL (可选)             │
│   WebSocket (实时通信)      │  gRPC (高性能调用)          │
│   [可添加认证/限流/日志]    │  [可扩展协议]               │
├───────────────────────────┼─────────────────────────────┤
│                   核心引擎层 (Core Engine)                 │
│                                                           │
│   ┌─────────────────────────────────────────────┐       │
│   │              Engine (编排器)                   │       │
│   │  [可配置Pipeline步骤/可插拔Agent]              │       │
│   ├─────────────────────────────────────────────┤       │
│   │  Agent层:                                      │       │
│   │  ├── PlannerAgent    [✅ 可继承/可替换]         │       │
│   │  ├── ArchitectAgent  [✅ 可继承/可替换]         │       │
│   │  ├── CoderAgent      [✅ 可继承/可替换]         │       │
│   │  ├── TesterAgent     [✅ 可继承/可替换]         │       │
│   │  ├── ReviewerAgent   [✅ 可继承/可替换]         │       │
│   │  ├── ScannerAgent    [✅ 可继承/可扩展规则]      │       │
│   │  └── DevOpsAgent     [✅ 可继承/可替换]         │       │
│   │                                               │       │
│   │  🔑 关键: 继承BaseAgent即可创建自定义Agent     │       │
│   └─────────────────────────────────────────────┘       │
├───────────────────────────┼─────────────────────────────┤
│                   工具与集成层 (Tools & Integration)      │
│                                                           │
│   MCP Protocol (核心扩展机制):                            │
│   ├── 内置MCP Servers (8个)                               │
│   ├── 社区MCP Servers (112+)                             │
│   └── 🎯 自定义MCP Server ← 你的扩展入口               │
│                                                           │
│   LLM Provider (抽象层):                                  │
│   ├── OpenAI Compatible                                 │
│   ├── Anthropic                                         │
│   ├── Ollama (本地模型)                                 │
│   └── 🎯 Custom Provider ← 接入你的私有模型            │
│                                                           │
│   Memory System (记忆系统):                               │
│   ├── ShortTermMemory (短期)                             │
│   ├── LongTermMemory (长期)                              │
│   └── 🎯 Custom Memory Store ← 对接你的知识库            │
├───────────────────────────┼─────────────────────────────┤
│                   数据与存储层 (Data & Storage)             │
│                                                           │
│   PostgreSQL (元数据/用户/配置)                           │
│   Redis (缓存/会话/队列)                                  │
│   MinIO/S3 (文件存储)                                     │
│   Elasticsearch (搜索索引)                                 │
│   🎯 可替换为任何兼容的数据源                              │
└───────────────────────────┴─────────────────────────────┘

推荐扩展优先级:
  ⭐⭐⭐ MCP自定义Server (最快见效,1-3天)
  ⭐⭐⭐ LLM Provider自定义 (接入自有模型,2-5天)
  ⭐⭐⭐ MonkeyScan规则扩展 (行业适配,3-7天)
  ⭐⭐   自定义Agent (复杂业务逻辑,1-2周)
  ⭐⭐   UI定制 (嵌入现有系统,1-3周)
  ⭐     架构级改造 (多租户/高可用,1-2月)

2.2 技术选型建议

二次开发技术栈推荐:

┌──────────────┬────────────────┬────────────────────────┐
│ 扩展类型       │ 推荐技术栈      │ 说明                    │
├──────────────┼────────────────┼────────────────────────┤
│ MCP Server    │ TypeScript    │ 与MonkeyCode同语言,     │
│              │ + SDK         │ 类型安全,生态一致       │
│              │              │                         │
│ 自定义Agent   │ TypeScript    │ 继承BaseAgent基类       │
│              │              │ 覆写buildPrompt方法      │
│              │              │                         │
│ Scanner规则  │ TypeScript   │ 或YAML声明式规则        │
│              │ / YAML       │ 低复杂度用YAML足够      │
│              │              │                         │
│ LLM Provider  │ TypeScript   │ 实现LlmProvider接口     │
│              │              │ 支持任何OpenAI兼容API   │
│              │              │                         │
│ UI定制        │ React + TS   │ MonkeyCode前端技术栈    │
│              │              │ 可直接复用组件           │
│              │              │                         │
│ 后端API扩展   │ Express/Fast  │ 在Server层添加新路由    │
│              │ ify          │ 或中间件                │
│              │              │                         │
│ 存储扩展      │ TypeORM/     │ MonkeyCode使用TypeORM   │
│              │ Prisma       │ 添加新的Entity即可       │
└──────────────┴────────────────┴────────────────────────┘

三、实战:5种最常见的二次开发方式

方式一:自定义MCP Server(推荐入门)

3.1.1 最简MCP Server示例

// my-custom-mcp-server/src/index.ts
// 企业内部工单系统MCP Server —— 最简实现

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

// 1. 创建Server实例
const server = new McpServer({
  name: 'enterprise-ticket-system',
  version: '1.0.0',
  description: '企业内部工单系统MCP工具',
});

// 2. 定义工具:查询工单
server.tool(
  'get_ticket',
  '根据工单号查询详细信息',
  {
    ticketId: z.string().describe('工单编号,如TK-20260708-001'),
  },
  async ({ ticketId }) => {
    // 这里调用你们内部的工单系统API
    const ticket = await fetchTicketFromInternalApi(ticketId);
    
    return {
      content: [{
        type: 'text' as const,
        text: JSON.stringify({
          id: ticket.id,
          title: ticket.title,
          status: ticket.status,      // open/in_progress/resolved/closed
          priority: ticket.priority,  // P0/P1/P2/P3
          assignee: ticket.assignee,
          createdAt: ticket.createdAt,
          updatedAt: ticket.updatedAt,
          description: ticket.description,
          comments: ticket.comments.slice(0, 5), // 最近5条评论
        }, null, 2),
      }],
    };
  }
);

// 3. 定义工具:创建工单
server.tool(
  'create_ticket',
  '自动创建一个新的工单',
  {
    title: z.string().describe('工单标题'),
    description: z.string().describe('问题描述'),
    priority: z.enum(['P0', 'P1', 'P2', 'P3']).default('P2'),
    category: z.string().optional().describe('工单分类'),
    relatedComponent: z.string().optional().describe('关联的系统组件'),
  },
  async ({ title, description, priority, category, relatedComponent }) => {
    const newTicket = await createTicketViaInternalApi({
      title,
      description,
      priority,
      category,
      relatedComponent,
      createdBy: 'monkeycode-agent', // 标记来源
    });
    
    return {
      content: [{
        type: 'text' as const,
        text: `✅ 工单已创建:\n` +
              `- 编号: ${newTicket.id}\n` +
              `- 链接: ${newTicket.url}\n` +
              `- 处理人: ${newTicket.assignee || '待分配'}\n` +
              `- 预计响应时间: ${newTicket.slaResponseTime}`,
      }],
    };
  }
);

// 4. 定义工具:更新工单状态
server.tool(
  'update_ticket_status',
  '更新工单状态或添加评论',
  {
    ticketId: z.string(),
    action: z.enum(['add_comment', 'change_status', 'reassign']),
    content: z.string().describe('评论内容/新状态/新负责人'),
  },
  async ({ ticketId, action, content }) => {
    const result = await updateTicket(ticketId, action, content);
    
    return {
      content: [{
        type: 'text' as const,
        text: `✅ 工单 ${ticketId} 已更新:\n操作: ${action}\n结果: ${JSON.stringify(result)}`,
      }],
    };
  }
);

// 5. 启动服务
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('Enterprise Ticket System MCP Server running...');
}

main().catch(console.error);

// ======== 辅助函数(需要根据实际API实现)========

async function fetchTicketFromInternalApi(id: any) {
  // TODO: 替换为你公司的实际API调用
  // 示例: return await axios.get(`https://internal-api.company.com/tickets/${id}`);
  return {
    id,
    title: '登录页面无法打开',
    status: 'open',
    priority: 'P1',
    assignee: '张三',
    createdAt: '2026-07-07T10:30:00Z',
    updatedAt: '2026-07-07T14:20:00Z',
    description: '用户反馈点击登录按钮后页面无响应',
    comments: [
      { author: '用户', content: '无法登录,已尝试清除缓存无效', time: '10:30' },
      { author: '客服', content: '已确认问题复现,转技术处理', time: '11:00' },
    ],
  };
}

async function createTicketViaInternalApi(data: any) {
  // TODO: 替换为实际的API调用
  return {
    id: `TK-${Date.now()}`,
    url: `https://ticket.company.com/TK-${Date.now()}`,
    assignee: '值班工程师',
    slaResponseTime: '30分钟内',
  };
}

async function updateTicket(id: any, action: any, content: any) {
  // TODO: 替换为实际的API调用
  return { success: true };
}

3.1.2 将MCP Server注册到MonkeyCode

# monkeycode-config.yaml
# MonkeyCode配置文件 — 添加自定义MCP Server

mcp_servers:
  # ... 其他内置/社区Server ...
  
  # 👇 你的自定义Server
  enterprise-ticket:
    command: npx
    args:
      - "ts-node" 
      - "/path/to/my-custom-mcp-server/src/index.ts"
    env:
      TICKET_API_BASE_URL: "${TICKET_API_BASE_URL}"
      TICKET_API_TOKEN: "${TICKET_API_TOKEN}"
      
  # 可以同时注册多个自定义Server
  enterprise-cmdb:
    command: npx
    args: ["@company/mcp-cmdb"]
    env:
      CMDB_API_KEY: "${CMDB_API_KEY}"
      
  enterprise-oa:
    command: npx  
    args: ["@company/mcp-oa-approval"]
    env:
      OA_SYSTEM_URL: "${OA_INTERNAL_URL}"

3.1.3 效果验证

注册成功后,Agent就可以在对话中使用这些工具了:

用户(开发者):
  "帮我查一下工单 TK-20260708-001 的状态,
   如果还没解决,帮我加一条评论说正在排查中"

Agent执行过程:
  Step 1: 调用 get_ticket("TK-20260708-001")
          → 返回: 状态=open, 分配给张三
  
  Step 2: 判断需要更新工单
          → 调用 update_ticket_status({
              ticketId: "TK-20260708-001",
              action: "add_comment",
              content: "[MonkeyCode Agent] 正在排查中,
                       预计30分钟内给出初步结论"
            })
          
  Step 3: 返回给用户
          → "已查询到工单TK-20260708-001:
             状态:处理中 | 负责人:张三 | 优先级:P1
             
             已自动添加评论:'正在排查中,预计30分钟内给出初步结论'
             
             [查看工单](https://ticket.company.com/TK-20260708-001)"

这就是MCP的力量——
几行代码就让Agent具备了操作企业内部系统的能力!

方式二:自定义Scanner规则

3.2.1 YAML声明式规则(最简单)

# monkeycode-custom-rules/company-security-rules.yaml
# 公司级安全扫描规则集

ruleSet:
  name: "my-company-security-standard"
  version: "2.0"
  description: "符合我公司安全编码规范的扫描规则"
  
  categories:
    
    # 规则类1: 公司内部API规范
    - category: internal_api_compliance
      rules:
        - id: MYCOMPANY-001
          name: "内部API必须携带TraceId"
          severity: MEDIUM
          description: "所有HTTP请求必须携带X-Trace-ID头用于链路追踪"
          patterns:
            - pattern: "(axios|fetch|request|got)\\("
              type: code_pattern
              scope: source_code
          mustContain:
            - pattern: "X-Trace-ID|X-Request-Id|traceId"
              within: 5lines  # 在匹配行附近5行内必须出现
          autoFix: "在请求头中添加 X-Trace-ID: generateUuid()"
          docsLink: "https://wiki.internal.company.com/api-tracing-guide"
            
        - id: MYCOMPANY-002
          name: "禁止硬编码内部服务地址"
          severity: HIGH
          description: "内部服务地址必须从配置中心获取"
          forbiddenPatterns:
            - pattern: "http(s)?://(192\\.168\\.|10\\.|172\\.(1[6-9]|2[0-9]|3[01])\\.)"
              type: regex
              message: "检测到硬编码的内网IP地址"
            - pattern: "http(s)?://internal-service-[a-z]+\\.company\\.com"
              type: regex
              message: "检测到硬编码的服务域名"
          suggestedReplacement: "使用ConfigService.get('service.url')获取地址"
          
        - id: MYCOMPANY-003
          name: "敏感接口必须记录审计日志"
          severity: HIGH
          description: "涉及资金/权限/用户数据的接口必须记录审计日志"
          sensitivePatterns:
            - "/api/v1/(payment|transfer|withdraw|grant|revoke)"
            - "/api/v1/admin/"
            - "/api/v1/user/(delete|password|role)"
          requiredAnnotation:
            - "@AuditLog"  # 必须有此注解
            - "auditLogger.info"  # 或包含此调用

    # 规则类2: 公司编码风格
    - category: coding_style
      rules:
        - id: MYCOMPANY-010
          name: "错误码必须使用统一枚举"
          severity: LOW
          description: "业务错误码必须从ErrorCodeEnum中选择"
          patterns:
            - pattern: "throw new (BusinessError|AppError|HttpException)"
              checkArgument: true
              allowedValues: "ErrorCodeEnum的所有成员"
              
        - id: MYCOMPANY-011
          name: "Controller方法必须有Swagger注解"
          severity: MEDIUM
          description: "所有对外暴露的REST接口必须添加@Api/@Operation注解"
          scope:
            - "**/*Controller.java"
            - "**/*Controller.ts"
          requiredAnnotations:
            - "@(Api|Get|Post|Put|Delete|RequestMapping)"
            - "@(ApiOperation|Operation|Summary)"

    # 规则类3: 性能规范
    - category: performance
      rules:
        - id: MYCOMPANY-020
          name: "禁止N+1查询"
          severity: HIGH
          description: "循环内不得执行数据库查询"
          detection:
            - forLoopPattern: "\\b(for|foreach|while|\\.forEach|\\.map)\\s*\\("
              bodyContainsQueryCall: true  # 循环体内有数据库调用
          suggestedFix: "改为批量查询后在内存中处理"
          
        - id: MYCOMPANY-021
          name: "分页查询必须限制最大页数"
          severity: MEDIUM
          description: "分页参数pageSize不得超过100"
          patterns:
            - pattern: "(pageSize|page_size|limit|size)\\s*[:=]\\s*(\\d+)"
              maxValue: 100
              autoClamp: true  # 自动将超出的值clamp到100

  # CI/CD门禁配置
  ciGate:
    enabled: true
    blockOnSeverity: ["CRITICAL", "HIGH"]
    maxMediumPerFile: 3  # 每个文件最多3个Medium问题
    reportFormat: "json"  # 输出格式
    notifyOnBlock: true   # 阻断时发送通知

3.2.2 TypeScript编程式规则(高级)

// custom-scanner-rules/no-hardcoded-secret-enhanced.ts
// 增强版密钥检测规则 —— 支持多种编码方式

import { ScanRule, ScanResult, RuleContext } from '@chaitin/monkeycode-scanner';

export class EnhancedSecretDetectionRule implements ScanRule {
  readonly id = 'MYCOMPANY-SECRET-001';
  readonly name = '增强版密钥/凭证泄露检测';
  readonly severity = 'CRITICAL';
  readonly category = 'security';

  /**
   * 主扫描方法
   */
  async scan(context: RuleContext): Promise<ScanResult[]> {
    const issues: ScanResult[] = [];
    const { filePath, fileContent, language } = context;

    // 1. 基础模式检测(正则)
    const basicPatterns = this.getBasicPatterns();
    for (const pattern of basicPatterns) {
      const matches = this.regexScan(fileContent, pattern.regex);
      for (const match of matches) {
        issues.push(this.createIssue(filePath, match.line, match.column,
          `${pattern.name}: 检测到${pattern.type}`,
          pattern.severity,
          pattern.remediation
        ));
      }
    }

    // 2. 变量名语义分析(检测可疑变量名)
    if (this.hasSuspiciousVariableNames(fileContent)) {
      issues.push(this.createIssue(filePath, 0, 0,
        '发现疑似凭据变量名但值被外部引入,请确认安全性',
        'MEDIUM',
        '即使值来自环境变量,也建议加密存储'
      ));
    }

    // 3. Base64编码检测(有些开发者会Base64编码密钥来"隐藏")
    const base64Matches = this.detectBase64Secrets(fileContent);
    issues.push(...base64Matches);

    // 4. 字符串拼接检测(密钥分段拼接以规避检测)
    const concatMatches = this.detectStringConcatenation(fileContent);
    issues.push(...concatMatches);

    return issues;
  }

  /**
   * 基础正则模式
   */
  private getBasicPatterns(): PatternDef[] {
    return [
      // AWS Access Key
      { name: 'AWS Access Key', regex: /AKIA[A-Z0-9]{16}/g, type: 'AWS_ACCESS_KEY', severity: 'CRITICAL' as const },
      // AWS Secret Key(部分遮蔽)
      { name: 'AWS Secret Key', regex: /(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])/g, type: 'AWS_SECRET_KEY', severity: 'CRITICAL' as const },
      // JWT Secret
      { name: 'JWT Secret', regex: /jwt[_\-]?secret\s*[:=]\s*['"][\w\-\.+\/=]{20,}['"]/gi, type: 'JWT_SECRET', severity: 'CRITICAL' as const },
      // 数据库连接串
      { name: 'Database URL', regex: /(mongodb|mysql|postgres|redis):\/\/[^'"`\s]+\:[^'"`\s]+@/gi, type: 'DATABASE_URL', severity: 'CRITICAL' as const },
      // API Key通用模式
      { name: 'Generic API Key', regex: /api[_\-]?key\s*[:=]\s*['"][\w\-]{20,}['"]/gi, type: 'API_KEY', severity: 'HIGH' as const },
      // 私钥文件路径
      { name: 'Private Key Path', regex: /['"](\.\/|\/).*\.(pem|key)['"]/gi, type: 'PRIVATE_KEY_PATH', severity: 'HIGH' as const },
    ];
  }

  /**
   * 检测Base64编码的秘密
   * 很多开发者会用Base64编码来"隐藏"密钥
   */
  private detectBase64Secrets(content: string): ScanResult[] {
    const issues: ScanResult[] = [];
    // 匹配看起来像Base64的长字符串(通常>=40字符,只含Base64字符集)
    const base64CandidateRegex = /['"]([A-Za-z0-9+/]{40,}={0,2})['"]/g;
    let match;
    
    while ((match = base64CandidateRegex.exec(content)) !== null) {
      const candidate = match[1];
      try {
        const decoded = Buffer.from(candidate, 'base64').toString('utf8');
        // 如果解码后像是一个密钥/密码/Token
        if (this.looksLikeSecret(decoded)) {
          issues.push(this.createIssue(
            '', match.index, 0,  // 行号需要额外计算
            `检测到可能的Base64编码密钥: 解码后内容疑似"${decoded.substring(0, 10)}..."`,
            'CRITICAL',
            '不要试图通过编码来隐藏密钥,应使用密钥管理系统(KMS)'
          ));
        }
      } catch {
        // 不是有效的Base64,忽略
      }
    }
    return issues;
  }

  /**
   * 检测字符串拼接方式隐藏密钥
   * 例如: const secret = "sk-" + "live_" + "abc123..."
   */
  private detectStringConcatenation(content: string): ScanResult[] {
    const issues: ScanResult[] = [];
    // 检测连续的字符串拼接
    const concatPattern = /(?:['"`][\w\-_]{2,15}['"`]\s*\+\s*['"`][\w\-_]{2,15}['"`]\s*\+\s*['"`][\w\-_]{10,}['"`])/g;
    let match;
    
    while ((match = concatPattern.exec(content)) !== null) {
      const concatenated = match[0].replace(/['"`\s+]/g, '');
      // 如果拼接后的结果看起来像一个API Key或Token
      if (/^(sk-|pk_|sg_|ghp_|xox[bps]-)/i.test(concatenated)) {
        issues.push(this.createIssue(
          '',
          match.index, 0,
          `检测到疑似通过字符串拼接隐藏的凭证: ${concatenated.substring(0, 8)}...`,
          'HIGH',
          '不要通过拼接来规避密钥检测,应使用环境变量或KMS'
        ));
      }
    }
    return issues;
  }

  private looksLikeSecret(decoded: string): boolean {
    // 启发式判断解码后的内容是否像是密钥
    const secretIndicators = [
      /^sk-/, /^pk_/,
      /^(password|passwd|secret|token|key|credential)/i,
      /^[A-Za-z0-9]{32,}$/,  // 纯字母数字长串
      /-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/,
    ];
    return secretIndicators.some(pattern => pattern.test(decoded.trim()));
  }

  private hasSuspiciousVariableNames(content: string): boolean {
    const suspiciousNames = [
      /(?:const|let|var)\s+(?:password|passwd|secret|apikey|api_key|token|credential)[_\w]*\s*=/gi,
    ];
    return suspiciousNames.some(regex => regex.test(content));
  }

  private regexScan(content: string, regex: RegExp): Array<{line: number, column: number}> {
    const results = [];
    const lines = content.split('\n');
    lines.forEach((line, lineIndex) => {
      let match;
      const localRegex = new RegExp(regex.source, regex.flags);
      while ((match = localRegex.exec(line)) !== null) {
        results.push({ line: lineIndex + 1, column: match.index + 1 });
      }
    });
    return results;
  }

  private createIssue(filePath: string, line: number, column: number, 
    message: string, severity: string, remediation: string): ScanResult {
    return {
      ruleId: this.id,
      ruleName: this.name,
      severity: severity as any,
      filePath,
      line,
      column,
      message,
      remediation,
      category: this.category,
    };
  }
}

方式三:自定义Agent

// custom-agents/SecurityReviewerAgent.ts
// 企业安全审查专家Agent —— 继承并扩展ReviewAgent

import { ReviewerAgent, ReviewerInput, ReviewOutput } from '@chaitin/monkeycode-agent-core';

/**
 * SecurityReviewerAgent
 * 
 * 在标准ReviewAgent的基础上增加:
 * 1. 安全专项审查维度
 * 2. OWASP Top 10对照检查
 * 3. 合规条款映射
 * 4. 安全修复建议生成
 */
export class SecurityReviewerAgent extends ReviewerAgent {
  readonly name = 'security-reviewer';
  readonly description = '企业级安全审查Agent,专注安全合规维度的代码审查';
  readonly role = '资深安全工程师 + 安全架构师';

  // 安全审查专用的额外Prompt片段
  private securitySystemPrompt = `
## 你是一位拥有10年经验的安全工程师。

## 安全审查重点(除常规代码质量外,你必须额外关注以下方面):

### OWASP Top 10 (2021):
1. **访问控制失效** - 检查是否有越权访问风险
2. **加密机制失败** - 检查加密算法是否过时/弱加密
3. **注入攻击** - SQL/NoSQL/XSS/命令注入
4. **不安全设计** - 架构层面的安全隐患
5. **安全配置错误** - 默认配置/不必要的功能开启
6. **易受攻击的组件** - 有CVE漏洞的依赖版本
7. **身份认证失败** - 弱密码策略/Session管理缺陷
8. **软件和数据完整性缺失** - 反篡改机制
9. **安全日志和监控不足** - 审计追踪缺失
10. **服务端请求伪造(SSRF)** - 未校验的用户可控URL

### 企业安全红线(违反任一条必须标记为BLOCKER):
- 生产代码中不允许有console.log/debugger
- 不允许硬编码任何形式的凭证
- 所有外部输入必须经过校验和转义
- 敏感操作必须记录审计日志
- 加密必须使用公司批准的算法(AES-256-GCM/SM4)

### 输出格式要求:
你的审查结果必须包含:
{
  "overallScore": "A/B/C/D/F",
  "summary": "总体评价",
  "criticalIssues": [...],
  "warnings": [...],
  "infoItems": [...],
  "owaspMapping": {
    "A01": "...",
    "A02": "...",
    // 映射到OWASP Top 10类别
  },
  "complianceStatus": {
    "pciDss": "pass/fail/partial",
    "gdpr": "pass/fail/partial",
    "iso27001": "pass/fail/partial"
  },
  "remediationPlan": [
    {"issue": "...", "priority": "P0/P1/P2", "fix": "..."}
  ]
}`;

  /**
   * 覆写buildPrompt方法,注入安全审查专用的Prompt
   */
  protected async buildPrompt(input: ReviewerInput, options?: any) {
    const basePrompt = await super.buildPrompt(input, options);
    
    // 在基础Prompt后面追加安全专项指令
    basePrompt.userMessage += '\n\n' + this.securitySystemPrompt;
    
    // 如果有安全扫描结果,注入作为参考
    if (input.scanResult) {
      basePrompt.userMessage += `\n\n## 已有的安全扫描结果\n${JSON.stringify(input.scanResult, null, 2)}`;
    }
    
    return basePrompt;
  }

  /**
   * 覆写postProcess方法,增加安全评分计算
   */
  protected async postProcess(rawResponse: any, originalInput: ReviewerInput): Promise<SecurityReviewOutput> {
    const baseOutput = await super.postProcess(rawResponse, originalInput);
    
    // 计算安全专项评分
    const securityScore = this.calculateSecurityScore(baseOutput);
    
    return {
      ...baseOutput,
      securityScore,
      owaspSummary: this.generateOwaspSummary(baseOutput),
      remediationPriorityList: this.prioritizeRemediations(baseOutput),
    };
  }

  private calculateSecurityScore(output: any): SecurityScore {
    // 根据严重程度数量计算评分
    const criticalCount = output.criticalIssues?.length || 0;
    const warningCount = output.warnings?.length || 0;
    
    let grade: string;
    let score: number;
    
    if (criticalCount === 0 && warningCount === 0) {
      grade = 'A+'; score = 98;
    } else if (criticalCount === 0 && warningCount <= 2) {
      grade = 'A'; score = 92;
    } else if (criticalCount === 0 && warningCount <= 5) {
      grade = 'B'; score = 82;
    } else if (criticalCount <= 1 && warningCount <= 8) {
      grade = 'C'; score = 70;
    } else if (criticalCount <= 3) {
      grade = 'D'; score = 55;
    } else {
      grade = 'F'; score = 30;
    }
    
    return { grade, score, criticalCount, warningCount };
  }

  private generateOwaspSummary(output: any): OwaspMapping {
    // 将问题映射到OWASP类别
    const mapping: Record<string, string[]> = {};
    for (const issue of [...(output.criticalIssues || []), ...(output.warnings || [])]) {
      const category = this.mapToOwaspCategory(issue);
      if (!mapping[category]) mapping[category] = [];
      mapping[category].push(issue.message);
    }
    return mapping;
  }

  private mapToOwaspCategory(issue: any): string {
    const msg = (issue.message || '').toLowerCase();
    if (msg.includes('sql') || msg.includes('injection')) return 'A03: Injection';
    if (msg.includes('xss') || msg.includes('cross-site')) return 'A03: XSS';
    if (msg.includes('auth') || msg.includes('access control')) return 'A01: Broken Access Control';
    if (msg.includes('crypto') || msg.includes('encrypt')) return 'A02: Cryptographic Failures';
    if (msg.includes('config') || msg.includes('default')) return 'A05: Security Misconfiguration';
    if (msg.includes('dependency') || msg.includes('vuln')) return 'A06: Vulnerable Components';
    if (msg.includes('log') || msg.includes('audit')) return 'A09: Logging & Monitoring';
    return 'A99: Other';
  }

  private prioritizeRemediations(output: any): RemediationItem[] {
    const items = [
      ...(output.criticalIssues || []).map((i: any) => ({
        issue: i.message,
        priority: 'P0' as const,
        fix: i.suggestion || '需要人工评估',
        category: this.mapToOwaspCategory(i),
      })),
      ...(output.warnings || []).map((i: any) => ({
        issue: i.message,
        priority: i.severity === 'HIGH' ? 'P1' : 'P2' as const,
        fix: i.suggestion || '建议修复',
        category: this.mapToOwaspCategory(i),
      })),
    ];
    
    // 按优先级排序
    const priorityOrder = { P0: 0, P1: 1, P2: 2 };
    items.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
    
    return items;
  }
}

// 类型定义
interface SecurityReviewOutput extends ReviewOutput {
  securityScore: SecurityScore;
  owaspSummary: OwaspMapping;
  remediationPriorityList: RemediationItem[];
}

interface SecurityScore {
  grade: string;
  score: number;
  criticalCount: number;
  warningCount: number;
}

type OwaspMapping = Record<string, string[]>;

interface RemediationItem {
  issue: string;
  priority: 'P0' | 'P1' | 'P2';
  fix: string;
  category: string;
}

方式四:自定义LLM Provider

// custom-providers/CompanyLlmProvider.ts
// 接入企业私有化部署的大语言模型

import { LlmProvider, LlmChatParams, LlmResponse } from '@chaitin/monkeycode-agent-core';

/**
 * CompanyLlmProvider
 * 
 * 支持接入任何OpenAI API兼容的私有化LLM服务。
 * 包括但不限于:
 * - vLLM部署的开源模型(Qwen/Llama/DeepSeek等)
 * - 企业微调后的私有模型
 * - 国产大模型(通义千问/文心一言/智谱GLM等)
 * - 通过API Gateway统一管理的模型服务
 */
export class CompanyLlmProvider implements LlmProvider {
  readonly modelName: string;
  private baseUrl: string;
  private apiKey: string;
  private defaultParams: Partial<LlmChatParams>;

  constructor(config: CompanyLlmConfig) {
    this.modelName = config.modelName || 'company-llm-default';
    this.baseUrl = config.baseUrl;  // 例如: https://llm-gateway.internal.company.com/v1
    this.apiKey = config.apiKey;
    this.defaultParams = {
      temperature: config.temperature ?? 0.3,
      maxTokens: config.maxTokens ?? 8192,
      topP: config.topP ?? 0.9,
    };
  }

  /**
   * 核心方法:聊天补全
   */
  async chatCompletion(params: LlmChatParams): Promise<LlmResponse> {
    const mergedParams = { ...this.defaultParams, ...params };
    
    const response = await fetch(`${this.baseUrl}/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`,
        'X-Request-Source': 'monkeycode-enterprise',  // 标识来源
      },
      body: JSON.stringify({
        model: this.modelName,
        messages: mergedParams.messages,
        temperature: mergedParams.temperature,
        max_tokens: mergedParams.maxTokens,
        top_p: mergedParams.topP,
        tools: mergedParams.tools,  // 函数调用/工具使用定义
        stream: false,
      }),
    });

    if (!response.ok) {
      const errorBody = await response.text();
      throw new Error(`LLM API Error (${response.status}): ${errorBody}`);
    }

    const data = await response.json();
    const choice = data.choices?.[0];

    if (!choice) {
      throw new Error('Empty response from LLM API');
    }

    // 解析工具调用(如果有)
    const toolCalls = choice.message?.tool_calls?.map((tc: any) => ({
      id: tc.id,
      name: tc.function?.name,
      arguments: tc.function?.arguments ? 
        (typeof tc.function.arguments === 'string' ? 
          JSON.parse(tc.function.arguments) : tc.function.arguments) : undefined,
    }));

    return {
      content: choice.message?.content || '',
      toolCalls: toolCalls?.length > 0 ? toolCalls : undefined,
      finishReason: choice.finish_reason,
      usage: data.usage ? {
        promptTokens: data.usage.prompt_tokens,
        completionTokens: data.usage.completion_tokens,
        totalTokens: data.usage.total_tokens,
      } : undefined,
    };
  }

  /**
   * 流式输出(可选实现)
   */
  async *chatCompletionStream(params: LlmChatParams): AsyncGenerator<string> {
    const mergedParams = { ...this.defaultParams, ...params };
    
    const response = await fetch(`${this.baseUrl}/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`,
      },
      body: JSON.stringify({
        model: this.modelName,
        messages: mergedParams.messages,
        temperature: mergedParams.temperature,
        max_tokens: mergedParams.maxTokens,
        stream: true,
      }),
    });

    if (!response.ok) {
      throw new Error(`Stream API Error: ${response.statusText}`);
    }

    const reader = response.body!.getReader();
    const decoder = new TextDecoder();
    let buffer = '';

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      buffer += decoder.decode(value, { stream: true });
      const lines = buffer.split('\n');
      buffer = lines.pop() || ''; // 保留不完整的行

      for (const line of lines) {
        if (line.startsWith('data: ')) {
          const data = line.slice(6).trim();
          if (data === '[DONE]') return;
          
          try {
            const parsed = JSON.parse(data);
            const delta = parsed.choices?.[0]?.delta?.content;
            if (delta) yield delta;
          } catch {
            // 忽略解析错误
          }
        }
      }
    }
  }

  /**
   * Token计数(用于成本控制和Prompt长度管理)
   */
  async countTokens(text: string): Promise<number> {
    // 使用简单的估算:中文约1.5 token/字,英文约1.33 token/word
    // 更精确的方式是调用模型的tokenizer API
    const chineseChars = (text.match(/[\u4e00-\u9fff]/g) || []).length;
    const englishWords = (text.match(/[a-zA-Z]+/g) || []).length;
    const otherChars = text.length - chineseChars - englishWords * 5; // 粗略估算
    
    return Math.ceil(chineseChars * 1.5 + englishWords * 1.33 + otherChars * 0.5);
  }

  /**
   * 模型信息
   */
  getModelInfo(): ModelInfo {
    return {
      name: this.modelName,
      provider: 'company-private',
      contextWindow: 128000,  // 根据实际模型调整
      maxOutputTokens: 8192,
      supportsFunctionCalling: true,
      supportsVision: false,  // 根据实际情况调整
    };
  }
}

// 配置类型
interface CompanyLlmConfig {
  baseUrl: string;
  apiKey: string;
  modelName?: string;
  temperature?: number;
  maxTokens?: number;
  topP?: number;
}

interface ModelInfo {
  name: string;
  provider: string;
  contextWindow: number;
  maxOutputTokens: number;
  supportsFunctionCalling: boolean;
  supportsVision: boolean;
}

方式五:前端UI嵌入

// custom-ui/MonkeyCodePanel.tsx
// 将MonkeyCode作为Panel嵌入公司内部管理后台

import React, { useState, useEffect } from 'react';
import { MonkeyCodeEmbedSDK } from '@chaitin/monkeycode-embed-sdk';

/**
 * MonkeyCodePanel
 * 
 * 将MonkeyCode的核心功能嵌入到公司现有的管理后台中。
 * 员工无需切换窗口即可使用AI编程助手。
 */
export const MonkeyCodePanel: React.FC<PanelProps> = ({ 
  projectId, 
  currentFilePath,
  onCodeGenerated,
  onScanComplete,
}) => {
  const [sdk, setSdk] = useState<MonkeyCodeEmbedSDK | null>(null);
  const [activeTab, setActiveTab] = useState<'chat' | 'scan' | 'sdd'>('chat');

  useEffect(() => {
    // 初始化嵌入式SDK
    const instance = new MonkeyCodeEmbedSDK({
      baseUrl: '/api/monkeycode-proxy',  // 通过公司API网关代理
      authToken: getAuthToken(),          // 使用公司统一的认证Token
      theme: 'company-dark',             // 匹配公司主题
      locale: 'zh-CN',
      features: {
        chat: true,
        scan: true,
        sddEditor: true,
        codeGeneration: true,
        // 禁用不需要的功能
        settings: false,                 // 隐藏设置面板
        accountManagement: false,         // 隐藏账号管理
      },
      callbacks: {
        onReady: () => console.log('MonkeyCode Panel ready'),
        onError: (err) => handleError(err),
        onCodeGenerated: (code) => onCodeGenerated?.(code),
        onScanComplete: (result) => onScanComplete?.(result),
      },
    });

    instance.init('monkeycode-container');
    setSdk(instance);

    return () => instance.destroy();
  }, []);

  return (
    <div className="monkeycode-panel">
      {/* 自定义Tab栏 */}
      <div className="panel-tabs">
        <button 
          className={activeTab === 'chat' ? 'active' : ''}
          onClick={() => { setActiveTab('chat'); sdk?.switchMode('chat'); }}
        >
          💬 AI助手
        </button>
        <button 
          className={activeTab === 'scan' ? 'active' : ''}
          onClick={() => { setActiveTab('scan'); sdk?.switchMode('scan'); }}
        >
          🛡️ 安全扫描
        </button>
        <button 
          className={activeTab === 'sdd' ? 'active' : ''}
          onClick={() => { setActiveTab('sdd'); sdk?.switchMode('sdd'); }}
        >
          📋 SDD编辑
        </button>
      </div>

      {/* MonkeyCode渲染容器 */}
      <div id="monkeycode-container" className="panel-content" />

      {/* 底部状态栏 */}
      <div className="panel-status">
        <span>项目: {projectId}</span>
        <span>文件: {currentFilePath}</span>
        <span className="status-indicator" />
      </div>
    </div>
  );
};

// 样式(Tailwind CSS示例)
/*
.monkeycode-panel {
  @apply flex flex-col h-full bg-gray-900 rounded-lg overflow-hidden;
  border: 1px solid gray-700;
}

.panel-tabs {
  @apply flex gap-1 px-2 py-1 bg-gray-800 border-b border-gray-700;
  
  button {
    @apply px-3 py-1 rounded text-sm text-gray-400 hover:text-white 
           hover:bg-gray-700 transition-colors;
    
    &.active {
      @apply text-blue-400 bg-gray-700;
    }
  }
}

.panel-content {
  @apply flex-1 overflow-auto;
  /* MonkeyCode SDK会在此容器内渲染 */
}

.panel-status {
  @apply flex items-center justify-between px-3 py-1 
         bg-gray-800 text-xs text-gray-500 border-t border-gray-700;
}

.status-indicator {
  @apply w-2 h-2 rounded-full bg-green-500 animate-pulse;
}
*/

四、二次开发的最佳实践

4.1 开发流程建议

推荐的二次开发工作流:

Phase 1: 需求明确(1-2天)
  ├── 明确要解决的业务痛点
  ├── 评估哪种扩展方式最合适
  ├── 估算工作量和技术风险
  └── 输出: 《二次开发需求规格说明书》

Phase 2: 技术方案设计(2-3天)
  ├── 阅读MonkeyCode相关模块的源码
  ├── 设计扩展点的接口契约
  ├── 评估对上游版本升级的影响
  └── 输出: 《技术设计方案》

Phase 3: 开发实现(3-10天,视复杂度而定)
  ├── Fork MonkeyCode仓库(或创建独立包)
  ├── 编写扩展代码
  ├── 编写单元测试
  └── 本地调试验证

Phase 4: 集成测试(2-3天)
  ├── 在测试环境部署
  ├── 邀请内部用户试用
  ├── 收集反馈并迭代
  └── 性能测试和安全审查

Phase 5: 上线运维(持续)
  ├── 编写部署文档
  ├── 配置监控告警
  ├── 建立版本升级策略
  └── 定期同步上游更新

4.2 版本升级策略

如何优雅地跟随上游版本升级?

策略1: 最小改动法(推荐)
  ├── 只做"加法",不改上游代码
  ├── 通过Plugin/Extension/Mechanism扩展
  ├── 升级时只需重新应用你的扩展
  └── 优点: 升级成本低,冲突少

策略2: Feature Branch法
  ├── Fork一份自己的分支
  ├── 在分支上做定制修改
  ├── 上游更新时定期Rebase
  └── 优点: 完全掌控;缺点: Rebase可能有冲突

策略3: Modular Override法(高级)
  ├── 将需要定制的模块抽出来
  ├── 通过依赖注入替换为自己的实现
  ├── 上游更新时只关注接口变化
  └── 优点: 清晰的隔离;缺点: 初始投入较大

我的建议:
  大多数情况下用策略1就够用了。
  MCP Server和Scanner规则都是"加法式"扩展,
  不需要改上游一行代码就能实现强大的定制能力。

4.3 常见坑位

⚠️ 坑1: 过度定制导致无法升级
  症状: 改了太多上游代码,每次升级都是噩梦
  解决: 坚持"组合优于修改"原则,能用扩展机制就不用改源码

⚠️ 坑2: 忽视性能影响
  症状: 自定义规则太复杂,扫描一个文件要30秒
  解决: 设置合理的超时和规则复杂度上限;对大型文件采样扫描

⚠️ 坑3: 安全规则误报过多
  症状: 团队觉得扫描全是噪音,逐渐关闭
  解决: 精心调优规则,建立"白名单机制",定期清理误报

⚠️ 坑4: 自定义Agent的Prompt失控
  症状: Agent输出格式不稳定,下游处理报错
  解决: 严格的输出Schema校验 + Fallback逻辑 + 最多3次重试

⚠️ 坑5: MCP Server进程崩溃
  症状: 自定义MCP Server偶尔挂掉导致整个Pipeline失败
  解决: 进程守护(systemd/supervisor) + 健康检查 + 自动重启

五、总结

╔══════════════════════════════════════════════════════╗
║                                                      ║
║  MonkeyCode的开源架构为二次开发提供了               ║
║  极其灵活的扩展能力。                                ║
║                                                      ║
║  从简单到复杂的扩展路径:                             ║
║                                                      ║
║  🟢 入门级(1-3天):                                ║
║     自定义MCP Server → 让Agent连接你的内部系统       ║
║                                                      ║
║  🟡 进阶级(3-7天):                                ║
║     自定义Scanner规则 → 符合行业安全/编码规范         ║
║     自定义LLM Provider → 接入私有化大模型            ║
║                                                      ║
║  🔴 高级(1-4周):                                   ║
║     自定义Agent → 实现复杂的业务逻辑                  ║
║     UI嵌入 → 无缝集成到现有平台                      ║
║     架构改造 → 多租户/高可用/分布式部署              ║
║                                                      ║
║  核心原则:                                          ║
║  ✓ 能用配置/插件解决的就不要改代码                    ║
║  ✓ 保持与上游版本的兼容性                            ║
║  ✓ 先跑通最小可行产品(MVP),再逐步完善               ║
║  ✓ 把你的改进贡献回社区(如果适用的话)🎉            ║
║                                                      ║
╚══════════════════════════════════════════════════════╝

系列导航


本文基于MonkeyCode开源项目的扩展机制编写,所有代码示例均可在v1.2.x版本上运行。

关键词:#MonkeyCode #二次开发 #企业定制 #MCP协议 #自定义Agent #Scanner规则 #LLM集成 #开源扩展 #企业级 #技术架构

posted on 2026-07-08 18:09  MonkeyCode  阅读(30)  评论(0)    收藏  举报