feat: noface角色重命名及file://协议路径转换优化

## 主要变更
- **角色重命名**: wumian → noface,更符合英文命名规范
- **file://协议优化**: 新增FileProtocol.js支持本地文件访问
- **路径转换修复**: 智能处理Shell反斜杠转义问题
- **ResourceManager增强**: 支持基础协议直接处理

## 技术改进
- 修复复杂路径格式兼容性(如WeChat路径、中文字符、特殊符号)
- 自动清理反斜杠转义符(Application\ Support → Application Support)
- 完善错误处理机制和用户提示

## 文件变更
- 新增: noface角色完整文件结构(role + 2个execution文件)
- 新增: FileProtocol.js协议处理器
- 更新: ResourceManager.js基础协议支持
- 更新: package.registry.json角色注册信息

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sean
2025-06-26 17:26:04 +08:00
parent 5181bfeff1
commit d6455987ab
6 changed files with 717 additions and 178 deletions

View File

@ -0,0 +1,187 @@
const ResourceProtocol = require('./ResourceProtocol')
const path = require('path')
const fs = require('fs').promises
/**
* 文件协议实现
* 实现@file://协议,用于访问本地文件系统中的文件
*/
class FileProtocol extends ResourceProtocol {
constructor (options = {}) {
super('file', options)
}
/**
* 设置注册表(保持与其他协议的一致性)
*/
setRegistry (registry) {
// File协议不使用注册表但为了一致性提供此方法
this.registry = registry || {}
}
/**
* 获取协议信息
* @returns {object} 协议信息
*/
getProtocolInfo () {
return {
name: 'file',
description: '文件系统协议,提供本地文件访问',
location: 'file://{path}',
examples: [
'file://package.json',
'file:///absolute/path/to/file.txt',
'file://./relative/path/file.md',
'file://../parent/file.json'
],
params: this.getSupportedParams()
}
}
/**
* 支持的查询参数
* @returns {object} 参数说明
*/
getSupportedParams () {
return {
...super.getSupportedParams(),
encoding: 'string - 文件编码 (utf8, ascii, binary等)',
exists: 'boolean - 仅返回存在的文件'
}
}
/**
* 验证文件协议路径
* @param {string} resourcePath - 资源路径
* @returns {boolean} 是否有效
*/
validatePath (resourcePath) {
if (!super.validatePath(resourcePath)) {
return false
}
// 基本路径验证 - 允许相对路径和绝对路径
return typeof resourcePath === 'string' && resourcePath.length > 0
}
/**
* 解析文件路径
* @param {string} resourcePath - 原始资源路径
* @param {QueryParams} queryParams - 查询参数
* @returns {Promise<string>} 解析后的绝对路径
*/
async resolvePath (resourcePath, queryParams) {
let resolvedPath
if (path.isAbsolute(resourcePath)) {
// 绝对路径直接使用
resolvedPath = resourcePath
} else {
// 相对路径相对于当前工作目录解析
resolvedPath = path.resolve(process.cwd(), resourcePath)
}
// 规范化路径
resolvedPath = path.normalize(resolvedPath)
return resolvedPath
}
/**
* 加载资源内容
* @param {string} resolvedPath - 解析后的路径
* @param {QueryParams} queryParams - 查询参数
* @returns {Promise<string>} 资源内容
*/
async loadContent (resolvedPath, queryParams) {
try {
// 检查路径是否存在
const stats = await fs.stat(resolvedPath)
if (stats.isDirectory()) {
return await this.loadDirectoryContent(resolvedPath, queryParams)
} else if (stats.isFile()) {
return await this.loadFileContent(resolvedPath, queryParams)
} else {
throw new Error(`不支持的文件类型: ${resolvedPath}`)
}
} catch (error) {
if (error.code === 'ENOENT') {
// 如果设置了exists参数为false返回空内容而不是错误
if (queryParams && queryParams.get('exists') === 'false') {
return ''
}
throw new Error(`文件或目录不存在: ${resolvedPath}`)
}
throw error
}
}
/**
* 加载文件内容
* @param {string} filePath - 文件路径
* @param {QueryParams} queryParams - 查询参数
* @returns {Promise<string>} 文件内容
*/
async loadFileContent (filePath, queryParams) {
const encoding = queryParams?.get('encoding') || 'utf8'
return await fs.readFile(filePath, encoding)
}
/**
* 加载目录内容
* @param {string} dirPath - 目录路径
* @param {QueryParams} queryParams - 查询参数
* @returns {Promise<string>} 目录内容列表
*/
async loadDirectoryContent (dirPath, queryParams) {
const entries = await fs.readdir(dirPath, { withFileTypes: true })
// 应用类型过滤
const typeFilter = queryParams?.get('type')
let filteredEntries = entries
if (typeFilter) {
filteredEntries = entries.filter(entry => {
switch (typeFilter) {
case 'file': return entry.isFile()
case 'dir': return entry.isDirectory()
case 'both': return true
default: return true
}
})
}
// 格式化输出
const format = queryParams?.get('format') || 'list'
switch (format) {
case 'json':
return JSON.stringify(
filteredEntries.map(entry => ({
name: entry.name,
type: entry.isDirectory() ? 'directory' : 'file',
path: path.join(dirPath, entry.name)
})),
null,
2
)
case 'paths':
return filteredEntries
.map(entry => path.join(dirPath, entry.name))
.join('\n')
case 'list':
default:
return filteredEntries
.map(entry => {
const type = entry.isDirectory() ? '[DIR]' : '[FILE]'
return `${type} ${entry.name}`
})
.join('\n')
}
}
}
module.exports = FileProtocol

View File

@ -11,6 +11,8 @@ const RoleProtocol = require('./protocols/RoleProtocol')
const ThoughtProtocol = require('./protocols/ThoughtProtocol')
const ExecutionProtocol = require('./protocols/ExecutionProtocol')
const KnowledgeProtocol = require('./protocols/KnowledgeProtocol')
const UserProtocol = require('./protocols/UserProtocol')
const FileProtocol = require('./protocols/FileProtocol')
class ResourceManager {
constructor() {
@ -36,6 +38,8 @@ class ResourceManager {
// 基础协议 - 直接文件系统映射
this.protocols.set('package', new PackageProtocol())
this.protocols.set('project', new ProjectProtocol())
this.protocols.set('file', new FileProtocol())
this.protocols.set('user', new UserProtocol())
// 逻辑协议 - 需要注册表查询
this.protocols.set('role', new RoleProtocol())
@ -158,11 +162,23 @@ class ResourceManager {
await this.initializeWithNewArchitecture()
}
// 处理@!开头的DPML格式如 @!role://java-developer
if (resourceId.startsWith('@!')) {
// 处理@开头的DPML格式 @file://path, @!role://java-developer
if (resourceId.startsWith('@')) {
const parsed = this.protocolParser.parse(resourceId)
// 从RegistryData查找资源
// 对于基础协议file, user, package, project直接通过协议处理器加载
const basicProtocols = ['file', 'user', 'package', 'project']
if (basicProtocols.includes(parsed.protocol)) {
const content = await this.loadResourceByProtocol(resourceId)
return {
success: true,
content,
resourceId,
reference: resourceId
}
}
// 对于逻辑协议从RegistryData查找资源
const resourceData = this.registryData.findResourceById(parsed.path, parsed.protocol)
if (!resourceData) {
throw new Error(`Resource not found: ${parsed.protocol}:${parsed.path}`)