更新资源管理器和命令逻辑:新增角色创建和生成相关功能,优化资源加载流程,支持用户自定义资源的发现与合并,同时增强错误处理和描述提取逻辑,提升系统的灵活性和用户体验。
This commit is contained in:
@ -85,10 +85,16 @@ ${COMMANDS.HELLO}
|
||||
*/
|
||||
async analyzeRoleDependencies (roleInfo) {
|
||||
try {
|
||||
// 处理文件路径,将@package://前缀替换为实际路径
|
||||
// 处理文件路径,将@package://和@project://前缀替换为实际路径
|
||||
let filePath = roleInfo.file
|
||||
if (filePath.startsWith('@package://')) {
|
||||
filePath = filePath.replace('@package://', '')
|
||||
} else if (filePath.startsWith('@project://')) {
|
||||
// 对于@project://路径,使用当前工作目录作为基础路径
|
||||
const ProjectProtocol = require('../../resource/protocols/ProjectProtocol')
|
||||
const projectProtocol = new ProjectProtocol()
|
||||
const relativePath = filePath.replace('@project://', '')
|
||||
filePath = path.join(process.cwd(), relativePath)
|
||||
}
|
||||
|
||||
// 读取角色文件内容
|
||||
|
||||
@ -26,23 +26,25 @@ class HelloCommand extends BasePouchCommand {
|
||||
}
|
||||
|
||||
try {
|
||||
// 从ResourceManager获取统一注册表
|
||||
// 使用新的ResourceManager架构
|
||||
const ResourceManager = require('../../resource/resourceManager')
|
||||
const resourceManager = new ResourceManager()
|
||||
await resourceManager.initialize() // 确保初始化完成
|
||||
|
||||
let registeredRoles = {}
|
||||
if (resourceManager.registry && resourceManager.registry.protocols && resourceManager.registry.protocols.role && resourceManager.registry.protocols.role.registry) {
|
||||
registeredRoles = resourceManager.registry.protocols.role.registry
|
||||
}
|
||||
|
||||
// 动态发现本地角色并合并
|
||||
const discoveredRoles = await this.discoverLocalRoles()
|
||||
|
||||
// 合并注册表中的角色和动态发现的角色
|
||||
this.roleRegistry = {
|
||||
...registeredRoles,
|
||||
...discoveredRoles
|
||||
// 加载统一注册表(包含系统+用户资源)
|
||||
const unifiedRegistry = await resourceManager.loadUnifiedRegistry()
|
||||
|
||||
// 提取角色数据
|
||||
const roleData = unifiedRegistry.role || {}
|
||||
|
||||
// 转换为HelloCommand期望的格式
|
||||
this.roleRegistry = {}
|
||||
for (const [roleId, roleInfo] of Object.entries(roleData)) {
|
||||
this.roleRegistry[roleId] = {
|
||||
file: roleInfo.file,
|
||||
name: roleInfo.name || roleId,
|
||||
description: this.extractDescription(roleInfo) || `${roleInfo.name || roleId}专业角色`,
|
||||
source: roleInfo.source || 'unknown'
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有任何角色,使用基础角色
|
||||
@ -51,31 +53,21 @@ class HelloCommand extends BasePouchCommand {
|
||||
assistant: {
|
||||
file: '@package://prompt/domain/assistant/assistant.role.md',
|
||||
name: '🙋 智能助手',
|
||||
description: '通用助理角色,提供基础的助理服务和记忆支持'
|
||||
description: '通用助理角色,提供基础的助理服务和记忆支持',
|
||||
source: 'fallback'
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('角色注册表加载失败,尝试动态发现:', error.message)
|
||||
console.warn('角色注册表加载失败,使用基础角色:', error.message)
|
||||
|
||||
// fallback到动态发现
|
||||
try {
|
||||
const discoveredRoles = await this.discoverLocalRoles()
|
||||
this.roleRegistry = Object.keys(discoveredRoles).length > 0 ? discoveredRoles : {
|
||||
assistant: {
|
||||
file: '@package://prompt/domain/assistant/assistant.role.md',
|
||||
name: '🙋 智能助手',
|
||||
description: '通用助理角色,提供基础的助理服务和记忆支持'
|
||||
}
|
||||
}
|
||||
} catch (discoveryError) {
|
||||
console.warn('动态角色发现也失败了:', discoveryError.message)
|
||||
this.roleRegistry = {
|
||||
assistant: {
|
||||
file: '@package://prompt/domain/assistant/assistant.role.md',
|
||||
name: '🙋 智能助手',
|
||||
description: '通用助理角色,提供基础的助理服务和记忆支持'
|
||||
}
|
||||
// 使用基础角色作为fallback
|
||||
this.roleRegistry = {
|
||||
assistant: {
|
||||
file: '@package://prompt/domain/assistant/assistant.role.md',
|
||||
name: '🙋 智能助手',
|
||||
description: '通用助理角色,提供基础的助理服务和记忆支持',
|
||||
source: 'fallback'
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,6 +75,21 @@ class HelloCommand extends BasePouchCommand {
|
||||
return this.roleRegistry
|
||||
}
|
||||
|
||||
/**
|
||||
* 从角色信息中提取描述
|
||||
* @param {Object} roleInfo - 角色信息对象
|
||||
* @returns {string} 角色描述
|
||||
*/
|
||||
extractDescription(roleInfo) {
|
||||
// 尝试从不同字段提取描述
|
||||
if (roleInfo.description) {
|
||||
return roleInfo.description
|
||||
}
|
||||
|
||||
// 如果有更多元数据,可以在这里扩展提取逻辑
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有角色列表(转换为数组格式)
|
||||
*/
|
||||
@ -92,10 +99,29 @@ class HelloCommand extends BasePouchCommand {
|
||||
id,
|
||||
name: roleInfo.name,
|
||||
description: roleInfo.description,
|
||||
file: roleInfo.file
|
||||
file: roleInfo.file,
|
||||
source: roleInfo.source
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源标签
|
||||
* @param {string} source - 资源来源
|
||||
* @returns {string} 来源标签
|
||||
*/
|
||||
getSourceLabel(source) {
|
||||
switch (source) {
|
||||
case 'user-generated':
|
||||
return '(用户生成)'
|
||||
case 'system':
|
||||
return '(系统角色)'
|
||||
case 'fallback':
|
||||
return '(默认角色)'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
async getContent (args) {
|
||||
await this.loadRoleRegistry()
|
||||
const allRoles = await this.getAllRoles()
|
||||
@ -111,7 +137,8 @@ class HelloCommand extends BasePouchCommand {
|
||||
|
||||
// 清楚显示角色ID和激活命令
|
||||
allRoles.forEach((role, index) => {
|
||||
content += `### ${index + 1}. ${role.name}
|
||||
const sourceLabel = this.getSourceLabel(role.source)
|
||||
content += `### ${index + 1}. ${role.name} ${sourceLabel}
|
||||
**角色ID**: \`${role.id}\`
|
||||
**专业能力**: ${role.description}
|
||||
**激活命令**: \`${buildCommand.action(role.id)}\`
|
||||
|
||||
@ -11,6 +11,16 @@ const ProjectProtocol = require('./protocols/ProjectProtocol')
|
||||
const UserProtocol = require('./protocols/UserProtocol')
|
||||
const PromptProtocol = require('./protocols/PromptProtocol')
|
||||
|
||||
// 常量定义
|
||||
const USER_RESOURCE_DIR = '.promptx'
|
||||
const RESOURCE_DOMAIN_PATH = ['resource', 'domain']
|
||||
const SUPPORTED_RESOURCE_TYPES = ['role', 'thought', 'execution']
|
||||
const DPML_TAGS = {
|
||||
role: { start: '<role>', end: '</role>' },
|
||||
thought: { start: '<thought>', end: '</thought>' },
|
||||
execution: { start: '<execution>', end: '</execution>' }
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源管理器 - 统一管理各种协议的资源加载
|
||||
*/
|
||||
@ -41,17 +51,95 @@ class ResourceManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载统一资源注册表
|
||||
* 加载统一资源注册表(合并系统和用户资源)
|
||||
*/
|
||||
async loadUnifiedRegistry () {
|
||||
const registryPath = path.resolve(__dirname, '../../../resource.registry.json')
|
||||
try {
|
||||
// 加载系统资源注册表
|
||||
const registryPath = path.resolve(__dirname, '../../../resource.registry.json')
|
||||
|
||||
if (!await fs.pathExists(registryPath)) {
|
||||
throw new Error(`统一资源注册表文件不存在: ${registryPath}`)
|
||||
if (!await fs.pathExists(registryPath)) {
|
||||
throw new Error(`统一资源注册表文件不存在: ${registryPath}`)
|
||||
}
|
||||
|
||||
const systemRegistry = await fs.readJSON(registryPath)
|
||||
|
||||
// 发现用户资源
|
||||
const userResources = await this.discoverUserResources()
|
||||
|
||||
// 从系统注册表中提取资源数据
|
||||
const extractedSystemResources = {}
|
||||
for (const resourceType of SUPPORTED_RESOURCE_TYPES) {
|
||||
const protocolConfig = systemRegistry.protocols[resourceType]
|
||||
if (protocolConfig && protocolConfig.registry) {
|
||||
extractedSystemResources[resourceType] = protocolConfig.registry
|
||||
}
|
||||
}
|
||||
|
||||
// 合并资源,用户资源覆盖系统资源
|
||||
const mergedRegistry = { ...systemRegistry }
|
||||
|
||||
// 合并各种资源类型
|
||||
for (const resourceType of SUPPORTED_RESOURCE_TYPES) {
|
||||
// 确保有基础结构
|
||||
if (!mergedRegistry[resourceType]) {
|
||||
mergedRegistry[resourceType] = {}
|
||||
}
|
||||
|
||||
// 先添加系统资源
|
||||
if (extractedSystemResources[resourceType]) {
|
||||
if (!mergedRegistry[resourceType]) mergedRegistry[resourceType] = {}
|
||||
for (const [id, resourceInfo] of Object.entries(extractedSystemResources[resourceType])) {
|
||||
mergedRegistry[resourceType][id] = {
|
||||
...resourceInfo,
|
||||
source: 'system'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 再添加用户资源(覆盖同名的系统资源)
|
||||
if (userResources[resourceType]) {
|
||||
for (const [id, resourceInfo] of Object.entries(userResources[resourceType])) {
|
||||
let filePath = resourceInfo.file || resourceInfo
|
||||
|
||||
// 将绝对路径转换为@project://相对路径格式
|
||||
if (path.isAbsolute(filePath)) {
|
||||
// 简单的路径转换:去掉项目根目录前缀
|
||||
const projectRoot = process.cwd()
|
||||
if (filePath.startsWith(projectRoot)) {
|
||||
const relativePath = path.relative(projectRoot, filePath)
|
||||
filePath = `@project://${relativePath}`
|
||||
}
|
||||
}
|
||||
|
||||
// 对于role资源类型,需要保持对象格式以包含name和description
|
||||
if (resourceType === 'role') {
|
||||
mergedRegistry[resourceType][id] = {
|
||||
file: filePath,
|
||||
name: resourceInfo.name || id,
|
||||
description: resourceInfo.description || `${resourceInfo.name || id}专业角色`,
|
||||
source: 'user-generated',
|
||||
format: resourceInfo.format,
|
||||
type: resourceInfo.type
|
||||
}
|
||||
} else {
|
||||
// 对于thought和execution,协议处理器期望的是文件路径字符串
|
||||
if (!mergedRegistry[resourceType]) mergedRegistry[resourceType] = {}
|
||||
mergedRegistry[resourceType][id] = filePath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.registry = mergedRegistry
|
||||
return mergedRegistry
|
||||
} catch (error) {
|
||||
// 如果加载失败,至少返回一个基本结构
|
||||
logger.warn(`加载统一注册表失败: ${error.message}`)
|
||||
const fallbackRegistry = { role: {} }
|
||||
this.registry = fallbackRegistry
|
||||
return fallbackRegistry
|
||||
}
|
||||
|
||||
const registryContent = await fs.readJSON(registryPath)
|
||||
this.registry = registryContent
|
||||
}
|
||||
|
||||
/**
|
||||
@ -204,6 +292,178 @@ class ResourceManager {
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 发现用户资源
|
||||
* @returns {Promise<Object>} 用户资源注册表
|
||||
*/
|
||||
async discoverUserResources() {
|
||||
try {
|
||||
const PackageProtocol = require('./protocols/PackageProtocol')
|
||||
const packageProtocol = new PackageProtocol()
|
||||
const packageRoot = await packageProtocol.getPackageRoot()
|
||||
|
||||
const userResourcePath = path.join(packageRoot, USER_RESOURCE_DIR, ...RESOURCE_DOMAIN_PATH)
|
||||
|
||||
// 检查用户资源目录是否存在
|
||||
if (!await fs.pathExists(userResourcePath)) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return await this.scanResourceDirectory(userResourcePath)
|
||||
} catch (error) {
|
||||
// 出错时返回空对象,不抛出异常
|
||||
logger.warn(`用户资源发现失败: ${error.message}`)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描资源目录
|
||||
* @param {string} basePath - 基础路径
|
||||
* @returns {Promise<Object>} 发现的资源
|
||||
*/
|
||||
async scanResourceDirectory(basePath) {
|
||||
const resources = {}
|
||||
|
||||
try {
|
||||
const directories = await fs.readdir(basePath)
|
||||
|
||||
for (const roleDir of directories) {
|
||||
const rolePath = path.join(basePath, roleDir)
|
||||
|
||||
try {
|
||||
const stat = await fs.stat(rolePath)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
// 扫描角色文件
|
||||
await this.scanRoleResources(rolePath, roleDir, resources)
|
||||
|
||||
// 扫描其他资源类型(thought, execution)
|
||||
await this.scanOtherResources(rolePath, roleDir, resources)
|
||||
}
|
||||
} catch (dirError) {
|
||||
// 跳过无法访问的目录
|
||||
logger.debug(`跳过目录 ${roleDir}: ${dirError.message}`)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`扫描资源目录失败 ${basePath}: ${error.message}`)
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描角色资源
|
||||
* @param {string} rolePath - 角色目录路径
|
||||
* @param {string} roleId - 角色ID
|
||||
* @param {Object} resources - 资源容器
|
||||
*/
|
||||
async scanRoleResources(rolePath, roleId, resources) {
|
||||
const roleFile = path.join(rolePath, `${roleId}.role.md`)
|
||||
|
||||
if (await fs.pathExists(roleFile)) {
|
||||
try {
|
||||
const content = await fs.readFile(roleFile, 'utf8')
|
||||
|
||||
// 验证DPML格式
|
||||
if (this.validateDPMLFormat(content, 'role')) {
|
||||
const name = this.extractRoleName(content)
|
||||
|
||||
if (!resources.role) resources.role = {}
|
||||
resources.role[roleId] = {
|
||||
file: roleFile,
|
||||
name: name || roleId,
|
||||
source: 'user-generated',
|
||||
format: 'dpml',
|
||||
type: 'role'
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略单个文件的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描其他资源类型
|
||||
* @param {string} rolePath - 角色目录路径
|
||||
* @param {string} roleId - 角色ID
|
||||
* @param {Object} resources - 资源容器
|
||||
*/
|
||||
async scanOtherResources(rolePath, roleId, resources) {
|
||||
for (const resourceType of SUPPORTED_RESOURCE_TYPES.filter(type => type !== 'role')) {
|
||||
const resourceDir = path.join(rolePath, resourceType)
|
||||
|
||||
if (await fs.pathExists(resourceDir)) {
|
||||
try {
|
||||
const files = await fs.readdir(resourceDir)
|
||||
|
||||
for (const file of files) {
|
||||
if (file.endsWith(`.${resourceType}.md`)) {
|
||||
const resourceName = file.replace(`.${resourceType}.md`, '')
|
||||
const filePath = path.join(resourceDir, file)
|
||||
const content = await fs.readFile(filePath, 'utf8')
|
||||
|
||||
if (this.validateDPMLFormat(content, resourceType)) {
|
||||
if (!resources[resourceType]) resources[resourceType] = {}
|
||||
resources[resourceType][resourceName] = {
|
||||
file: filePath,
|
||||
name: resourceName,
|
||||
source: 'user-generated',
|
||||
format: 'dpml',
|
||||
type: resourceType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.debug(`扫描${resourceType}资源失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证DPML格式
|
||||
* @param {string} content - 文件内容
|
||||
* @param {string} type - 资源类型
|
||||
* @returns {boolean} 是否为有效格式
|
||||
*/
|
||||
validateDPMLFormat(content, type) {
|
||||
const tags = DPML_TAGS[type]
|
||||
if (!tags) {
|
||||
return false
|
||||
}
|
||||
|
||||
return content.includes(tags.start) && content.includes(tags.end)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从角色内容中提取名称
|
||||
* @param {string} content - 角色文件内容
|
||||
* @returns {string} 角色名称
|
||||
*/
|
||||
extractRoleName(content) {
|
||||
// 简单的名称提取逻辑
|
||||
const match = content.match(/#\s*([^\n]+)/)
|
||||
return match ? match[1].trim() : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载系统资源注册表(兼容现有方法)
|
||||
* @returns {Promise<Object>} 系统资源注册表
|
||||
*/
|
||||
async loadSystemRegistry() {
|
||||
const registryPath = path.resolve(__dirname, '../../../resource.registry.json')
|
||||
|
||||
if (!await fs.pathExists(registryPath)) {
|
||||
throw new Error(`统一资源注册表文件不存在: ${registryPath}`)
|
||||
}
|
||||
|
||||
return await fs.readJSON(registryPath)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ResourceManager
|
||||
|
||||
Reference in New Issue
Block a user