总线开发工程-init

This commit is contained in:
2026-01-15 17:41:51 +08:00
parent a191766b45
commit 46fea93115
6 changed files with 416 additions and 0 deletions

59
SL/bus/Amily2Bus.js Normal file
View File

@@ -0,0 +1,59 @@
class Amily2Bus {
constructor() {
/** @type {Logger|null} */
this.Logger = null;
/** @type {FilePipe|null} */
this.FilePipe = null;
// 已注册插件列表,防止重复注册
this.registry = new Set();
console.log('[Amily2Bus] Core container initialized with secure registry.');
}
/**
* 注册插件并获取专属上下文
* @param {string} pluginName 插件名称
* @returns {Object} 包含该插件专属 API 的上下文对象
*/
register(pluginName) {
if (!pluginName || typeof pluginName !== 'string') {
throw new Error('[Amily2Bus] Invalid plugin name.');
}
if (this.registry.has(pluginName)) {
console.warn(`[Amily2Bus] Plugin '${pluginName}' is already registered.`);
} else {
this.registry.add(pluginName);
console.log(`[Amily2Bus] Plugin registered: ${pluginName}`);
}
// 返回该插件专属的 API 上下文 (Capability Token)
return {
// 绑定了身份的日志接口
log: (origin, type, message) => {
if (this.Logger) {
// 自动填充 plugin 参数
this.Logger.log(pluginName, origin, type, message);
}
},
// 绑定了身份的文件接口
file: {
read: (path) => {
return this.FilePipe ? this.FilePipe.read(pluginName, path) : null;
},
write: (path, data) => {
return this.FilePipe ? this.FilePipe.write(pluginName, path, data) : false;
}
}
};
}
}
// 挂载全局单例
if (!window.Amily2Bus) {
window.Amily2Bus = new Amily2Bus();
}
export default Amily2Bus;

3
SL/bus/README.md Normal file
View File

@@ -0,0 +1,3 @@
# Amily2Bus
Amily2总线类提供基础的文件操作方法和标准日志操作方法便于开发者使用和规范化日志处理。同时提供了插件注册和事件监听机制。

61
SL/bus/file/FilePipe.js Normal file
View File

@@ -0,0 +1,61 @@
class FilePipe {
constructor() {
this.name = "FilePipe";
// 模拟的根存储路径,实际环境可能对应 IndexedDB 的 Store Name 或 Node 的 baseDir
this.basePath = "/virtual_fs/";
}
/**
* 安全路径解析与校验
* @param {string} plugin 插件名称(命名空间)
* @param {string} relativePath 相对路径
* @returns {string|null} 合法的绝对路径,如果违规则返回 null
*/
_resolvePath(plugin, relativePath) {
if (!plugin || typeof plugin !== 'string') {
console.error(`[FilePipe] Security Error: Invalid plugin identity.`);
return null;
}
// 简单防越权:禁止包含 ".."
if (relativePath.includes('..')) {
console.error(`[FilePipe] Security Error: Directory traversal attempt blocked for plugin '${plugin}'. Path: ${relativePath}`);
return null;
}
// 强制限定在插件目录下
// 格式: /virtual_fs/PluginName/filename
return `${this.basePath}${plugin}/${relativePath}`;
}
/**
* 读取文件
* @param {string} plugin 调用方插件名
* @param {string} path 文件相对路径
*/
async read(plugin, path) {
const safePath = this._resolvePath(plugin, path);
if (!safePath) return null;
console.log(`[FilePipe] Reading from: ${safePath}`);
// TODO: Implement actual file reading logic
return null;
}
/**
* 写入文件
* @param {string} plugin 调用方插件名
* @param {string} path 文件相对路径
* @param {any} data 数据
*/
async write(plugin, path, data) {
const safePath = this._resolvePath(plugin, path);
if (!safePath) return false;
console.log(`[FilePipe] Writing to: ${safePath}`);
// TODO: Implement actual file writing logic
return true;
}
}
export default FilePipe;

137
SL/bus/log/Logger.js Normal file
View File

