Merge branch 'revolution-feature' into develop

This commit is contained in:
sean
2025-06-19 14:54:25 +08:00
42 changed files with 4659 additions and 734 deletions

View File

@ -4,6 +4,7 @@ const { cli } = require('../core/pouch');
const { MCPOutputAdapter } = require('../adapters/MCPOutputAdapter');
const { getExecutionContext, getDebugInfo } = require('../utils/executionContext');
const { getToolDefinitions } = require('../mcp/toolDefinitions');
const treeKill = require('tree-kill');
/**
* MCP Server 适配器 - 函数调用架构
@ -78,8 +79,16 @@ class MCPServerCommand {
/**
* 启动MCP Server
*/
async execute() {
async execute(options = {}) {
try {
// 设置进程清理处理器
this.setupProcessCleanup();
// 如果需要启动DACP服务
if (options.withDacp) {
await this.startDACPService();
}
this.log('🚀 启动MCP Server...');
const transport = new StdioServerTransport();
await this.server.connect(transport);
@ -89,18 +98,301 @@ class MCPServerCommand {
return new Promise((resolve) => {
// MCP服务器现在正在运行监听stdin输入
process.on('SIGINT', () => {
this.log('🛑 收到终止信号关闭MCP Server');
this.log('🛑 收到SIGINT信号正在关闭...');
this.cleanup();
resolve();
});
process.on('SIGTERM', () => {
this.log('🛑 收到终止信号关闭MCP Server');
this.log('🛑 收到SIGTERM信号正在关闭...');
this.cleanup();
resolve();
});
});
} catch (error) {
// 输出到stderr
console.error(`❌ MCP Server 启动失败: ${error.message}`);
this.cleanup();
throw error;
}
}
/**
* 设置进程清理处理器
*/
setupProcessCleanup() {
// 处理各种退出情况
const exitHandler = (signal) => {
this.log(`收到信号: ${signal}`);
this.cleanup();
process.exit(0);
};
// 捕获所有可能的退出信号
process.on('exit', () => this.cleanup());
process.on('SIGHUP', () => exitHandler('SIGHUP'));
process.on('SIGQUIT', () => exitHandler('SIGQUIT'));
process.on('uncaughtException', (err) => {
console.error('未捕获的异常:', err);
this.cleanup();
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的Promise拒绝:', reason);
this.cleanup();
process.exit(1);
});
}
/**
* 清理子进程
*/
cleanup() {
if (this.dacpProcess && !this.dacpProcess.killed && this.dacpProcess.pid) {
this.log('🛑 正在终止DACP服务及其所有子进程...');
// 使用 tree-kill 终止整个进程树
treeKill(this.dacpProcess.pid, 'SIGTERM', (err) => {
if (err) {
this.log(`⚠️ 优雅终止失败: ${err.message}`);
// 3秒后强制终止
setTimeout(() => {
if (this.dacpProcess && !this.dacpProcess.killed && this.dacpProcess.pid) {
this.log('⚠️ DACP服务未响应SIGTERM强制终止整个进程树...');
treeKill(this.dacpProcess.pid, 'SIGKILL', (killErr) => {
if (killErr) {
this.log(`❌ 强制终止失败: ${killErr.message}`);
} else {
this.log('✅ DACP服务进程树已强制终止');
}
});
}
}, 3000);
} else {
this.log('✅ DACP服务进程树已优雅终止');
}
});
}
}
/**
* 检测DACP服务是否已经运行
* @param {string} host - 主机地址
* @param {number} port - 端口号
* @returns {Promise<boolean>} 服务是否运行
*/
async isDACPServiceRunning(host = 'localhost', port = 3002) {
const http = require('http');
return new Promise((resolve) => {
const options = {
hostname: host,
port: port,
path: '/health',
method: 'GET',
timeout: 2000 // 2秒超时
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const healthData = JSON.parse(data);
// 检查是否是DACP服务且状态健康
const isHealthy = healthData.status === 'healthy';
const isDACPService = healthData.service && healthData.service.includes('DACP');
resolve(isHealthy && isDACPService);
} catch (error) {
resolve(false);
}
});
});
req.on('error', () => {
resolve(false);
});
req.on('timeout', () => {
req.destroy();
resolve(false);
});
req.end();
});
}
/**
* 获取DACP服务信息
* @param {string} host - 主机地址
* @param {number} port - 端口号
* @returns {Promise<Object|null>} 服务信息
*/
async getDACPServiceInfo(host = 'localhost', port = 3002) {
const http = require('http');
return new Promise((resolve) => {
const options = {
hostname: host,
port: port,
path: '/info',
method: 'GET',
timeout: 2000
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const serviceInfo = JSON.parse(data);
resolve(serviceInfo);
} catch (error) {
resolve(null);
}
});
});
req.on('error', () => {
resolve(null);
});
req.on('timeout', () => {
req.destroy();
resolve(null);
});
req.end();
});
}
/**
* 启动DACP服务
*/
async startDACPService() {
const { spawn } = require('child_process');
const path = require('path');
try {
this.log('🔍 检测DACP服务状态...');
// 先检测是否已有DACP服务运行
const isRunning = await this.isDACPServiceRunning();
if (isRunning) {
// 服务已存在,获取服务信息并直接使用
const serviceInfo = await this.getDACPServiceInfo();
console.error(''); // 空行分隔
console.error('=====================================');
console.error('🔄 发现现有DACP服务直接复用');
console.error('📍 DACP服务地址: http://localhost:3002');
if (serviceInfo) {
console.error(`🏷️ 服务名称: ${serviceInfo.service?.name || 'Unknown'}`);
console.error(`📦 服务版本: ${serviceInfo.service?.version || 'Unknown'}`);
console.error(`🔧 可用操作: ${serviceInfo.available_actions?.join(', ') || 'Unknown'}`);
}
console.error('=====================================');
console.error(''); // 空行分隔
return; // 直接返回,不启动新服务
}
this.log('🚀 启动新的DACP服务...');
// DACP服务路径
const dacpPath = path.join(__dirname, '../../dacp/dacp-promptx-service');
// 启动DACP服务作为子进程
// 注意:不能直接使用 'inherit'因为会干扰MCP的stdio通信
// 但我们需要看到DACP的启动信息
this.dacpProcess = spawn('npm', ['start'], {
cwd: dacpPath,
stdio: ['ignore', 'pipe', 'pipe'], // stdin忽略, stdout和stderr都输出到pipe
shell: true,
detached: false // tree-kill 会处理整个进程树,不需要 detached
});
// 将DACP的输出转发到stderr这样不会干扰MCP的stdout
this.dacpProcess.stdout.on('data', (data) => {
const output = data.toString().trim();
if (output) {
console.error(`[DACP] ${output}`);
}
});
this.dacpProcess.stderr.on('data', (data) => {
const output = data.toString().trim();
if (output) {
console.error(`[DACP ERROR] ${output}`);
}
});
// 监听子进程退出
this.dacpProcess.on('exit', (code, signal) => {
this.log(`DACP服务已退出 (code: ${code}, signal: ${signal})`);
this.dacpProcess = null;
});
// 监听子进程错误
this.dacpProcess.on('error', (err) => {
console.error(`DACP进程错误: ${err.message}`);
});
// 等待服务启动 - 通过监听输出来判断
await new Promise((resolve, reject) => {
let started = false;
const timeout = setTimeout(() => {
if (!started) {
reject(new Error('DACP服务启动超时'));
}
}, 10000); // 10秒超时
// 监听输出,判断服务是否启动
const checkStarted = (data) => {
const output = data.toString();
// 检查是否包含启动成功的标志
if (output.includes('Running at http://localhost:') ||
output.includes('🚀') ||
output.includes('DACP') ||
output.includes('3002')) {
if (!started) {
started = true;
clearTimeout(timeout);
console.error(''); // 空行分隔
console.error('=====================================');
console.error('✅ DACP服务启动成功');
console.error('📍 DACP服务地址: http://localhost:3002');
console.error('🔧 支持的Actions: send_email, schedule_meeting, create_document');
console.error('=====================================');
console.error(''); // 空行分隔
resolve();
}
}
};
this.dacpProcess.stdout.on('data', checkStarted);
this.dacpProcess.on('error', (err) => {
clearTimeout(timeout);
reject(new Error(`DACP服务启动失败: ${err.message}`));
});
this.dacpProcess.on('exit', (code) => {
if (!started) {
clearTimeout(timeout);
reject(new Error(`DACP服务意外退出退出码: ${code}`));
}
});
});
} catch (error) {
this.log(`❌ DACP服务启动失败: ${error.message}`);
throw error;
}
}
@ -189,7 +481,9 @@ class MCPServerCommand {
result.push('--tags', args.tags);
}
return result;
}
},
'promptx_dacp': (args) => [args]
};
const mapper = paramMapping[toolName];

