refactor: 重构整个资源引用协议
This commit is contained in:
@ -2,7 +2,7 @@ const BasePouchCommand = require('../BasePouchCommand')
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const { buildCommand } = require('../../../../constants')
|
||||
const SimplifiedRoleDiscovery = require('../../resource/SimplifiedRoleDiscovery')
|
||||
const ResourceManager = require('../../resource/resourceManager')
|
||||
const logger = require('../../../utils/logger')
|
||||
|
||||
/**
|
||||
@ -12,8 +12,8 @@ const logger = require('../../../utils/logger')
|
||||
class HelloCommand extends BasePouchCommand {
|
||||
constructor () {
|
||||
super()
|
||||
// 移除roleRegistry缓存,改为每次实时扫描
|
||||
this.discovery = new SimplifiedRoleDiscovery()
|
||||
// 使用新的ResourceManager架构替代SimplifiedRoleDiscovery
|
||||
this.resourceManager = new ResourceManager()
|
||||
}
|
||||
|
||||
getPurpose () {
|
||||
@ -21,25 +21,41 @@ class HelloCommand extends BasePouchCommand {
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态加载角色注册表 - 使用SimplifiedRoleDiscovery
|
||||
* 动态加载角色注册表 - 使用新的ResourceManager架构
|
||||
* 移除缓存机制,每次都实时扫描,确保角色发现的一致性
|
||||
*/
|
||||
async loadRoleRegistry () {
|
||||
// 移除缓存检查,每次都实时扫描
|
||||
// 原因:1) 客户端应用,action频次不高 2) 避免新角色创建后的状态不一致问题
|
||||
|
||||
try {
|
||||
// 使用新的SimplifiedRoleDiscovery算法
|
||||
const allRoles = await this.discovery.discoverAllRoles()
|
||||
// 使用新的ResourceManager架构初始化
|
||||
await this.resourceManager.initializeWithNewArchitecture()
|
||||
|
||||
// 转换为HelloCommand期望的格式,不缓存
|
||||
// 获取所有角色相关的资源
|
||||
const roleRegistry = {}
|
||||
for (const [roleId, roleInfo] of Object.entries(allRoles)) {
|
||||
roleRegistry[roleId] = {
|
||||
file: roleInfo.file,
|
||||
name: roleInfo.name || roleId,
|
||||
description: this.extractDescription(roleInfo) || `${roleInfo.name || roleId}专业角色`,
|
||||
source: roleInfo.source || 'unknown'
|
||||
|
||||
// 从ResourceRegistry中获取所有role:开头的资源
|
||||
const registry = this.resourceManager.registry
|
||||
for (const [resourceId, reference] of registry.index) {
|
||||
if (resourceId.startsWith('role:')) {
|
||||
const roleId = resourceId.substring(5) // 移除 'role:' 前缀
|
||||
|
||||
try {
|
||||
// 尝试加载角色内容以提取元数据
|
||||
const result = await this.resourceManager.loadResource(resourceId)
|
||||
if (result.success) {
|
||||
const name = this.extractRoleNameFromContent(result.content) || roleId
|
||||
const description = this.extractDescriptionFromContent(result.content) || `${name}专业角色`
|
||||
|
||||
roleRegistry[roleId] = {
|
||||
file: reference,
|
||||
name,
|
||||
description,
|
||||
source: reference.startsWith('@package://') ? 'system' : 'user-generated'
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 单个角色加载失败不影响其他角色
|
||||
logger.warn(`角色${roleId}加载失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +86,37 @@ class HelloCommand extends BasePouchCommand {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从角色信息中提取描述
|
||||
* 从角色内容中提取角色名称
|
||||
* @param {string} content - 角色文件内容
|
||||
* @returns {string|null} 角色名称
|
||||
*/
|
||||
extractRoleNameFromContent(content) {
|
||||
if (!content || typeof content !== 'string') {
|
||||
return null
|
||||
}
|
||||
|
||||
// 提取Markdown标题
|
||||
const match = content.match(/^#\s*(.+)$/m)
|
||||
return match ? match[1].trim() : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 从角色内容中提取描述
|
||||
* @param {string} content - 角色文件内容
|
||||
* @returns {string|null} 角色描述
|
||||
*/
|
||||
extractDescriptionFromContent(content) {
|
||||
if (!content || typeof content !== 'string') {
|
||||
return null
|
||||
}
|
||||
|
||||
// 提取Markdown引用(描述)
|
||||
const match = content.match(/^>\s*(.+)$/m)
|
||||
return match ? match[1].trim() : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 从角色信息中提取描述(保持向后兼容)
|
||||
* @param {Object} roleInfo - 角色信息对象
|
||||
* @returns {string} 角色描述
|
||||
*/
|
||||
@ -245,6 +291,36 @@ ${buildCommand.action(allRoles[0]?.id || 'assistant')}
|
||||
* 现在使用SimplifiedRoleDiscovery.discoverAllRoles()替代
|
||||
* 这避免了glob依赖和跨平台兼容性问题
|
||||
*/
|
||||
|
||||
/**
|
||||
* 调试方法:打印所有注册的资源
|
||||
*/
|
||||
async debugRegistry() {
|
||||
await this.loadRoleRegistry()
|
||||
|
||||
console.log('\n🔍 HelloCommand - 注册表调试信息')
|
||||
console.log('='.repeat(50))
|
||||
|
||||
if (this.roleRegistry && Object.keys(this.roleRegistry).length > 0) {
|
||||
console.log(`📊 发现 ${Object.keys(this.roleRegistry).length} 个角色资源:\n`)
|
||||
|
||||
Object.entries(this.roleRegistry).forEach(([id, roleInfo]) => {
|
||||
console.log(`🎭 ${id}`)
|
||||
console.log(` 名称: ${roleInfo.name || '未命名'}`)
|
||||
console.log(` 描述: ${roleInfo.description || '无描述'}`)
|
||||
console.log(` 文件: ${roleInfo.file}`)
|
||||
console.log(` 来源: ${roleInfo.source || '未知'}`)
|
||||
console.log('')
|
||||
})
|
||||
} else {
|
||||
console.log('🔍 没有发现任何角色资源')
|
||||
}
|
||||
|
||||
// 同时显示ResourceManager的注册表
|
||||
console.log('\n📋 ResourceManager 注册表:')
|
||||
console.log('-'.repeat(30))
|
||||
this.resourceManager.registry.printAll('底层资源注册表')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HelloCommand
|
||||
|
||||
@ -1,108 +0,0 @@
|
||||
const path = require('path')
|
||||
const { glob } = require('glob')
|
||||
|
||||
class ResourceDiscovery {
|
||||
constructor() {
|
||||
this.__dirname = __dirname
|
||||
}
|
||||
|
||||
async discoverResources(scanPaths) {
|
||||
const discovered = []
|
||||
|
||||
for (const basePath of scanPaths) {
|
||||
// Discover role files
|
||||
const roleFiles = await glob(`${basePath}/**/*.role.md`)
|
||||
for (const file of roleFiles) {
|
||||
discovered.push({
|
||||
id: `role:${this.extractId(file, '.role.md')}`,
|
||||
reference: this.generateReference(file)
|
||||
})
|
||||
}
|
||||
|
||||
// Discover execution mode files
|
||||
const execFiles = await glob(`${basePath}/**/execution/*.execution.md`)
|
||||
for (const file of execFiles) {
|
||||
discovered.push({
|
||||
id: `execution:${this.extractId(file, '.execution.md')}`,
|
||||
reference: this.generateReference(file)
|
||||
})
|
||||
}
|
||||
|
||||
// Discover thought mode files
|
||||
const thoughtFiles = await glob(`${basePath}/**/thought/*.thought.md`)
|
||||
for (const file of thoughtFiles) {
|
||||
discovered.push({
|
||||
id: `thought:${this.extractId(file, '.thought.md')}`,
|
||||
reference: this.generateReference(file)
|
||||
})
|
||||
}
|
||||
|
||||
// Discover knowledge files
|
||||
const knowledgeFiles = await glob(`${basePath}/**/knowledge/*.knowledge.md`)
|
||||
for (const file of knowledgeFiles) {
|
||||
discovered.push({
|
||||
id: `knowledge:${this.extractId(file, '.knowledge.md')}`,
|
||||
reference: this.generateReference(file)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return discovered
|
||||
}
|
||||
|
||||
extractId(filePath, suffix) {
|
||||
return path.basename(filePath, suffix)
|
||||
}
|
||||
|
||||
generateReference(filePath) {
|
||||
// Protocol detection rules based on file path patterns
|
||||
if (filePath.includes('node_modules/promptx')) {
|
||||
// Find the node_modules/promptx part and get relative path after it
|
||||
const promptxIndex = filePath.indexOf('node_modules/promptx')
|
||||
const afterPromptx = filePath.substring(promptxIndex + 'node_modules/promptx/'.length)
|
||||
return `@package://${afterPromptx}`
|
||||
} else if (filePath.includes('.promptx')) {
|
||||
const relativePath = path.relative(process.cwd(), filePath)
|
||||
return `@project://${relativePath}`
|
||||
} else {
|
||||
// Check if it's a package file (contains '/prompt/' and matches package root)
|
||||
const packageRoot = this.findPackageRoot()
|
||||
if (filePath.startsWith(packageRoot + '/prompt') || filePath.includes('/prompt/')) {
|
||||
const promptIndex = filePath.indexOf('/prompt/')
|
||||
if (promptIndex >= 0) {
|
||||
const afterPrompt = filePath.substring(promptIndex + 1) // Keep the 'prompt/' part
|
||||
return `@package://${afterPrompt}`
|
||||
}
|
||||
}
|
||||
return `@file://${filePath}`
|
||||
}
|
||||
}
|
||||
|
||||
findPackageRoot() {
|
||||
// Return the mocked package root for testing
|
||||
if (this.__dirname.includes('/mock/')) {
|
||||
return '/mock/package/root'
|
||||
}
|
||||
|
||||
// Simple implementation: find the package root directory
|
||||
let dir = this.__dirname
|
||||
while (dir !== '/' && dir !== '') {
|
||||
// Look for the package root containing prompt/ directory
|
||||
if (path.basename(dir) === 'src' || path.basename(path.dirname(dir)) === 'src') {
|
||||
return path.dirname(dir)
|
||||
}
|
||||
dir = path.dirname(dir)
|
||||
}
|
||||
|
||||
// Fallback: return directory that contains this file structure
|
||||
const segments = this.__dirname.split(path.sep)
|
||||
const srcIndex = segments.findIndex(seg => seg === 'src')
|
||||
if (srcIndex > 0) {
|
||||
return segments.slice(0, srcIndex).join(path.sep)
|
||||
}
|
||||
|
||||
return this.__dirname
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ResourceDiscovery
|
||||
@ -1,285 +0,0 @@
|
||||
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
|
||||
@ -14,14 +14,14 @@ class CrossPlatformFileScanner {
|
||||
* @param {Object} options - 扫描选项
|
||||
* @param {Array<string>} options.extensions - 文件扩展名列表,如 ['.role.md', '.execution.md']
|
||||
* @param {Array<string>} options.subdirs - 限制扫描的子目录,如 ['domain', 'execution']
|
||||
* @param {number} options.maxDepth - 最大扫描深度,默认10
|
||||
* @param {number} options.maxDepth - 最大扫描深度,默认5
|
||||
* @returns {Promise<Array<string>>} 匹配的文件路径列表
|
||||
*/
|
||||
async scanFiles(baseDir, options = {}) {
|
||||
const {
|
||||
extensions = [],
|
||||
subdirs = null,
|
||||
maxDepth = 10
|
||||
maxDepth = 5
|
||||
} = options
|
||||
|
||||
if (!await fs.pathExists(baseDir)) {
|
||||
@ -43,15 +43,19 @@ class CrossPlatformFileScanner {
|
||||
const resourceConfig = {
|
||||
role: {
|
||||
extensions: ['.role.md'],
|
||||
subdirs: ['domain'] // 角色文件通常在domain目录下
|
||||
subdirs: null // 不限制子目录,在所有地方查找role文件
|
||||
},
|
||||
execution: {
|
||||
extensions: ['.execution.md'],
|
||||
subdirs: ['execution'] // 执行模式文件在execution目录下
|
||||
subdirs: null // 不限制子目录,在所有地方查找execution文件
|
||||
},
|
||||
thought: {
|
||||
extensions: ['.thought.md'],
|
||||
subdirs: ['thought'] // 思维模式文件在thought目录下
|
||||
subdirs: null // 不限制子目录,在所有地方查找thought文件
|
||||
},
|
||||
knowledge: {
|
||||
extensions: ['.knowledge.md'],
|
||||
subdirs: null // 不限制子目录,在所有地方查找knowledge文件
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -82,6 +82,82 @@ class DiscoveryManager {
|
||||
return allResources
|
||||
}
|
||||
|
||||
/**
|
||||
* 发现并合并所有注册表(新架构方法)
|
||||
* @returns {Promise<Map>} 合并后的资源注册表 Map<resourceId, reference>
|
||||
*/
|
||||
async discoverRegistries() {
|
||||
const registryPromises = this.discoveries.map(async (discovery) => {
|
||||
try {
|
||||
// 优先使用新的discoverRegistry方法
|
||||
if (typeof discovery.discoverRegistry === 'function') {
|
||||
const registry = await discovery.discoverRegistry()
|
||||
return registry instanceof Map ? registry : new Map()
|
||||
} else {
|
||||
// 向后兼容:将discover()结果转换为注册表格式
|
||||
const resources = await discovery.discover()
|
||||
const registry = new Map()
|
||||
if (Array.isArray(resources)) {
|
||||
resources.forEach(resource => {
|
||||
if (resource.id && resource.reference) {
|
||||
registry.set(resource.id, resource.reference)
|
||||
}
|
||||
})
|
||||
}
|
||||
return registry
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[DiscoveryManager] ${discovery.source} registry discovery failed: ${error.message}`)
|
||||
return new Map()
|
||||
}
|
||||
})
|
||||
|
||||
// 并行执行所有发现器
|
||||
const registryResults = await Promise.allSettled(registryPromises)
|
||||
|
||||
// 收集所有成功的注册表
|
||||
const registries = []
|
||||
registryResults.forEach((result, index) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
registries.push(result.value)
|
||||
} else {
|
||||
console.warn(`[DiscoveryManager] ${this.discoveries[index].source} registry discovery rejected: ${result.reason}`)
|
||||
registries.push(new Map())
|
||||
}
|
||||
})
|
||||
|
||||
// 按发现器优先级合并注册表
|
||||
return this._mergeRegistries(registries)
|
||||
}
|
||||
|
||||
/**
|
||||
* 按源类型发现注册表
|
||||
* @param {string} source - 发现器源类型
|
||||
* @returns {Promise<Map>} 指定源的资源注册表
|
||||
*/
|
||||
async discoverRegistryBySource(source) {
|
||||
const discovery = this._findDiscoveryBySource(source)
|
||||
if (!discovery) {
|
||||
throw new Error(`Discovery source ${source} not found`)
|
||||
}
|
||||
|
||||
if (typeof discovery.discoverRegistry === 'function') {
|
||||
return await discovery.discoverRegistry()
|
||||
} else {
|
||||
// 向后兼容:将discover()结果转换为注册表格式
|
||||
const resources = await discovery.discover()
|
||||
const registry = new Map()
|
||||
if (Array.isArray(resources)) {
|
||||
resources.forEach(resource => {
|
||||
if (resource.id && resource.reference) {
|
||||
registry.set(resource.id, resource.reference)
|
||||
}
|
||||
})
|
||||
}
|
||||
return registry
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按源类型发现资源
|
||||
* @param {string} source - 发现器源类型
|
||||
@ -133,6 +209,30 @@ class DiscoveryManager {
|
||||
return this.discoveries.length
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并多个注册表
|
||||
* @param {Array<Map>} registries - 注册表数组,按优先级排序(数字越小优先级越高)
|
||||
* @returns {Map} 合并后的注册表
|
||||
* @private
|
||||
*/
|
||||
_mergeRegistries(registries) {
|
||||
const mergedRegistry = new Map()
|
||||
|
||||
// 从后往前合并:先添加低优先级的,再让高优先级的覆盖
|
||||
// registries按优先级升序排列 [high(0), med(1), low(2)]
|
||||
// 我们从低优先级开始,让高优先级的覆盖
|
||||
for (let i = registries.length - 1; i >= 0; i--) {
|
||||
const registry = registries[i]
|
||||
if (registry instanceof Map) {
|
||||
for (const [key, value] of registry) {
|
||||
mergedRegistry.set(key, value) // 直接设置,让高优先级的最终覆盖
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mergedRegistry
|
||||
}
|
||||
|
||||
/**
|
||||
* 按优先级排序发现器
|
||||
* @private
|
||||
|
||||
@ -19,22 +19,18 @@ class PackageDiscovery extends BaseDiscovery {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发现包级资源
|
||||
* 发现包级资源 (新架构 - 纯动态扫描)
|
||||
* @returns {Promise<Array>} 发现的资源列表
|
||||
*/
|
||||
async discover() {
|
||||
const resources = []
|
||||
|
||||
try {
|
||||
// 1. 加载静态注册表资源
|
||||
const registryResources = await this._loadStaticRegistryResources()
|
||||
resources.push(...registryResources)
|
||||
|
||||
// 2. 扫描prompt目录资源
|
||||
// 扫描prompt目录资源(新架构只使用动态扫描)
|
||||
const scanResources = await this._scanPromptDirectory()
|
||||
resources.push(...scanResources)
|
||||
|
||||
// 3. 规范化所有资源
|
||||
// 规范化所有资源
|
||||
return resources.map(resource => this.normalizeResource(resource))
|
||||
|
||||
} catch (error) {
|
||||
@ -44,64 +40,44 @@ class PackageDiscovery extends BaseDiscovery {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从静态注册表加载资源
|
||||
* @returns {Promise<Array>} 注册表中的资源列表
|
||||
* 发现包级资源注册表 (新架构 - 纯动态扫描)
|
||||
* @returns {Promise<Map>} 发现的资源注册表 Map<resourceId, reference>
|
||||
*/
|
||||
async _loadStaticRegistryResources() {
|
||||
async discoverRegistry() {
|
||||
try {
|
||||
const registry = await this._loadStaticRegistry()
|
||||
const resources = []
|
||||
// 扫描动态资源(新架构只使用动态扫描)
|
||||
const scanResults = await this._scanPromptDirectory()
|
||||
const registry = this._buildRegistryFromScanResults(scanResults)
|
||||
|
||||
if (registry.protocols) {
|
||||
// 遍历所有协议
|
||||
for (const [protocol, protocolInfo] of Object.entries(registry.protocols)) {
|
||||
if (protocolInfo.registry) {
|
||||
// 遍历协议下的所有资源
|
||||
for (const [resourceId, resourceInfo] of Object.entries(protocolInfo.registry)) {
|
||||
const reference = typeof resourceInfo === 'string'
|
||||
? resourceInfo
|
||||
: resourceInfo.file
|
||||
return registry
|
||||
|
||||
if (reference) {
|
||||
resources.push({
|
||||
id: `${protocol}:${resourceId}`,
|
||||
reference: reference
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resources
|
||||
} catch (error) {
|
||||
console.warn(`[PackageDiscovery] Failed to load static registry: ${error.message}`)
|
||||
return []
|
||||
console.warn(`[PackageDiscovery] Registry discovery failed: ${error.message}`)
|
||||
return new Map()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 加载静态注册表文件
|
||||
* @returns {Promise<Object>} 注册表内容
|
||||
* 从扫描结果构建Map
|
||||
* @param {Array} scanResults - 扫描结果数组
|
||||
* @returns {Map} 资源注册表 Map<resourceId, reference>
|
||||
*/
|
||||
async _loadStaticRegistry() {
|
||||
const packageRoot = await this._findPackageRoot()
|
||||
|
||||
// 尝试主要路径:src/resource.registry.json
|
||||
const primaryPath = path.join(packageRoot, 'src', 'resource.registry.json')
|
||||
if (await fs.pathExists(primaryPath)) {
|
||||
return await fs.readJSON(primaryPath)
|
||||
_buildRegistryFromScanResults(scanResults) {
|
||||
const registry = new Map()
|
||||
|
||||
for (const resource of scanResults) {
|
||||
if (resource.id && resource.reference) {
|
||||
registry.set(resource.id, resource.reference)
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试后备路径:resource.registry.json
|
||||
const alternativePath = path.join(packageRoot, 'resource.registry.json')
|
||||
if (await fs.pathExists(alternativePath)) {
|
||||
return await fs.readJSON(alternativePath)
|
||||
}
|
||||
|
||||
throw new Error('Static registry file not found')
|
||||
return registry
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 扫描prompt目录发现资源
|
||||
* @returns {Promise<Array>} 扫描发现的资源列表
|
||||
@ -118,7 +94,7 @@ class PackageDiscovery extends BaseDiscovery {
|
||||
const resources = []
|
||||
|
||||
// 定义要扫描的资源类型
|
||||
const resourceTypes = ['role', 'execution', 'thought']
|
||||
const resourceTypes = ['role', 'execution', 'thought', 'knowledge']
|
||||
|
||||
// 并行扫描所有资源类型
|
||||
for (const resourceType of resourceTypes) {
|
||||
|
||||
@ -45,6 +45,50 @@ class ProjectDiscovery extends BaseDiscovery {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发现项目级资源注册表 (新架构方法)
|
||||
* @returns {Promise<Map>} 发现的资源注册表 Map<resourceId, reference>
|
||||
*/
|
||||
async discoverRegistry() {
|
||||
try {
|
||||
// 1. 查找项目根目录
|
||||
const projectRoot = await this._findProjectRoot()
|
||||
|
||||
// 2. 检查.promptx目录是否存在
|
||||
const hasPrompxDir = await this._checkPrompxDirectory(projectRoot)
|
||||
if (!hasPrompxDir) {
|
||||
return new Map()
|
||||
}
|
||||
|
||||
// 3. 扫描项目资源
|
||||
const resources = await this._scanProjectResources(projectRoot)
|
||||
|
||||
// 4. 构建注册表
|
||||
return this._buildRegistryFromResources(resources)
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[ProjectDiscovery] Registry discovery failed: ${error.message}`)
|
||||
return new Map()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从资源列表构建注册表
|
||||
* @param {Array} resources - 资源列表
|
||||
* @returns {Map} 资源注册表 Map<resourceId, reference>
|
||||
*/
|
||||
_buildRegistryFromResources(resources) {
|
||||
const registry = new Map()
|
||||
|
||||
for (const resource of resources) {
|
||||
if (resource.id && resource.reference) {
|
||||
registry.set(resource.id, resource.reference)
|
||||
}
|
||||
}
|
||||
|
||||
return registry
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找项目根目录
|
||||
* @returns {Promise<string>} 项目根目录路径
|
||||
@ -97,7 +141,7 @@ class ProjectDiscovery extends BaseDiscovery {
|
||||
const resources = []
|
||||
|
||||
// 定义要扫描的资源类型
|
||||
const resourceTypes = ['role', 'execution', 'thought']
|
||||
const resourceTypes = ['role', 'execution', 'thought', 'knowledge']
|
||||
|
||||
// 并行扫描所有资源类型
|
||||
for (const resourceType of resourceTypes) {
|
||||
@ -187,6 +231,10 @@ class ProjectDiscovery extends BaseDiscovery {
|
||||
return trimmedContent.includes('<execution>') && trimmedContent.includes('</execution>')
|
||||
case 'thought':
|
||||
return trimmedContent.includes('<thought>') && trimmedContent.includes('</thought>')
|
||||
case 'knowledge':
|
||||
// knowledge类型比较灵活,只要文件有内容就认为是有效的
|
||||
// 可以是纯文本、链接、图片等任何形式的知识内容
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
@ -10,6 +10,14 @@ class ExecutionProtocol extends ResourceProtocol {
|
||||
constructor () {
|
||||
super('execution')
|
||||
this.registry = {}
|
||||
this.registryManager = null // 统一注册表管理器
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册表管理器
|
||||
*/
|
||||
setRegistryManager(manager) {
|
||||
this.registryManager = manager
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,7 +48,36 @@ class ExecutionProtocol extends ResourceProtocol {
|
||||
*/
|
||||
async resolvePath (resourcePath, queryParams) {
|
||||
const executionId = resourcePath.trim()
|
||||
const fullResourceId = `execution:${executionId}`
|
||||
|
||||
// 优先使用统一注册表管理器
|
||||
if (this.registryManager) {
|
||||
const reference = this.registryManager.registry.get(fullResourceId)
|
||||
if (!reference) {
|
||||
const availableExecutions = this.registryManager.registry.keys()
|
||||
.filter(id => id.startsWith('execution:'))
|
||||
.map(id => id.replace('execution:', ''))
|
||||
throw new Error(`执行模式 "${executionId}" 未在注册表中找到。可用执行模式:${availableExecutions.join(', ')}`)
|
||||
}
|
||||
|
||||
let resolvedPath = reference
|
||||
|
||||
// 处理 @package:// 前缀
|
||||
if (resolvedPath.startsWith('@package://')) {
|
||||
const PackageProtocol = require('./PackageProtocol')
|
||||
const packageProtocol = new PackageProtocol()
|
||||
const relativePath = resolvedPath.replace('@package://', '')
|
||||
resolvedPath = await packageProtocol.resolvePath(relativePath)
|
||||
} else if (resolvedPath.startsWith('@project://')) {
|
||||
// 处理 @project:// 前缀,转换为绝对路径
|
||||
const relativePath = resolvedPath.replace('@project://', '')
|
||||
resolvedPath = path.join(process.cwd(), relativePath)
|
||||
}
|
||||
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
// 向后兼容:使用旧的registry
|
||||
if (!this.registry[executionId]) {
|
||||
throw new Error(`执行模式 "${executionId}" 未在注册表中找到`)
|
||||
}
|
||||
|
||||
121
src/lib/core/resource/protocols/KnowledgeProtocol.js
Normal file
121
src/lib/core/resource/protocols/KnowledgeProtocol.js
Normal file
@ -0,0 +1,121 @@
|
||||
const ResourceProtocol = require('./ResourceProtocol')
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
|
||||
/**
|
||||
* 知识资源协议处理器
|
||||
* 处理 knowledge:// 协议的资源解析
|
||||
*/
|
||||
class KnowledgeProtocol extends ResourceProtocol {
|
||||
constructor () {
|
||||
super('knowledge')
|
||||
this.registry = {}
|
||||
this.registryManager = null // 统一注册表管理器
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册表管理器
|
||||
*/
|
||||
setRegistryManager(manager) {
|
||||
this.registryManager = manager
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册表
|
||||
*/
|
||||
setRegistry (registry) {
|
||||
this.registry = registry || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议信息
|
||||
*/
|
||||
getProtocolInfo () {
|
||||
return {
|
||||
name: 'knowledge',
|
||||
description: '知识资源协议',
|
||||
location: 'knowledge://{knowledge_id}',
|
||||
examples: [
|
||||
'knowledge://xiaohongshu-marketing',
|
||||
'knowledge://ai-tools-guide'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析资源路径
|
||||
*/
|
||||
async resolvePath (resourcePath, queryParams) {
|
||||
const knowledgeId = resourcePath.trim()
|
||||
const fullResourceId = `knowledge:${knowledgeId}`
|
||||
|
||||
// 优先使用统一注册表管理器
|
||||
if (this.registryManager) {
|
||||
const reference = this.registryManager.registry.get(fullResourceId)
|
||||
if (!reference) {
|
||||
const availableKnowledge = this.registryManager.registry.keys()
|
||||
.filter(id => id.startsWith('knowledge:'))
|
||||
.map(id => id.replace('knowledge:', ''))
|
||||
throw new Error(`知识资源 "${knowledgeId}" 未在注册表中找到。可用知识资源:${availableKnowledge.join(', ')}`)
|
||||
}
|
||||
|
||||
let resolvedPath = reference
|
||||
|
||||
// 处理 @package:// 前缀
|
||||
if (resolvedPath.startsWith('@package://')) {
|
||||
const PackageProtocol = require('./PackageProtocol')
|
||||
const packageProtocol = new PackageProtocol()
|
||||
const relativePath = resolvedPath.replace('@package://', '')
|
||||
resolvedPath = await packageProtocol.resolvePath(relativePath)
|
||||
} else if (resolvedPath.startsWith('@project://')) {
|
||||
// 处理 @project:// 前缀,转换为绝对路径
|
||||
const relativePath = resolvedPath.replace('@project://', '')
|
||||
resolvedPath = path.join(process.cwd(), relativePath)
|
||||
}
|
||||
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
// 向后兼容:使用旧的registry
|
||||
if (!this.registry[knowledgeId]) {
|
||||
throw new Error(`知识资源 "${knowledgeId}" 未在注册表中找到`)
|
||||
}
|
||||
|
||||
let resolvedPath = this.registry[knowledgeId]
|
||||
|
||||
// 处理 @package:// 前缀
|
||||
if (resolvedPath.startsWith('@package://')) {
|
||||
const PackageProtocol = require('./PackageProtocol')
|
||||
const packageProtocol = new PackageProtocol()
|
||||
const relativePath = resolvedPath.replace('@package://', '')
|
||||
resolvedPath = await packageProtocol.resolvePath(relativePath)
|
||||
} else if (resolvedPath.startsWith('@project://')) {
|
||||
// 处理 @project:// 前缀,转换为绝对路径
|
||||
const relativePath = resolvedPath.replace('@project://', '')
|
||||
resolvedPath = path.join(process.cwd(), relativePath)
|
||||
}
|
||||
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载资源内容
|
||||
*/
|
||||
async loadContent (resolvedPath, queryParams) {
|
||||
try {
|
||||
const content = await fs.readFile(resolvedPath, 'utf-8')
|
||||
return content
|
||||
} catch (error) {
|
||||
throw new Error(`无法加载知识资源文件 ${resolvedPath}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证资源路径
|
||||
*/
|
||||
validatePath (resourcePath) {
|
||||
return /^[a-zA-Z0-9_-]+$/.test(resourcePath)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = KnowledgeProtocol
|
||||
@ -258,12 +258,17 @@ class PackageProtocol extends ResourceProtocol {
|
||||
findPackageJson (startPath = __dirname) {
|
||||
let currentPath = path.resolve(startPath)
|
||||
|
||||
while (currentPath !== path.parse(currentPath).root) {
|
||||
let maxIterations = 50 // Prevent infinite loops
|
||||
while (currentPath !== path.parse(currentPath).root && maxIterations-- > 0) {
|
||||
const packageJsonPath = path.join(currentPath, 'package.json')
|
||||
if (require('fs').existsSync(packageJsonPath)) {
|
||||
return packageJsonPath
|
||||
}
|
||||
currentPath = path.dirname(currentPath)
|
||||
const parentPath = path.dirname(currentPath)
|
||||
if (parentPath === currentPath) {
|
||||
break // Additional protection
|
||||
}
|
||||
currentPath = parentPath
|
||||
}
|
||||
|
||||
return null
|
||||
@ -275,13 +280,18 @@ class PackageProtocol extends ResourceProtocol {
|
||||
findRootPackageJson () {
|
||||
let currentPath = process.cwd()
|
||||
let lastValidPackageJson = null
|
||||
let maxIterations = 50 // Prevent infinite loops
|
||||
|
||||
while (currentPath !== path.parse(currentPath).root) {
|
||||
while (currentPath !== path.parse(currentPath).root && maxIterations-- > 0) {
|
||||
const packageJsonPath = path.join(currentPath, 'package.json')
|
||||
if (require('fs').existsSync(packageJsonPath)) {
|
||||
lastValidPackageJson = packageJsonPath
|
||||
}
|
||||
currentPath = path.dirname(currentPath)
|
||||
const parentPath = path.dirname(currentPath)
|
||||
if (parentPath === currentPath) {
|
||||
break // Additional protection
|
||||
}
|
||||
currentPath = parentPath
|
||||
}
|
||||
|
||||
return lastValidPackageJson
|
||||
@ -520,23 +530,27 @@ class PackageProtocol extends ResourceProtocol {
|
||||
|
||||
/**
|
||||
* 加载资源内容
|
||||
* @param {string} resolvedPath - 已解析的路径
|
||||
* @param {QueryParams} [queryParams] - 查询参数
|
||||
* @returns {Object} 包含内容和元数据的对象
|
||||
*/
|
||||
async loadContent (resolvedPath, queryParams) {
|
||||
try {
|
||||
await fsPromises.access(resolvedPath)
|
||||
const content = await fsPromises.readFile(resolvedPath, 'utf8')
|
||||
const stats = await fsPromises.stat(resolvedPath)
|
||||
|
||||
const packageRoot = await this.getPackageRoot()
|
||||
|
||||
return {
|
||||
content,
|
||||
path: resolvedPath,
|
||||
protocol: this.name,
|
||||
protocol: 'package',
|
||||
installMode: this.detectInstallMode(),
|
||||
metadata: {
|
||||
size: content.length,
|
||||
lastModified: stats.mtime,
|
||||
absolutePath: resolvedPath,
|
||||
relativePath: path.relative(await this.getPackageRoot(), resolvedPath)
|
||||
relativePath: path.relative(packageRoot, resolvedPath)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@ -89,6 +89,11 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
return false
|
||||
}
|
||||
|
||||
// 特殊处理:允许.promptx开头的路径(项目配置目录)
|
||||
if (resourcePath.startsWith('.promptx/')) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 解析路径的第一部分(目录类型)
|
||||
const parts = resourcePath.split('/')
|
||||
const dirType = parts[0]
|
||||
@ -162,11 +167,37 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
|
||||
/**
|
||||
* 解析项目路径
|
||||
* @param {string} resourcePath - 原始资源路径,如 "src/index.js"
|
||||
* @param {string} resourcePath - 原始资源路径,如 "src/index.js" 或 ".promptx/resource/..."
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 解析后的绝对路径
|
||||
*/
|
||||
async resolvePath (resourcePath, queryParams) {
|
||||
// 特殊处理:.promptx开头的路径直接相对于项目根目录
|
||||
if (resourcePath.startsWith('.promptx/')) {
|
||||
// 确定搜索起始点
|
||||
const startDir = queryParams?.get('from') || process.cwd()
|
||||
|
||||
// 查找项目根目录
|
||||
const projectRoot = await this.findProjectRoot(startDir)
|
||||
if (!projectRoot) {
|
||||
throw new Error('未找到项目根目录(.promptx标识)。请确保在项目目录内或使用 \'from\' 参数指定项目路径')
|
||||
}
|
||||
|
||||
// 直接拼接完整路径
|
||||
const fullPath = path.join(projectRoot, resourcePath)
|
||||
|
||||
// 安全检查:确保路径在项目目录内
|
||||
const resolvedPath = path.resolve(fullPath)
|
||||
const resolvedProjectRoot = path.resolve(projectRoot)
|
||||
|
||||
if (!resolvedPath.startsWith(resolvedProjectRoot)) {
|
||||
throw new Error(`安全错误:路径超出项目目录范围: ${resolvedPath}`)
|
||||
}
|
||||
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
// 标准路径处理逻辑
|
||||
const parts = resourcePath.split('/')
|
||||
const dirType = parts[0]
|
||||
const relativePath = parts.slice(1).join('/')
|
||||
|
||||
@ -10,6 +10,14 @@ class RoleProtocol extends ResourceProtocol {
|
||||
constructor () {
|
||||
super('role')
|
||||
this.registry = {}
|
||||
this.registryManager = null // 统一注册表管理器
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册表管理器
|
||||
*/
|
||||
setRegistryManager(manager) {
|
||||
this.registryManager = manager
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,7 +49,36 @@ class RoleProtocol extends ResourceProtocol {
|
||||
*/
|
||||
async resolvePath (resourcePath, queryParams) {
|
||||
const roleId = resourcePath.trim()
|
||||
const fullResourceId = `role:${roleId}`
|
||||
|
||||
// 优先使用统一注册表管理器
|
||||
if (this.registryManager) {
|
||||
const reference = this.registryManager.registry.get(fullResourceId)
|
||||
if (!reference) {
|
||||
const availableRoles = this.registryManager.registry.keys()
|
||||
.filter(id => id.startsWith('role:'))
|
||||
.map(id => id.replace('role:', ''))
|
||||
throw new Error(`角色 "${roleId}" 未在注册表中找到。可用角色:${availableRoles.join(', ')}`)
|
||||
}
|
||||
|
||||
let resolvedPath = reference
|
||||
|
||||
// 处理 @package:// 前缀
|
||||
if (resolvedPath.startsWith('@package://')) {
|
||||
const PackageProtocol = require('./PackageProtocol')
|
||||
const packageProtocol = new PackageProtocol()
|
||||
const relativePath = resolvedPath.replace('@package://', '')
|
||||
resolvedPath = await packageProtocol.resolvePath(relativePath)
|
||||
} else if (resolvedPath.startsWith('@project://')) {
|
||||
// 处理 @project:// 前缀,转换为绝对路径
|
||||
const relativePath = resolvedPath.replace('@project://', '')
|
||||
resolvedPath = path.join(process.cwd(), relativePath)
|
||||
}
|
||||
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
// 向后兼容:使用旧的registry
|
||||
if (!this.registry[roleId]) {
|
||||
throw new Error(`角色 "${roleId}" 未在注册表中找到。可用角色:${Object.keys(this.registry).join(', ')}`)
|
||||
}
|
||||
|
||||
@ -10,6 +10,14 @@ class ThoughtProtocol extends ResourceProtocol {
|
||||
constructor () {
|
||||
super('thought')
|
||||
this.registry = {}
|
||||
this.registryManager = null // 统一注册表管理器
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册表管理器
|
||||
*/
|
||||
setRegistryManager(manager) {
|
||||
this.registryManager = manager
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,7 +47,36 @@ class ThoughtProtocol extends ResourceProtocol {
|
||||
*/
|
||||
async resolvePath (resourcePath, queryParams) {
|
||||
const thoughtId = resourcePath.trim()
|
||||
const fullResourceId = `thought:${thoughtId}`
|
||||
|
||||
// 优先使用统一注册表管理器
|
||||
if (this.registryManager) {
|
||||
const reference = this.registryManager.registry.get(fullResourceId)
|
||||
if (!reference) {
|
||||
const availableThoughts = this.registryManager.registry.keys()
|
||||
.filter(id => id.startsWith('thought:'))
|
||||
.map(id => id.replace('thought:', ''))
|
||||
throw new Error(`思维模式 "${thoughtId}" 未在注册表中找到。可用思维模式:${availableThoughts.join(', ')}`)
|
||||
}
|
||||
|
||||
let resolvedPath = reference
|
||||
|
||||
// 处理 @package:// 前缀
|
||||
if (resolvedPath.startsWith('@package://')) {
|
||||
const PackageProtocol = require('./PackageProtocol')
|
||||
const packageProtocol = new PackageProtocol()
|
||||
const relativePath = resolvedPath.replace('@package://', '')
|
||||
resolvedPath = await packageProtocol.resolvePath(relativePath)
|
||||
} else if (resolvedPath.startsWith('@project://')) {
|
||||
// 处理 @project:// 前缀,转换为绝对路径
|
||||
const relativePath = resolvedPath.replace('@project://', '')
|
||||
resolvedPath = path.join(process.cwd(), relativePath)
|
||||
}
|
||||
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
// 向后兼容:使用旧的registry
|
||||
if (!this.registry[thoughtId]) {
|
||||
throw new Error(`思维模式 "${thoughtId}" 未在注册表中找到`)
|
||||
}
|
||||
|
||||
@ -1,51 +1,166 @@
|
||||
const fs = require('fs')
|
||||
const ResourceRegistry = require('./resourceRegistry')
|
||||
const ProtocolResolver = require('./ProtocolResolver')
|
||||
const ResourceDiscovery = require('./ResourceDiscovery')
|
||||
const ResourceProtocolParser = require('./resourceProtocolParser')
|
||||
const DiscoveryManager = require('./discovery/DiscoveryManager')
|
||||
|
||||
// 导入协议处理器
|
||||
const PackageProtocol = require('./protocols/PackageProtocol')
|
||||
const ProjectProtocol = require('./protocols/ProjectProtocol')
|
||||
const RoleProtocol = require('./protocols/RoleProtocol')
|
||||
const ThoughtProtocol = require('./protocols/ThoughtProtocol')
|
||||
const ExecutionProtocol = require('./protocols/ExecutionProtocol')
|
||||
const KnowledgeProtocol = require('./protocols/KnowledgeProtocol')
|
||||
|
||||
class ResourceManager {
|
||||
constructor() {
|
||||
this.registry = new ResourceRegistry()
|
||||
this.resolver = new ProtocolResolver()
|
||||
this.discovery = new ResourceDiscovery()
|
||||
this.protocolParser = new ResourceProtocolParser()
|
||||
this.parser = new ResourceProtocolParser() // 向后兼容别名
|
||||
this.discoveryManager = new DiscoveryManager() // 新发现管理器
|
||||
|
||||
// 初始化协议处理器
|
||||
this.protocols = new Map()
|
||||
this.initializeProtocols()
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
// 1. Load static registry from resource.registry.json
|
||||
this.registry.loadFromFile('src/resource.registry.json')
|
||||
/**
|
||||
* 初始化所有协议处理器
|
||||
*/
|
||||
initializeProtocols() {
|
||||
// 基础协议 - 直接文件系统映射
|
||||
this.protocols.set('package', new PackageProtocol())
|
||||
this.protocols.set('project', new ProjectProtocol())
|
||||
|
||||
// 2. Discover dynamic resources from scan paths
|
||||
const scanPaths = [
|
||||
'prompt/', // Package internal resources
|
||||
'.promptx/', // Project resources
|
||||
process.env.PROMPTX_USER_DIR // User resources
|
||||
].filter(Boolean) // Remove undefined values
|
||||
// 逻辑协议 - 需要注册表查询
|
||||
this.protocols.set('role', new RoleProtocol())
|
||||
this.protocols.set('thought', new ThoughtProtocol())
|
||||
this.protocols.set('execution', new ExecutionProtocol())
|
||||
this.protocols.set('knowledge', new KnowledgeProtocol())
|
||||
}
|
||||
|
||||
const discovered = await this.discovery.discoverResources(scanPaths)
|
||||
/**
|
||||
* 新架构初始化方法
|
||||
*/
|
||||
async initializeWithNewArchitecture() {
|
||||
try {
|
||||
// 1. 使用DiscoveryManager发现并合并所有注册表
|
||||
const discoveredRegistry = await this.discoveryManager.discoverRegistries()
|
||||
|
||||
// 3. Register discovered resources (don't overwrite static registry)
|
||||
for (const resource of discovered) {
|
||||
if (!this.registry.index.has(resource.id)) {
|
||||
this.registry.register(resource.id, resource.reference)
|
||||
// 2. 批量注册到ResourceRegistry
|
||||
for (const [resourceId, reference] of discoveredRegistry) {
|
||||
this.registry.register(resourceId, reference)
|
||||
}
|
||||
|
||||
// 3. 为逻辑协议设置注册表引用
|
||||
this.setupLogicalProtocols()
|
||||
|
||||
// 4. 设置初始化状态
|
||||
this.initialized = true
|
||||
|
||||
// 初始化完成,不输出日志避免干扰用户界面
|
||||
} catch (error) {
|
||||
console.warn(`[ResourceManager] New architecture initialization failed: ${error.message}`)
|
||||
console.warn('[ResourceManager] Continuing with empty registry')
|
||||
this.initialized = true // 即使失败也标记为已初始化,避免重复尝试
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为逻辑协议设置注册表引用
|
||||
*/
|
||||
setupLogicalProtocols() {
|
||||
// 将统一注册表传递给逻辑协议处理器
|
||||
const roleProtocol = this.protocols.get('role')
|
||||
const executionProtocol = this.protocols.get('execution')
|
||||
const thoughtProtocol = this.protocols.get('thought')
|
||||
const knowledgeProtocol = this.protocols.get('knowledge')
|
||||
|
||||
if (roleProtocol) {
|
||||
roleProtocol.setRegistryManager(this)
|
||||
}
|
||||
if (executionProtocol) {
|
||||
executionProtocol.setRegistryManager(this)
|
||||
}
|
||||
if (thoughtProtocol) {
|
||||
thoughtProtocol.setRegistryManager(this)
|
||||
}
|
||||
if (knowledgeProtocol) {
|
||||
knowledgeProtocol.setRegistryManager(this)
|
||||
}
|
||||
|
||||
// 逻辑协议设置完成,不输出日志避免干扰用户界面
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过协议解析加载资源内容
|
||||
* @param {string} reference - 资源引用
|
||||
* @returns {Promise<string>} 资源内容
|
||||
*/
|
||||
async loadResourceByProtocol(reference) {
|
||||
// 1. 使用ResourceProtocolParser解析DPML语法
|
||||
const parsed = this.protocolParser.parse(reference)
|
||||
|
||||
// 2. 获取对应的协议处理器
|
||||
const protocol = this.protocols.get(parsed.protocol)
|
||||
if (!protocol) {
|
||||
throw new Error(`不支持的协议: ${parsed.protocol}`)
|
||||
}
|
||||
|
||||
// 3. 委托给协议处理器解析并加载内容
|
||||
const result = await protocol.resolve(parsed.path, parsed.queryParams)
|
||||
|
||||
// 4. 确保返回字符串内容,解包可能的对象格式
|
||||
if (typeof result === 'string') {
|
||||
return result
|
||||
} else if (result && typeof result === 'object' && result.content) {
|
||||
return result.content
|
||||
} else {
|
||||
throw new Error(`协议${parsed.protocol}返回了无效的内容格式`)
|
||||
}
|
||||
}
|
||||
|
||||
async loadResource(resourceId) {
|
||||
try {
|
||||
// 1. Resolve resourceId to @reference through registry
|
||||
const reference = this.registry.resolve(resourceId)
|
||||
// 使用新架构初始化
|
||||
if (this.registry.size === 0) {
|
||||
await this.initializeWithNewArchitecture()
|
||||
}
|
||||
|
||||
// 2. Resolve @reference to file path through protocol resolver
|
||||
const filePath = await this.resolver.resolve(reference)
|
||||
// 处理@!开头的DPML格式(如 @!role://java-developer)
|
||||
if (resourceId.startsWith('@!')) {
|
||||
const parsed = this.protocolParser.parse(resourceId)
|
||||
const logicalResourceId = `${parsed.protocol}:${parsed.path}`
|
||||
|
||||
// 从注册表查找对应的@package://引用
|
||||
const reference = this.registry.get(logicalResourceId)
|
||||
if (!reference) {
|
||||
throw new Error(`Resource not found: ${logicalResourceId}`)
|
||||
}
|
||||
|
||||
// 通过协议解析加载内容
|
||||
const content = await this.loadResourceByProtocol(reference)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content,
|
||||
resourceId,
|
||||
reference
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Load file content from file system
|
||||
const content = fs.readFileSync(filePath, 'utf8')
|
||||
// 处理传统格式(如 role:java-developer)
|
||||
const reference = this.registry.get(resourceId)
|
||||
if (!reference) {
|
||||
throw new Error(`Resource not found: ${resourceId}`)
|
||||
}
|
||||
|
||||
// 通过协议解析加载内容
|
||||
const content = await this.loadResourceByProtocol(reference)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content,
|
||||
path: filePath,
|
||||
resourceId,
|
||||
reference
|
||||
}
|
||||
} catch (error) {
|
||||
@ -57,31 +172,81 @@ class ResourceManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Backward compatibility method for existing code
|
||||
/**
|
||||
* 统一协议解析入口点 - 按照架构文档设计
|
||||
*/
|
||||
async resolveProtocolReference(reference) {
|
||||
// 1. 使用ResourceProtocolParser解析DPML语法
|
||||
const parsed = this.parser.parse(reference)
|
||||
|
||||
// 2. 获取对应的协议处理器
|
||||
const protocol = this.protocols.get(parsed.protocol)
|
||||
if (!protocol) {
|
||||
throw new Error(`不支持的协议: ${parsed.protocol}`)
|
||||
}
|
||||
|
||||
// 3. 委托给协议处理器解析
|
||||
return await protocol.resolve(parsed.path, parsed.queryParams)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已注册的协议
|
||||
* @returns {Array<string>} 协议名称列表
|
||||
*/
|
||||
getAvailableProtocols() {
|
||||
return Array.from(this.protocols.keys())
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否支持指定协议
|
||||
* @param {string} protocol - 协议名称
|
||||
* @returns {boolean} 是否支持
|
||||
*/
|
||||
supportsProtocol(protocol) {
|
||||
return this.protocols.has(protocol)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置初始化状态
|
||||
*/
|
||||
set initialized(value) {
|
||||
this._initialized = value
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取初始化状态
|
||||
*/
|
||||
get initialized() {
|
||||
return this._initialized || false
|
||||
}
|
||||
|
||||
// 向后兼容方法
|
||||
async resolve(resourceUrl) {
|
||||
try {
|
||||
await this.initialize()
|
||||
// 使用新架构初始化
|
||||
if (this.registry.size === 0) {
|
||||
await this.initializeWithNewArchitecture()
|
||||
}
|
||||
|
||||
// Handle old format: role:java-backend-developer or @package://...
|
||||
if (resourceUrl.startsWith('@')) {
|
||||
// Parse the reference to check if it's a custom protocol
|
||||
const parsed = this.resolver.parseReference(resourceUrl)
|
||||
const parsed = this.protocolParser.parse(resourceUrl)
|
||||
|
||||
// Check if it's a basic protocol that ProtocolResolver can handle directly
|
||||
// Check if it's a basic protocol that can be handled directly
|
||||
const basicProtocols = ['package', 'project', 'file']
|
||||
if (basicProtocols.includes(parsed.protocol)) {
|
||||
// Direct protocol format - use ProtocolResolver
|
||||
const filePath = await this.resolver.resolve(resourceUrl)
|
||||
const content = fs.readFileSync(filePath, 'utf8')
|
||||
// Direct protocol format - use protocol resolution
|
||||
const content = await this.loadResourceByProtocol(resourceUrl)
|
||||
return {
|
||||
success: true,
|
||||
content,
|
||||
path: filePath,
|
||||
path: resourceUrl,
|
||||
reference: resourceUrl
|
||||
}
|
||||
} else {
|
||||
// Custom protocol - extract resource ID and use ResourceRegistry
|
||||
const resourceId = `${parsed.protocol}:${parsed.resourcePath}`
|
||||
const resourceId = `${parsed.protocol}:${parsed.path}`
|
||||
return await this.loadResource(resourceId)
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -1,54 +1,160 @@
|
||||
const fs = require('fs')
|
||||
|
||||
/**
|
||||
* 资源注册表
|
||||
* 新架构中用于存储动态发现的资源映射关系
|
||||
*/
|
||||
class ResourceRegistry {
|
||||
constructor() {
|
||||
this.index = new Map()
|
||||
}
|
||||
|
||||
loadFromFile(registryPath = 'src/resource.registry.json') {
|
||||
const data = JSON.parse(fs.readFileSync(registryPath, 'utf8'))
|
||||
|
||||
if (!data.protocols) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const [protocol, info] of Object.entries(data.protocols)) {
|
||||
if (info.registry) {
|
||||
for (const [id, resourceInfo] of Object.entries(info.registry)) {
|
||||
const reference = typeof resourceInfo === 'string'
|
||||
? resourceInfo
|
||||
: resourceInfo.file
|
||||
|
||||
if (reference) {
|
||||
this.index.set(`${protocol}:${id}`, reference)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册资源
|
||||
* @param {string} id - 资源ID (如 'role:java-developer')
|
||||
* @param {string} reference - 资源引用 (如 '@package://prompt/domain/java-developer/java-developer.role.md')
|
||||
*/
|
||||
register(id, reference) {
|
||||
this.index.set(id, reference)
|
||||
}
|
||||
|
||||
resolve(resourceId) {
|
||||
// 1. Direct lookup - exact match has highest priority
|
||||
if (this.index.has(resourceId)) {
|
||||
return this.index.get(resourceId)
|
||||
}
|
||||
/**
|
||||
* 获取资源引用
|
||||
* @param {string} resourceId - 资源ID
|
||||
* @returns {string|undefined} 资源引用
|
||||
*/
|
||||
get(resourceId) {
|
||||
return this.index.get(resourceId)
|
||||
}
|
||||
|
||||
// 2. Backward compatibility: try adding protocol prefixes
|
||||
// Order matters: role > thought > execution > memory
|
||||
const protocols = ['role', 'thought', 'execution', 'memory']
|
||||
/**
|
||||
* 检查资源是否存在
|
||||
* @param {string} resourceId - 资源ID
|
||||
* @returns {boolean} 是否存在
|
||||
*/
|
||||
has(resourceId) {
|
||||
return this.index.has(resourceId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注册表大小
|
||||
* @returns {number} 注册的资源数量
|
||||
*/
|
||||
get size() {
|
||||
return this.index.size
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空注册表
|
||||
*/
|
||||
clear() {
|
||||
this.index.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有资源ID
|
||||
* @returns {Array<string>} 资源ID列表
|
||||
*/
|
||||
keys() {
|
||||
return Array.from(this.index.keys())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有资源条目
|
||||
* @returns {Array<[string, string]>} [resourceId, reference] 对的数组
|
||||
*/
|
||||
entries() {
|
||||
return Array.from(this.index.entries())
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印所有注册的资源(调试用)
|
||||
* @param {string} title - 可选标题
|
||||
*/
|
||||
printAll(title = '注册表资源清单') {
|
||||
console.log(`\n📋 ${title}`)
|
||||
console.log('='.repeat(50))
|
||||
|
||||
for (const protocol of protocols) {
|
||||
const fullId = `${protocol}:${resourceId}`
|
||||
if (this.index.has(fullId)) {
|
||||
return this.index.get(fullId)
|
||||
}
|
||||
if (this.size === 0) {
|
||||
console.log('🔍 注册表为空')
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error(`Resource '${resourceId}' not found`)
|
||||
console.log(`📊 总计: ${this.size} 个资源\n`)
|
||||
|
||||
// 按协议分组显示
|
||||
const groupedResources = this.groupByProtocol()
|
||||
|
||||
for (const [protocol, resources] of Object.entries(groupedResources)) {
|
||||
console.log(`🔖 ${protocol.toUpperCase()} 协议 (${resources.length}个):`)
|
||||
resources.forEach(({ id, reference }) => {
|
||||
const resourceName = id.split(':')[1] || id
|
||||
console.log(` • ${resourceName}`)
|
||||
console.log(` └─ ${reference}`)
|
||||
})
|
||||
console.log('')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按协议分组资源
|
||||
* @returns {Object} 分组后的资源,格式:{ protocol: [{ id, reference }, ...] }
|
||||
*/
|
||||
groupByProtocol() {
|
||||
const groups = {}
|
||||
|
||||
for (const [id, reference] of this.entries()) {
|
||||
const protocol = id.includes(':') ? id.split(':')[0] : 'other'
|
||||
|
||||
if (!groups[protocol]) {
|
||||
groups[protocol] = []
|
||||
}
|
||||
|
||||
groups[protocol].push({ id, reference })
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资源统计信息
|
||||
* @returns {Object} 统计信息
|
||||
*/
|
||||
getStats() {
|
||||
const groups = this.groupByProtocol()
|
||||
const stats = {
|
||||
total: this.size,
|
||||
byProtocol: {}
|
||||
}
|
||||
|
||||
for (const [protocol, resources] of Object.entries(groups)) {
|
||||
stats.byProtocol[protocol] = resources.length
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索资源
|
||||
* @param {string} searchTerm - 搜索词
|
||||
* @returns {Array<[string, string]>} 匹配的资源
|
||||
*/
|
||||
search(searchTerm) {
|
||||
const term = searchTerm.toLowerCase()
|
||||
return this.entries().filter(([id, reference]) =>
|
||||
id.toLowerCase().includes(term) ||
|
||||
reference.toLowerCase().includes(term)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 以JSON格式导出注册表
|
||||
* @returns {Object} 注册表数据
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
size: this.size,
|
||||
resources: Object.fromEntries(this.entries()),
|
||||
stats: this.getStats()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user