From d405f1d624336e5939654b918432ba59247da789 Mon Sep 17 00:00:00 2001 From: SilenceLurker Date: Sun, 18 Jan 2026 11:40:35 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20ModelCaller=20=E5=92=8C=20?= =?UTF-8?q?Options=20=E7=B1=BB=EF=BC=8C=E9=87=8D=E6=9E=84=20Logger?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=BC=BA=20Amily2Bus=20=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0=20README=20=E5=92=8C=20TODO=20?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SL/bus/Amily2Bus.js | 142 ++++++++++++++++++------ SL/bus/README.md | 112 ++++++++++++++++++- SL/bus/api/ModelCaller.js | 223 ++++++++++++++++++++++++++++++++++++++ SL/bus/api/Options.js | 98 +++++++++++++++++ SL/bus/api/RequestBody.js | 68 ++++++++++++ SL/bus/log/Logger.js | 36 +++--- TODO.md | 8 +- 7 files changed, 637 insertions(+), 50 deletions(-) create mode 100644 SL/bus/api/ModelCaller.js create mode 100644 SL/bus/api/Options.js create mode 100644 SL/bus/api/RequestBody.js diff --git a/SL/bus/Amily2Bus.js b/SL/bus/Amily2Bus.js index 957e026..c5be439 100644 --- a/SL/bus/Amily2Bus.js +++ b/SL/bus/Amily2Bus.js @@ -1,5 +1,7 @@ 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 = () => { @@ -21,34 +23,87 @@ const getSafeConsole = () => { class Amily2Bus { constructor() { this.safeConsole = getSafeConsole(); - /** @type {Logger|null} */ - this.Logger = new Logger(); - /** @type {FilePipe|null} */ - this.FilePipe = new FilePipe(); - - // 已注册插件列表,防止重复注册 - this.registry = new Set(); - this.safeConsole.log('[Amily2Bus] Core container initialized with secure registry.'); + // 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} [origin='Bus' 来源模块,默认为 'Bus' * @param {string} [plugin='Global'] 来源插件/命名空间,调试时可指定如 'Console' */ log(type, message, origin = 'Bus', plugin = 'Global') { - this.safeConsole.error('[Amily2Bus DEBUG] log called (via SafeConsole):', { type, loggerExists: !!this.Logger }); if (this.Logger) { this.Logger.process(plugin, origin, type, message); } } - /** - * 注册插件并获取专属上下文 + * 注册插件并获取专属上下文 (严格锁机制) * @param {string} pluginName 插件名称 * @returns {Object} 包含该插件专属 API 的上下文对象 */ @@ -58,23 +113,17 @@ class Amily2Bus { } if (this.registry.has(pluginName)) { - console.warn(`[Amily2Bus] Plugin '${pluginName}' is already registered.`); - } else { - this.registry.add(pluginName); - console.log(`[Amily2Bus] Plugin registered: ${pluginName}`); + const errorMsg = `[Amily2Bus] Security Error: Plugin '${pluginName}' is already registered and locked.`; + this.safeConsole.error(errorMsg); + throw new Error(errorMsg); } // 返回该插件专属的 API 上下文 (Capability Token) - return { - // 绑定了身份的日志接口 - log: (origin, type, message) => { - if (this.Logger) { - // 自动填充 plugin 参数 - this.Logger.log(pluginName, origin, type, message); - } - }, - - // 绑定了身份的文件接口 + 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; @@ -82,18 +131,49 @@ class Amily2Bus { 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 instanceof Amily2Bus)) { +// 挂载全局单例 +if (!window.Amily2Bus) { window.Amily2Bus = new Amily2Bus(); } - export function initializeAmilyBus() { - if (!window.Amily2Bus || !(window.Amily2Bus instanceof Amily2Bus)) { + if (!(window.Amily2Bus instanceof Amily2Bus)) { window.Amily2Bus = new Amily2Bus(); console.log('[Amily2] Amily2Bus 已成功初始化并附加到 window 对象'); } -} \ No newline at end of file +} + diff --git a/SL/bus/README.md b/SL/bus/README.md index ee18cdb..eee4b23 100644 --- a/SL/bus/README.md +++ b/SL/bus/README.md @@ -1,3 +1,111 @@ -# Amily2Bus +# Amily2Bus (Amily2 总线系统) -Amily2总线类,提供基础的文件操作方法和标准日志操作方法,便于开发者使用和规范化日志处理。同时提供了插件注册和事件监听机制。 +Amily2Bus 是 Amily2-Chat-Optimisation 插件系统的核心基础设施。它为所有子模块和外部插件提供了一个规范化、安全且高兼容性的运行环境。 + +## 核心特性 + +- **安全控制台 (SafeConsole)**: 通过 Iframe 逃生通道获取纯净 Console 引用,绕过 SillyTavern 等环境的日志劫持。 +- **能力令牌 (Capability Token)**: 插件注册后获取专属上下文,自动绑定身份,实现日志追踪与文件隔离。 +- **防超时网络层 (FakeStream)**: `ModelCaller` 支持伪流式聚合,通过持续保持 TCP 连接活跃,彻底解决 CloudFlare 524 超时问题。 +- **位运算日志系统**: 基于位掩码的日志级别控制,支持针对特定插件或模块动态调整输出粒度。 +- **异步责任链**: 预置 `Chain` 模块,支持插件化的异步中间件处理流程。 + +--- + +## 快速开始 + +### 1. 初始化 + +总线通常在系统启动时自动挂载到 `window.Amily2Bus`。 + +```javascript +import { initializeAmilyBus } from './SL/bus/Amily2Bus.js'; +initializeAmilyBus(); +``` + +### 2. 插件注册 + +所有插件必须注册以获取专属上下文: + +```javascript +const amily = window.Amily2Bus.register('MyAwesomePlugin'); +``` + +--- + +## 模块说明 + +### 1. 标准日志 (Logger) + +支持 `debug`, `info`, `warn`, `error` 四个级别。 + +```javascript +// 自动绑定插件名,输出格式: [时间] [MyAwesomePlugin::Main] [INFO]: 消息内容 +amily.log('Main', 'info', '插件已就绪'); +``` + +### 2. 网络请求 (ModelCaller & Options) + +统一处理 API 调用,支持自动切换 ST 配置文件 (Profile) 及防超时处理。 + +```javascript +const { Options } = amily.model; + +const opt = Options.builder() + .setMode('direct') // 'direct' (直连) 或 'preset' (ST预设) + .setFakeStream(true) // 开启伪流式聚合,防止 524 超时 + .setApiUrl('...') + .setApiKey('...') + .setModel('gpt-4o') + .build(); + +const result = await amily.model.call(messages, opt); +``` + +### 3. 文件操作 (FilePipe) + +提供基于插件命名的虚拟文件系统隔离,防止插件间非法访问。 + +```javascript +// 写入文件 (自动定位到 /virtual_fs/MyAwesomePlugin/config.json) +await amily.file.write('config.json', { theme: 'dark' }); + +// 读取文件 +const data = await amily.file.read('config.json'); +``` + +### 4. 责任链 (Chain) + +用于处理复杂的、可扩展的异步逻辑流。 + +```javascript +import { Chain } from './SL/bus/chain/Chain.js'; + +const pipeline = new Chain(); +pipeline.use(async (ctx, next) => { + ctx.data += " -> 步骤1处理"; + await next(); +}); + +const context = { data: "开始" }; +await pipeline.execute(context); +``` + +--- + +## 目录结构 + +- `Amily2Bus.js`: 总线入口,协调各模块。 +- `log/Logger.js`: 位运算日志管理器。 +- `file/FilePipe.js`: 安全文件操作管道。 +- `api/ModelCaller.js`: 核心 API 调用器。 +- `api/Options.js`: API 请求配置构建器。 +- `chain/Chain.js`: 异步责任链工具。 + +--- + +## 开发规范 + +1. **强制类型**: `model.call` 必须接收 `Options` 类的实例,建议始终使用 `Options.builder()` 构建参数。 +2. **路径安全**: 使用 `file` 接口时,禁止在路径中使用 `..` 等跳转符,系统会自动进行安全校验。 +3. **日志分级**: 生产环境默认屏蔽 `debug` 级别,调试时可通过 `window.Amily2Bus.Logger.setLevel('PluginName', 'all')` 动态开启。 diff --git a/SL/bus/api/ModelCaller.js b/SL/bus/api/ModelCaller.js new file mode 100644 index 0000000..a308b12 --- /dev/null +++ b/SL/bus/api/ModelCaller.js @@ -0,0 +1,223 @@ +import { getRequestHeaders } from "/script.js"; +import { getContext } from "/scripts/extensions.js"; +import { amilyHelper } from '../../core/tavern-helper/main.js'; +import Options from './Options.js'; +import RequestBody from './RequestBody.js'; + +/** + * ModelCaller Service + * 负责执行 API 调用逻辑,旨在替换 NccsApi 及其他散乱的请求逻辑。 + * 支持:标准直连、ST预设调用、伪流式聚合(防超时)、数据归一化。 + */ +export default class ModelCaller { + /** + * 构造函数注入 Logger 依赖 + * @param {Object} loggerDelegate - { log: (level, msg, origin, plugin) => void } + */ + constructor(loggerDelegate) { + /** @type {Object} */ + this.logger = loggerDelegate; + this.defaultHeaders = { + 'Content-Type': 'application/json' + }; + } + + /** + * 统一调用入口 + * @param {string} callerName - 调用者名称(日志用) + * @param {Array} messages - 聊天消息历史 + * @param {Options} options - 配置对象实例 + * @returns {Promise} - 返回归一化后的文本内容 + */ + async call(callerName, messages, options) { + // 1. 强制类型检查 + if (!(options instanceof Options)) { + const errorMsg = `[ModelCaller] Options must be instance of Options class.`; + throw new TypeError(errorMsg); + } + + // 2. 逻辑中直接使用 options 属性 + this._log('info', `API Request [${options.mode}] Stream: ${options.fakeStream}`, callerName); + + try { + // 统一构建请求体 DTO + const requestBody = new RequestBody(messages, options); + let result; + + if (options.mode === 'preset') { + result = await this._callPreset(callerName, requestBody, options); + } else { + result = await this._callDirect(callerName, requestBody, options); + } + + return this._normalize(result); + } catch (error) { + this._log('error', `Request Failed: ${error.message}`, callerName); + throw error; + } + } + + // 内部日志封装 + _log(level, msg, plugin) { + if (this.logger && typeof this.logger.log === 'function') { + this.logger.log(level, msg, 'ModelCaller', plugin); + } + } + + // ======================================================================== + // 模式一:Direct (标准直连) + // 对应 NccsApi 中的 callNccsOpenAITest + // ======================================================================== + async _callDirect(callerName, requestBody, options) { + // 构建标准 OpenAI 兼容 Body + // 目标通常是 ST 的后端代理接口 + const url = '/api/backends/chat-completions/generate'; + const payload = requestBody.toPayload(); // 使用 DTO 生成数据 + + const fetchOpts = { + method: 'POST', + headers: { ...getRequestHeaders(), ...this.defaultHeaders }, + body: JSON.stringify(payload) + }; + + return options.fakeStream + ? this._fetchFakeStream(url, fetchOpts) + : this._fetchStandard(url, fetchOpts); + } + + // ======================================================================== + // 模式二:Preset (ST预设调用) + // 对应 NccsApi 中的 callNccsSillyTavernPreset + // ======================================================================== + async _callPreset(callerName, requestBody, options) { + const context = getContext(); + + // 1. 记录并切换 Profile + const originalProfile = await amilyHelper.triggerSlash('/profile'); + const targetProfile = context.extensionSettings?.connectionManager?.profiles?.find(p => p.id === options.presetId); + + if (!targetProfile) throw new Error(`Preset ID ${options.presetId} not found`); + + if (originalProfile !== targetProfile.name) { + this._log('info', `Switching profile: ${originalProfile} -> ${targetProfile.name}`, callerName); + const escapedName = targetProfile.name.replace(/"/g, '\\"'); + await amilyHelper.triggerSlash(`/profile await=true "${escapedName}"`); + } + + try { + // 2. 根据流式需求分流处理 + if (options.fakeStream) { + // 【流式预设请求】 + // 难点:ST 的 ConnectionManagerRequestService 不暴露流。 + // 策略:切换 Profile 后,手动向生成接口发送请求。 + const url = '/api/backends/chat-completions/generate'; + // Preset 模式下只需要最小载荷 + const payload = requestBody.toMinimalPayload(); + + const fetchOpts = { + method: 'POST', + headers: { ...getRequestHeaders(), ...this.defaultHeaders }, + body: JSON.stringify(payload) + }; + return await this._fetchFakeStream(url, fetchOpts); + } else { + // 【非流式预设请求】 + // 直接使用 ST 原生服务,最稳妥 + if (!context.ConnectionManagerRequestService) throw new Error('ST Request Service unavailable'); + return await context.ConnectionManagerRequestService.sendRequest( + targetProfile.id, + requestBody.messages, + options.maxTokens + ); + } + + } finally { + // 3. 恢复 Profile + if (originalProfile) { + try { + const current = await amilyHelper.triggerSlash('/profile'); + if (originalProfile !== current) { + const escapedOriginal = originalProfile.replace(/"/g, '\\"'); + await amilyHelper.triggerSlash(`/profile await=true "${escapedOriginal}"`); + } + } catch (e) { + this._log('warn', `Failed to restore profile: ${e.message}`, callerName); + } + } + } + } + + // ======================================================================== + // 网络层核心 + // ======================================================================== + + async _fetchStandard(url, options) { + const response = await fetch(url, options); + if (!response.ok) { + // const text = await response.text(); + throw new Error(`HTTP ${response.status}`); + } + return await response.json(); + } + + // 伪流式聚合:防 CloudFlare 超时 + async _fetchFakeStream(url, options) { + const response = await fetch(url, options); + + if (!response.ok) { + throw new Error(`Stream HTTP ${response.status}`); + } + + if (!response.body) { + return await response.json(); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder("utf-8"); + let aggregated = ""; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + // 持续读取保持连接活跃 + aggregated += decoder.decode(value, { stream: true }); + } + aggregated += decoder.decode(); + + try { + return JSON.parse(aggregated); + } catch (e) { + // 如果是 SSE 格式或其他非 JSON 格式,暂且返回文本 + return aggregated; + } + } finally { + reader.releaseLock(); + } + } + + // ======================================================================== + // 数据归一化 + // ======================================================================== + + _normalize(data) { + // 如果是 JSON 字符串则解析 + if (typeof data === 'string') { + try { data = JSON.parse(data); } catch (e) { return data; } + } + + // 处理 OpenAI 格式 + if (data?.choices?.[0]?.message?.content) { + return data.choices[0].message.content.trim(); + } + + // 处理常规 content 格式 + if (data?.content) { + return data.content.trim(); + } + + // Fallback + return typeof data === 'object' ? JSON.stringify(data) : data; + } +} + diff --git a/SL/bus/api/Options.js b/SL/bus/api/Options.js new file mode 100644 index 0000000..45ec288 --- /dev/null +++ b/SL/bus/api/Options.js @@ -0,0 +1,98 @@ +/** + * ModelCaller 请求配置类 + * 支持构造函数直接传入对象,或使用 Builder 链式调用 + */ +export class Options { + constructor(config = {}) { + /** @type {'direct'|'preset'} */ + this.mode = config.mode || 'direct'; + /** @type {boolean} */ + this.fakeStream = config.fakeStream ?? false; + /** @type {string} */ + this.apiUrl = config.apiUrl || ''; + /** @type {string} */ + this.apiKey = config.apiKey || ''; + /** @type {string} */ + this.model = config.model || ''; + /** @type {string} */ + this.presetId = config.presetId || ''; + /** @type {number} */ + this.maxTokens = config.maxTokens || 4000; + /** @type {number} */ + this.temperature = config.temperature || 0.7; + /** @type {Object} 额外透传参数 */ + this.params = config.params || {}; + } + + /** + * 获取 Builder 实例 + * @returns {OptionsBuilder} + */ + static builder() { + return new OptionsBuilder(); + } +} + +/** + * Options 构建器类 + */ +class OptionsBuilder { + constructor() { + this.config = {}; + } + + setMode(mode) { + this.config.mode = mode; + return this; + } + + setFakeStream(enabled) { + this.config.fakeStream = enabled; + return this; + } + + setApiUrl(url) { + this.config.apiUrl = url; + return this; + } + + setApiKey(key) { + this.config.apiKey = key; + return this; + } + + setModel(model) { + this.config.model = model; + return this; + } + + setPresetId(id) { + this.config.presetId = id; + return this; + } + + setMaxTokens(tokens) { + this.config.maxTokens = tokens; + return this; + } + + setTemperature(temp) { + this.config.temperature = temp; + return this; + } + + setParams(params) { + this.config.params = { ...(this.config.params || {}), ...params }; + return this; + } + + /** + * 构建最终的 Options 对象 + * @returns {Options} + */ + build() { + return new Options(this.config); + } +} + +export default Options; \ No newline at end of file diff --git a/SL/bus/api/RequestBody.js b/SL/bus/api/RequestBody.js new file mode 100644 index 0000000..3de496d --- /dev/null +++ b/SL/bus/api/RequestBody.js @@ -0,0 +1,68 @@ +import Options from './Options.js'; + +/** + * RequestBody (DTO) + * 严格约束发送给 LLM/ST 后端的请求体结构 + * 类似于 Java 中的 RequestBean + */ +export class RequestBody { + /** + * @param {Array} messages + * @param {Options} options + */ + constructor(messages, options) { + if (!Array.isArray(messages)) throw new TypeError('messages must be an array'); + if (!(options instanceof Options)) throw new TypeError('options must be an instance of Options'); + + this.messages = messages; + this.options = options; + } + + /** + * 构建标准 OpenAI 兼容的 Payload + * @returns {Object} 纯净的 JSON 对象 + */ + toPayload() { + const { apiUrl, apiKey, model, maxTokens, temperature, params } = this.options; + const isGoogle = apiUrl && apiUrl.includes('googleapis.com'); + + // 基础字段 (Base Fields) + const payload = { + chat_completion_source: 'openai', + messages: this.messages, + model: model, + reverse_proxy: apiUrl, + proxy_password: apiKey, + stream: false, // 这里的 stream 是指 ST 后端行为,始终为 false + max_tokens: maxTokens, + temperature: temperature, + // 允许 Options 中的 params 覆盖上述字段 + ...params + }; + + // 平台特定字段处理 (Platform Specific Logic) + if (!isGoogle) { + Object.assign(payload, { + custom_prompt_post_processing: 'strict', + presence_penalty: 0.12, + include_reasoning: false, + reasoning_effort: 'medium' + }); + } + + return payload; + } + + /** + * 仅获取消息体的 Payload (用于 Preset 模式) + */ + toMinimalPayload() { + return { + messages: this.messages, + max_tokens: this.options.maxTokens, + ...this.options.params + }; + } +} + +export default RequestBody; \ No newline at end of file diff --git a/SL/bus/log/Logger.js b/SL/bus/log/Logger.js index 0c09656..8e43576 100644 --- a/SL/bus/log/Logger.js +++ b/SL/bus/log/Logger.js @@ -18,7 +18,9 @@ class Logger { all: 0xF // 15 }; - constructor() { + constructor(safeConsole = null) { + // 使用传入的 safeConsole,如果没有则回退到全局 console + this.safeConsole = safeConsole || (typeof window !== 'undefined' ? window.console : console); // 全局默认级别 (默认开启 info, warn, error) this.globalLevel = Logger.LOG_LEVEL_CODE.info | Logger.LOG_LEVEL_CODE.warn | Logger.LOG_LEVEL_CODE.error; @@ -49,7 +51,7 @@ class Logger { if (levelInput.includes('|')) { return levelInput.split('|').reduce((mask, l) => mask | (Logger.LOG_LEVEL_CODE[l.trim()] || 0), 0); } - console.warn(`[Logger] Unknown log level string: ${levelInput}`); + this.safeConsole.warn(`[Logger] Unknown log level string: ${levelInput}`); return 0; } @@ -70,10 +72,10 @@ class Logger { if (target === 'Global') { this.globalLevel = mask; - console.log(`[Logger] Global log level mask set to: ${mask.toString(2)}`); + this.safeConsole.log(`[Logger] Global log level mask set to: ${mask.toString(2)}`); } else { this.levelConfig[target] = mask; - console.log(`[Logger] Log level mask for '${target}' set to: ${mask.toString(2)}`); + this.safeConsole.log(`[Logger] Log level mask for '${target}' set to: ${mask.toString(2)}`); } } @@ -89,11 +91,11 @@ class Logger { if (target === 'Global') { currentMask = this.globalLevel; this.globalLevel = currentMask | maskToAdd; - console.log(`[Logger] Added level to Global. New mask: ${this.globalLevel.toString(2)}`); + this.safeConsole.log(`[Logger] Added level to Global. New mask: ${this.globalLevel.toString(2)}`); } else { currentMask = this.levelConfig[target] !== undefined ? this.levelConfig[target] : this.globalLevel; this.levelConfig[target] = currentMask | maskToAdd; - console.log(`[Logger] Added level to '${target}'. New mask: ${this.levelConfig[target].toString(2)}`); + this.safeConsole.log(`[Logger] Added level to '${target}'. New mask: ${this.levelConfig[target].toString(2)}`); } } @@ -110,11 +112,11 @@ class Logger { currentMask = this.globalLevel; // 使用 & ~mask 实现移除 this.globalLevel = currentMask & ~maskToRemove; - console.log(`[Logger] Removed level from Global. New mask: ${this.globalLevel.toString(2)}`); + this.safeConsole.log(`[Logger] Removed level from Global. New mask: ${this.globalLevel.toString(2)}`); } else { currentMask = this.levelConfig[target] !== undefined ? this.levelConfig[target] : this.globalLevel; this.levelConfig[target] = currentMask & ~maskToRemove; - console.log(`[Logger] Removed level from '${target}'. New mask: ${this.levelConfig[target].toString(2)}`); + this.safeConsole.log(`[Logger] Removed level from '${target}'. New mask: ${this.levelConfig[target].toString(2)}`); } } @@ -145,7 +147,8 @@ class Logger { */ process(plugin, origin, type, message, inFile = false) { // [DEBUG] 强制输出以确认方法被调用 (使用 error 级别防止被过滤) - console.error('[Logger DEBUG] Process called:', { plugin, origin, type, message }); + // 【核心修改】:使用 safeConsole 替代全局 console + this.safeConsole.error('[Logger DEBUG] Process called:', { plugin, origin, type, message }); // 1. 默认归属处理 const safePlugin = plugin || 'Global'; @@ -163,25 +166,26 @@ class Logger { } const timestamp = new Date().toLocaleTimeString(); - // 格式: [12:00:00] [PluginName::ClassName] [INFO]: message + // 格式: [12:00:00] [PluginName::ClassName] [INFO: message const fullMessage = `[${timestamp}] [${safePlugin}::${safeOrigin}] [${type.toUpperCase()}]: ${message}`; // 5. Console Output + // 【核心修改】:使用 safeConsole 替代全局 console switch (type) { case 'debug': - console.debug(fullMessage); + this.safeConsole.debug(fullMessage); break; case 'info': - console.info(fullMessage); + this.safeConsole.info(fullMessage); break; case 'warn': - console.warn(fullMessage); + this.safeConsole.warn(fullMessage); break; case 'error': - console.error(fullMessage); + this.safeConsole.error(fullMessage); break; default: - console.log(fullMessage); + this.safeConsole.log(fullMessage); break; } @@ -200,7 +204,7 @@ class Logger { } else { // Fallback: 如果总线未就绪,仅在控制台警告一次,避免死循环 if (!this._warned) { - console.warn('[Logger] FilePipe system not linked. Log not saved to file.'); + this.safeConsole.warn('[Logger] FilePipe system not linked. Log not saved to file.'); this._warned = true; } } diff --git a/TODO.md b/TODO.md index 8711baf..9c9bfcb 100644 --- a/TODO.md +++ b/TODO.md @@ -32,6 +32,12 @@ - 添加了**TODO.md**,现在可以记录任务清单并更清楚的记录开发完成状态了。 - 无实际功能更新 -### 中间版本未维护 +### 中间版本未维护该文件 ### 1.8.3 + +以下为修复内容 + +以下为更新内容: + +- 添加记忆管理并发调用