View File

@ -449,7 +449,8 @@ class MCPStreamableHttpCommand {
result.push('--tags', args.tags);
}
return result;
}
},
'promptx_dacp': (args) => [args]
};
const mapper = paramMapping[toolName];

View File

@ -30,7 +30,8 @@ class PouchCLI {
action: commands.ActionCommand,
learn: commands.LearnCommand,
recall: commands.RecallCommand,
remember: commands.RememberCommand
remember: commands.RememberCommand,
dacp: commands.DACPCommand
})
// 将命令注册到状态机

View File

@ -0,0 +1,192 @@
const BasePouchCommand = require('../BasePouchCommand');
const http = require('http');
/**
* DACP服务调用命令
* 负责调用DACP服务实现从AI建议到AI行动的转换
*/
class DACPCommand extends BasePouchCommand {
constructor() {
super();
// 统一的DACP服务端点
// 所有service_id都路由到同一个服务
this.defaultEndpoint = 'http://localhost:3002/dacp';
}
/**
* 验证参数格式
* @param {Object} args - 参数对象
*/
validateArgs(args) {
if (!args.service_id) {
throw new Error('缺少必需参数: service_id');
}
if (!args.action) {
throw new Error('缺少必需参数: action');
}
if (!args.parameters) {
throw new Error('缺少必需参数: parameters');
}
if (!args.parameters.user_request) {
throw new Error('缺少必需参数: parameters.user_request');
}
}
/**
* 获取服务端点
* @param {string} serviceId - 服务ID
* @returns {string} 服务端点URL
*/
getServiceEndpoint(serviceId) {
// 现在所有服务都指向同一个端点
// serviceId 只是用来在DACP服务内部路由到不同的action
return this.defaultEndpoint;
}
/**
* 执行DACP服务调用内部方法
* @param {Object} args - 调用参数
* @returns {Promise<Object>} DACP响应
*/
async callDACPService(args) {
try {
// 验证参数
this.validateArgs(args);
const { service_id, action, parameters } = args;
// 获取服务端点(现在是统一的)
const endpoint = this.getServiceEndpoint(service_id);
// 构造DACP请求
const dacpRequest = {
service_id,
action,
parameters,
request_id: `req_${Date.now()}`
};
// 调用DACP服务
const result = await this.makeHttpRequest(endpoint, dacpRequest);
return result;
} catch (error) {
// 统一错误处理
if (error.message.startsWith('缺少必需参数') ||
error.message.startsWith('未找到DACP服务') ||
error.message.startsWith('DACP响应解析失败')) {
throw error;
}
throw new Error(`DACP服务调用失败: ${error.message}`);
}
}
/**
* 发送HTTP请求
* @param {string} url - 请求URL
* @param {Object} data - 请求数据
* @returns {Promise<Object>} 响应数据
*/
makeHttpRequest(url, data) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const options = {
hostname: urlObj.hostname,
port: urlObj.port,
path: urlObj.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(JSON.stringify(data))
}
};
const req = http.request(options, (res) => {
let responseData = '';
res.on('data', (chunk) => {
responseData += chunk;
});
res.on('end', () => {
try {
const result = JSON.parse(responseData);
resolve(result);
} catch (error) {
reject(new Error(`DACP响应解析失败: ${error.message}`));
}
});
});
req.on('error', (error) => {
reject(error);
});
req.write(JSON.stringify(data));
req.end();
});
}
// BasePouchCommand的抽象方法实现虽然不会被用到
getPurpose() {
return '调用DACP专业服务让PromptX角色拥有执行能力';
}
async getContent(args) {
try {
// 处理参数:如果是数组,取第一个元素;否则直接使用
const dacpArgs = Array.isArray(args) ? args[0] : args;
// 执行DACP调用
const result = await this.callDACPService(dacpArgs);
// 格式化响应
if (result.success) {
const executionResult = result.data.execution_result;
const metrics = result.data.performance_metrics;
return `🚀 DACP服务调用成功
📋 执行结果:
${JSON.stringify(executionResult, null, 2)}
⏱️ 性能指标:
- 执行时间: ${metrics.execution_time}
- 资源使用: ${metrics.resource_usage}
🎯 请求ID: ${result.request_id}`;
} else {
return `❌ DACP服务调用失败
错误信息: ${result.error?.message || '未知错误'}
错误代码: ${result.error?.code || 'UNKNOWN'}
🎯 请求ID: ${result.request_id}`;
}
} catch (error) {
return `❌ DACP服务调用异常
错误详情: ${error.message}
💡 请检查:
1. DACP服务是否运行 (http://localhost:3002/health)
2. 服务ID是否正确
3. 操作名称是否有效
4. 参数格式是否正确`;
}
}
getPATEOAS(args) {
return {
currentState: 'dacp_ready',
nextActions: []
};
}
}
module.exports = DACPCommand;