@@ -0,0 +1,137 @@
/**
* 日志总类,用于记录日志信息
*/
class Logger {
static LOG_HEADER_DEBUG = '[DEBUG]';
static LOG_HEADER_INFO = '[INFO]';
static LOG_HEADER_WARN = '[WARN]';
static LOG_HEADER_ERROR = '[ERROR]';
static LEVEL_WEIGHTS = {
'debug': 0,
'info': 1,
'warn': 2,
'error': 3
};
constructor() {
// 全局默认级别
this.globalLevel = 'info';
// 针对特定插件或模块的配置
// 结构示例:
// {
// "PluginA": "debug", // PluginA 下所有模块默认为 debug
// "PluginB::ModuleX": "error" // 仅 PluginB 下的 ModuleX 为 error
// }
this.levelConfig = {};
}
/**
* 设置日志级别
* @param {string} target 目标范围,可以是 'Global'、'PluginName' 或 'PluginName::ModuleName'
* @param {string} level 'debug' | 'info' | 'warn' | 'error'
*/
setLevel(target, level) {
if (!Logger.LEVEL_WEIGHTS.hasOwnProperty(level)) {
console.warn(`[Logger] Invalid log level: ${level}`);
return;
}
if (target === 'Global') {
this.globalLevel = level;
console.log(`[Logger] Global log level set to: ${level}`);
} else {
this.levelConfig[target] = level;
console.log(`[Logger] Log level for '${target}' set to: ${level}`);
}
}
/**
* 获取指定上下文的生效日志级别(级联查找)
* @param {string} plugin
* @param {string} origin (Module)
*/
_getEffectiveLevel(plugin, origin) {
// 1. 检查精确匹配 "Plugin::Module"
const specificKey = `${plugin}::${origin}`;
if (this.levelConfig.hasOwnProperty(specificKey)) {
return this.levelConfig[specificKey];
}
// 2. 检查插件级匹配 "Plugin"
if (this.levelConfig.hasOwnProperty(plugin)) {
return this.levelConfig[plugin];
}
// 3. 返回全局默认
return this.globalLevel;
}
log(plugin, origin, type, message, inFile = false) {
// 获取当前上下文生效的日志级别权重
const effectiveLevelName = this._getEffectiveLevel(plugin, origin);
const currentWeight = Logger.LEVEL_WEIGHTS[effectiveLevelName];
const weight = Logger.LEVEL_WEIGHTS[type] !== undefined ? Logger.LEVEL_WEIGHTS[type] : 1;
// 级别筛选
if (weight < currentWeight) {
return;
}
const timestamp = new Date().toLocaleTimeString();
// 格式: [12:00:00] [PluginName::ClassName] [INFO]: message
const fullMessage = `[${timestamp}] [${plugin}::${origin}] [${type.toUpperCase()}]: ${message}`;
// 1. Console Output
switch (type) {
case 'debug':
console.debug(fullMessage);
break;
case 'info':
console.info(fullMessage);
break;
case 'warn':
console.warn(fullMessage);
break;
case 'error':
console.error(fullMessage);
break;
default:
console.log(fullMessage);
break;
}
// 2. File Output (via FilePipe)
if (inFile) {
// Logger 自身也需要作为系统组件注册,获取写入权限
// 注意:为了性能,建议在 constructor 里注册一次保存起来,这里为了逻辑展示暂时简化
if (!this.sysBus) {
if (window.Amily2Bus && window.Amily2Bus.register) {
this.sysBus = window.Amily2Bus.register('SystemLogger');
}
}
if (this.sysBus && this.sysBus.file) {
// 使用注册后的安全接口写入,无需再手动传 'SystemLogger'
// TODO: 这里的路径 'runtime.log' 后续可以做成配置项
this.sysBus.file.write('runtime.log', fullMessage + '\n');
} else {
// Fallback: 如果总线未就绪,仅在控制台警告一次,避免死循环
if (!this._warned) {
console.warn('[Logger] FilePipe system not linked. Log not saved to file.');
this._warned = true;
}
}
}
}
}
// Ensure Amily2Bus namespace exists to prevent crash if loaded out of order
window.Amily2Bus = window.Amily2Bus || {};
window.Amily2Bus.Logger = new Logger();
export default Logger;

View File

@@ -31,3 +31,7 @@
- 添加了**TODO.md**,现在可以记录任务清单并更清楚的记录开发完成状态了。 - 添加了**TODO.md**,现在可以记录任务清单并更清楚的记录开发完成状态了。
- 无实际功能更新 - 无实际功能更新
### 中间版本未维护
### 1.8.3

152
core/bus.js Normal file
View File

