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:
187
src/lib/core/resource/protocols/FileProtocol.js
Normal file
187
src/lib/core/resource/protocols/FileProtocol.js
Normal 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
|
||||
@ -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}`)
|
||||
|
||||
Reference in New Issue
Block a user