feat: 实现本地角色动态发现机制 - 双重角色发现机制:同时支持npm仓库角色和本地项目角色 - 智能环境检测:自动适配开发、npx、全局、本地、monorepo等部署环境 - 安全机制完善:路径验证、权限检查、多层容错处理 - 向后兼容保证,不影响现有功能

This commit is contained in:
Cen-Yaozu
2025-06-01 21:26:14 +08:00
parent 4a0ad6e61c
commit 05cb5f54c0
48 changed files with 7124 additions and 20 deletions

View File

@ -31,10 +31,22 @@ class HelloCommand extends BasePouchCommand {
const resourceManager = new ResourceManager()
await resourceManager.initialize() // 确保初始化完成
let registeredRoles = {}
if (resourceManager.registry && resourceManager.registry.protocols && resourceManager.registry.protocols.role && resourceManager.registry.protocols.role.registry) {
this.roleRegistry = resourceManager.registry.protocols.role.registry
} else {
// 备用:如果资源系统不可用,使用基础角色
registeredRoles = resourceManager.registry.protocols.role.registry
}
// 动态发现本地角色并合并
const discoveredRoles = await this.discoverLocalRoles()
// 合并注册表中的角色和动态发现的角色
this.roleRegistry = {
...registeredRoles,
...discoveredRoles
}
// 如果没有任何角色,使用基础角色
if (Object.keys(this.roleRegistry).length === 0) {
this.roleRegistry = {
assistant: {
file: '@package://prompt/domain/assistant/assistant.role.md',
@ -44,12 +56,26 @@ class HelloCommand extends BasePouchCommand {
}
}
} catch (error) {
console.warn('角色注册表加载失败,使用基础角色:', error.message)
this.roleRegistry = {
assistant: {
file: '@package://prompt/domain/assistant/assistant.role.md',
name: '🙋 智能助手',
description: '通用助理角色,提供基础的助理服务和记忆支持'
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: '通用助理角色,提供基础的助理服务和记忆支持'
}
}
}
}
@ -183,6 +209,64 @@ ${buildCommand.action(allRoles[0]?.id || 'assistant')}
const allRoles = await this.getAllRoles()
return allRoles.map(role => role.id)
}
/**
* 动态发现本地角色文件
*/
async discoverLocalRoles () {
const PackageProtocol = require('../../resource/protocols/PackageProtocol')
const packageProtocol = new PackageProtocol()
const glob = require('glob')
const path = require('path')
try {
const packageRoot = await packageProtocol.getPackageRoot()
const domainPath = path.join(packageRoot, 'prompt', 'domain')
// 扫描所有角色目录
const rolePattern = path.join(domainPath, '*', '*.role.md')
const roleFiles = glob.sync(rolePattern)
const discoveredRoles = {}
for (const roleFile of roleFiles) {
try {
const content = await fs.readFile(roleFile, 'utf-8')
const relativePath = path.relative(packageRoot, roleFile)
const roleName = path.basename(roleFile, '.role.md')
// 尝试从文件内容中提取角色信息
let description = '本地发现的角色'
let name = `🎭 ${roleName}`
// 简单的元数据提取(支持多行)
const descMatch = content.match(/description:\s*(.+?)(?:\n|$)/i)
if (descMatch) {
description = descMatch[1].trim()
}
const nameMatch = content.match(/name:\s*(.+?)(?:\n|$)/i)
if (nameMatch) {
name = nameMatch[1].trim()
}
discoveredRoles[roleName] = {
file: `@package://${relativePath}`,
name,
description,
source: 'local-discovery'
}
} catch (error) {
console.warn(`跳过无效的角色文件: ${roleFile}`, error.message)
}
}
return discoveredRoles
} catch (error) {
console.warn('动态角色发现失败:', error.message)
return {}
}
}
}
module.exports = HelloCommand

View File