View File

@ -8,6 +8,7 @@ const ActionCommand = require('./ActionCommand')
const LearnCommand = require('./LearnCommand')
const RecallCommand = require('./RecallCommand')
const RememberCommand = require('./RememberCommand')
const DACPCommand = require('./DACPCommand')
module.exports = {
InitCommand,
@ -15,5 +16,6 @@ module.exports = {
ActionCommand,
LearnCommand,
RecallCommand,
RememberCommand
RememberCommand,
DACPCommand
}

View File

@ -0,0 +1,114 @@
/**
* PromptX 资源文件命名管理器
* 统一管理所有资源文件的命名规范:[id].[tag].md
*/
class ResourceFileNaming {
/**
* 资源文件命名模式
* 格式:[id].[tag].md
* 示例sean-product-philosophy.thought.md
*/
static NAMING_PATTERN = /^(.+)\.(\w+)\.md$/;
/**
* 解析资源文件名
* @param {string} fileName - 文件名
* @returns {Object|null} 解析结果 {id, tag} 或 null
*/
static parseFileName(fileName) {
const match = fileName.match(this.NAMING_PATTERN);
if (match) {
const [, id, tag] = match;
return { id, tag };
}
return null;
}
/**
* 生成资源文件名
* @param {string} id - 资源ID
* @param {string} tag - 资源标签
* @returns {string} 生成的文件名
*/
static generateFileName(id, tag) {
return `${id}.${tag}.md`;
}
/**
* 验证文件名是否符合规范
* @param {string} fileName - 文件名
* @returns {boolean} 是否符合规范
*/
static isValidFileName(fileName) {
return this.NAMING_PATTERN.test(fileName);
}
/**
* 检查文件是否为指定标签类型
* @param {string} fileName - 文件名
* @param {string} expectedTag - 期望的标签
* @returns {boolean} 是否匹配
*/
static hasTag(fileName, expectedTag) {
const parsed = this.parseFileName(fileName);
return parsed && parsed.tag === expectedTag;
}
/**
* 从文件路径提取资源ID
* @param {string} filePath - 文件路径
* @param {string} expectedTag - 期望的标签
* @returns {string|null} 资源ID或null
*/
static extractResourceId(filePath, expectedTag) {
const path = require('path');
const fileName = path.basename(filePath);
const parsed = this.parseFileName(fileName);
if (parsed && parsed.tag === expectedTag) {
return parsed.id;
}
return null;
}
/**
* 扫描目录中指定标签的所有文件
* @param {string} directory - 目录路径
* @param {string} tag - 标签类型
* @returns {Promise<Array>} 文件路径数组
*/
static async scanTagFiles(directory, tag) {
const fs = require('fs-extra');
const path = require('path');
try {
if (!await fs.pathExists(directory)) {
return [];
}
const files = await fs.readdir(directory);
const tagFiles = [];
for (const file of files) {
if (this.hasTag(file, tag)) {
tagFiles.push(path.join(directory, file));
}
}
return tagFiles;
} catch (error) {
return [];
}
}
/**
* 获取支持的资源标签类型
* @returns {Array<string>} 支持的标签类型
*/
static getSupportedTags() {
return ['role', 'thought', 'execution', 'knowledge'];
}
}
module.exports = ResourceFileNaming;

View File

@ -1,6 +1,7 @@
const BaseDiscovery = require('./BaseDiscovery')
const RegistryData = require('../RegistryData')
const ResourceData = require('../ResourceData')
const ResourceFileNaming = require('../ResourceFileNaming')
const logger = require('../../../utils/logger')
const path = require('path')
const fs = require('fs-extra')
@ -243,26 +244,31 @@ class PackageDiscovery extends BaseDiscovery {
registryData.addResource(resourceData)
}
// 查找thought文件
// 查找thought文件 - 使用统一命名管理器
const thoughtDir = path.join(itemPath, 'thought')
if (await fs.pathExists(thoughtDir)) {
const thoughtFile = path.join(thoughtDir, `${item}.thought.md`)
if (await fs.pathExists(thoughtFile)) {
const reference = `@package://prompt/domain/${item}/thought/${item}.thought.md`
const resourceData = new ResourceData({
id: item,
source: 'package',
protocol: 'thought',
name: ResourceData._generateDefaultName(item, 'thought'),
description: ResourceData._generateDefaultDescription(item, 'thought'),
reference: reference,
metadata: {
scannedAt: new Date().toISOString()
}
})
registryData.addResource(resourceData)
const thoughtFiles = await ResourceFileNaming.scanTagFiles(thoughtDir, 'thought')
for (const thoughtFile of thoughtFiles) {
const thoughtId = ResourceFileNaming.extractResourceId(thoughtFile, 'thought')
if (thoughtId) {
const fileName = path.basename(thoughtFile)
const reference = `@package://prompt/domain/${item}/thought/${fileName}`
const resourceData = new ResourceData({
id: thoughtId,
source: 'package',
protocol: 'thought',
name: ResourceData._generateDefaultName(thoughtId, 'thought'),
description: ResourceData._generateDefaultDescription(thoughtId, 'thought'),
reference: reference,
metadata: {
scannedAt: new Date().toISOString()
}
})
registryData.addResource(resourceData)
}
}
}

View File

@ -110,6 +110,46 @@ const TOOL_DEFINITIONS = [
content: z.string().describe('要保存的重要信息或经验'),
tags: z.string().optional().describe('自定义标签,用空格分隔,可选')
})
},
{
name: 'promptx_dacp',
description: '🚀 [DACP专业服务调用器] 让PromptX角色拥有执行能力 - 调用邮件发送、日程管理、文档处理等专业服务将AI建议转化为实际行动。支持自然语言需求智能路由到合适的DACP服务包。',
inputSchema: {
type: 'object',
properties: {
service_id: {
type: 'string',
description: 'DACP服务IDdacp-email-service'
},
action: {
type: 'string',
description: '具体操作send_email'
},
parameters: {
type: 'object',
properties: {
user_request: {
type: 'string',
description: '用户自然语言需求'
},
context: {
type: 'object',
description: '上下文信息'
}
},
required: ['user_request']
}
},
required: ['service_id', 'action', 'parameters']
},
zodSchema: z.object({
service_id: z.string().describe('DACP服务IDdacp-email-service'),
action: z.string().describe('具体操作send_email'),
parameters: z.object({
user_request: z.string().describe('用户自然语言需求'),
context: z.object({}).optional().describe('上下文信息')
})
})
}
];

