mirror of
https://github.com/SilenceLurker/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 12:25:51 +00:00
180 lines
6.7 KiB
JavaScript
180 lines
6.7 KiB
JavaScript
import Logger from './log/Logger.js';
|
||
import FilePipe from './file/FilePipe.js';
|
||
import ModelCaller from './api/ModelCaller.js';
|
||
import Options from './api/Options.js';
|
||
|
||
// 【逃生通道】创建一个纯净的 Console 对象,绕过任何潜在的劫持
|
||
const getSafeConsole = () => {
|
||
try {
|
||
if (window._amilySafeConsole) return window._amilySafeConsole;
|
||
|
||
const iframe = document.createElement('iframe');
|
||
iframe.style.display = 'none';
|
||
document.body.appendChild(iframe);
|
||
const safe = iframe.contentWindow.console;
|
||
// document.body.removeChild(iframe); // 保持 iframe 以维持 console 引用有效
|
||
window._amilySafeConsole = safe;
|
||
return safe;
|
||
} catch (e) {
|
||
return window.console; // Fallback
|
||
}
|
||
};
|
||
|
||
class Amily2Bus {
|
||
constructor() {
|
||
this.safeConsole = getSafeConsole();
|
||
|
||
// 1. 初始化 Logger
|
||
/** @type {Logger} */
|
||
this.Logger = new Logger(this.safeConsole);
|
||
/** @type {FilePipe} */
|
||
this.FilePipe = new FilePipe();
|
||
|
||
// 2. 依赖注入 (Dependency Injection)
|
||
// 创建一个 Logger 代理适配器传给 ModelCaller
|
||
const loggerDelegate = {
|
||
log: (type, message, origin, plugin) => {
|
||
// 回调 Bus 的 Logger 实例
|
||
this.Logger.process(plugin || 'Global', origin || 'Model', type, message);
|
||
}
|
||
};
|
||
|
||
// ModelCaller 不再包含 Bus,只包含 logger 代理
|
||
/** @type {ModelCaller} */
|
||
this.ModelCaller = new ModelCaller(loggerDelegate);
|
||
|
||
// 存储上下文引用(严格锁:每个插件名仅限一次成功注册)
|
||
this.registry = new Map();
|
||
// 存储公开的联动接口(联动模块)
|
||
this.publicRegistry = new Map();
|
||
|
||
this.safeConsole.log('[Amily2Bus] Core Initialized (Decoupled Architecture).');
|
||
|
||
// 3. 自动注册并锁定 PUBLIC 命名空间
|
||
this._initPublicNamespace();
|
||
}
|
||
|
||
/**
|
||
* 初始化系统保留的 PUBLIC 模块
|
||
* 用于提供系统级信息的联动查询,防止 PUBLIC 命名被滥用
|
||
*/
|
||
_initPublicNamespace() {
|
||
try {
|
||
// 这里利用 register 的机制,直接抢占 'PUBLIC' 并加上严格锁
|
||
const sysCtx = this.register('PUBLIC');
|
||
|
||
// 暴露系统级能力给 query('PUBLIC')
|
||
sysCtx.expose({
|
||
description: 'Amily2 System Public Interface',
|
||
version: '2.0.0-Core',
|
||
|
||
// 允许查询当前有哪些插件暴露了公共接口
|
||
getAvailableModules: () => {
|
||
return Array.from(this.publicRegistry.keys());
|
||
},
|
||
|
||
// 允许查询当前所有已注册(被锁定的)插件名
|
||
getRegisteredPlugins: () => {
|
||
return Array.from(this.registry.keys());
|
||
},
|
||
|
||
// 简单的系统状态检查
|
||
ping: () => 'pong'
|
||
});
|
||
|
||
// 内部记录一条初始化日志
|
||
sysCtx.log('System', 'info', 'PUBLIC namespace reserved and strictly locked.');
|
||
|
||
} catch (e) {
|
||
this.safeConsole.error('[Amily2Bus] CRITICAL: Failed to init PUBLIC namespace.', e);
|
||
}
|
||
}
|
||
/**
|
||
* 直接记录系统级日志 (Global Scope)
|
||
* 支持手动指定来源,方便终端调试或非插件环境调用
|
||
* @param {string} type 日志级别 (debug, info, warn, error)
|
||
* @param {string} message 消息内容
|
||
* @param {string} [origin='Bus' 来源模块,默认为 'Bus'
|
||
* @param {string} [plugin='Global'] 来源插件/命名空间,调试时可指定如 'Console'
|
||
*/
|
||
log(type, message, origin = 'Bus', plugin = 'Global') {
|
||
if (this.Logger) {
|
||
this.Logger.process(plugin, origin, type, message);
|
||
}
|
||
}
|
||
/**
|
||
* 注册插件并获取专属上下文 (严格锁机制)
|
||
* @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)) {
|
||
const errorMsg = `[Amily2Bus] Security Error: Plugin '${pluginName}' is already registered and locked.`;
|
||
this.safeConsole.error(errorMsg);
|
||
throw new Error(errorMsg);
|
||
}
|
||
|
||
// 返回该插件专属的 API 上下文 (Capability Token)
|
||
const context = {
|
||
// 1. 日志能力 (绑定了身份的日志接口)
|
||
log: (origin, type, message) => this.Logger.log(pluginName, origin, type, message),
|
||
|
||
// 2. 文件能力 (绑定了身份的文件接口)
|
||
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;
|
||
}
|
||
},
|
||
|
||
// 3. 网络能力 (ModelCaller)
|
||
model: {
|
||
// 暴露 Options 类,方便插件直接 new context.model.Options() 或使用 builder
|
||
Options: Options,
|
||
// 插件调用时,Bus 负责将 pluginName 传给无状态的 ModelCaller
|
||
call: (messages, options) => this.ModelCaller.call(pluginName, messages, options)
|
||
},
|
||
|
||
/**
|
||
* 将本插件的能力暴露给公共查询池,实现插件间联动
|
||
* @param {Object} apiMethods
|
||
*/
|
||
expose: (apiMethods) => {
|
||
if (typeof apiMethods !== 'object') throw new Error('Exposed API must be an object');
|
||
this.publicRegistry.set(pluginName, Object.freeze(apiMethods));
|
||
this.log('info', `Module exposed to public registry.`, 'Bus', pluginName);
|
||
}
|
||
};
|
||
|
||
this.registry.set(pluginName, context);
|
||
this.safeConsole.log(`[Amily2Bus] Plugin registered: ${pluginName}`);
|
||
return context;
|
||
}
|
||
|
||
/**
|
||
* 联动查询:获取其他插件通过 expose 暴露的能力
|
||
* @param {string} pluginName 目标插件名称
|
||
* @returns {Object|null}
|
||
*/
|
||
query(pluginName) {
|
||
return this.publicRegistry.get(pluginName) || null;
|
||
}
|
||
}
|
||
// 挂载全局单例
|
||
if (!window.Amily2Bus) {
|
||
window.Amily2Bus = new Amily2Bus();
|
||
}
|
||
export function initializeAmilyBus() {
|
||
if (!(window.Amily2Bus instanceof Amily2Bus)) {
|
||
window.Amily2Bus = new Amily2Bus();
|
||
console.log('[Amily2] Amily2Bus 已成功初始化并附加到 window 对象');
|
||
}
|
||
}
|
||
|