@@ -0,0 +1,152 @@
import { eventSource, event_types } from '/script.js';
export const LOG_LEVELS = {
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
NONE: 99
};
class FilePipe {
constructor(pluginName, bus) {
this.pluginName = pluginName;
this.bus = bus;
this.basePath = `extensions/${pluginName}`;
}
/**
* 验证路径是否合法 (仅允许访问插件自己的目录)
* @param {string} path
* @returns {boolean}
*/
validatePath(path) {
// 简单示例:防止 ../ 越权,并确保路径以 basePath 或允许的相对路径开头
// 实际上 SillyTavern 的 writeFile API 通常已经限制在特定目录,
// 但这里我们做逻辑层的隔离。
if (path.includes('..')) {
this.bus.log(this.pluginName, 'FilePipe', 'ERROR', `非法路径尝试: ${path}`);
return false;
}
return true;
}
async read(fileName) {
if (!this.validatePath(fileName)) throw new Error("Access Denied");
// 这里对接实际的文件读取逻辑,例如 fetch 或 ST 的 API
// return await readFileContent(this.basePath + '/' + fileName);
console.log(`[Mock] Reading ${fileName} for ${this.pluginName}`);
}
async write(fileName, content) {
if (!this.validatePath(fileName)) throw new Error("Access Denied");
// 这里对接实际的文件写入逻辑
// return await writeFileContent(this.basePath + '/' + fileName, content);
console.log(`[Mock] Writing to ${fileName} for ${this.pluginName}`);
}
}
class Amily2Bus {
constructor() {
this.plugins = new Map();
this.logConfig = {
globalLevel: LOG_LEVELS.INFO,
filters: {} // e.g., "Amily2.Network": LOG_LEVELS.DEBUG
};
console.log("[Amily2-Bus] 总线系统初始化...");
}
/**
* 注册插件
* @param {Object} manifest - 必须包含 name 字段
* @param {Object} instance - 插件实例或主要对象
* @returns {FilePipe|null} - 返回专属的文件管线对象,注册失败返回 null
*/
register(manifest, instance = {}) {
if (!manifest || !manifest.name) {
this.log('System', 'Bus', 'ERROR', '注册失败: 无效的 manifest');
return null;
}
const name = manifest.name;
if (this.plugins.has(name)) {
this.log('System', 'Bus', 'WARN', `插件 ${name} 已注册,正在覆盖...`);
}
this.plugins.set(name, {
manifest,
instance,
registeredAt: Date.now()
});
this.log('System', 'Bus', 'INFO', `插件注册成功: ${name}`);
// 返回该插件专属的 FilePipe
return new FilePipe(name, this);
}
/**
* 获取已注册的插件实例
* @param {string} name
*/
getPlugin(name) {
return this.plugins.get(name)?.instance;
}
/**
* 设置日志级别
* @param {string} target - "Global" 或 "PluginName.ModuleName"
* @param {string|number} level - "DEBUG", "INFO", etc.
*/
setLogLevel(target, level) {
const lvl = typeof level === 'string' ? LOG_LEVELS[level.toUpperCase()] : level;
if (target === 'Global') {
this.logConfig.globalLevel = lvl;
} else {
this.logConfig.filters[target] = lvl;
}
}
/**
* 统一日志方法
* @param {string} plugin - 插件名
* @param {string} module - 模块名
* @param {string} level - 级别 (DEBUG, INFO, WARN, ERROR)
* @param {string|Object} message - 内容
*/
log(plugin, module, level, message) {
const currentLevel = LOG_LEVELS[level.toUpperCase()] || LOG_LEVELS.INFO;
const configKey = `${plugin}.${module}`;
// 检查特定配置,如果没有则使用全局配置
const requiredLevel = this.logConfig.filters[configKey] !== undefined
? this.logConfig.filters[configKey]
: this.logConfig.globalLevel;
if (currentLevel < requiredLevel) {
return; // 级别不足,忽略
}
const timestamp = new Date().toLocaleTimeString();
const formattedMsg = typeof message === 'object' ? JSON.stringify(message) : message;
// 样式化输出
let color = '#7f8c8d'; // Default (INFO)
if (level === 'WARN') color = '#f39c12';
if (level === 'ERROR') color = '#c0392b';
if (level === 'DEBUG') color = '#3498db';
console.log(
`%c[${timestamp}] [${plugin}::${module}]%c ${formattedMsg}`,
`color: ${color}; font-weight: bold;`,
`color: inherit;`
);
}
}
// 单例模式,挂载到 window 方便全局调用
if (!window.Amily2Bus) {
window.Amily2Bus = new Amily2Bus();
}
export default window.Amily2Bus;