MonkeyCode 插件生态与扩展机制:构建可插拔的 AI 编程助手架构实践指南
引言
"好的软件不是写出来的,而是生长出来的。"
在 AI 编程助手领域,单一产品无法满足所有开发者的需求。有人需要与特定框架深度集成,有人需要企业级合规检查,还有人希望将 AI 能力嵌入到自定义工作流中。这就是为什么 MonkeyCode 从设计之初就采用了插件化架构——让社区的力量成为产品进化的核心引擎。
作为完全开源的 AI 编程助手(Apache License 2.0),MonkeyCode 不仅开放了核心代码,更通过精心设计的插件 API 让每个人都能参与生态建设。本文将深入解析 MonkeyCode 的插件架构设计、开发实战、以及如何通过开源协作打造繁荣的开发者生态。
🎯 核心信息
- GitHub 仓库: https://github.com/monkeycode-ai/monkeycode
- 开源协议: Apache License 2.0
- 欢迎提交 Issue: 功能建议请标记
enhancement标签- 插件开发文档: docs.monkeycode.dev/plugins
一、为什么选择插件化架构?
1.1 插件化的核心价值
┌─────────────────────────────────────────────────────────────┐
│ MonkeyCode 插件化架构的核心价值 │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 核心稳定性 │◄──►│ 扩展灵活性 │ │
│ │ │ │ │ │
│ │ • AI 引擎稳定 │ │ • 社区创新 │ │
│ │ • 基础功能可靠 │ │ • 快速迭代 │ │
│ │ • 向后兼容 │ │ • 定制化需求 │ │
│ │ • 安全基线 │ │ • 长尾场景覆盖 │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ └──────────┬───────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ 用户价值最大化 │ │
│ │ │ │
│ │ 稳定 + 灵活 = 满意度 │ │
│ └─────────────────────┘ │
│ │
│ 💡 关键洞察: 核心做减法(聚焦),插件做加法(扩展) │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 架构选型对比
| 维度 | 单体架构 | 微服务架构 | 插件化架构 | 混合架构 |
|---|---|---|---|---|
| 部署复杂度 | ⭐ 简单 | ⭐⭐⭐ 复杂 | ⭐⭐ 中等 | ⭐⭐⭐ 复杂 |
| 扩展性 | ❌ 差 | ✅ 好 | ✅✅ 优秀 | ✅ 好 |
| 定制能力 | ❌ 差 | ✅ 中等 | ✅✅ 优秀 | ✅ 好 |
| 性能开销 | ✅ 低 | ⚠️ 中等 | ✅ 低 | ⚠️ 中等 |
| 隔离性 | ❌ 无 | ✅ 强 | ⚠️ 部分 | ✅ 强 |
| 社区参与 | ❌ 困难 | ⚠️ 中等 | ✅✅ 容易 | ⚠️ 中等 |
| 适用场景 | 小型工具 | 大型平台 | 桌面/IDE 工具 | 企业系统 |
MonkeyCode 选择插件化架构的理由:
- IDE 插件的天然属性: 本身就是 VSCode/JetBrains 的插件,用户对"插件"概念熟悉
- 本地运行优先: 大部分计算在本地完成,不需要微服务的网络开销
- 开发者友好: 降低贡献门槛,一个插件文件即可扩展功能
- 安全可控: 插件运行在沙箱中,不影响核心功能稳定性
二、MonkeyCode 插件架构总览
2.1 整体架构图
┌─────────────────────────────────────────────────────────────────────────┐
│ MonkeyCode 插件架构全景图 │
│ │
│ ══════════════════════════════════════════════════════════════════ │
│ │
│ 【用户层】 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ VSCode │ │JetBrains │ │ Vim/ │ │ CLI / │ │
│ │ 插件 │ │ 插件 │ │ Neovim │ │ Headless │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ └──────────────┴────────────┴──────────────┘ │
│ │ │
│ ═════════════════════════╧═══════════════════════════════════════ │
│ │
│ 【宿主层 — Host Layer】 │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ MonkeyCode Core │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Plugin Host │ │ LLM Engine │ │ Config Mgr │ │ │
│ │ │ (管理器) │ │ (AI 核心) │ │ (配置中心) │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
│ │ │ │ │ │ │
│ │ ┌──────┴────────────────┴────────────────┴──────┐ │ │
│ │ │ Event Bus (事件总线) │ │ │
│ │ └───────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ ═════════════════════════╧═══════════════════════════════════════ │
│ │
│ 【插件层 — Plugin Layer】 │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Language │ │ Framework │ │ Compliance │ │ │
│ │ │ Server │ │ Adapter │ │ Checker │ │ │
│ │ │ (语言服务) │ │ (框架适配) │ │ (合规检查) │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Code Style │ │ Custom │ │ Analytics │ │ │
│ │ │ Enforcer │ │ Command │ │ Reporter │ │ │
│ │ │ (代码风格) │ │ (自定义命令)│ │ (分析报告) │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Integration│ │ Theme / UI │ │ │
│ │ │ Bridge │ │ Extension │ │ │
│ │ │ (集成桥接) │ │ (主题/UI) │ │ │
│ │ └────────────┘ └────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ══════════════════════════════════════════════════════════════════ │
│ │
│ 【基础设施层】 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Plugin │ │ Sandbox │ │ Version │ │ Hot │ │
│ │ Registry │ │ Runtime │ │ Resolver │ │ Reload │ │
│ │ (注册中心)│ │ (沙箱) │ │ (版本解析)│ │ (热加载) │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
╚═══════════════════════════════════════════════════════════════════════╝
2.2 核心组件说明
// ===== monkeycode/plugins/core/types.ts =====
/**
* MonkeyCode 插件系统核心类型定义
*/
/** 插件元数据 */
export interface PluginManifest {
/** 插件唯一标识符 (反向域名格式) */
id: string; // e.g., "com.example.my-plugin"
/** 显示名称 */
name: string;
/** 版本号 (语义化版本) */
version: string; // e.g., "1.2.3"
/** 描述 */
description: string;
/** 作者信息 */
author: {
name: string;
email?: string;
url?: string;
};
/** 许可证 */
license: string;
/** 主入口文件 */
main: string;
/** MonkeyCode 版本兼容性 */
engines: {
monkeycode: string; // semver range, e.g., ">=2.0.0 <3.0.0"
};
/** 插件类别 */
category: PluginCategory;
/** 关键词 (用于搜索) */
keywords?: string[];
/** 依赖的其他插件 */
dependencies?: string[];
/** 可选依赖 */
peerDependencies?: string[];
/** 贡献点声明 */
contributes?: {
commands?: CommandContribution[];
views?: ViewContribution[];
hooks?: HookContribution[];
configurations?: ConfigurationContribution[];
languages?: LanguageContribution[];
themes?: ThemeContribution[];
};
/** 插件图标 (SVG 或 URL) */
icon?: string;
/** 首页链接 */
homepage?: string;
/** 仓库地址 */
repository?: {
type: 'git';
url: string;
};
/** Bug 反馈地址 */
bugs?: {
url: string;
email?: string;
};
}
/** 插件类别枚举 */
export enum PluginCategory {
LANGUAGE_SERVER = 'language-server', // 语言服务
FRAMEWORK_ADAPTER = 'framework-adapter', // 框架适配
COMPLIANCE = 'compliance', // 合规检查
CODE_STYLE = 'code-style', // 代码风格
CUSTOM_COMMAND = 'custom-command', // 自定义命令
ANALYTICS = 'analytics', // 数据分析
INTEGRATION = 'integration', // 第三方集成
THEME = 'theme', // 主题/UI
UTILITY = 'utility' // 工具类
}
/** 插件上下文 — 运行时注入给插件 */
export interface PluginContext {
/** 插件自身的元数据 */
readonly manifest: PluginManifest;
/** 日志记录器 */
logger: PluginLogger;
/** 配置访问器 */
config: ConfigurationAccessor;
/** 状态存储 */
state: StateStorage;
/** 事件总线订阅 */
subscriptions: Disposable[];
/** 访问核心 API */
core: {
/** AI 引擎接口 */
ai: AIEngineAPI;
/** 编辑器接口 */
editor: EditorAPI;
/** 文件系统接口 */
filesystem: FileSystemAPI;
/** 终端接口 */
terminal: TerminalAPI;
/** UI 接口 */
ui: UIAPI;
};
}
/** 插件激活函数类型 */
export type ActivateFunction = (
context: PluginContext
) => Promise<PluginResult | void>;
/** 插件停用函数类型 */
export type DeactivateFunction = () => Promise<void> | void;
/** 插件入口导出 */
export interface PluginModule {
activate: ActivateFunction;
deactivate?: DeactivateFunction;
}
2.3 事件驱动通信模型
// ===== monkeycode/plugins/core/event-bus.ts =====
/**
* MonkeyCode 插件事件总线
*
* 所有插件间通信都通过事件总线进行,
* 确保松耦合和可追溯性。
*/
import { EventEmitter } from 'events';
/** 定义所有可用的事件类型 */
export enum PluginEventType {
// === 生命周期事件 ===
PLUGIN_ACTIVATED = 'plugin:activated',
PLUGIN_DEACTIVATED = 'plugin:deactivated',
PLUGIN_ERROR = 'plugin:error',
// === 编辑器事件 ===
EDITOR_FILE_OPENED = 'editor:fileOpened',
EDITOR_FILE_SAVED = 'editor:fileSaved',
EDITOR_SELECTION_CHANGED = 'editor:selectionChanged',
EDITOR_CURSOR_MOVED = 'editor:cursorMoved',
// === AI 事件 ===
AI_COMPLETION_REQUESTED = 'ai:completionRequested',
AI_COMPLETION_RECEIVED = 'ai:completionReceived',
AI_EXPLANATION_REQUESTED = 'ai:explanationRequested',
AI_REVIEW_STARTED = 'ai:reviewStarted',
AI_REVIEW_COMPLETED = 'ai:reviewCompleted',
// === 配置事件 ===
CONFIG_CHANGED = 'config:changed',
// === 自定义事件 (前缀 'custom:') ===
// custom:* 允许插件定义自己的事件
}
/** 事件载荷基础接口 */
export interface BaseEventPayload {
timestamp: number;
sourcePluginId: string;
sourceVersion: string;
}
/** 编辑器文件打开事件 */
export interface FileOpenedEventPayload extends BaseEventPayload {
filePath: string;
languageId: string;
fileSize: number;
encoding: string;
}
/** AI 补全请求事件 */
export interface CompletionEventPayload extends BaseEventPayload {
requestId: string;
filePath: string;
position: { line: number; column: number };
prefix: string;
suffix: string;
modelUsed: string;
latencyMs?: number;
result?: string;
error?: string;
}
class PluginEventBus extends EventEmitter {
private eventLog: Array<{
type: PluginEventType;
payload: BaseEventPayload;
timestamp: number;
}> = [];
private maxLogSize = 10000;
/**
* 发布事件
*/
emit<T extends BaseEventPayload>(
eventType: PluginEventType,
payload: T
): boolean {
// 记录事件日志
this.eventLog.push({
type: eventType,
payload,
timestamp: Date.now(),
});
// 防止日志无限增长
if (this.eventLog.length > this.maxLogSize) {
this.eventLog = this.eventLog.slice(-this.maxLogSize / 2);
}
return super.emit(eventType, payload);
}
/**
* 订阅事件 (带自动清理)
*/
subscribe<T extends BaseEventPayload>(
eventType: PluginEventType,
handler: (payload: T) => void,
pluginId: string
): () => void {
const wrappedHandler = (payload: T) => {
try {
handler(payload);
} catch (error) {
console.error(
`[EventBus] Error in handler for ${eventType} ` +
`from plugin ${pluginId}:`,
error
);
// 通知错误
this.emit(PluginEventType.PLUGIN_ERROR, {
timestamp: Date.now(),
sourcePluginId: pluginId,
sourceVersion: 'unknown',
error: error instanceof Error ? error.message : String(error),
eventType,
} as any);
}
};
super.on(eventType, wrappedHandler);
// 返回取消订阅函数
return () => super.off(eventType, wrappedHandler);
}
/**
* 获取事件历史 (用于调试)
*/
getRecentEvents(count = 50): typeof this.eventLog {
return this.eventLog.slice(-count);
}
/**
* 获取特定插件的事件统计
*/
getPluginStats(pluginId: string): {
emitted: number;
subscribed: string[];
} {
const emitted = this.eventLog.filter(
e => e.payload.sourcePluginId === pluginId
).length;
return { emitted, subscribed: [] }; // 简化实现
}
}
// 全局单例
export const globalEventBus = new PluginEventBus();
export default globalEventBus;
三、插件开发实战指南
3.1 最小可行插件 (MVP)
// ===== my-first-plugin/package.json =====
{
"name": "@monkeycode/hello-world",
"version": "1.0.0",
"description": "我的第一个 MonkeyCode 插件 — Hello World 示例",
"main": "index.js",
"monkeyCode": {
"id": "dev.example.hello-world",
"displayName": "Hello World",
"description": "一个简单的示例插件,展示插件开发的基本流程",
"category": "utility",
"engines": {
"monkeyCode": ">=2.0.0"
},
"contributes": {
"commands": [
{
"command": "helloWorld.sayHello",
"title": "Say Hello",
"category": "Hello World"
},
{
"command": "helloWorld.showInfo",
"title": "Show Plugin Info",
"category": "Hello World"
}
]
}
},
"keywords": ["hello-world", "example", "tutorial"],
"author": {
"name": "Your Name",
"email": "your@email.com"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/yourname/my-first-plugin.git"
}
}
// ===== my-first-plugin/index.js =====
/**
* MonkeyCode Hello World 插件
*
* 这是一个最小可行的插件示例,
* 展示了插件的基本结构和生命周期。
*/
// 插件激活函数 — 当插件被加载时调用
async function activate(context) {
const { logger, core, subscriptions } = context;
// ========== 1. 注册命令 ==========
// 注册 "Say Hello" 命令
const helloDisposable = core.ui.registerCommand(
'helloWorld.sayHello',
async () => {
// 使用 AI 引擎生成个性化问候
const greeting = await core.ai.chatCompletion({
messages: [
{
role: 'system',
content: '你是一个友好的编程助手。用温暖的方式问候开发者。'
},
{
role: 'user',
content: '请用一种有趣的方式向开发者打招呼!'
}
],
temperature: 0.8,
maxTokens: 100
});
// 在编辑器中显示问候语
core.ui.showInformationMessage(
`🐵 ${greeting.choices[0].message.content}\n\n` +
`— 来自 Hello World 插件 v${context.manifest.version}`
);
logger.info('Hello command executed');
}
);
subscriptions.push(helloDisposable);
// 注册 "Show Info" 命令
const infoDisposable = core.ui.registerCommand(
'helloWorld.showInfo',
async () => {
const info = [
`📦 插件名称: ${context.manifest.name}`,
`🆔 插件 ID: ${context.manifest.id}`,
`📌 版本: ${context.manifest.version}`,
`👤 作者: ${context.manifest.author.name}`,
`📄 许可证: ${context.manifest.license}`,
'',
'感谢您使用 MonkeyCode 插件系统!',
'',
'💡 提示: 访问 https://github.com/monkeycode-ai/monkeycode ' +
'了解更多插件开发信息'
].join('\n');
core.ui.showInformationMessage(info);
}
);
subscriptions.push(infoDisposable);
// ========== 2. 监听事件 ==========
// 监听文件保存事件
const saveSubscription = context.subscribe(
'editor:fileSaved',
async (payload) => {
logger.debug(`File saved: ${payload.filePath}`);
// 可选: 对保存的文件做一些处理
// 例如: 自动添加文件头注释、格式检查等
}
);
subscriptions.push(saveSubscription);
// ========== 3. 初始化状态 ==========
// 读取或初始化插件状态
let usageCount = await context.state.get('usageCount') || 0;
await context.state.set('usageCount', usageCount);
logger.info(`Plugin "${context.manifest.name}" activated successfully`);
logger.info(`Usage count so far: ${usageCount}`);
// 返回插件结果 (可选)
return {
status: 'ok',
message: 'Hello World plugin is ready to use!',
};
}
// 插件停用函数 — 当插件被卸载时调用
async function deactivate() {
// 清理资源 (subscriptions 会由框架自动清理)
console.log('Hello World plugin deactivated');
}
// 导出插件模块
module.exports = {
activate,
deactivate
};
3.2 进阶:语言服务插件
// ===== monkeycode-plugin-rust/index.ts =====
/**
* MonkeyCode Rust 语言支持插件
*
* 为 Rust 语言提供增强的 AI 编程支持:
* - Rust 特有的代码补全优化
* - Cargo.toml 依赖智能感知
* - Macro 展开辅助
* - 生命周期标注建议
*/
import {
PluginContext,
PluginResult,
LanguageServerPlugin,
CompletionItem,
Diagnostic,
} from '@monkeycode/plugin-api';
interface RustPluginConfig {
enableMacroExpansion: boolean;
suggestLifetimeAnnotations: boolean;
cargoIntegrationEnabled: boolean;
rustAnalyzerPath?: string;
}
export async function activate(context: PluginContext): Promise<PluginResult> {
const { logger, config, core, state } = context;
// 读取配置
const pluginConfig = config.get<RustPluginConfig>('rustSupport') ?? {
enableMacroExpansion: true,
suggestLifetimeAnnotations: true,
cargoIntegrationEnabled: true,
};
logger.info('Rust support plugin activating...', pluginConfig);
// ========== 1. 注册 Rust 语言特性 ==========
// 注册 Rust 特定的补全提供器
const rustCompletionProvider = core.ai.registerCompletionProvider(
['rust'], // 仅对 Rust 文件生效
{
// 在触发补全前,先收集 Rust 上下文
async beforeCompletion(document, position) {
const fileContent = document.getText();
// 解析当前作用域
const scopeInfo = analyzeRustScope(fileContent, position);
// 如果在 macro!() 内部,提供 macro 相关提示
if (scopeInfo.insideMacro && pluginConfig.enableMacroExpansion) {
return {
contextHints: {
insideMacro: true,
macroName: scopeInfo.macroName,
availableMacros: getAvailableMacros(fileContent),
},
promptOverride: buildMacroPrompt(scopeInfo),
};
}
// 如果在 struct 定义附近,建议 derive 属性
if (scopeInfo.nearStructDef) {
return {
contextHints: {
nearStructDef: true,
suggestedDerives: [
'Debug', 'Clone', 'PartialEq', 'Eq',
'Hash', 'Serialize', 'Deserialize',
],
},
};
}
return { contextHints: scopeInfo };
},
// 后处理 AI 生成的补全结果
async afterCompletion(items: CompletionItem[]): Promise<CompletionItem[]> {
return items.map(item => {
// 给 Rust 特定补全添加额外信息
if (item.label.startsWith('fn ') || item.label.startsWith('pub fn ')) {
item.detail = '🦀 Rust Function';
}
// 为 lifetime 参数添加特殊标记
if (item.label.includes("'")) {
item.documentation = {
value: '**Lifetime Parameter**\n\n' +
'此补全包含 Rust 生命周期参数。\n' +
'确保使用正确的生命周期标注以避免编译错误。',
kind: 'markdown',
};
}
return item;
});
},
}
);
// ========== 2. Cargo.toml 集成 ==========
if (pluginConfig.cargoIntegrationEnabled) {
// 监听 Cargo.toml 变化,自动更新依赖上下文
const cargoWatcher = core.filesystem.watchFiles(
'**/Cargo.toml',
async (uri) => {
logger.info(`Cargo.toml changed: ${uri}`);
const cargoContent = await core.filesystem.readFile(uri);
const deps = parseCargoDependencies(cargoContent);
// 将依赖信息存入状态供后续使用
await state.set('cargoDependencies', deps);
logger.info(`Parsed ${deps.length} dependencies from Cargo.toml`);
}
);
context.subscriptions.push(cargoWatcher);
}
// ========== 3. 诊断增强 ==========
// 注册 Rust 编译器错误增强解释器
const diagnosticEnhancer = core.editor.registerDiagnosticEnhancer(
['rust'],
async (diagnostic: Diagnostic) => {
// 只对借用检查器和生命周期错误使用 AI 增强
if (
diagnostic.code === 'E0502' || // cannot borrow as mutable
diagnostic.code === 'E0597' || // does not live long enough
diagnostic.code.startsWith('E0') &&
diagnostic.message.includes('lifetime')
) {
// 使用 AI 解释这个 Rust 错误并提供修复建议
const explanation = await core.ai.chatCompletion({
messages: [
{
role: 'system',
content: `你是 Rust 专家。请用中文简洁地解释以下 Rust 编译错误,
并给出具体的修复代码示例。`
},
{
role: 'user',
content: `错误代码: ${diagnostic.code}\n` +
`错误信息: ${diagnostic.message}\n` +
`所在行: ${diagnostic.range.start.line}`
}
],
temperature: 0.3,
maxTokens: 500,
});
return {
...diagnostic,
enhancedMessage: explanation.choices[0].message.content,
severity: 'info', // 降低严重程度,因为已有了解释
actions: [
{
title: '查看详细解释',
command: 'rust.showBorrowExplanation',
arguments: [diagnostic],
},
{
title: '生成修复方案',
command: 'rust.generateFix',
arguments: [diagnostic],
},
],
};
}
return diagnostic;
}
);
context.subscriptions.push(diagnosticEnhancer);
// ========== 4. 注册自定义命令 ==========
context.subscriptions.push(
core.ui.registerCommand('rust.expandMacro', async () => {
const editor = core.editor.activeEditor;
if (!editor || editor.languageId !== 'rust') {
core.ui.showWarningMessage('请在 Rust 文件中使用此命令');
return;
}
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
// 使用 AI 展开 macro
const expansion = await core.ai.chatCompletion({
messages: [
{
role: 'system',
content: '你是 Rust macro 展开专家。展开给定的 Rust macro 调用。'
},
{
role: 'user',
content: `请展开以下 Rust macro:\n\`\`\`rust\n${selectedText}\n\`\`\``
}
],
temperature: 0.1,
});
// 显示展开结果
core.ui.showDiffView(selectedText, expansion.choices[0].message.content, {
title: 'Macro Expansion Result',
});
})
);
logger.info('✅ Rust support plugin activated successfully');
return {
status: 'ok',
features: [
'Rust-aware code completion',
'Cargo.toml integration',
'Borrow checker error explanation',
'Macro expansion assistant',
],
};
}
// ========== 辅助函数 ==========
function analyzeRustScope(content: string, position: { line: number; column: number }) {
// 简化的 Rust 作用域分析
const lines = content.split('\n');
const currentLine = lines[position.line] || '';
return {
insideMacro: /(\w+)!\s*\(/.test(currentLine),
macroName: currentLine.match(/(\w+)!/)?.[1],
nearStructDef: /\bstruct\s+\w+/.test(currentLine),
insideImpl: /^\s*(pub\s+)?(unsafe\s+)?(async\s+)?fn\s+/.test(currentLine),
};
}
function getAvailableMacros(content: string): string[] {
const macroRegex = /macro_rules!\s+(\w+)/g;
const macros: string[] = [];
let match;
while ((match = macroRegex.exec(content)) !== null) {
macros.push(match[1]);
}
return macros;
}
function buildMacroPrompt(scope: any): string {
return `You are completing Rust code inside the macro "${scope.macroName}".
Available macros in scope: ${scope.availableMacros.join(', ')}.`;
}
function parseCargoDependencies(content: string): Array<{name: string, version: string}> {
// 简化的 Cargo.toml 解析
const deps: Array<{name: string, version: string}> = [];
const depRegex = /^(\w+)\s*=\s*["']([^"']+)["']/gm;
let match;
while ((match = depRegex.exec(content)) !== null) {
deps.push({ name: match[1], version: match[2] });
}
return deps;
}
export async function deactivate() {
console.log('Rust support plugin deactivated');
}
3.3 企业级合规检查插件
# ===== monkeycode_plugin_compliance/compliance_checker.py =====
"""
MonkeyCode 企业级合规检查插件
功能:
- 代码安全扫描 (SQL注入/XSS/硬编码密钥等)
- 许可证兼容性检查
- PII (个人隐私数据) 检测
- 编码规范强制执行
- 自定义规则引擎
"""
import re
import json
import hashlib
from dataclasses import dataclass, field
from typing import List, Optional, Dict, Any
from enum import Enum
class Severity(Enum):
INFO = "info"
WARNING = "warning"
ERROR = "error"
CRITICAL = "critical"
class RuleCategory(Enum):
SECURITY = "security"
PRIVACY = "privacy"
LICENSE = "license"
CODE_STYLE = "code_style"
PERFORMANCE = "performance"
CUSTOM = "custom"
@dataclass
class ComplianceRule:
"""合规规则定义"""
rule_id: str
name: str
description: str
category: RuleCategory
severity: Severity
pattern: re.Pattern # 正则表达式模式
suggestion: str
enabled: bool = True
languages: List[str] = field(default_factory=lambda: ["*"])
tags: List[str] = field(default_factory=list)
@dataclass
class ComplianceIssue:
"""合规问题"""
rule_id: str
file_path: str
line_number: int
column: int
severity: Severity
message: str
suggestion: str
snippet: str
category: RuleCategory
auto_fix_available: bool = False
fix_suggestion: Optional[str] = None
# 内置规则库
BUILTIN_RULES = [
# 安全类规则
ComplianceRule(
rule_id="SEC001",
name="硬编码密码检测",
description="检测代码中的硬编码密码和密钥",
category=RuleCategory.SECURITY,
severity=Severity.CRITICAL,
pattern=re.compile(
r'(?:password|passwd|pwd|secret|api_key|apikey|token)\s*[=:]\s*["\'][^"\']{8,}["\']',
re.IGNORECASE
),
suggestion="使用环境变量或密钥管理服务存储敏感信息",
tags=["security", "credentials", "owasp"],
),
ComplianceRule(
rule_id="SEC002",
name="SQL 注入风险检测",
description="检测可能的 SQL 注入漏洞",
category=RuleCategory.SECURITY,
severity=Severity.ERROR,
pattern=re.compile(
r'(?:execute|query|raw)\s*\(\s*(?:f["\']|["\'].*%\s*(?:s|d)|"\s*\+)',
re.IGNORECASE
),
suggestion="使用参数化查询或 ORM 来防止 SQL 注入",
languages=["python", "javascript", "java", "php"],
tags=["security", "sql-injection", "owasp"],
),
ComplianceRule(
rule_id="SEC003",
name="XSS 风险检测",
description="检测可能的跨站脚本攻击风险",
category=RuleCategory.SECURITY,
severity=Severity.ERROR,
pattern=re.compile(
r'(?:innerHTML|document\.write|\.html\(|v-html)\s*\(',
re.IGNORECASE
),
suggestion="使用安全的 DOM 操作方法或文本内容插入",
languages=["javascript", "typescript"],
tags=["security", "xss", "owasp"],
),
ComplianceRule(
rule_id="SEC004",
name="不安全的随机数生成",
description="检测使用了不安全的伪随机数生成器",
category=RuleCategory.SECURITY,
severity=Severity.WARNING,
pattern=re.compile(
r'\b(?:random|Math\.random)\s*\(',
re.IGNORECASE
),
suggestion="对于安全敏感的场景,使用加密安全的随机数生成器",
tags=["security", "cryptography"],
),
# 隐私类规则
ComplianceRule(
rule_id="PRV001",
name="个人身份信息 (PII) 检测",
description="检测代码中可能包含的个人隐私数据",
category=RuleCategory.PRIVACY,
severity=Severity.WARNING,
pattern=re.compile(
r'(?:email|phone|id_card|ssn|credit_card|身份证|手机号|邮箱)\s*[=:]\s*["\']',
re.IGNORECASE
),
suggestion="确保 PII 数据经过脱敏处理后再使用或存储",
tags=["privacy", "gdpr", "pii"],
),
ComplianceRule(
rule_id="PRV002",
name="日志中的敏感数据",
description="检测可能将敏感数据写入日志的代码",
category=RuleCategory.PRIVACY,
severity=Severity.WARNING,
pattern=re.compile(
r'(?:logger|log|console\.(log|debug|info))\s*\([^)]*(?:password|token|secret)',
re.IGNORECASE
),
suggestion="不要在日志中输出敏感信息",
tags=["privacy", "logging"],
),
# 许可证类规则
ComplianceRule(
rule_id="LIC001",
name="许可证头缺失检测",
description="检测源文件是否缺少许可证声明",
category=RuleCategory.LICENSE,
severity=Severity.INFO,
pattern=re.compile(r'^$', re.MULTILINE), # 特殊处理
suggestion="在每个源文件头部添加 SPDX 许可证标识",
languages=["python", "javascript", "java", "go", "rust"],
tags=["license", "spdx", "compliance"],
),
# 代码质量规则
ComplianceRule(
rule_id="STY001",
name="TODO/FIXME/HACK 检测",
description="检测代码中的技术债务标记",
category=RuleCategory.CODE_STYLE,
severity=Severity.INFO,
pattern=re.compile(
r'#?\s*(TODO|FIXME|HACK|XXX|BUG|TEMP)\s*[:\-]?',
re.IGNORECASE
),
suggestion="为每个 TODO 创建 Issue 并设置截止日期",
tags=["tech-debt", "code-quality"],
),
ComplianceRule(
rule_id="STY002",
name="调试代码残留检测",
description="检测可能遗留的调试代码",
category=RuleCategory.CODE_STYLE,
severity=Severity.WARNING,
pattern=re.compile(
r'(?:console\.(log|debug|warn)|print\s*\(|debugger|pdb\.set_trace)',
re.IGNORECASE
),
suggestion="移除调试代码后再提交到生产分支",
languages=["python", "javascript", "typescript"],
tags=["debugging", "code-quality"],
),
]
class ComplianceChecker:
"""MonkeyCode 合规检查引擎"""
def __init__(self):
self.rules: Dict[str, ComplianceRule] = {}
self.custom_rules: List[ComplianceRule] = []
self._load_builtin_rules()
def _load_builtin_rules(self):
"""加载内置规则"""
for rule in BUILTIN_RULES:
self.rules[rule.rule_id] = rule
def add_custom_rule(self, rule: ComplianceRule):
"""添加自定义规则"""
self.custom_rules.append(rule)
self.rules[rule.rule_id] = rule
def remove_rule(self, rule_id: str):
"""移除规则"""
if rule_id in self.rules:
del self.rules[rule_id]
def scan_file(
self,
file_path: str,
content: str,
language: str = "text",
enabled_categories: Optional[List[RuleCategory]] = None,
min_severity: Severity = Severity.INFO,
) -> List[ComplianceIssue]:
"""
扫描单个文件
Args:
file_path: 文件路径
content: 文件内容
language: 编程语言
enabled_categories: 启用的规则类别 (None 表示全部启用)
min_severity: 最低报告级别
Returns:
发现的问题列表
"""
issues: List[ComplianceIssue] = []
lines = content.split('\n')
for rule_id, rule in self.rules.items():
if not rule.enabled:
continue
# 类别过滤
if enabled_categories and rule.category not in enabled_categories:
continue
# 严重级别过滤
severity_order = {
Severity.INFO: 0,
Severity.WARNING: 1,
Severity.ERROR: 2,
Severity.CRITICAL: 3,
}
if severity_order.get(rule.severity, 0) < severity_order.get(min_severity, 0):
continue
# 语言过滤
if "*" not in rule.languages and language.lower() not in [l.lower() for l in rule.languages]:
continue
# 特殊处理: 许可证头检测
if rule.rule_id == "LIC001":
if not self._has_license_header(content[:500]):
issues.append(ComplianceIssue(
rule_id=rule.rule_id,
file_path=file_path,
line_number=1,
column=1,
severity=rule.severity,
message=f"{rule.name}: {rule.description}",
suggestion=rule.suggestion,
snippet=lines[0] if lines else "",
category=rule.category,
))
continue
# 正则匹配
for line_num, line in enumerate(lines, start=1):
matches = rule.pattern.finditer(line)
for match in matches:
issues.append(ComplianceIssue(
rule_id=rule.rule_id,
file_path=file_path,
line_number=line_num,
column=match.start() + 1,
severity=rule.severity,
message=f"{rule.name}: {rule.description}",
suggestion=rule.suggestion,
snippet=line.strip(),
category=rule.category,
auto_fix_available=self._can_auto_fix(rule.rule_id),
fix_suggestion=self._get_auto_fix(rule.rule_id, match),
))
return issues
def scan_project(
self,
files: Dict[str, Dict[str, str]],
enabled_categories: Optional[List[RuleCategory]] = None,
min_severity: Severity = Severity.WARNING,
) -> Dict[str, Any]:
"""
扫描整个项目
Args:
files: {file_path: {"content": "...", "language": "..."}}
enabled_categories: 启用的规则类别
min_severity: 最低报告级别
Returns:
扫描结果摘要
"""
all_issues: List[ComplianceIssue] = []
file_count = len(files)
for file_path, file_info in files.items():
issues = self.scan_file(
file_path=file_path,
content=file_info["content"],
language=file_info.get("language", "text"),
enabled_categories=enabled_categories,
min_severity=min_severity,
)
all_issues.extend(issues)
# 统计
stats = {
"total_files_scanned": file_count,
"total_issues_found": len(all_issues),
"by_severity": {},
"by_category": {},
"by_file": {},
"by_rule": {},
}
for issue in all_issues:
# 按严重程度统计
sev = issue.severity.value
stats["by_severity"][sev] = stats["by_severity"].get(sev, 0) + 1
# 按类别统计
cat = issue.category.value
stats["by_category"][cat] = stats["by_category"].get(cat, 0) + 1
# 按文件统计
if issue.file_path not in stats["by_file"]:
stats["by_file"][issue.file_path] = []
stats["by_file"][issue.file_path].append(issue)
# 按规则统计
if issue.rule_id not in stats["by_rule"]:
stats["by_rule"][issue.rule_id] = 0
stats["by_rule"][issue.rule_id] += 1
# 计算合规评分
score = self._calculate_compliance_score(stats, file_count)
return {
"summary": stats,
"issues": [self._issue_to_dict(i) for i in all_issues],
"compliance_score": score,
"recommendations": self._generate_recommendations(stats),
"scan_timestamp": __import__("datetime").datetime.now().isoformat(),
}
def _has_license_header(self, header_content: str) -> bool:
"""检查是否有许可证头"""
license_patterns = [
r'SPDX-License-Identifier:',
r'Copyright\s+[©c]',
r'Licensed under the',
r'MIT License',
r'Apache License',
r'GPL',
r'BSD',
]
for pattern in license_patterns:
if re.search(pattern, header_content, re.IGNORECASE):
return True
return False
def _can_auto_fix(self, rule_id: str) -> bool:
"""判断是否可以自动修复"""
auto_fixable = {"STY002"} # 目前只支持调试代码移除
return rule_id in auto_fixable
def _get_auto_fix(self, rule_id: str, match) -> Optional[str]:
"""获取自动修复建议"""
if rule_id == "STY002":
return f"删除此行: {match.group(0)}"
return None
def _calculate_compliance_score(self, stats: dict, file_count: int) -> float:
"""计算合规评分 (0-100)"""
if file_count == 0:
return 100.0
critical = stats["by_severity"].get("critical", 0)
error = stats["by_severity"].get("error", 0)
warning = stats["by_severity"].get("warning", 0)
info = stats["by_severity"].get("info", 0)
total = critical + error + warning + info
if total == 0:
return 100.0
# 加权扣分
deductions = critical * 25 + error * 10 + warning * 3 + info * 0.5
score = max(0, 100 - deductions / max(file_count, 1))
return round(score, 1)
def _generate_recommendations(self, stats: dict) -> List[str]:
"""生成改进建议"""
recommendations = []
if stats["by_severity"].get("critical", 0) > 0:
recommendations.append(
f"🔴 发现 {stats['by_severity']['critical']} 个严重安全问题,"
f"请立即修复!"
)
if stats["by_severity"].get("error", 0) > 0:
recommendations.append(
f"⚠️ 发现 {stats['by_severity']['error']} 个高危问题,"
f"建议在本迭代内修复。"
)
if stats["by_category"].get("security", 0) > 5:
recommendations.append(
"📋 安全问题较多,建议安排一次安全审计。"
)
if stats["by_category"].get("privacy", 0) > 0:
recommendations.append(
"🔒 发现潜在的隐私数据问题,请确认符合 GDPR/PIPL 要求。"
)
if not recommendations:
recommendations.append("✅ 未发现重大合规问题,继续保持!")
return recommendations
@staticmethod
def _issue_to_dict(issue: ComplianceIssue) -> dict:
"""转换为字典"""
return {
"rule_id": issue.rule_id,
"file_path": issue.file_path,
"line": issue.line_number,
"column": issue.column,
"severity": issue.severity.value,
"message": issue.message,
"suggestion": issue.suggestion,
"snippet": issue.snippet,
"category": issue.category.value,
"auto_fix_available": issue.auto_fix_available,
}
# 使用示例
if __name__ == '__main__':
checker = ComplianceChecker()
# 扫描示例文件
sample_code = '''
import os
def connect_db():
password = "SuperSecret123!" # SEC001
query = f"SELECT * FROM users WHERE id = {user_input}" # SEC002
print(f"Connecting with password={password}") # PRV002
console.log(debug_info) # STY002
# TODO: Fix this later # STY001
'''
results = checker.scan_file(
file_path="app.py",
content=sample_code,
language="python"
)
print(f"📊 发现 {len(results)} 个合规问题:\n")
for issue in results:
print(f" [{issue.severity.value.upper()}] Line {issue.line_number}: "
f"{issue.message}")
print(f" 💡 建议: {issue.suggestion}")
print()
四、插件沙箱与安全机制
4.1 多层安全防护体系
┌─────────────────────────────────────────────────────────────┐
│ MonkeyCode 插件安全防护体系 │
│ │
│ 第一层: 安装前验证 │
│ ├── 🔍 代码签名验证 │
│ ├── 📋 Manifest 格式校验 │
│ ├── 🚫 恶意模式扫描 (已知攻击特征) │
│ ├── 📊 依赖项分析 (供应链安全) │
│ └── 👥 社区信誉评分 │
│ │
│ 第二层: 运行时隔离 │
│ ├── 📦 沙箱执行环境 (受限的 Node.js/V8 隔离) │
│ ├── 🚧 文件系统访问控制 (仅允许声明的路径) │
│ ├── 🌐 网络访问白名单 (需显式声明) │
│ ├── ⏱️ CPU/内存使用限制 │
│ └── 🔄 API 调用频率限制 │
│ │
│ 第三层: 行为监控 │
│ ├── 📈 性能指标实时监控 │
│ ├── 🚨 异常行为检测 (异常 CPU/内存/网络) │
│ ├── 📝 完整操作审计日志 │
│ └── 🛡️ 自动熔断机制 │
│ │
│ 第四层: 用户控制 │
│ ├── ✅ 权限显式授权 (安装时确认) │
│ ├── 👁️ 权限使用透明展示 │
│ ├── 🗑️ 一键卸载 + 数据清除 │
│ └── 📊 隐私影响评估报告 │
│ │
└─────────────────────────────────────────────────────────────┘
4.2 沙箱配置示例
# monkeycode/plugins/sandbox-config.yaml
sandbox_configuration:
runtime:
type: "isolated-v8" # 使用 V8 Isolate 进行隔离
memory_limit_mb: 256
cpu_limit_percent: 30
max_execution_time_ms: 5000 # 单次操作最大执行时间
heap_snapshot_on_oom: true
filesystem:
allowed_operations:
- "read" # 默认允许读
denied_operations:
- "write" # 写入需要显式权限
- "delete" # 删除需要显式权限
- "exec" # 执行需要显式权限
allowed_paths:
- "$PLUGIN_DIR/**" # 插件自身目录
- "$TEMP_DIR/$PLUGIN_ID/**" # 插件临时目录
- "$HOME/.config/monkeyCode/plugins/$PLUGIN_ID/**"
path_aliases:
PLUGIN_DIR: "./plugins/{id}"
TEMP_DIR: "/tmp/monkeyCode"
network:
default_policy: "deny-all"
allowed_domains: [] # 默认不允许任何网络访问
rate_limits:
requests_per_minute: 60
bytes_per_minute: 1048576 # 1MB/min
requires_permission_for:
- "http:"
- "https:"
- "ws:"
- "wss:"
api_access:
core_ai:
allowed_methods:
- "chatCompletion"
- "codeCompletion"
- "explainCode"
rate_limit: "100/hour"
max_tokens_per_request: 2000
core_editor:
allowed_methods:
- "getActiveEditor"
- "getText"
- "getSelection"
denied_methods:
- "writeFile" # 需要显式权限
- "executeCommand"
core_ui:
allowed_methods:
- "showInformationMessage"
- "showWarningMessage"
- "showErrorMessage"
- "registerCommand"
denied_methods:
- "openExternal" # 打开外部链接需要权限
child_process:
allowed: false # 默认禁止子进程
allowlist_executables: [] # 白名单为空
native_modules:
allowed: false # 默认禁止原生模块 (N-API 等)
五、插件市场与分发策略
5.1 插件发布流程
#!/bin/bash
# ===== scripts/publish-plugin.sh =====
# MonkeyCode 插件发布脚本
set -e
echo "🐵 MonkeyCode Plugin Publisher"
echo "=============================="
# 1. 检查必要文件
REQUIRED_FILES=("package.json" "README.md" "LICENSE")
for file in "${REQUIRED_FILES[@]}"; do
if [ ! -f "$file" ]; then
echo "❌ Missing required file: $file"
exit 1
fi
done
echo "✅ All required files present"
# 2. 验证 package.json 中的 monkeyCode 字段
if ! node -e "require('./package.json').monkeyCode" 2>/dev/null; then
echo "❌ Invalid or missing 'monkeyCode' field in package.json"
exit 1
fi
echo "✅ Plugin manifest valid"
# 3. 运行 lint 检查
echo "🔍 Running lint checks..."
npm run lint --if-present
echo "✅ Lint passed"
# 4. 运行测试
echo "🧪 Running tests..."
npm test --if-present
echo "✅ Tests passed"
# 5. 构建
echo "📦 Building..."
npm run build --if-present
echo "✅ Build complete"
# 6. 打包
VERSION=$(node -e "console.log(require('./package.json').version)")
NAME=$(node -e "console.log(require('./package.json').name)")
PACKAGE_NAME="${NAME}-${VERSION}.tgz"
npm pack --pack-destination ./dist
echo "📦 Package created: ${PACKAGE_NAME}"
# 7. 发布到注册表
echo "🚀 Publishing to MonkeyCode Plugin Registry..."
# 这里会调用官方 CLI 工具上传
# mcplugin publish ./dist/${PACKAGE_NAME}
echo ""
echo "==========================================="
echo "🎉 Plugin published successfully!"
echo ""
echo "Next steps:"
echo " 1. Your plugin will be reviewed by the team"
echo " 2. Once approved, it will appear in the marketplace"
echo " 3. Users can install it via: mcplugin install ${NAME}"
echo ""
echo "📖 Don't forget to promote your plugin!"
echo " GitHub: https://github.com/monkeycode-ai/monkeycode/issues"
echo " (tag with 'plugin')"
5.2 插件质量评分标准
| 维度 | 权重 | 评分标准 |
|---|---|---|
| 功能性 | 30% | 插件是否解决了实际问题?功能是否完整? |
| 代码质量 | 20% | 代码是否清晰?是否有测试?是否有文档? |
| 安全性 | 15% | 是否遵循安全最佳实践?是否有已知漏洞? |
| 性能 | 15% | 是否有性能瓶颈?启动时间是否合理? |
| 用户体验 | 10% | UI/UX 是否直观?错误提示是否清晰? |
| 维护性 | 10% | 是否活跃维护?是否响应 Issue? |
评分等级:
- ⭐⭐⭐⭐⭐ (90-100): 精选推荐 (Featured)
- ⭐⭐⭐⭐ (75-89): 推荐安装 (Recommended)
- ⭐⭐⭐ (60-74): 合格可用 (Good)
- ⭐⭐ (40-59): 需要改进 (Needs Work)
- ⭐ (0-39): 不予上架 (Rejected)
六、经验总结与最佳实践
6.1 插件开发十大原则
┌─────────────────────────────────────────────────────────────┐
│ MonkeyCode 插件开发十大原则 │
│ │
│ 1️⃣ 做一件事情,并把它做好 │
│ → 单一职责原则,一个插件解决一个问题 │
│ │
│ 2️⃣ 尊重用户的编辑器体验 │
│ → 不要阻塞主线程,不要频繁弹窗,静默优先 │
│ │
│ 3️⃣ 优雅降级是必须的 │
│ → AI 服务不可用时,插件仍应能正常工作(降低功能) │
│ │
│ 4️⃣ 日志是你的朋友 │
│ → 结构化日志,合理的日志级别,帮助用户排查问题 │
│ │
│ 5️⃣ 性能意识贯穿始终 │
│ → 缓存、懒加载、增量处理,避免不必要的计算 │
│ │
│ 6️⃣ 安全第一 │
│ → 最小权限原则,不请求不必要的权限 │
│ │
│ 7️⃣ 文档即代码 │
│ → README 要完整,API 要有注释,示例要能跑通 │
│ │
│ 8️⃣ 测试不是可选项 │
│ → 单元测试 + 集成测试,CI 必须通过才能发布 │
│ │
│ 9️⃣ 关注兼容性 │
│ → 声明清晰的版本兼容范围,处理好 breaking change │
│ │
│ 🔟 倾听用户反馈 │
│ → Issue 是最宝贵的输入,积极回应每一个反馈 │
│ │
└─────────────────────────────────────────────────────────────┘
6.2 常见陷阱与解决方案
| 陷阱 | 症状 | 解决方案 |
|---|---|---|
| 内存泄漏 | 长时间运行后 IDE 卡顿 | 确保所有事件监听器在 deactivate 时清理 |
| 阻塞主线程 | 编辑器卡顿/无响应 | 将耗时操作放入 Worker 或异步执行 |
| 过度请求 AI API | 成本超限/速率限制 | 实现缓存、去重、批量请求 |
| 忽略错误处理 | 插件崩溃影响其他功能 | try-catch 包裹所有外部调用 |
| 硬编码配置 | 无法适配不同团队需求 | 提供配置面板,支持 workspace-level 配置 |
| 缺少优雅降级 | AI 服务不可用时完全不可用 | 设计 fallback 逻辑,逐步降级功能 |
| 全局状态污染 | 多个插件互相干扰 | 使用命名空间隔离,避免修改全局对象 |
结语
"插件生态的健康程度,是一个开源项目成熟度的真正标志。"
MonkeyCode 的插件系统不仅仅是一个技术架构——它是一种社区协作的理念。我们相信:
- 🌱 每一个好想法都值得被实现 — 通过插件,任何人都可以将自己的创意变为现实
- 🤝 协作比竞争更有力量 — 开源的本质是共建共享
- 🎯 多样性是创新的源泉 — 不同背景的开发者带来不同视角
- 🚀 生态系统 > 单一产品 — 一个健康的生态比任何单一功能都更有价值
如果你有一个想法,想要为 MonkeyCode 开发插件,现在就开始吧!
🛠️ 开始你的插件开发之旅:
- 📖 完整插件开发文档
- 🧩 API 参考
- 💡 示例插件集合
- 💬 开发者交流区
- 🐛 提交插件相关 Issue
MonkeyCode — 用开放的架构,连接每一位创造者的想象力。 🐵🔌✨
浙公网安备 33010602011771号