添加 ModelCaller 和 Options 类,重构 Logger,增强 Amily2Bus 架构,更新 README 和 TODO 文件

This commit is contained in:
2026-01-18 11:40:35 +08:00
parent 4e9f2defb7
commit d405f1d624
7 changed files with 637 additions and 50 deletions

View File

@@ -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} */
// 1. 初始化 Logger
/** @type {Logger} */
this.Logger = new Logger(this.safeConsole);
/** @type {FilePipe} */
this.FilePipe = new FilePipe();
// 已注册插件列表,防止重复注册
this.registry = new Set();
// 2. 依赖注入 (Dependency Injection)
// 创建一个 Logger 代理适配器传给 ModelCaller
const loggerDelegate = {
log: (type, message, origin, plugin) => {
// 回调 Bus 的 Logger 实例
this.Logger.process(plugin || 'Global', origin || 'Model', type, message);
}
};
this.safeConsole.log('[Amily2Bus] Core container initialized with secure registry.');
// 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 对象');
}
}

View File

@@ -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')` 动态开启。

223
SL/bus/api/ModelCaller.js Normal file
View File

@@ -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<string>} - 返回归一化后的文本内容
*/
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;
}
}

98
SL/bus/api/Options.js Normal file
View File

@@ -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;

68
SL/bus/api/RequestBody.js Normal file
View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -32,6 +32,12 @@
- 添加了**TODO.md**,现在可以记录任务清单并更清楚的记录开发完成状态了。
- 无实际功能更新
### 中间版本未维护
### 中间版本未维护该文件
### 1.8.3
以下为修复内容
以下为更新内容:
- 添加记忆管理并发调用