优化角色注册,发现,nuwa 角色的提示词等
This commit is contained in:
179
src/lib/core/resource/DPMLContentParser.js
Normal file
179
src/lib/core/resource/DPMLContentParser.js
Normal file
@ -0,0 +1,179 @@
|
||||
/**
|
||||
* DPML内容解析器
|
||||
* 统一处理DPML标签内的混合内容(@引用 + 直接内容)
|
||||
* 确保标签语义完整性
|
||||
*/
|
||||
class DPMLContentParser {
|
||||
/**
|
||||
* 解析DPML标签的完整语义内容
|
||||
* @param {string} content - 标签内的原始内容
|
||||
* @param {string} tagName - 标签名称
|
||||
* @returns {Object} 完整的语义结构
|
||||
*/
|
||||
parseTagContent(content, tagName) {
|
||||
if (!content || !content.trim()) {
|
||||
return {
|
||||
fullSemantics: '',
|
||||
references: [],
|
||||
directContent: '',
|
||||
metadata: {
|
||||
tagName,
|
||||
hasReferences: false,
|
||||
hasDirectContent: false,
|
||||
contentType: 'empty'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cleanContent = content.trim()
|
||||
const references = this.extractReferencesWithPosition(cleanContent)
|
||||
const directContent = this.extractDirectContent(cleanContent)
|
||||
|
||||
return {
|
||||
// 完整语义内容(用户看到的最终效果)
|
||||
fullSemantics: cleanContent,
|
||||
|
||||
// 引用部分(需要解析和加载的资源)
|
||||
references,
|
||||
|
||||
// 直接部分(用户原创内容)
|
||||
directContent,
|
||||
|
||||
// 元数据
|
||||
metadata: {
|
||||
tagName,
|
||||
hasReferences: references.length > 0,
|
||||
hasDirectContent: directContent.length > 0,
|
||||
contentType: this.determineContentType(cleanContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取所有@引用
|
||||
* @param {string} content - 内容
|
||||
* @returns {Array} 引用数组
|
||||
*/
|
||||
extractReferences(content) {
|
||||
// 使用新的位置信息方法,但保持向下兼容
|
||||
return this.extractReferencesWithPosition(content).map(ref => ({
|
||||
fullMatch: ref.fullMatch,
|
||||
priority: ref.priority,
|
||||
protocol: ref.protocol,
|
||||
resource: ref.resource,
|
||||
isRequired: ref.isRequired,
|
||||
isOptional: ref.isOptional
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增:获取引用的位置信息
|
||||
* @param {string} content - 内容
|
||||
* @returns {Array} 包含位置信息的引用数组
|
||||
*/
|
||||
extractReferencesWithPosition(content) {
|
||||
if (!content) {
|
||||
return []
|
||||
}
|
||||
|
||||
const resourceRegex = /@([!?]?)([a-zA-Z][a-zA-Z0-9_-]*):\/\/([a-zA-Z0-9_\/.,-]+?)(?=[\s\)\],]|$)/g
|
||||
const matches = []
|
||||
let match
|
||||
|
||||
while ((match = resourceRegex.exec(content)) !== null) {
|
||||
matches.push({
|
||||
fullMatch: match[0],
|
||||
priority: match[1],
|
||||
protocol: match[2],
|
||||
resource: match[3],
|
||||
position: match.index, // 位置信息
|
||||
isRequired: match[1] === '!',
|
||||
isOptional: match[1] === '?'
|
||||
})
|
||||
}
|
||||
|
||||
return matches.sort((a, b) => a.position - b.position) // 按位置排序
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取直接内容(移除@引用后的剩余内容)
|
||||
* @param {string} content - 内容
|
||||
* @returns {string} 直接内容
|
||||
*/
|
||||
extractDirectContent(content) {
|
||||
// 移除所有@引用行,保留其他内容
|
||||
const withoutReferences = content.replace(/^.*@[!?]?[a-zA-Z][a-zA-Z0-9_-]*:\/\/.*$/gm, '')
|
||||
|
||||
// 清理多余的空行
|
||||
const cleaned = withoutReferences.replace(/\n{3,}/g, '\n\n').trim()
|
||||
|
||||
return cleaned
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含引用
|
||||
* @param {string} content - 内容
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasReferences(content) {
|
||||
return /@[!?]?[a-zA-Z][a-zA-Z0-9_-]*:\/\//.test(content)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含直接内容
|
||||
* @param {string} content - 内容
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasDirectContent(content) {
|
||||
const withoutReferences = this.extractDirectContent(content)
|
||||
return withoutReferences.length > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定内容类型
|
||||
* @param {string} content - 内容
|
||||
* @returns {string} 内容类型
|
||||
*/
|
||||
determineContentType(content) {
|
||||
const hasRefs = this.hasReferences(content)
|
||||
const hasDirect = this.hasDirectContent(content)
|
||||
|
||||
if (hasRefs && hasDirect) return 'mixed'
|
||||
if (hasRefs) return 'references-only'
|
||||
if (hasDirect) return 'direct-only'
|
||||
return 'empty'
|
||||
}
|
||||
|
||||
/**
|
||||
* 从DPML文档中提取指定标签的内容
|
||||
* @param {string} dpmlContent - 完整的DPML文档内容
|
||||
* @param {string} tagName - 标签名称
|
||||
* @returns {string} 标签内容
|
||||
*/
|
||||
extractTagContent(dpmlContent, tagName) {
|
||||
const regex = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`, 'i')
|
||||
const match = dpmlContent.match(regex)
|
||||
return match ? match[1] : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析完整的DPML角色文档
|
||||
* @param {string} roleContent - 角色文档内容
|
||||
* @returns {Object} 解析后的角色语义结构
|
||||
*/
|
||||
parseRoleDocument(roleContent) {
|
||||
const dpmlTags = ['personality', 'principle', 'knowledge']
|
||||
const roleSemantics = {}
|
||||
|
||||
dpmlTags.forEach(tagName => {
|
||||
const tagContent = this.extractTagContent(roleContent, tagName)
|
||||
if (tagContent) {
|
||||
roleSemantics[tagName] = this.parseTagContent(tagContent, tagName)
|
||||
}
|
||||
})
|
||||
|
||||
return roleSemantics
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DPMLContentParser
|
||||
83
src/lib/core/resource/SemanticRenderer.js
Normal file
83
src/lib/core/resource/SemanticRenderer.js
Normal file
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* SemanticRenderer - DPML语义渲染器
|
||||
*
|
||||
* 核心理念:@引用 = 语义占位符
|
||||
* 在标签的原始位置插入引用内容,保持完整的语义流程
|
||||
*/
|
||||
class SemanticRenderer {
|
||||
/**
|
||||
* 语义占位符渲染:将@引用替换为实际内容
|
||||
* @param {Object} tagSemantics - 标签语义结构
|
||||
* @param {string} tagSemantics.fullSemantics - 完整的语义内容
|
||||
* @param {Array} tagSemantics.references - 引用列表
|
||||
* @param {ResourceManager} resourceManager - 资源管理器
|
||||
* @returns {string} 完整融合的语义内容
|
||||
*/
|
||||
async renderSemanticContent(tagSemantics, resourceManager) {
|
||||
if (!tagSemantics || !tagSemantics.fullSemantics) {
|
||||
return ''
|
||||
}
|
||||
|
||||
let content = tagSemantics.fullSemantics
|
||||
|
||||
if (!tagSemantics.references || tagSemantics.references.length === 0) {
|
||||
return content.trim()
|
||||
}
|
||||
|
||||
// 按出现顺序处理每个@引用(保持位置语义)
|
||||
// 需要按位置排序确保正确的替换顺序
|
||||
const sortedReferences = [...tagSemantics.references].sort((a, b) => a.position - b.position)
|
||||
|
||||
for (const ref of sortedReferences) {
|
||||
try {
|
||||
// 解析引用内容
|
||||
const result = await resourceManager.resolve(ref.fullMatch)
|
||||
|
||||
// 检查解析是否成功
|
||||
if (result.success) {
|
||||
// 提取标签内容(去掉外层DPML标签)
|
||||
const cleanContent = this.extractTagInnerContent(result.content, ref.protocol)
|
||||
// 用<reference>标签包装引用内容,标明这是占位符渲染
|
||||
const wrappedContent = `<reference protocol="${ref.protocol}" resource="${ref.resource}">\n${cleanContent}\n</reference>`
|
||||
// 在原始位置替换@引用为实际内容
|
||||
const refIndex = content.indexOf(ref.fullMatch)
|
||||
if (refIndex !== -1) {
|
||||
content = content.substring(0, refIndex) + wrappedContent + content.substring(refIndex + ref.fullMatch.length)
|
||||
} else {
|
||||
content = content.replace(ref.fullMatch, wrappedContent)
|
||||
}
|
||||
} else {
|
||||
// 解析失败时的优雅降级
|
||||
content = content.replace(ref.fullMatch, `<!-- 引用解析失败: ${ref.fullMatch} - ${result.error?.message || 'Unknown error'} -->`)
|
||||
}
|
||||
} catch (error) {
|
||||
// 引用解析失败时的优雅降级
|
||||
content = content.replace(ref.fullMatch, `<!-- 引用解析失败: ${ref.fullMatch} - ${error.message} -->`)
|
||||
}
|
||||
}
|
||||
|
||||
return content.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取DPML标签内的内容
|
||||
* @param {string} content - 包含DPML标签的完整内容
|
||||
* @param {string} protocol - 协议名称(thought, execution等)
|
||||
* @returns {string} 标签内的纯内容
|
||||
*/
|
||||
extractTagInnerContent(content, protocol) {
|
||||
// 根据协议类型确定标签名
|
||||
const tagName = protocol
|
||||
const regex = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`, 'i')
|
||||
const match = content.match(regex)
|
||||
|
||||
if (match && match[1]) {
|
||||
return match[1].trim()
|
||||
}
|
||||
|
||||
// 如果没有匹配到标签,返回原内容(可能已经是纯内容)
|
||||
return content.trim()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SemanticRenderer
|
||||
285
src/lib/core/resource/SimplifiedRoleDiscovery.js
Normal file
285
src/lib/core/resource/SimplifiedRoleDiscovery.js
Normal file
@ -0,0 +1,285 @@
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const logger = require('../../utils/logger')
|
||||
|
||||
/**
|
||||
* SimplifiedRoleDiscovery - 简化的角色发现算法
|
||||
*
|
||||
* 设计原则:
|
||||
* 1. 系统角色:完全依赖静态注册表,零动态扫描
|
||||
* 2. 用户角色:最小化文件系统操作,简单有效
|
||||
* 3. 统一接口:单一发现入口,无重复逻辑
|
||||
* 4. 跨平台安全:使用Node.js原生API,避免glob
|
||||
*/
|
||||
class SimplifiedRoleDiscovery {
|
||||
constructor() {
|
||||
this.USER_RESOURCE_DIR = '.promptx'
|
||||
this.RESOURCE_DOMAIN_PATH = ['resource', 'domain']
|
||||
}
|
||||
|
||||
/**
|
||||
* 发现所有角色(系统 + 用户)
|
||||
* @returns {Promise<Object>} 合并后的角色注册表
|
||||
*/
|
||||
async discoverAllRoles() {
|
||||
logger.debug('[SimplifiedRoleDiscovery] 开始发现所有角色...')
|
||||
try {
|
||||
// 并行加载,提升性能
|
||||
const [systemRoles, userRoles] = await Promise.all([
|
||||
this.loadSystemRoles(),
|
||||
this.discoverUserRoles()
|
||||
])
|
||||
|
||||
logger.debug('[SimplifiedRoleDiscovery] 系统角色数量:', Object.keys(systemRoles).length)
|
||||
logger.debug('[SimplifiedRoleDiscovery] 用户角色数量:', Object.keys(userRoles).length)
|
||||
logger.debug('[SimplifiedRoleDiscovery] 用户角色列表:', Object.keys(userRoles))
|
||||
|
||||
// 用户角色覆盖同名系统角色
|
||||
const mergedRoles = this.mergeRoles(systemRoles, userRoles)
|
||||
logger.debug('[SimplifiedRoleDiscovery] 合并后总角色数量:', Object.keys(mergedRoles).length)
|
||||
logger.debug('[SimplifiedRoleDiscovery] 最终角色列表:', Object.keys(mergedRoles))
|
||||
|
||||
return mergedRoles
|
||||
} catch (error) {
|
||||
logger.warn(`[SimplifiedRoleDiscovery] 角色发现失败: ${error.message}`)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载系统角色(零文件扫描)
|
||||
* @returns {Promise<Object>} 系统角色注册表
|
||||
*/
|
||||
async loadSystemRoles() {
|
||||
try {
|
||||
const registryPath = path.resolve(__dirname, '../../../resource.registry.json')
|
||||
|
||||
if (!await fs.pathExists(registryPath)) {
|
||||
console.warn('系统资源注册表文件不存在')
|
||||
return {}
|
||||
}
|
||||
|
||||
const registry = await fs.readJSON(registryPath)
|
||||
return registry.protocols?.role?.registry || {}
|
||||
} catch (error) {
|
||||
console.warn(`加载系统角色失败: ${error.message}`)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发现用户角色(最小化扫描)
|
||||
* @returns {Promise<Object>} 用户角色注册表
|
||||
*/
|
||||
async discoverUserRoles() {
|
||||
try {
|
||||
const userRolePath = await this.getUserRolePath()
|
||||
logger.debug('[SimplifiedRoleDiscovery] 用户角色路径:', userRolePath)
|
||||
|
||||
// 快速检查:目录不存在直接返回
|
||||
if (!await fs.pathExists(userRolePath)) {
|
||||
logger.debug('[SimplifiedRoleDiscovery] 用户角色目录不存在')
|
||||
return {}
|
||||
}
|
||||
|
||||
logger.debug('[SimplifiedRoleDiscovery] 开始扫描用户角色目录...')
|
||||
const result = await this.scanUserRolesOptimized(userRolePath)
|
||||
logger.debug('[SimplifiedRoleDiscovery] 用户角色扫描完成,发现角色:', Object.keys(result))
|
||||
return result
|
||||
} catch (error) {
|
||||
logger.warn(`[SimplifiedRoleDiscovery] 用户角色发现失败: ${error.message}`)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化的用户角色扫描算法
|
||||
* @param {string} basePath - 用户角色基础路径
|
||||
* @returns {Promise<Object>} 发现的用户角色
|
||||
*/
|
||||
async scanUserRolesOptimized(basePath) {
|
||||
const roles = {}
|
||||
|
||||
try {
|
||||
// 使用withFileTypes提升性能,一次读取获得文件类型
|
||||
const entries = await fs.readdir(basePath, { withFileTypes: true })
|
||||
|
||||
// 只处理目录,跳过文件
|
||||
const directories = entries.filter(entry => entry.isDirectory())
|
||||
|
||||
// 并行检查所有角色目录(性能优化)
|
||||
const rolePromises = directories.map(dir =>
|
||||
this.checkRoleDirectory(basePath, dir.name)
|
||||
)
|
||||
|
||||
const roleResults = await Promise.allSettled(rolePromises)
|
||||
|
||||
// 收集成功的角色
|
||||
roleResults.forEach((result, index) => {
|
||||
if (result.status === 'fulfilled' && result.value) {
|
||||
const roleName = directories[index].name
|
||||
roles[roleName] = result.value
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`扫描用户角色目录失败: ${error.message}`)
|
||||
}
|
||||
|
||||
return roles
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查单个角色目录
|
||||
* @param {string} basePath - 基础路径
|
||||
* @param {string} roleName - 角色名称
|
||||
* @returns {Promise<Object|null>} 角色信息或null
|
||||
*/
|
||||
async checkRoleDirectory(basePath, roleName) {
|
||||
logger.debug(`[SimplifiedRoleDiscovery] 检查角色目录: ${roleName}`)
|
||||
try {
|
||||
const roleDir = path.join(basePath, roleName)
|
||||
const roleFile = path.join(roleDir, `${roleName}.role.md`)
|
||||
logger.debug(`[SimplifiedRoleDiscovery] 角色文件路径: ${roleFile}`)
|
||||
|
||||
// 核心检查:主角色文件必须存在
|
||||
const fileExists = await fs.pathExists(roleFile)
|
||||
logger.debug(`[SimplifiedRoleDiscovery] 角色文件${roleName}是否存在: ${fileExists}`)
|
||||
|
||||
if (!fileExists) {
|
||||
logger.debug(`[SimplifiedRoleDiscovery] 角色${roleName}文件不存在,跳过`)
|
||||
return null
|
||||
}
|
||||
|
||||
// 简化验证:只检查基础DPML标签
|
||||
logger.debug(`[SimplifiedRoleDiscovery] 读取角色文件内容: ${roleName}`)
|
||||
const content = await fs.readFile(roleFile, 'utf8')
|
||||
const isValid = this.isValidRoleFile(content)
|
||||
logger.debug(`[SimplifiedRoleDiscovery] 角色${roleName}内容验证: ${isValid}`)
|
||||
|
||||
if (!isValid) {
|
||||
logger.debug(`[SimplifiedRoleDiscovery] 角色${roleName}内容格式无效,跳过`)
|
||||
return null
|
||||
}
|
||||
|
||||
// 返回角色信息(简化元数据)
|
||||
const roleInfo = {
|
||||
file: roleFile,
|
||||
name: this.extractRoleName(content) || roleName,
|
||||
description: this.extractDescription(content) || `${roleName}专业角色`,
|
||||
source: 'user-generated'
|
||||
}
|
||||
|
||||
logger.debug(`[SimplifiedRoleDiscovery] 角色${roleName}检查成功:`, roleInfo.name)
|
||||
return roleInfo
|
||||
|
||||
} catch (error) {
|
||||
// 单个角色失败不影响其他角色
|
||||
logger.warn(`[SimplifiedRoleDiscovery] 角色${roleName}检查失败: ${error.message}`)
|
||||
logger.debug(`[SimplifiedRoleDiscovery] 错误堆栈:`, error.stack)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化的DPML验证(只检查关键标签)
|
||||
* @param {string} content - 文件内容
|
||||
* @returns {boolean} 是否为有效角色文件
|
||||
*/
|
||||
isValidRoleFile(content) {
|
||||
if (!content || typeof content !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
const trimmedContent = content.trim()
|
||||
if (trimmedContent.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
return trimmedContent.includes('<role>') && trimmedContent.includes('</role>')
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化的角色名称提取
|
||||
* @param {string} content - 文件内容
|
||||
* @returns {string|null} 提取的角色名称
|
||||
*/
|
||||
extractRoleName(content) {
|
||||
if (!content) return null
|
||||
|
||||
// 提取Markdown标题
|
||||
const match = content.match(/^#\s*(.+)$/m)
|
||||
return match ? match[1].trim() : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化的描述提取
|
||||
* @param {string} content - 文件内容
|
||||
* @returns {string|null} 提取的描述
|
||||
*/
|
||||
extractDescription(content) {
|
||||
if (!content) return null
|
||||
|
||||
// 提取Markdown引用(描述)
|
||||
const match = content.match(/^>\s*(.+)$/m)
|
||||
return match ? match[1].trim() : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并角色(用户优先)
|
||||
* @param {Object} systemRoles - 系统角色
|
||||
* @param {Object} userRoles - 用户角色
|
||||
* @returns {Object} 合并后的角色注册表
|
||||
*/
|
||||
mergeRoles(systemRoles, userRoles) {
|
||||
if (!systemRoles || typeof systemRoles !== 'object') {
|
||||
systemRoles = {}
|
||||
}
|
||||
|
||||
if (!userRoles || typeof userRoles !== 'object') {
|
||||
userRoles = {}
|
||||
}
|
||||
|
||||
return {
|
||||
...systemRoles, // 系统角色作为基础
|
||||
...userRoles // 用户角色覆盖同名系统角色
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户角色路径
|
||||
* @returns {Promise<string>} 用户角色目录路径
|
||||
*/
|
||||
async getUserRolePath() {
|
||||
const projectRoot = await this.findProjectRoot()
|
||||
return path.join(projectRoot, this.USER_RESOURCE_DIR, ...this.RESOURCE_DOMAIN_PATH)
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化的项目根目录查找
|
||||
* @returns {Promise<string>} 项目根目录路径
|
||||
*/
|
||||
async findProjectRoot() {
|
||||
let currentDir = process.cwd()
|
||||
|
||||
// 向上查找包含package.json的目录
|
||||
while (currentDir !== path.dirname(currentDir)) {
|
||||
const packageJsonPath = path.join(currentDir, 'package.json')
|
||||
|
||||
try {
|
||||
if (await fs.pathExists(packageJsonPath)) {
|
||||
return currentDir
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略权限错误,继续向上查找
|
||||
}
|
||||
|
||||
currentDir = path.dirname(currentDir)
|
||||
}
|
||||
|
||||
// 如果没找到package.json,返回当前工作目录
|
||||
return process.cwd()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SimplifiedRoleDiscovery
|
||||
Reference in New Issue
Block a user