View File

@ -0,0 +1,207 @@
const fs = require('fs-extra')
const path = require('path')
const os = require('os')
/**
* DACP用户级配置管理器
* 管理 ~/.promptx/dacp/ 下的配置文件
*/
class DACPConfigManager {
constructor() {
this.userHome = os.homedir()
this.dacpConfigDir = path.join(this.userHome, '.promptx', 'dacp')
}
/**
* 确保DACP配置目录存在
*/
async ensureConfigDir() {
await fs.ensureDir(this.dacpConfigDir)
}
/**
* 获取指定action的配置文件路径
* @param {string} action - action名称如 'send_email'
* @returns {string} 配置文件完整路径
*/
getConfigPath(action) {
return path.join(this.dacpConfigDir, `${action}.json`)
}
/**
* 读取action配置
* @param {string} action - action名称
* @returns {Promise<Object|null>} 配置对象或null
*/
async readActionConfig(action) {
const configPath = this.getConfigPath(action)
try {
if (await fs.pathExists(configPath)) {
return await fs.readJson(configPath)
}
return null
} catch (error) {
console.warn(`读取DACP配置失败 ${action}:`, error.message)
return null
}
}
/**
* 写入action配置
* @param {string} action - action名称
* @param {Object} config - 配置对象
*/
async writeActionConfig(action, config) {
await this.ensureConfigDir()
const configPath = this.getConfigPath(action)
await fs.writeJson(configPath, config, { spaces: 2 })
}
/**
* 检查action配置是否存在
* @param {string} action - action名称
* @returns {Promise<boolean>}
*/
async hasActionConfig(action) {
const configPath = this.getConfigPath(action)
return await fs.pathExists(configPath)
}
/**
* 验证邮件配置
* @param {Object} config - 邮件配置对象
* @returns {Object} 验证结果 {valid: boolean, errors: string[]}
*/
validateEmailConfig(config) {
const errors = []
if (!config) {
errors.push('配置对象不能为空')
return { valid: false, errors }
}
// 验证provider
if (!config.provider) {
errors.push('缺少邮件服务提供商(provider)配置')
}
// 验证SMTP配置
if (!config.smtp) {
errors.push('缺少SMTP配置')
} else {
if (!config.smtp.user) {
errors.push('缺少SMTP用户名(smtp.user)')
}
if (!config.smtp.password) {
errors.push('缺少SMTP密码(smtp.password)')
}
}
// 验证发件人配置
if (!config.sender) {
errors.push('缺少发件人配置(sender)')
} else {
if (!config.sender.email) {
errors.push('缺少发件人邮箱(sender.email)')
}
if (!config.sender.name) {
errors.push('缺少发件人姓名(sender.name)')
}
}
return {
valid: errors.length === 0,
errors
}
}
/**
* 获取邮件服务提供商配置
* @param {string} provider - 提供商名称
* @returns {Object} 提供商配置
*/
getProviderConfig(provider) {
const providers = {
gmail: {
smtp: 'smtp.gmail.com',
port: 587,
secure: false,
requireAuth: true
},
outlook: {
smtp: 'smtp-mail.outlook.com',
port: 587,
secure: false,
requireAuth: true
},
qq: {
smtp: 'smtp.qq.com',
port: 465,
secure: true,
requireAuth: true
},
'163': {
smtp: 'smtp.163.com',
port: 465,
secure: true,
requireAuth: true
},
'126': {
smtp: 'smtp.126.com',
port: 465,
secure: true,
requireAuth: true
}
}
return providers[provider] || null
}
/**
* 生成配置错误提示信息
* @param {string} action - action名称
* @param {Object} validation - 验证结果
* @returns {string} 错误提示信息
*/
generateConfigErrorMessage(action, validation = null) {
const configPath = this.getConfigPath(action)
let message = `\n📧 DACP邮件服务配置缺失\n\n`
if (!validation) {
// 配置文件不存在
message += `❌ 配置文件不存在: ${configPath}\n\n`
message += `📝 请创建配置文件,内容如下:\n\n`
message += `{\n`
message += ` "provider": "gmail",\n`
message += ` "smtp": {\n`
message += ` "user": "your-email@gmail.com",\n`
message += ` "password": "your-app-password"\n`
message += ` },\n`
message += ` "sender": {\n`
message += ` "name": "Your Name",\n`
message += ` "email": "your-email@gmail.com"\n`
message += ` }\n`
message += `}\n\n`
message += `💡 支持的邮件服务商: gmail, outlook, qq, 163, 126\n\n`
message += `🔐 Gmail用户需要使用应用专用密码:\n`
message += ` 1. 进入 Google 账户设置\n`
message += ` 2. 启用两步验证\n`
message += ` 3. 生成应用专用密码\n`
message += ` 4. 使用生成的密码替换上面的 "your-app-password"\n`
} else {
// 配置不完整
message += `❌ 配置文件存在但不完整: ${configPath}\n\n`
message += `缺少以下配置项:\n`
validation.errors.forEach(error => {
message += `${error}\n`
})
message += `\n请检查并完善配置文件。\n`
}
return message
}
}
module.exports = DACPConfigManager