@ -0,0 +1,232 @@
const BasePouchCommand = require('../BasePouchCommand')
const fs = require('fs-extra')
const path = require('path')
const PackageProtocol = require('../../resource/protocols/PackageProtocol')
const { buildCommand } = require('../../../../constants')
/**
* 角色注册锦囊命令
* 负责将新创建的角色注册到系统中
*/
class RegisterCommand extends BasePouchCommand {
constructor () {
super()
this.packageProtocol = new PackageProtocol()
}
getPurpose () {
return '注册新创建的角色到系统中,使其可以被发现和激活'
}
async getContent (args) {
const [roleId] = args
if (!roleId) {
return `❌ 请指定要注册的角色ID
🔍 使用方法:
\`\`\`bash
${buildCommand.register('<角色ID>')}
\`\`\`
💡 例如:
\`\`\`bash
${buildCommand.register('my-custom-role')}
\`\`\``
}
try {
// 1. 检查角色文件是否存在
const roleExists = await this.checkRoleExists(roleId)
if (!roleExists) {
return `❌ 角色文件不存在!
请确保以下文件存在:
- prompt/domain/${roleId}/${roleId}.role.md
- prompt/domain/${roleId}/thought/${roleId}.thought.md
- prompt/domain/${roleId}/execution/${roleId}.execution.md
💡 您可以使用角色设计师来创建完整的角色套件:
\`\`\`bash
${buildCommand.action('role-designer')}
\`\`\``
}
// 2. 提取角色元数据
const roleMetadata = await this.extractRoleMetadata(roleId)
// 3. 注册角色到系统
const registrationResult = await this.registerRole(roleId, roleMetadata)
if (registrationResult.success) {
return `✅ 角色 "${roleId}" 注册成功!
📋 **注册信息**
- 名称:${roleMetadata.name}
- 描述:${roleMetadata.description}
- 文件路径:${roleMetadata.filePath}
🎯 **下一步操作**
\`\`\`bash
${buildCommand.action(roleId)}
\`\`\`
💡 现在您可以激活这个角色了!`
} else {
return `❌ 角色注册失败:${registrationResult.error}
🔍 请检查:
- 角色文件格式是否正确
- 是否有写入权限
- 注册表文件是否可访问`
}
} catch (error) {
console.error('Register command error:', error)
return `❌ 注册角色 "${roleId}" 时发生错误:${error.message}
💡 请确保角色文件存在且格式正确。`
}
}
/**
* 检查角色文件是否存在
*/
async checkRoleExists (roleId) {
try {
const packageRoot = await this.packageProtocol.getPackageRoot()
const roleFile = path.join(packageRoot, 'prompt', 'domain', roleId, `${roleId}.role.md`)
return await fs.pathExists(roleFile)
} catch (error) {
return false
}
}
/**
* 提取角色元数据
*/
async extractRoleMetadata (roleId) {
const packageRoot = await this.packageProtocol.getPackageRoot()
const roleFile = path.join(packageRoot, 'prompt', 'domain', roleId, `${roleId}.role.md`)
const content = await fs.readFile(roleFile, 'utf-8')
const relativePath = path.relative(packageRoot, roleFile)
// 提取元数据
let name = `🎭 ${roleId}`
let description = '用户自定义角色'
// 从注释中提取元数据(支持多行)
const nameMatch = content.match(/name:\s*(.+?)(?:\n|$)/i)
if (nameMatch) {
name = nameMatch[1].trim()
}
const descMatch = content.match(/description:\s*(.+?)(?:\n|$)/i)
if (descMatch) {
description = descMatch[1].trim()
}
// 如果没有找到注释,尝试从文件内容推断
if (name === `🎭 ${roleId}` && description === '用户自定义角色') {
// 可以根据角色内容进行更智能的推断
if (content.includes('产品')) {
name = `📊 ${roleId}`
} else if (content.includes('开发') || content.includes('代码')) {
name = `💻 ${roleId}`
} else if (content.includes('设计')) {
name = `🎨 ${roleId}`
}
}
return {
name,
description,
filePath: `@package://${relativePath}`
}
}
/**
* 注册角色到系统
*/
async registerRole (roleId, metadata) {
try {
const packageRoot = await this.packageProtocol.getPackageRoot()
const registryPath = path.join(packageRoot, 'src', 'resource.registry.json')
// 读取当前注册表
const registry = await fs.readJson(registryPath)
// 添加新角色
if (!registry.protocols.role.registry) {
registry.protocols.role.registry = {}
}
registry.protocols.role.registry[roleId] = {
file: metadata.filePath,
name: metadata.name,
description: metadata.description
}
// 写回注册表
await fs.writeJson(registryPath, registry, { spaces: 2 })
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
}
getPATEOAS (args) {
const [roleId] = args
if (!roleId) {
return {
currentState: 'register_awaiting_role',
availableTransitions: ['hello', 'action'],
nextActions: [
{
name: '查看可用角色',
description: '查看已注册的角色',
command: buildCommand.hello(),
priority: 'medium'
},
{
name: '创建新角色',
description: '使用角色设计师创建新角色',
command: buildCommand.action('role-designer'),
priority: 'high'
}
],
metadata: {
message: '需要指定角色ID'
}
}
}
return {
currentState: 'register_completed',
availableTransitions: ['action', 'hello'],
nextActions: [
{
name: '激活角色',
description: '激活刚注册的角色',
command: buildCommand.action(roleId),
priority: 'high'
},
{
name: '查看所有角色',
description: '查看角色列表',
command: buildCommand.hello(),
priority: 'medium'
}
],
metadata: {
registeredRole: roleId,
systemVersion: '锦囊串联状态机 v1.0'
}
}
}
}
module.exports = RegisterCommand