* 验证插件配置 * @param pluginConfig 插件配置对象 * @param existingPlugins 已存在的插件列表 * @returns 验证结果 { valid: boolean, error?: string }
(
pluginConfig: any,
existingPlugins: any[]
)
| 358 | * @returns 验证结果 { valid: boolean, error?: string } |
| 359 | */ |
| 360 | private validatePluginConfig( |
| 361 | pluginConfig: any, |
| 362 | existingPlugins: any[] |
| 363 | ): { valid: boolean; error?: string } { |
| 364 | // 检查 title 是否冲突(如果有 title 字段) |
| 365 | // 排除开发版插件(name 以 __dev 结尾),因为开发版和安装版可以共存,title 相同是合理的 |
| 366 | if (pluginConfig.title) { |
| 367 | const titleConflict = existingPlugins.find( |
| 368 | (p: any) => p.title === pluginConfig.title && !isDevelopmentPluginName(p.name) |
| 369 | ) |
| 370 | if (titleConflict) { |
| 371 | return { |
| 372 | valid: false, |
| 373 | error: `插件标题 "${pluginConfig.title}" 已被插件 "${titleConflict.name}" 使用,请使用不同的标题` |
| 374 | } |
| 375 | } |
| 376 | } |
| 377 | |
| 378 | // 校验必填字段 |
| 379 | const requiredFields = ['name', 'version'] |
| 380 | for (const field of requiredFields) { |
| 381 | if (!pluginConfig[field]) { |
| 382 | return { valid: false, error: `缺少必填字段: ${field}` } |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | // 检查插件是否声明了 features 或 tools(至少需要一个) |
| 387 | const hasFeatures = Array.isArray(pluginConfig.features) && pluginConfig.features.length > 0 |
| 388 | const hasTools = |
| 389 | pluginConfig.tools && |
| 390 | typeof pluginConfig.tools === 'object' && |
| 391 | !Array.isArray(pluginConfig.tools) && |
| 392 | Object.keys(pluginConfig.tools).length > 0 |
| 393 | |
| 394 | // features 和 tools 不能同时为空 |
| 395 | if (!hasFeatures && !hasTools) { |
| 396 | return { valid: false, error: 'features 和 tools 不能同时为空' } |
| 397 | } |
| 398 | |
| 399 | // 校验 features 字段(传统插件功能) |
| 400 | if (hasFeatures) { |
| 401 | for (const feature of pluginConfig.features) { |
| 402 | if (!feature.code || !Array.isArray(feature.cmds)) { |
| 403 | return { valid: false, error: 'feature 缺少必填字段 (code, cmds)' } |
| 404 | } |
| 405 | } |
| 406 | } |
| 407 | |
| 408 | // 校验 tools 字段(MCP 工具声明) |
| 409 | if (hasTools) { |
| 410 | for (const [toolName, tool] of Object.entries(pluginConfig.tools)) { |
| 411 | // 工具名必须使用小写 snake_case 命名(符合 MCP 规范) |
| 412 | if (!/^[a-z][a-z0-9_]*$/.test(toolName)) { |
| 413 | return { valid: false, error: `tools.${toolName} 必须使用小写 snake_case 命名` } |
| 414 | } |
| 415 | if (!tool || typeof tool !== 'object') { |
| 416 | return { valid: false, error: `tools.${toolName} 配置无效` } |
| 417 | } |
no test coverage detected