Caliber.js Framework Library

一个旨在帮助开发者快速构建功能强大、可维护的现代油猴脚本的框架。它提供模块化架构、自动化的UI设置面板、响应式生命周期管理、高性能DOM调度器与网络请求拦截器等核心功能,让您专注于实现创意,而非繁琐的底层细节。

Skrip ini tidak untuk dipasang secara langsung. Ini adalah pustaka skrip lain untuk disertakan dengan direktif meta // @require https://update.greasyfork.org/scripts/545792/1641440/Caliberjs%20Framework%20Library.js

// ==UserScript==
// @name         Caliber.js Framework Library
// @namespace    You Boy
// @version      1.0.0
// @description  一个旨在帮助开发者快速构建功能强大、可维护的现代油猴脚本的框架。它提供模块化架构、自动化的UI设置面板、响应式生命周期管理、高性能DOM调度器与网络请求拦截器等核心功能,让您专注于实现创意,而非繁琐的底层细节。
// @author       You Boy
// @license      MIT
// @grant        none
// ==/UserScript==
((window) => {
  'use strict';

  if (window.Caliber) {
    console.warn('Caliber.js has already been loaded.');
    return;
  }

  const Caliber = (() => {
    'use strict';

    // #region --- 类型定义 (Type Definitions) ---
    /**
     * @typedef {object} DomBatchProcessorInstance 高性能DOM批量处理调度器实例
     * @property {(selector: string, callback: (node: HTMLElement) => void, options?: object) => Symbol} register - 注册一个DOM处理任务,返回一个唯一的任务ID。
     * @property {(taskId: Symbol) => void} unregister - 根据任务ID注销一个DOM处理任务。
     */

    /**
     * @typedef {object} ConfigManagerInstance 配置管理器实例
     * @property {() => object} getConfig - 获取当前已加载的配置对象。
     * @property {(path: string, value: any) => Promise<void>} updateAndSave - 按路径更新配置项并自动保存。
     * @property {() => Promise<void>} save - 手动将当前配置保存到存储中。
     */

    /**
     * @typedef {object} ModuleManagerInstance 模块管理器实例
     * @property {(ModuleClass: typeof Module) => void} register - 注册一个模块类。
     * @property {() => Module[]} getAllRegisteredModules - 获取所有已注册模块的元数据,主要用于UI生成。
     */

    /**
     * @typedef {object} LoggerInstance 日志记录器实例
     * @property {(message: any, ...args: any[]) => void} log - 输出一条标准日志。
     * @property {(message: any, ...args: any[]) => void} warn - 输出一条警告日志。
     * @property {(message: any, ...args: any[]) => void} error - 输出一条错误日志。
     * @property {(tag: string, styleOptions?: object) => LoggerInstance} createTaggedLogger - 创建一个带有自定义标签和样式的子日志记录器。
     */

    /**
     * @typedef {object} EventBusInstance 全局事件总线实例
     * @property {(eventName: string, callback: (data: any) => void) => void} on - 订阅一个事件。
     * @property {(eventName: string, callback: (data: any) => void) => void} off - 取消订阅一个事件。
     * @property {(eventName: string, data?: any) => void} emit - 发布一个事件。
     */

    /**
     * @typedef {object} StorageAdapter 存储服务适配器
     * @property {() => Promise<object>} get - 异步获取存储的配置对象。
     * @property {(value: object) => Promise<void>} set - 异步将配置对象写入存储。
     */

    /**
     * @typedef {object} StyleAdapter 样式服务适配器
     * @property {(cssString: string, id: string) => Promise<void>} add - 注入一段CSS样式,并关联一个唯一ID。
     * @property {(id: string) => void} remove - 根据ID移除之前注入的样式。
     */

    /**
     * @typedef {string} FetchInterceptorRequestString
     * 
     * 定义当一个网络请求被成功匹配时,在**发起实际请求之前**要执行的修改逻辑。
     * 这段代码在宿主页面的上下文中运行,拥有访问`window`对象的全部能力,但必须是一个无闭包依赖的纯字符串。
     * 
     *  示例:为一个API请求添加或修改一个查询参数
     * .onRequest(`
     *   [可用上下文]
     *   - url: {string} 原始的、完整的请求URL字符串。
     *   - config: {object} 原始的fetch配置对象 (如 method, headers, body 等)。
     *   - urlObject: {URL} 由框架预先创建的URL实例,强烈推荐使用它来操作URL。
     * 
     *   1. (推荐) 使用内置的 urlObject 来修改URL
     *   urlObject.searchParams.set('new_param', 'caliber_rocks');
     * 
     *   2. (可选) 修改 fetch 配置对象
     *   config.headers = { ...config.headers, 'X-Caliber-Injected': 'true' };
     *
     *   [返回契约]
     *   必须返回一个包含 'url' 和 'config' 键的对象。
     *   - url: {string} 最终将要被请求的URL字符串。
     *   - config: {object} 最终将要被使用的fetch配置对象。
     *   return { url: urlObject.toString(), config };
     * `)
     * 
     *  示例:添加或修改URL查询参数
     * > .onRequest(`
     *   [可用上下文]
     *   - url: {string} 原始的、完整的请求URL字符串。
     *   - config: {object} 原始的fetch配置对象 (如 method, headers, body 等)。
     *   - urlObject: {URL} 由框架预先创建的URL实例,强烈推荐使用它来操作URL。
     * 
     *   (推荐) 使用内置的 urlObject 来修改URL的查询参数
     *   > urlObject.searchParams.set('page', '1');
     *   > urlObject.searchParams.set('limit', '50');
     *
     *   必须返回一个包含 'url' 和 'config' 键的对象。
     *   > return { url: urlObject.toString(), config };
     * 
     * > `)
     * 
     */

    /**
     * @typedef {object} FetchInterceptorBuilder 网络请求拦截器构建器
     * @property {(handlerString: FetchInterceptorRequestString) => FetchInterceptorBuilder} onRequest - 定义请求被拦截时要执行的逻辑。
     * @property {(callback: (responseData: any) => void) => FetchInterceptorBuilder} onResponse - 定义在沙箱中处理响应数据的回调函数。
     * @property {(id: string|string[]) => void} register - 最终确定并注册这个拦截器。
     */

    /**
     * @typedef {object} PageScopeExecutorInstance 页面作用域代码执行器服务实例
     * @property {(codeString: string) => Promise<any>} execute - 在宿主页面环境中异步执行一段JS代码,并返回其可序列化的结果。
     */

    /**
     * @callback ResponseCallback
     * @param {any} responseData - 从注入脚本传回的、已解析的响应数据。
     */

    /**
     * @typedef {object} FetchInterceptorInstance 网络请求拦截器服务实例
     * @property {(urlOrOptions: string|{url: string, method?: string, match?: string|RegExp|Array<string|RegExp>}) => FetchInterceptorBuilder} target - [推荐] 启动一个链式调用来构建拦截器。
     * @property {(options: {targetUrl: string, method?: string, handler: string}) => string} createHook - (底层) 创建一个钩子函数字符串。
     * @property {(path: string[]|string, hookFunctionString: string, awaitsResponse?: boolean) => void} addHook - (底层) 添加一个仅修改请求的钩子。
     * @property {(path: string[]|string, hookFunctionString: string, responseCallback: ResponseCallback) => void} addHookWithResponse - (底层) 添加一个带响应回调的钩子。
     * @property {(path: string[]|string) => void} removeHook - (底层/通用) 按ID移除一个已注册的拦截器。
     */

    /**
     * @typedef {object} DOMSanitizerInstance DOM净化与安全注入服务实例
     * @property {(htmlString: string) => (TrustedHTML|string)} createTrustedHTML - 创建一个受信任的HTML对象(如果Trusted Types策略可用)。
     * @property {(element: Element, htmlString: string) => void} setInnerHTML - 安全地设置一个元素的innerHTML。
     * @property {(doc: Document, codeString: string) => void} injectScript - 安全地向文档注入一段JavaScript代码。
     * @property {(doc: Document, cssString: string, id: string) => (HTMLStyleElement|null)} injectStyle - 安全地向文档注入一段CSS样式。
     */

    /**
     * @typedef {object} FrameworkServices 框架核心服务集合
     * @property {DomBatchProcessorInstance} scheduler - 高性能DOM批量处理调度器。
     * @property {FetchInterceptorInstance} interceptor - 网络请求拦截器。
     * @property {DOMSanitizerInstance} sanitizer - DOM净化与安全注入服务。
     * @property {PageScopeExecutorInstance} executor - 页面作用域代码执行器。
     */

    /**
     * @typedef {object} CaliberServices 内核服务包
     * @property {LoggerInstance} logger - 日志服务实例。
     * @property {EventBusInstance} eventBus - 全局事件总线实例。
     * @property {Window} hostWindow - 宿主环境的 window 对象 (通常是 unsafeWindow)。
     * @property {Document} hostDocument - 宿主环境的 document 对象。
     * @property {StorageAdapter} storage - 存储服务适配器 (e.g., GM.getValue/setValue)。
     * @property {StyleAdapter} style - 样式服务适配器 (e.g., GM.addStyle)。
     * @property {string} APP_NAME - 当前应用的名称。
     * @property {ConfigManagerInstance} configManager - 配置管理器实例。
     * @property {ModuleManagerInstance} moduleManager - 模块管理器实例。
     * @property {FrameworkServices} framework - 框架核心服务集合。
     */

    /**
     * @typedef {object} ModuleInterface 模块类的公共API与内部属性接口
     * @property {string} id - 模块的唯一标识符,用于配置和管理。
     * @property {string} name - 模块的显示名称,用于UI。
     * @property {string} description - 模块的功能描述,用于UI。
     * @property {object} defaultConfig - 模块的默认配置。
     * @property {string|RegExp|Array<string|RegExp>|null} [match=null] - (可选) 限制模块仅在匹配该规则的页面上运行。
     * 
     * @property {CaliberServices} _services - (底层) 内核注入的完整核心服务集合。
     * @property {object} _config - 模块在总配置对象中的专属部分。
     * @property {LoggerInstance} _logger - (快捷方式) 日志记录器。
     * @property {EventBusInstance} _eventBus - (快捷方式) 事件总线。
     * @property {Window} _hostWindow - (快捷方式) 宿主 window 对象。
     * @property {Document} _hostDocument - (快捷方式) 宿主 document 对象。
     * @property {DomBatchProcessorInstance} _scheduler - (快捷方式) 高性能DOM批量处理调度器。
     * @property {FetchInterceptorInstance} _interceptor - (快捷方式) 网络请求拦截器。
     * @property {DOMSanitizerInstance} _sanitizer - (快捷方式) DOM净化与安全注入服务。
     * @property {PageScopeExecutorInstance} _executor - (快捷方式) 页面作用域代码执行器
     * 
     * @property {(context: {params: object, query: object}) => void} onEnable - 当模块被启用时调用。会收到包含解析后URL参数的上下文对象。
     * @property {() => void} onDisable - 当模块被禁用时调用。必须在此处清理所有副作用。
     * @property {(key: string, newValue: any, oldValue: any) => void} onConfigChange - 当模块的特定配置项发生变化时调用。
     */

    // #endregion

    /**
     * @class Module - 功能模块的标准化基类
     * 
     * 所有功能模块都应继承此类,以确保接口统一和生命周期管理。
     * @implements {ModuleInterface}
     */
    class Module {
      id = 'base-module';
      name = 'Base Module';
      description = '这是一个模块模板。';
      defaultConfig = {};
      match = null;

      _services;
      _config;
      _logger;
      _eventBus;
      _hostWindow;
      _hostDocument;
      _scheduler;
      _interceptor;
      _sanitizer;
      _executor;

      /**
       * @param {CaliberServices} services - 内核注入的核心服务。
       * @param {object} moduleConfig - 该模块在总配置中的专属配置部分。
       */
      constructor(services, moduleConfig) {
        if (!services) {
          return;
        }

        this._services = services;
        this._config = moduleConfig;
        this._logger = services.logger;
        this._eventBus = services.eventBus;
        this._hostWindow = services.hostWindow;
        this._hostDocument = services.hostDocument;

        // framework内部工具快捷方式
        this._scheduler = services.framework.scheduler;
        this._interceptor = services.framework.interceptor;
        this._sanitizer = services.framework.sanitizer;
        this._executor = services.framework.executor;
      }
      /**
       * 当模块被启用时调用。所有事件监听和DOM操作应在此处初始化。
       * @param {{params: object, query: object}} context - 包含从URL解析出的命名参数 (`params`) 和查询参数 (`query`) 的对象。
       */
      onEnable(context) {
        this._logger.warn(`Module '${this.id}' is missing the 'onEnable' implementation.`);
      }

      onDisable() { }

      /**
       * 当模块的特定配置项发生变化时调用。
       * @param {string} key - 发生变化的配置键。
       * @param {*} newValue - 新的配置值。
       * @param {*} oldValue - 旧的配置值。
       */
      onConfigChange(key, newValue, oldValue) { }
    }

    /**
     * @class AppKernel - 应用程序内核
     * 
     * 负责管理模块生命周期、配置、UI和所有核心服务。
     */
    class AppKernel {
      #services = {};
      #moduleManager;
      #configManager;
      #uiManager;
      #logger;
      #auditor = null;

      /**
       * @param {object} injectedServices - 由 `createApp` 组装好的所有核心服务和配置。
       * @param {Window} injectedServices.hostWindow - 宿主 window 对象。
       * @param {Document} injectedServices.hostDocument - 宿主 document 对象。
       * @param {EventBusInstance} injectedServices.eventBus - 全局事件总线。
       * @param {LoggerInstance} injectedServices.logger - 主日志记录器。
       * @param {StorageAdapter} injectedServices.storage - 存储服务适配器。
       * @param {StyleAdapter} injectedServices.style - 样式服务适配器。
       * @param {FrameworkServices} injectedServices.framework - 框架内部服务集合。
       * @param {string} injectedServices.APP_NAME - 应用名称。
       * @param {string} injectedServices.SAFE_APP_NAME - 安全的应用名称。
       * @param {boolean} injectedServices.IS_DEBUG - 是否为调试模式。
       * @param {object} injectedServices.initialConfig - 框架的初始配置。
       */
      constructor(injectedServices) {
        const {
          // --- 运行时服务 ---
          hostWindow,
          hostDocument,
          eventBus,
          logger,
          storage,
          style,
          framework,
          APP_NAME,
          SAFE_APP_NAME,
          // --- 元数据/配置 ---
          IS_DEBUG,
          initialConfig,
        } = injectedServices;

        this.#logger = logger;

        // 将“运行时服务”组装到内核的 #services 对象中
        this.#services = { hostWindow, hostDocument, eventBus, logger, storage, style, framework, APP_NAME };

        // 使用元数据进行初始化
        if (IS_DEBUG) {
          this.#auditor = new ModuleAuditor(this.#logger, hostWindow, SAFE_APP_NAME);
          this.#auditor?.patchScheduler(framework.scheduler);
        }

        // 创建并添加 Kernel 自身管理的核心服务实例
        this.#configManager = new ConfigManager(storage, logger, initialConfig);
        this.#moduleManager = new ModuleManager(this.#services, this.#auditor);
        this.#uiManager = new UIManager(this.#services);

        // 将新创建的管理器添加回 #services 包,以便所有模块都能访问它们
        this.#services.configManager = this.#configManager;
        this.#services.moduleManager = this.#moduleManager;

        this.#logger.log('Kernel constructed.');

        this.#services.eventBus.on('command:toggle-settings-panel', this.#handleToggleSettingsPanel);
        this.#services.eventBus.on('config-updated', this.#onConfigUpdated);
      }

      #handleToggleSettingsPanel = async () => {
        const currentConfig = this.#configManager.getConfig();
        if (!currentConfig) {
          this.#logger.error("Cannot toggle settings panel: config not loaded yet.");
          return;
        }
        const newState = !currentConfig.settingsPanel.enabled;
        await this.#configManager.updateAndSave('settingsPanel.enabled', newState);

        this.#services.eventBus.emit('config-updated', {
          path: 'settingsPanel.enabled',
          value: newState,
          newConfig: this.#configManager.getConfig()
        });
      }

      #onConfigUpdated = (detail) => {
        if (detail.path === 'settingsPanel.enabled') {
          this.#logger.log(`Settings Panel visibility changed to: ${detail.value}`);
          if (detail.value) {
            this.#uiManager.showPanelTrigger();
          } else {
            this.#uiManager.hidePanelTrigger();
          }
        }
      }

      /**
       * 启动应用。这是整个应用逻辑的入口点。
       */
      async run() {
        this.#logger.log('Kernel is running...');

        const moduleDefaultConfigs = this.#moduleManager.getAllDefaultConfigs();
        const finalConfig = await this.#configManager.loadAndGetConfig(moduleDefaultConfigs);
        this.#moduleManager.initializeActiveModules(finalConfig);
        this.#uiManager.init(finalConfig);

        this.#logger.log('Kernel run sequence complete.');
      }

      /**
       * 注册一个功能模块类。
       * @param {typeof Module} ModuleClass - 要注册的模块类 (注意是类本身,不是实例)。
       */
      registerModule(ModuleClass) {
        this.#moduleManager.register(ModuleClass);
      }
    }

    /**
     * @class ConfigManager - 配置管理器
     * 
     * 负责加载、合并、保存配置。
     */
    class ConfigManager {
      #storage;
      #logger;
      #config;
      #initialConfig;

      constructor(storage, logger, initialConfig) {
        this.#storage = storage;
        this.#logger = logger;
        this.#initialConfig = initialConfig;
      }

      /**
       * 加载、合并配置,并返回最终结果。
       * @param {object} moduleDefaultConfigs - 所有模块的默认配置集合。
       * @returns {Promise<object>} 最终的运行时配置。
       */
      async loadAndGetConfig(moduleDefaultConfigs) {
        const userConfig = await this.#storage.get();
        const baseConfig = { ...this.#initialConfig, ...moduleDefaultConfigs };
        let mergedConfig = this.#deepMerge(baseConfig, userConfig);
        this.#config = this.#prune(mergedConfig, baseConfig);

        this.#logger.log('Configuration loaded, merged, and pruned:', this.#config);
        return this.#config;
      }

      getConfig() {
        return this.#config;
      }

      /**
       * 通过路径更新配置树中的一个值,并触发保存。
       * @param {string} path - 要更新的配置路径,例如 'modules.themeSwitcher.theme'
       * @param {*} value - 新的配置值
       */
      async updateAndSave(path, value) {
        this.#set(this.#config, path, value);
        await this.save();
      }

      /**
       * 将当前配置保存到存储中。
       */
      async save() {
        await this.#storage.set(this.#config);
        this.#logger.log('Configuration saved.');
      }

      #deepMerge(target, source) {
        const output = { ...target };
        if (this.#isObject(target) && this.#isObject(source)) {
          for (const key in source) {
            const targetValue = target[key];
            const sourceValue = source[key];

            if (this.#isObject(targetValue) && this.#isObject(sourceValue)) {
              output[key] = this.#deepMerge(targetValue, sourceValue);
            }
            else if (this.#isObject(targetValue) && !this.#isObject(sourceValue)) {
              continue;
            }
            else {
              output[key] = sourceValue;
            }
          }
        }
        return output;
      }

      #isObject = (item) => (item && typeof item === 'object' && !Array.isArray(item));

      #set = (obj, path, value) => {
        const keys = path.split('.');
        const lastKey = keys.pop();
        const finalObj = keys.reduce((o, k) => o[k] = o[k] || {}, obj);
        finalObj[lastKey] = value;
      };

      /**
       * 以 template 为模板,递归地净化 source 对象。
       * 1. 移除所有不存在于 template 中的键。
       * 2. 检查值的类型,如果 source 中的值的类型与 template 不匹配,则强制回退到 template 的默认值。
       * 这是框架数据自愈能力的核心。
       * @param {object} source - 要被净化的对象 (例如,合并后的配置)。
       * @param {object} template - 权威的结构模板 (例如,默认基础配置)。
       * @returns {object} 净化后的新对象。
       * @private
       */
      #prune(source, template) {
        const prunedSource = {};

        for (const key in template) {
          if (source.hasOwnProperty(key)) {
            const sourceValue = source[key];
            const templateValue = template[key];

            const sourceType = typeof sourceValue;
            const templateType = typeof templateValue;

            // 核心净化逻辑:
            // 1. 如果类型匹配且都是对象,则递归净化。
            // 2. 如果类型匹配但不是对象,则接受用户的源值。
            // 3. 如果类型不匹配,则无条件地丢弃用户的源值,回退到模板的默认值。
            if (sourceType === templateType) {
              if (this.#isObject(sourceValue) && this.#isObject(templateValue)) {
                prunedSource[key] = this.#prune(sourceValue, templateValue);
              } else {
                prunedSource[key] = sourceValue; // 类型匹配,接受用户的值
              }
            } else {
              this.#logger.warn(`Configuration type mismatch for key '${key}'. User value (${sourceType}) discarded. Falling back to default (${templateType}).`);
              prunedSource[key] = templateValue; // 类型不匹配,强制回退
            }
          } else {
            // 如果用户的配置中缺少这个键,直接使用模板的默认值
            prunedSource[key] = template[key];
          }
        }
        // 遍历 source 中存在、但 template 中不存在的键,并将它们保留下来
        for (const key in source) {
          if (!template.hasOwnProperty(key)) {
            prunedSource[key] = source[key];
            if (this.#logger) this.#logger.log(`Dynamically added key '${key}' from user config has been preserved.`);
          }
        }
        return prunedSource;
      }
    }

    /**
     * @class ModuleManager - 模块管理器
     * 
     * 负责注册、实例化和管理所有模块的生命周期。
     */
    class ModuleManager {
      #services;
      #registeredModuleClasses = new Map();
      #activeModuleInstances = new Map();
      #auditor = null;

      constructor(services, auditor) {
        this.#services = services;
        this.#auditor = auditor;
        this.#services.eventBus.on('config-updated', this.#onConfigUpdated);
        this.#services.eventBus.on('navigate', this.#onNavigate);
      }

      /**
       * 注册一个模块类。
       * @param {typeof Module} ModuleClass
       */
      register(ModuleClass) {
        if (typeof ModuleClass !== 'function') {
          this.#services.logger.warn(`Attempted to register an invalid value. Expected a class.`, ModuleClass);
          return;
        }

        const tempInstance = new ModuleClass();

        // 校验模块实例是否符合最基本的规范 (必须有 id 和 name)
        const isValidId = tempInstance.id && typeof tempInstance.id === 'string' && tempInstance.id !== 'base-module';
        const hasValidName = tempInstance.name && typeof tempInstance.name === 'string';

        if (!isValidId || !hasValidName) {
          this.#services.logger.warn(`Attempted to register an invalid module. It must have a valid 'id' and 'name' property.`, tempInstance);
          return;
        }

        // 检查 ID 是否重复
        if (this.#registeredModuleClasses.has(tempInstance.id)) {
          this.#services.logger.warn(`Attempt to register module with duplicate ID: '${tempInstance.id}'. Skipping.`);
          return;
        }
        this.#registeredModuleClasses.set(tempInstance.id, ModuleClass);
        this.#services.logger.log(`Module class '${tempInstance.name} (${tempInstance.id})' registered.`);
      }

      /**
       * 根据最终配置,初始化所有应该被激活的模块。
       * @param {object} config - 最终的运行时配置。
       */
      initializeActiveModules(config) {
        this.#services.logger.log('Initializing active modules...');
        this.#onNavigate();
      }

      /**
       * 遍历所有已注册的模块类,收集它们的默认配置。
       * @returns {object} 一个包含所有模块默认配置的聚合对象。
       */
      getAllDefaultConfigs() {
        const modulesConfig = {};
        for (const ModuleClass of this.#registeredModuleClasses.values()) {
          const tempInstance = new ModuleClass();

          const processedDefaultConfig = {};
          for (const key in tempInstance.defaultConfig) {
            const item = tempInstance.defaultConfig[key];
            if (typeof item === 'object' && item !== null && 'value' in item) {
              processedDefaultConfig[key] = item.value;
            } else {
              processedDefaultConfig[key] = item;
            }
          }

          const initialEnabledState = processedDefaultConfig.enabled === true;

          delete processedDefaultConfig.enabled;

          modulesConfig[tempInstance.id] = {
            enabled: initialEnabledState,
            ...processedDefaultConfig,
          };

        }
        return { modules: modulesConfig };
      }

      /**
       * 返回所有已注册模块类的实例数组,用于UI生成。
       * @returns {Module[]}
       */
      getAllRegisteredModules() {
        const modules = [];
        for (const ModuleClass of this.#registeredModuleClasses.values()) {
          const moduleInstance = new ModuleClass();
          const clonedDefaultConfig = structuredClone(moduleInstance.defaultConfig);

          modules.push({
            id: moduleInstance.id,
            name: moduleInstance.name,
            description: moduleInstance.description,
            defaultConfig: clonedDefaultConfig,
          });
        }
        return modules;
      }

      /**
       * 当配置更新时被调用的核心响应函数
       * @param {object} detail - 包含 { path, value, newConfig } 的事件数据
       */
      #onConfigUpdated = (detail) => {
        const { path, value, newConfig } = detail;

        const enabledMatch = path.match(/^modules\.([^.]+)\.enabled$/);
        if (enabledMatch) {
          const moduleId = enabledMatch[1];
          this.#revalidateModuleState(moduleId, newConfig);
          return;
        }

        const optionMatch = path.match(/^modules\.([^.]+)\.(.+)$/);
        if (optionMatch) {
          const moduleId = optionMatch[1];
          const key = optionMatch[2];

          const moduleInstance = this.#activeModuleInstances.get(moduleId);
          if (moduleInstance) {
            const oldConfig = { ...moduleInstance._config };
            moduleInstance._config[key] = value;
            moduleInstance.onConfigChange(key, value, oldConfig[key]);
          }
        }
      }

      /**
       * 封装的启用模块的逻辑
       */
      #enableModule(id, ModuleClass, config, matchContext) {
        if (this.#activeModuleInstances.has(id)) return;

        try {
          // 创建模块专用的服务门面
          const moduleConfig = config.modules[id];
          const configManagerFacade = {
            // 只暴露与当前模块相关的配置API
            getConfig: () => moduleConfig,
            updateAndSave: async (key, value) => {
              // 自动为路径添加模块ID前缀,防止跨模块修改
              const path = `modules.${id}.${key}`;
              await this.#services.configManager.updateAndSave(path, value);

              // 触发全局事件,以保持UI同步等
              this.#services.eventBus.emit('config-updated', {
                path,
                value,
                newConfig: this.#services.configManager.getConfig()
              });
            }
          };

          const moduleServicesFacade = {
            ...this.#services, // 继承所有安全的服务
            configManager: configManagerFacade, // 覆盖为安全的门面
            moduleManager: null, // 彻底隐藏ModuleManager,模块不应该直接操作它
          };

          // 使用安全的门面对象来实例化模块
          const moduleInstance = new ModuleClass(moduleServicesFacade, moduleConfig);

          this.#auditor?.auditStart(id);
          moduleInstance.onEnable(matchContext);
          this.#auditor?.auditEnd();

          this.#activeModuleInstances.set(id, moduleInstance);
          this.#services.logger.log(`Module '${id}' dynamically ENABLED.`);
        } catch (e) {
          this.#services.logger.error(`Failed to dynamically enable module '${id}'.`, e);
        }
      }

      /**
       * 封装的禁用模块的逻辑
       */
      #disableModule(id) {
        const moduleInstance = this.#activeModuleInstances.get(id);

        if (!moduleInstance) return;

        try {
          // 植入审计钩子
          this.#auditor?.auditStart(id);
          moduleInstance.onDisable();
          this.#auditor?.auditEnd();

          // 从活动实例映射中移除,并允许垃圾回收
          this.#activeModuleInstances.delete(id);

          // 在模块禁用后,运行泄漏检查
          this.#auditor?.runChecks(id);

          this.#services.logger.log(`Module '${id}' dynamically DISABLED.`);
        } catch (e) {
          this.#services.logger.error(`Failed to dynamically disable module '${id}'.`, e);
        }
      }

      /**
       * 在SPA导航时触发,重新评估所有模块的match状态。
       * @private
       */
      #onNavigate = () => {
        this.#services.logger.log('Navigation detected, re-evaluating all module states...');
        const currentConfig = this.#services.configManager.getConfig();
        if (!currentConfig) return;

        for (const id of this.#registeredModuleClasses.keys()) {
          this.#revalidateModuleState(id, currentConfig);
        }
      }

      /**
       * 重新评估一个模块的最终状态(启用/禁用),并执行相应操作。
       * 这是模块生命周期管理的唯一决策点。
       * @param {string} id - 模块ID
       * @param {object} config - 当前的全局配置
       * @private
       */
      #revalidateModuleState(id, config) {
        const ModuleClass = this.#registeredModuleClasses.get(id);
        if (!ModuleClass) return;

        const isEnabledInConfig = config.modules[id]?.enabled; // 用户配置
        const tempInstance = new ModuleClass();
        const matchResult = _CaliberInternals._checkMatch(tempInstance.match, this.#services.hostWindow);

        const shouldBeActive = isEnabledInConfig && !!matchResult;
        const isActiveNow = this.#activeModuleInstances.has(id);

        if (shouldBeActive && !isActiveNow) {
          this.#enableModule(id, ModuleClass, config, matchResult);
        } else if (!shouldBeActive && isActiveNow) {
          this.#disableModule(id);
        }
      }
    }

    /**
     * @class UIManager - UI管理器
     * 
     * 负责创建、管理和销毁UI组件,如下方的设置面板。
     */
    class UIManager {
      #appName = 'CaliberApp';
      #services;
      #logger;
      #hostDocument;
      #hostWindow;
      #panelElement = null;
      #lastKnownConfig = null;
      #guardianIntervalId = null;
      #sanitizer;

      /**
       * @param {CaliberServices} services 
       */
      constructor(services) {
        this.#services = services;
        this.#logger = services.logger;
        this.#hostDocument = services.hostDocument;
        this.#hostWindow = services.hostWindow;
        this.#appName = services.APP_NAME || this.#appName;
        this.#sanitizer = services.framework.sanitizer;
        this.#defineSettingsPanelComponent(this.#sanitizer);
      }

      /**
       * 根据最终配置决定是否显示设置面板的触发按钮
       * @param {object} finalConfig - 从Kernel传入的最终运行时配置
       */
      init(finalConfig) {
        this.#lastKnownConfig = finalConfig;
        if (finalConfig.settingsPanel.enabled) {
          this.showPanelTrigger();
        }
      }

      /**
       * 创建并显示设置面板的触发按钮
       */
      showPanelTrigger() {
        if (this.#panelElement) return;

        this.#logger.log('Showing settings panel trigger.');
        this.#panelElement = this.#hostDocument.createElement('settings-panel');
        const modules = this.#services.moduleManager.getAllRegisteredModules();
        this.#panelElement.setData(
          modules,
          this.#lastKnownConfig,
          this.#services.configManager,
          this.#services.eventBus,
          this.#hostWindow,
        );

        // 首次挂载
        this.#hostDocument.documentElement.appendChild(this.#panelElement);
        // 启动守护者
        this.#startGuardian();
      }

      hidePanelTrigger() {
        // 隐藏时,必须停止守护者
        this.#stopGuardian();
        if (this.#panelElement) {
          this.#panelElement.remove();
          this.#panelElement = null;
        }
        this.#logger.log('Settings panel trigger hidden.');
      }

      /**
       * 启动一个定时器,在一段时间内持续检查面板是否存在,如果被移除则重新挂载。
       * @private
       */
      #startGuardian() {
        // 先确保之前的守护者已停止
        this.#stopGuardian();

        const checkInterval = 500; // 每500毫秒检查一次
        const duration = 10000; // 守护10秒,足以覆盖绝大多数SPA的加载时间
        let elapsedTime = 0;

        this.#guardianIntervalId = setInterval(() => {
          if (!this.#panelElement) {
            // 如果面板被hidePanelTrigger正常移除了,就停止守护
            this.#stopGuardian();
            return;
          }

          // 检查我们的组件是否还在 html 元素中
          if (!this.#hostDocument.documentElement.contains(this.#panelElement)) {
            this.#logger.warn('Settings panel was removed from DOM. Re-appending...');
            this.#hostDocument.documentElement.appendChild(this.#panelElement);
          }

          elapsedTime += checkInterval;
          if (elapsedTime >= duration) {
            this.#logger.log('Guardian period ended. Panel is stable.');
            this.#stopGuardian(); // 停止检查,节省资源
          }
        }, checkInterval);
      }

      /**
       * 停止守护定时器。
       * @private
       */
      #stopGuardian() {
        if (this.#guardianIntervalId) {
          clearInterval(this.#guardianIntervalId);
          this.#guardianIntervalId = null;
        }
      }

      /**
       * 定义 <settings-panel> Web Component
       */
      #defineSettingsPanelComponent(sanitizer) {
        if (customElements.get('settings-panel')) return;

        const APP_NAME = this.#appName;

        class SettingsPanel extends HTMLElement {
          constructor() {
            super();
            this.attachShadow({ mode: 'open' });
            this._isOpen = false;
            this._modules = [];
            this._config = {};
            this._configManager = null;
            this._eventBus = null;
            this._boundOpenPanel = this.openPanel.bind(this);
            this._boundClosePanel = this.closePanel.bind(this);
            this._eventsBound = false;
          }

          // 外部数据注入接口
          setData(modules, config, configManager, eventBus, hostWindow) {
            this._modules = modules;
            this._config = config;
            this._configManager = configManager;
            this._eventBus = eventBus;
            this._hostWindow = hostWindow;
            this.render(); // 数据注入后重新渲染
          }

          connectedCallback() {
            if (!this.shadowRoot.firstChild) {
              this.render();
            }

            if (!this._eventsBound) {
              this.#addEventListeners();
              this._eventsBound = true;
            }

            this.#applyTheme();
          }

          /**
           * 当组件从DOM中移除时被调用,这是清理内存的关键。
           */
          disconnectedCallback() {
            this.#removeEventListeners();
            this._eventsBound = false;
          }

          /**
           * 集中添加所有事件监听器。
           * @private
           */
          #addEventListeners() {
            this.shadowRoot.querySelector('.trigger-btn').addEventListener('click', this._boundOpenPanel);
            this.shadowRoot.querySelector('.overlay').addEventListener('click', this._boundClosePanel);
            this.shadowRoot.querySelector('.drawer-content').addEventListener('change', this.#handleInputChange);
          }

          /**
           * 集中移除所有事件监听器,防止内存泄漏。
           * @private
           */
          #removeEventListeners() {
            const triggerBtn = this.shadowRoot.querySelector('.trigger-btn');
            if (triggerBtn) triggerBtn.removeEventListener('click', this._boundOpenPanel);

            const overlay = this.shadowRoot.querySelector('.overlay');
            if (overlay) overlay.removeEventListener('click', this._boundClosePanel);

            const content = this.shadowRoot.querySelector('.drawer-content');
            if (content) content.removeEventListener('change', this.#handleInputChange);
          }

          /**
           * input change 事件的统一处理函数。
           * @private
           */
          #handleInputChange = (e) => {
            const target = e.target;
            if (!target.dataset.configPath) return;

            const path = target.dataset.configPath;
            let value;
            switch (target.type) {
              case 'checkbox': value = target.checked; break;
              case 'number': value = Number(target.value); break;
              case 'text':
              case 'select-one':
              case 'color':
              default: value = target.value; break;
            }
            this.handleConfigChange(path, value);
          }

          openPanel = () => {
            if (this._isOpen) return;
            this.#applyTheme();
            this._isOpen = true;

            this.shadowRoot.querySelector('.drawer').classList.add('open');
            this.shadowRoot.querySelector('.overlay').classList.add('open');
            this.shadowRoot.querySelector('.trigger-btn').classList.add('hidden');
          }

          closePanel = () => {
            if (!this._isOpen) return;
            this._isOpen = false;

            this.shadowRoot.querySelector('.drawer').classList.remove('open');
            this.shadowRoot.querySelector('.overlay').classList.remove('open');
            this.shadowRoot.querySelector('.trigger-btn').classList.remove('hidden');
          }

          handleConfigChange = async (path, value) => {
            await this._configManager.updateAndSave(path, value);

            // 通知所有模块配置已更新
            this._eventBus.emit('config-updated', {
              path,
              value,
              newConfig: this._configManager.getConfig()
            });
          }

          /**
           * 检查系统颜色模式并为面板应用相应的主题。
           * @private
           */
          #applyTheme() {
            const drawer = this.shadowRoot.querySelector('.drawer');
            if (!drawer) return;

            const isSystemDark = this._hostWindow.matchMedia('(prefers-color-scheme: dark)').matches;

            if (isSystemDark) {
              drawer.dataset.theme = 'dark';
            } else {
              drawer.dataset.theme = 'light';
            }
          }

          render() {
            const styles = `
          :host {
              position: fixed;
              bottom: 25px;
              right: 25px;
              z-index: 9999;
              font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
          }

          /* --- 1. 触发按钮 --- */
          .trigger-btn {
              width: 42px;
              height: 42px;
              border-radius: 12px 0 0 12px;
              border: none;
              display: flex;
              align-items: center;
              justify-content: center;
              cursor: pointer;
              transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94), background-color 0.2s;
              opacity: 1;
              visibility: visible;
              background-color: rgba(255, 255, 255, 0.2);
              backdrop-filter: blur(6px);
              -webkit-backdrop-filter: blur(10px);
              box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
              position: fixed;
              right: 0;
              bottom: 50px;
          }

          .trigger-btn:hover {
              transform: scale(1.1);
              background-color: rgba(255, 255, 255, 0.3);
          }
          
          .trigger-btn .icon {
              font-size: 24px;
              color: #1d1d1f;
          }

          .trigger-btn.hidden {
              opacity: 0;
              visibility: hidden;
              transform: scale(0.8);
              pointer-events: none;
          }

          /* --- 2. 遮罩层 --- */
          .overlay {
              position: fixed;
              inset: 0;
              background-color: rgba(0, 0, 0, 0.45);
              opacity: 0;
              visibility: hidden;
              transition: opacity 0.3s ease, visibility 0.3s;
              cursor: pointer;
          }

          .overlay.open {
              opacity: 1;
              visibility: visible;
          }

          /* --- 3. 抽屉面板 --- */
          .drawer {
              position: fixed;
              top: 0;
              right: -100%;
              width: 360px;
              height: 100%;
              display: flex;
              flex-direction: column;
              transition: right 0.35s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.3s, box-shadow 0.3s;
              box-shadow: -4px 0 20px rgba(0,0,0,0.1);
              
              --bg-primary: #f3f4f5;
              --bg-secondary: #ffffff;
              --bg-tertiary: #f7f8f9;
              --bg-hover: #f7f8f9;
              --bg-input: #f7f8f9;
              --bg-input-focus: #ffffff;
              --bg-switch: #e5e7eb;
              --bg-switch-checked: #006ef4;
              
              --text-primary: #14191e;
              --text-secondary: #64696e;
              --text-tertiary: #8c9196;
              --text-placeholder: #b0b5b9;

              --border-primary: #e5e7eb;
              --border-secondary: #dadde0;
              --border-focus: #006ef4;
              --border-focus-shadow: rgba(0, 110, 244, 0.2);

              --shadow-primary: -4px 0 20px rgba(0,0,0,0.1);
              
              background-color: var(--bg-primary);

              --border-focus-shadow: rgba(0, 110, 244, 0.2);
              --select-arrow-svg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%2364696e' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
          }

          .drawer[data-theme="dark"] {
              --bg-primary: #1c1c1e;
              --bg-secondary: #2c2c2e;
              --bg-tertiary: #3a3a3c;
              --bg-hover: #3a3a3c;
              --bg-input: #3a3a3c;
              --bg-input-focus: #1c1c1e;
              --bg-switch: #3a3a3c;
              --bg-switch-checked: #0a84ff;
              
              --text-primary: #f5f5f7;
              --text-secondary: #aeaeb2;
              --text-tertiary: #8e8e93;
              --text-placeholder: #636366;

              --border-primary: #3a3a3c;
              --border-secondary: #545458;
              --border-focus: #0a84ff;
              --border-focus-shadow: rgba(10, 132, 255, 0.2);
              
              --shadow-primary: -4px 0 20px rgba(0,0,0,0.3);
              
              --border-focus-shadow: rgba(10, 132, 255, 0.2);
              --select-arrow-svg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23aeaeb2' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
          }

          .drawer {
              box-shadow: var(--shadow-primary);
          }

          .drawer.open {
              right: 0;
          }
          
          .drawer-header {
              display: flex;
              justify-content: space-between;
              align-items: center;
              padding: 16px 20px;
              flex-shrink: 0;
              transition: background-color 0.3s, border-color 0.3s;
              background-color: var(--bg-secondary);
              border-bottom: 1px solid var(--border-secondary);
          }

          .drawer-header h2 {
              margin: 0;
              font-size: 16px;
              font-weight: 600;
              color: var(--text-primary);
              transition: color 0.3s;
          }

          .drawer-content {
              flex-grow: 1;
              overflow-y: auto;
              padding: 16px;
          }
          
          /* --- 4. 表单与布局 --- */
          .details-wrapper {
              border-radius: 8px;
              margin-bottom: 12px;
              overflow: hidden;
              transition: background-color 0.3s, border-color 0.3s;
              background-color: var(--bg-secondary);
              border: 1px solid var(--border-primary);
          }
          
          summary {
              list-style: none;
              display: block;
              cursor: pointer;
              padding: 16px 20px;
              transition: background-color 0.2s;
          }
          summary::-webkit-details-marker {
              display: none;
          }
          summary:hover {
              background-color: var(--bg-hover);
          }
          .details-wrapper[noconfig] > summary {
              cursor: default;
          }
          .details-wrapper[noconfig] > summary {
              cursor: default;
          }
          .details-wrapper[open][noconfig] > .sub-items-container,
          .sub-items-container:empty {
              display: none;
          }

          .details-wrapper[open]:not([noconfig]) > summary {
              border-bottom: 1px solid var(--border-primary);
          }
          
          .summary-content {
              display: flex;
              align-items: center;
              margin-bottom: 0;
          }
          
          .form-info {
              margin-left: 16px;
              padding-top: 0;
              flex: 1;
          }

          .form-info h4 {
              margin: 0 0 4px;
              font-size: 14px;
              font-weight: 500;
              display: flex;
              justify-content: space-between;
              align-items: center;
              transition: color 0.3s;
              color: var(--text-primary);
          }
          .form-info h4 span {
              font-size: 12px;
              margin-left: 8px;
              transition: color 0.3s;
              color: var(--text-tertiary);
          }

          .form-info p {
              margin: 0;
              font-size: 12px;
              transition: color 0.3s;
              color: var(--text-tertiary);
          }
          
          .sub-items-container {
              padding: 16px 20px;
          }
          
          .sub-item {
              display: flex;
              flex-direction: column;
              justify-content: space-between;
              margin-bottom: 16px;
          }

          .sub-item:last-child {
              margin-bottom: 0;
          }
          
          .sub-item-label {
              font-size: 14px;
              flex: 1;
              transition: color 0.3s;
              color: var(--text-secondary);
          }
          
          .sub-item-info {
              flex-grow: 1;
              display: flex;
              justify-content: space-between;
              align-items: center;
          }
          
          .sub-item-desc {
              font-size: 12px;
              margin-top: 6px;
              padding: 0;
              transition: color 0.3s;
              color: var(--text-tertiary);

              p {
                  margin: 0;
                  line-height: 1.2;
              }
          }

          /* --- 5. Switch 开关样式 --- */
          .switch {
              position: relative;
              display: inline-block;
              width: 40px;
              height: 22px;
              flex-shrink: 0;
          }
          .switch input { opacity: 0; width: 0; height: 0; }
          .slider {
              position: absolute;
              cursor: pointer;
              inset: 0;
              border-radius: 22px;
              transition: .3s;
              background-color: var(--bg-switch);
          }
          .slider:before {
              position: absolute;
              content: "";
              height: 18px;
              width: 18px;
              left: 2px;
              bottom: 2px;
              background-color: white;
              border-radius: 50%;
              box-shadow: 0 1px 2px rgba(0,0,0,0.1);
              transition: .3s;
          }
          input:checked + .slider {
              background-color: var(--bg-switch-checked);
          }
          input:checked + .slider:before {
              transform: translateX(18px);
          }

          /* --- 6. Text/Number 输入框样式 --- */
          .text-input {
              appearance: none;
              -webkit-appearance: none;
              min-height: 30px;
              width: 120px;
              padding: 6px 10px;
              border-radius: 6px;
              font-size: 14px;
              outline: none;
              text-align: right;
              box-sizing: border-box;
              transition: all 0.2s ease-in-out;
              border: 1px solid var(--border-secondary);
              background-color: var(--bg-input);
              color: var(--text-primary);
          }

          .text-input:hover {
              border-color: var(--border-primary);
              background-color: var(--bg-tertiary);
          }

          .text-input:focus {
              border-color: var(--border-focus);
              background-color: var(--bg-input-focus);
              box-shadow: 0 0 0 3px var(--border-focus-shadow);
          }

          select.text-input {
              text-align: left;
              text-align-last: left;
              padding-right: 30px; /* 为箭头留出空间 */
              background-image: var(--select-arrow-svg);
              background-repeat: no-repeat;
              background-position: right 0.7rem center;
              background-size: 0.9em 0.9em;
          }

          input[type="color"].text-input {
              width: 50px;
              min-height: 34px;
              padding: 4px;
              background-color: transparent;
          }
          
          input[type="color"].text-input::-webkit-color-swatch-wrapper {
              padding: 0;
          }

          input[type="color"].text-input::-webkit-color-swatch {
              border: none;
              border-radius: 4px;
          }

          input[type="color"].text-input::-moz-color-swatch {
              border: none;
              border-radius: 4px;
          }

          /* --- 7. 其他辅助样式 --- */
          .info-only { padding: 0 8px 8px; margin-bottom: 0; }
          .info-text { font-size: 12px; text-align: center; width: 100%; margin: 0; transition: color 0.3s; color: var(--text-tertiary); }
          .no-sub-config-text { font-size: 12px; text-align: center; padding: 8px 0; color: var(--text-placeholder); }
          .main-divider { display: none; }
      `;

            const triggerButtonHTML = `<button class="trigger-btn">⚙️</button>`;

            const drawerHTML = `
            <div class="overlay"></div>
            <div class="drawer">
                <div class="drawer-header">
                    <h2>${APP_NAME} 设置</h2>
                </div>
                <div class="drawer-content">
                    ${this.generateFormHTML()}
                </div>
            </div>
        `;

            const fullHTML = `
            <style>${styles}</style>
            ${triggerButtonHTML}
            ${drawerHTML}
          `;
            sanitizer.setInnerHTML(this.shadowRoot, fullHTML);

            this.#syncUIWithConfig();
          }

          generateFormHTML() {
            let html = '';

            html += `<div class="form-group info-only"><p class="info-text">所有设置修改后将自动保存,部分设置需刷新页面生效。</p></div>`;

            for (const module of this._modules) {
              const moduleConfig = this._config.modules[module.id];
              if (!moduleConfig) continue;

              const configKeys = Object.keys(module.defaultConfig);
              const nonEnabledConfigKeys = configKeys.filter(key => key !== 'enabled');
              const optionCount = nonEnabledConfigKeys.length;

              html += `<details class="details-wrapper" ${optionCount === 0 ? 'noconfig' : ''}>
                     <summary>`;
              html += `<div class="form-group summary-content">
                     <label class="switch">
                       <input type="checkbox" data-config-path="modules.${module.id}.enabled" data-needs-reload="true">
                       <span class="slider"></span>
                     </label>
                     <div class="form-info">
                       <h4>${module.name}<span>${optionCount === 0 ? "" : " ⚙️"}</span></h4>
                       <p>${module.description}</p>
                     </div>
                   </div></summary>`;

              html += `<div class="sub-items-container">`;

              if (nonEnabledConfigKeys.length === 0) {
                html += ``;
              } else {
                for (const key of nonEnabledConfigKeys) {
                  const configItem = module.defaultConfig[key];
                  const value = moduleConfig[key];

                  const path = `modules.${module.id}.${key}`;
                  let inputHTML = '';
                  let labelText, controlType, itemDescription;

                  if (typeof configItem === 'object' && configItem !== null && 'value' in configItem) {
                    labelText = configItem.label || key;
                    controlType = configItem.type || 'string';
                    itemDescription = configItem.description || '';
                  } else {
                    labelText = key;
                    controlType = typeof configItem;
                    itemDescription = '';
                  }

                  switch (controlType) {
                    case 'boolean':
                      inputHTML = `<label class="switch"><input type="checkbox" data-config-path="${path}" ${value ? 'checked' : ''}><span class="slider"></span></label>`;
                      break;
                    case 'number':
                      inputHTML = `<input type="number" class="text-input" data-config-path="${path}" value="${value || 0}">`; // value: 如果不存在,默认为0
                      break;
                    case 'string':
                      inputHTML = `<input type="text" class="text-input" data-config-path="${path}" value="${value || ''}">`; // value: 如果不存在,默认为空字符串
                      break;
                    case 'select':
                      inputHTML = `<select class="text-input" data-config-path="${path}">`;
                      if (configItem.options && Array.isArray(configItem.options)) {
                        configItem.options.forEach(option => {
                          const selected = (option.value === value) ? 'selected' : '';
                          inputHTML += `<option value="${option.value}" ${selected}>${option.label || option.value}</option>`;
                        });
                      }
                      inputHTML += `</select>`;
                      break;
                    case 'color':
                      inputHTML = `<input type="color" class="text-input color-input" data-config-path="${path}" value="${value}">`;
                      break;
                    default:
                      html += `<div class="form-group sub-item"><span class="sub-item-label">${labelText}</span><p style="color:red;">(不支持的配置类型: ${controlType})</p></div>`;
                      continue; // 跳过不支持的类型
                  }

                  html += `
                <div class="form-group sub-item">
                  <div class="sub-item-info">
                    <label class="sub-item-label">${labelText}</label>
                    ${inputHTML}
                  </div>
                  ${itemDescription ? `<div class="sub-item-desc"><p>${itemDescription}</p></div>` : ''}
                </div>`;
                }
              }
              html += `</div></details><hr class="main-divider"/>`;
            }

            if (html.endsWith('<hr class="module-divider"/>')) {
              html = html.slice(0, -28);
            }
            return html;
          }


          /**
           * 遍历所有带 data-config-path 的输入框,并根据 this._config 设置其状态。
           * @private
           */
          #syncUIWithConfig() {
            this.shadowRoot.querySelectorAll('[data-config-path]').forEach(input => {
              const path = input.dataset.configPath;
              const keys = path.split('.');

              let value = this._config;
              for (const key of keys) {
                if (value === undefined || value === null) break;
                value = value[key];
              }

              if (value === undefined || value === null) return;

              switch (input.type) {
                case 'checkbox':
                  input.checked = Boolean(value);
                  break;
                case 'number':
                case 'text':
                case 'color':
                case 'select-one':
                  input.value = value;
                  break;
              }
            });
          }
        }

        customElements.define('settings-panel', SettingsPanel);
      }
    }

    /**
     * @class ModuleAuditor - (仅在Debug模式下激活) 模块审计员
     * 
     * 负责监控模块的副作用(事件监听、定时器),并在模块禁用后报告任何未被清理的资源泄漏。
     */
    class ModuleAuditor {
      #logger;
      #hostWindow;
      #originalAddEventListener;
      #originalRemoveEventListener;
      #originalSetInterval;
      #originalClearInterval;
      #originalSchedulerRegister;
      #originalSchedulerUnregister;
      #activeModuleId = null;
      #trackedResources = new Map();

      constructor(logger, hostWindow, SAFE_APP_NAME) {
        this.#logger = logger.createTaggedLogger('Auditor', { backgroundColor: '#9C27B0' });
        this.#hostWindow = hostWindow;
        this.#patchGlobalApis(SAFE_APP_NAME);
        this.#logger.warn('Module Auditor is active. Resource leakage will be reported.');
      }

      /**
       * 在 scheduler 实例创建后,由 Kernel 调用,用于代理其方法。
       * @param {DomBatchProcessorInstance} schedulerInstance 
       */
      patchScheduler(schedulerInstance) {
        if (!schedulerInstance) return;

        this.#originalSchedulerRegister = schedulerInstance.register;
        this.#originalSchedulerUnregister = schedulerInstance.unregister;

        const self = this;

        schedulerInstance.register = function (selector, callback, options) {
          const taskId = self.#originalSchedulerRegister.call(this, selector, callback, options);
          if (self.#activeModuleId) {
            const resources = self.#initializeTracking(self.#activeModuleId);
            resources.schedulers.add(taskId);
          }
          return taskId;
        };

        schedulerInstance.unregister = function (taskId) {
          // 全局查找并删除,因为 unregister 可能在模块上下文之外被调用
          for (const resources of self.#trackedResources.values()) {
            if (resources.schedulers.has(taskId)) {
              resources.schedulers.delete(taskId);
              break;
            }
          }
          return self.#originalSchedulerUnregister.call(this, taskId);
        };

        this.#logger.log('Scheduler has been patched for auditing.');
      }

      /**
       * 在调用模块的 onEnable/onDisable 之前调用,设置审计上下文。
       * @param {string} moduleId - 正在被审计的模块ID。
       */
      auditStart(moduleId) {
        this.#activeModuleId = moduleId;
      }

      /**
       * 在调用模块的 onEnable/onDisable 之后调用,清除审计上下文。
       */
      auditEnd() {
        this.#activeModuleId = null;
      }

      /**
       * 在模块被禁用后,运行泄漏检查。
       * @param {string} moduleId - 已被禁用的模块ID。
       */
      runChecks(moduleId) {
        const resources = this.#trackedResources.get(moduleId);
        if (!resources) return;

        let leaksFound = 0;

        // 修正: 检查事件监听器泄漏
        if (resources.events.size > 0) {
          leaksFound += resources.events.size;
          resources.events.forEach((types, target) => {
            this.#logger.error(`LEAK DETECTED in module '${moduleId}': Event listener(s) for type(s) [${[...types].join(', ')}] were NOT removed from element:`, target);
          });
        }

        // 检查定时器泄漏
        if (resources.intervals.size > 0) {
          leaksFound += resources.intervals.size;
          resources.intervals.forEach((id) => {
            this.#logger.error(`LEAK DETECTED in module '${moduleId}': A setInterval (ID: ${id}) was NOT cleared.`);
          });
        }

        // 检查调度器任务泄漏
        if (resources.schedulers.size > 0) {
          leaksFound += resources.schedulers.size;
          resources.schedulers.forEach((id) => {
            this.#logger.error(`LEAK DETECTED in module '${moduleId}': A scheduler task (ID: ${id.toString()}) was NOT unregistered.`);
          });
        }

        if (leaksFound === 0) {
          this.#logger.log(`Module '${moduleId}' passed audit. All tracked resources were cleaned up.`);
        }

        this.#trackedResources.delete(moduleId);
      }

      #initializeTracking(moduleId) {
        if (!this.#trackedResources.has(moduleId)) {
          this.#trackedResources.set(moduleId, {
            events: new Map(),
            intervals: new Set(),
            schedulers: new Set()
          });
        }
        return this.#trackedResources.get(moduleId);
      }

      #patchGlobalApis(SAFE_APP_NAME) {
        this.#originalAddEventListener = EventTarget.prototype.addEventListener;
        this.#originalRemoveEventListener = EventTarget.prototype.removeEventListener;
        this.#originalSetInterval = this.#hostWindow.setInterval;
        this.#originalClearInterval = this.#hostWindow.clearInterval;

        const self = this;
        const namespace = `__CALIBER_${SAFE_APP_NAME}`;
        const responseEventName = `${namespace}_RESPONSE`;

        // --- Patch addEventListener ---
        EventTarget.prototype.addEventListener = function (type, listener, options) {
          if (this === self.#hostWindow.document && type === responseEventName) {
            // 直接调用原始方法,不进行任何追踪
            return self.#originalAddEventListener.call(this, type, listener, options);
          }

          if (self.#activeModuleId) {
            const resources = self.#initializeTracking(self.#activeModuleId);
            if (!resources.events.has(this)) {
              resources.events.set(this, new Set());
            }
            resources.events.get(this).add(type);
          }
          return self.#originalAddEventListener.call(this, type, listener, options);
        };

        // --- Patch removeEventListener ---
        EventTarget.prototype.removeEventListener = function (type, listener, options) {
          for (const resources of self.#trackedResources.values()) {
            if (resources.events.has(this)) {
              const types = resources.events.get(this);
              types.delete(type);
              if (types.size === 0) {
                resources.events.delete(this);
              }
              break;
            }
          }
          return self.#originalRemoveEventListener.call(this, type, listener, options);
        };

        // --- Patch setInterval ---
        this.#hostWindow.setInterval = function (handler, timeout) {
          const intervalId = self.#originalSetInterval.call(this, handler, timeout);
          if (self.#activeModuleId) {
            const resources = self.#initializeTracking(self.#activeModuleId);
            resources.intervals.add(intervalId);
          }
          return intervalId;
        };

        // --- Patch clearInterval ---
        this.#hostWindow.clearInterval = function (id) {
          // 全局查找并删除,因为 clearInterval 可能在模块上下文之外被调用
          for (const resources of self.#trackedResources.values()) {
            if (resources.intervals.has(id)) {
              resources.intervals.delete(id);
              break;
            }
          }
          return self.#originalClearInterval.call(this, id);
        };
      }
    }

    /**
     * @class DomBatchProcessor - 高性能DOM批量处理调度器
     */
    class DomBatchProcessor {
      #observer = null;
      #taskQueue = [];
      #registeredTasks = new Map();
      #isLoopRunning = false;
      #isObserving = false;
      #batchSize;
      #logger;
      #hostDocument;

      /**
       * @param {number} batchSize - 在每个渲染帧中处理的最大任务数。
       * @param {LoggerInstance} logger - 用于日志输出的 logger 对象。
       * @param {Document} hostDocument - 宿主 document 对象。
       */
      constructor(batchSize, logger, hostDocument) {
        this.#batchSize = batchSize || 20;
        this.#logger = logger;
        this.#hostDocument = hostDocument;
        // 只创建 MutationObserver 实例,不立即启动监听(懒启动)
        this.#observer = new MutationObserver(this.#handleMutations);
      }

      /**
       * 注册一个DOM处理任务。
       * @param {string} selector - 用于匹配节点的CSS选择器。
       * @param {(node: HTMLElement) => void} callback - 匹配到节点时要执行的回调函数。
       * @param {object} [options] - (可选) 监听选项。
       * @param {boolean} [options.add=true] - 是否监听节点的添加。
       * @param {boolean} [options.attributes=false] - 是否监听节点属性的变化。
       * @param {string[]} [options.attributeFilter] - (可选) 只监听特定属性的变化。
       * @param {HTMLElement | string} [options.root] - (可选) 任务的根节点或根选择器。只有当DOM变动发生在此根节点内部时,任务才会被匹配。
       * @param {boolean} [options.processExisting=false] - (可选) 是否在注册时立即处理DOM中已存在的匹配节点。
       * @returns {Symbol} 一个唯一的任务ID,用于后续注销。
       */
      register(selector, callback, options = {}) {
        const taskId = Symbol(selector);
        this.#registeredTasks.set(taskId, {
          selector,
          callback,
          options: { add: true, ...options }
        });
        this.#logger.log(`Task registered for selector "${selector}"`, options.root ? `within root "${options.root}"` : '');

        if (options.processExisting) {
          const rootNode = (typeof options.root === 'string'
            ? this.#hostDocument.querySelector(options.root)
            : options.root) || this.#hostDocument;

          const existingNodes = rootNode.querySelectorAll(selector);

          if (existingNodes.length > 0) {
            this.#logger.log(`Explicitly processing ${existingNodes.length} existing node(s) for selector "${selector}".`);
            existingNodes.forEach(node => {
              // 直接推入队列,由 rAF 循环处理
              this.#taskQueue.push({ node, callback: callback });
            });
            // 确保处理循环启动
            this.#startLoop();
          }
        }

        this.#updateObserver();

        return taskId;
      }

      /**
       * 注销一个DOM处理任务。
       * @param {Symbol} taskId - 注册时返回的任务ID。
       */
      unregister(taskId) {
        if (this.#registeredTasks.has(taskId)) {
          const selector = this.#registeredTasks.get(taskId).selector;
          this.#registeredTasks.delete(taskId);
          this.#logger.log(`Task for selector "${selector}" unregistered.`);

          this.#updateObserver();
        }
      }

      /**
       * 根据当前所有注册任务的需求,动态计算并应用最优的 MutationObserver 配置。
       * 这是实现懒启动、自动休眠和动态功能切换的核心。
       * @private
       */
      #updateObserver() {
        if (this.#isObserving) {
          this.#observer.disconnect();
          this.#isObserving = false;
        }

        // 如果没有任何任务,则保持休眠状态
        if (this.#registeredTasks.size === 0) {
          this.#logger.log('Observer stopped as no tasks are registered.');
          return;
        }

        let needsChildList = false;
        let needsAttributes = false;
        const attributeFilterSet = new Set();
        let useAttributeFilter = true;

        // 遍历所有任务,聚合出最小的配置需求
        for (const task of this.#registeredTasks.values()) {
          if (task.options.add) needsChildList = true;
          if (task.options.attributes) {
            needsAttributes = true;
            if (task.options.attributeFilter && Array.isArray(task.options.attributeFilter)) {
              task.options.attributeFilter.forEach(attr => attributeFilterSet.add(attr));
            } else {
              // 只要有一个任务需要监听属性但未提供 filter,就不能启用 filter 优化
              useAttributeFilter = false;
            }
          }
        }

        const observerConfig = { subtree: true };
        if (needsChildList) observerConfig.childList = true;
        if (needsAttributes) {
          observerConfig.attributes = true;
          // 仅当所有需要监听属性的任务都指定了 filter 时,才应用 filter
          if (useAttributeFilter && attributeFilterSet.size > 0) {
            observerConfig.attributeFilter = Array.from(attributeFilterSet);
          }
        }

        // 只有在需要监听任何东西的情况下才启动观察
        if (observerConfig.childList || observerConfig.attributes) {
          this.#observer.observe(this.#hostDocument.documentElement, observerConfig);
          this.#isObserving = true;
          this.#logger.log('Observer (re)started with new config:', observerConfig);
        }
      }

      /**
       * MutationObserver的回调,负责过滤和排队任务。
       * @private
       */
      #handleMutations = (mutations) => {
        for (const mutation of mutations) {
          if (mutation.type === 'childList') {
            for (const addedNode of mutation.addedNodes) {
              // 只处理元素节点,忽略文本节点等
              if (addedNode.nodeType === Node.ELEMENT_NODE) {
                this.#queueMatchingTasks(addedNode, 'add');

                // 同时检查新增节点下的所有子元素是否也匹配
                const descendants = addedNode.querySelectorAll('*');
                for (const descendant of descendants) {
                  this.#queueMatchingTasks(descendant, 'add');
                }
              }
            }
          } else if (mutation.type === 'attributes') {
            if (mutation.target.nodeType === Node.ELEMENT_NODE) {
              this.#queueMatchingTasks(mutation.target, 'attributes', mutation.attributeName);
            }
          }
        }
        // 只要队列中有任务,就确保 rAF 循环在运行
        if (this.#taskQueue.length > 0) {
          this.#startLoop();
        }
      }

      /**
       * 辅助函数:检查一个节点是否匹配任何已注册的任务,并将其排队。
       * @private
       */
      #queueMatchingTasks(node, mutationType, attributeName = null) {
        for (const task of this.#registeredTasks.values()) {
          // --- 根节点靶向检查 ---
          if (task.options.root) {
            const rootNode = typeof task.options.root === 'string'
              ? this.#hostDocument.querySelector(task.options.root)
              : task.options.root;

            // 如果根节点不存在,或者变化节点不在根节点内部,则直接跳过此任务
            if (!rootNode || !rootNode.contains(node)) {
              continue;
            }
          }

          // 检查任务是否订阅了此类变更,并且节点是否匹配选择器
          if (task.options[mutationType] && node.matches(task.selector)) {
            // 对于属性变更,额外检查 attributeFilter
            if (mutationType === 'attributes' && task.options.attributeFilter) {
              if (!task.options.attributeFilter.includes(attributeName)) {
                continue; // 属性不匹配,跳过当前任务
              }
            }
            this.#taskQueue.push({ node, callback: task.callback });
          }
        }
      }

      /**
       * 启动 rAF 循环。
       * @private
       */
      #startLoop() {
        if (this.#isLoopRunning) return;
        this.#isLoopRunning = true;
        requestAnimationFrame(this.#processQueue);
      }

      /**
       * rAF 循环的核心,负责分批处理任务。
       * @private
       */
      #processQueue = () => {
        const batch = this.#taskQueue.splice(0, this.#batchSize);
        for (const task of batch) {
          try {
            // 在执行回调前,最好检查一下节点是否还在DOM中
            if (this.#hostDocument.documentElement.contains(task.node)) {
              task.callback(task.node);
            }
          } catch (e) {
            this.#logger.error('Error in DomBatchProcessor task callback:', e);
          }
        }

        if (this.#taskQueue.length > 0) {
          requestAnimationFrame(this.#processQueue);
        } else {
          this.#isLoopRunning = false;
        }
      }
    }

    /**
     * @class LoggerService - 专用的、可派生的日志服务
     */
    class LoggerService {
      #isDebug;
      #baseTagStyle = `color: white; padding: 2px 6px; border-radius: 4px; font-weight: bold;`;

      constructor(isDebug) {
        this.#isDebug = Boolean(isDebug);
      }

      /**
       * (私有辅助函数) 创建一组核心的日志方法。
       * @private
       */
      #createLogMethodsFor(tag, styles) {
        return {
          log: (message, ...args) => this.#isDebug && console.log(`%c${tag}`, styles.log, message, ...args),
          warn: (message, ...args) => this.#isDebug && console.warn(`%c${tag}`, styles.warn, message, ...args),
          error: (message, ...args) => console.error(`%c${tag}`, styles.error, message, ...args),
        };
      }

      /**
       * 创建一个主 logger 实例。
       */
      createMainLogger(appName) {
        const styles = {
          log: `background-color: #0057b8; ${this.#baseTagStyle}`,
          warn: `background-color: #ff9800; color: black; ${this.#baseTagStyle}`,
          error: `background-color: #f44336; ${this.#baseTagStyle}`,
        };
        const mainLogger = this.#createLogMethodsFor(appName, styles);

        mainLogger.createTaggedLogger = (tag, styleOptions = {}) => {
          const taggedStyles = {
            log: `background-color: ${styleOptions.backgroundColor || '#757575'}; color: ${styleOptions.color || 'white'}; ${this.#baseTagStyle}`,
            warn: `background-color: ${styleOptions.backgroundColor || '#757575'}; color: ${styleOptions.color || 'white'}; ${this.#baseTagStyle}`,
            error: `background-color: #f44336; color: white; ${this.#baseTagStyle}`, // 错误总是红色
          };
          return this.#createLogMethodsFor(tag, taggedStyles);
        };

        return mainLogger;
      }
    }

    /**
     * 框架内部工具
     */
    const _CaliberInternals = {
      /**
       * @private
       * [上下文创建器] 从应用名称派生出所有需要的上下文状态。
       * @param {string} appName - 原始的应用名称。
       * @returns {{safeAppName: string, instanceKey: string}} 包含派生状态的上下文对象。
       */
      _createAppContext: (appName) => {
        const utf8Bytes = new TextEncoder().encode(appName);
        let binaryString = '';
        utf8Bytes.forEach(byte => {
          binaryString += String.fromCharCode(byte);
        });
        let safeAppName = btoa(binaryString);
        safeAppName = safeAppName.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
        const instanceKey = `CALIBER_INSTANCE_${safeAppName}`;
        return { safeAppName, instanceKey };
      },

      /**
       * @private
       * [预检器] 对应用配置和运行环境执行所有预检。
       * @param {object} options - createApp 接收的原始选项。
       * @param {string} instanceKey - 由 _createAppContext 生成的实例键。
       * @param {object} logger - 用于报告错误的主 logger。
       * @param {Window} hostWindow - 宿主页面的 window 对象。
       * @returns {boolean} - 所有检查通过返回 true,否则返回 false。
       */
      runPreflightChecks: (options, instanceKey, logger, hostWindow) => {
        const { appName, modules, services } = options || {};

        if (!options || typeof options !== 'object' || !appName || typeof appName !== 'string' || !Array.isArray(modules) || !services || !services.storage || !services.command) {
          logger.error('Preflight check failed: Invalid configuration object.');
          return false;
        }

        if (modules.length === 0) {
          if (options.isDebug) logger.log(`Preflight check skipped: No modules provided.`);
          return false;
        }

        if (hostWindow[instanceKey]) {
          logger.warn('Preflight check failed: Script instance already running.');
          return false;
        }

        return true;
      },

      /**
       * 初始化所有框架核心服务。
       * @param {object} options - createApp 接收的原始选项。
       * @param {{safeAppName: string}} context - 部分上下文,包含 safeAppName。
       * @param {LoggerInstance} logger - 主 logger。
       * @param {Window} hostWindow - 宿主页面的 window 对象。
       * @param {Document} hostDocument - 宿主页面的 document 对象。
       * @param {DOMSanitizerInstance} sanitizer - DOM净化服务实例。
       * @returns {{eventBus: EventBusInstance, framework: FrameworkServices}} - 包含事件总线和框架服务集合的对象。
       */
      initializeCoreServices: (options, context, logger, hostWindow, hostDocument, sanitizer) => {
        const eventBus = _CaliberInternals.createEventBus(logger);
        const schedulerLogger = logger.createTaggedLogger('Scheduler', { backgroundColor: '#4CAF50' });

        const frameworkServices = {
          scheduler: new DomBatchProcessor(options.framework?.domProcessorBatchSize, schedulerLogger, hostDocument),
          interceptor: _CaliberInternals.createFetchInterceptor(logger, hostWindow, hostDocument, context.safeAppName, options.isDebug, sanitizer),
          sanitizer: sanitizer,
          executor: _CaliberInternals.createPageScopeExecutor(logger, context.safeAppName, sanitizer, hostDocument),
        };

        _CaliberInternals.patchHistoryForNavigation(eventBus, hostWindow);
        return { eventBus, framework: frameworkServices };
      },

      /**
       * @private
       * 核心匹配引擎 - 检查给定的匹配规则是否与当前页面URL匹配。
       * @param {string|RegExp|Array<string|RegExp>|null|undefined} matchRule - 匹配规则。
       * @param {Window} hostWindow - 宿主 window 对象。
       * @returns {object|false} - 不匹配返回false,匹配返回 { params, query }。
       */
      _checkMatch: (matchRule, hostWindow) => {
        const currentUrl = new URL(hostWindow.location.href);

        // 处理查询参数
        const query = {};
        const searchParams = currentUrl.searchParams;
        for (const [key, value] of searchParams.entries()) {
          const existing = query[key];
          if (existing !== undefined) {
            query[key] = Array.isArray(existing)
              ? [...existing, value]
              : [existing, value];
          } else {
            query[key] = value;
          }
        }

        // 路径规范化
        const rawPathname = currentUrl.pathname;
        const pathname = rawPathname.endsWith('/') && rawPathname.length > 1
          ? rawPathname.slice(0, -1)
          : rawPathname;

        const href = currentUrl.href;

        // 空规则快速返回
        if (matchRule == null) {
          return { params: {}, query };
        }

        // 规则数组处理
        const rules = Array.isArray(matchRule) ? matchRule : [matchRule];

        // 核心匹配逻辑
        for (const rule of rules) {
          const result = checkRule(rule);
          if (result) return result;
        }

        return false;

        // 辅助函数保持内部作用域
        function checkRule(rule) {
          if (rule == null) return { params: {}, query };
          if (!rule) return false;

          // 正则表达式规则
          if (rule instanceof RegExp) {
            const match = rule.exec(pathname) || rule.exec(href);
            return match
              ? { params: match.groups || {}, query }
              : false;
          }

          // 字符串规则
          if (typeof rule === 'string') {
            let isAbsolute = false;
            let rulePath = rule;
            let ruleProtocol = '';
            let ruleHost = '';

            try {
              const urlObj = new URL(rule);
              isAbsolute = true;
              ruleProtocol = urlObj.protocol;
              ruleHost = urlObj.host;
              rulePath = urlObj.pathname;
            } catch { }

            if (isAbsolute) {
              if (ruleProtocol !== currentUrl.protocol || ruleHost !== currentUrl.host) {
                return false;
              }
            }

            // 路径规范化
            const normalizedRule = rulePath.endsWith('/') && rulePath.length > 1
              ? rulePath.slice(0, -1)
              : rulePath;

            // 快速前缀匹配(无参数路径)
            if (pathname === normalizedRule || pathname.startsWith(normalizedRule + '/')) {
              return { params: {}, query };
            }

            // 参数化路径匹配
            return matchParamPath(normalizedRule);
          }

          return false;
        }

        // 参数化路径匹配
        function matchParamPath(pattern) {
          // 检查是否需要参数匹配
          const hasParams = pattern.includes(':') || pattern.includes('*');
          if (!hasParams) return false;

          // 构建正则表达式
          const parts = pattern.split('/').slice(1);
          let regexStr = '^';
          const paramNames = [];
          let hasWildcard = false;

          for (const part of parts) {
            if (hasWildcard) return false; // 通配符后不能有其他部分

            if (part.startsWith(':')) {
              const isOptional = part.endsWith('?');
              const name = isOptional ? part.slice(1, -1) : part.slice(1);
              paramNames.push(name);
              regexStr += isOptional ? '(?:/([^/]+))?' : '/([^/]+)';
            }
            else if (part === '*') {
              paramNames.push('_');
              regexStr += '(?:/(.*))?';
              hasWildcard = true;
            }
            else {
              regexStr += '/' + part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
            }
          }
          regexStr += '$';

          const regex = new RegExp(regexStr);
          const match = regex.exec(pathname);

          if (match) {
            const params = {};
            for (let i = 0; i < paramNames.length; i++) {
              params[paramNames[i]] = match[i + 1] ?? undefined;
            }
            return { params, query };
          }

          return false;
        }
      },

      /**
       * 创建一个事件总线实例。 (工厂职责)
       * @param {object} logger - 用于错误报告的logger实例。
       */
      createEventBus: (logger) => {
        const listeners = new Map();
        const log = logger || console;

        return {
          on: (eventName, callback) => {
            if (!listeners.has(eventName)) {
              listeners.set(eventName, []);
            }
            listeners.get(eventName).push(callback);
          },
          off: (eventName, callback) => {
            if (listeners.has(eventName)) {
              const eventListeners = listeners.get(eventName);
              const index = eventListeners.indexOf(callback);
              if (index > -1) {
                eventListeners.splice(index, 1);
              }
            }
          },
          emit: (eventName, data) => {
            if (listeners.has(eventName)) {
              [...listeners.get(eventName)].forEach(callback => {
                try {
                  callback(data);
                } catch (e) {
                  log.error(`[EventBus] Error in callback for event "${eventName}":`, e);
                }
              });
            }
          }
        };
      },

      /**
       * 代理 history API 以感知SPA导航。 (配置器职责)
       * @param {object} bus - 事件总线实例。
       */
      patchHistoryForNavigation: (bus, hostWindow) => {
        const history = hostWindow.history;
        const originalPushState = history.pushState;
        const originalReplaceState = history.replaceState;

        history.pushState = function (...args) {
          originalPushState.apply(this, args);
          bus.emit('navigate');
        };

        history.replaceState = function (...args) {
          originalReplaceState.apply(this, args);
          bus.emit('navigate');
        };

        hostWindow.addEventListener('popstate', () => bus.emit('navigate'));
      },

      /**
       * @private
       * [服务工厂] 创建一个通用的DOM净化与安全注入服务。
       * 这是框架中所有CSP/Trusted-Types对抗策略的唯一来源。
       * @param {object} logger - 用于报告错误的 logger 实例。
       * @returns {object} DOMSanitizer 服务实例。
       */
      createDOMSanitizer: (logger) => {
        let _policy = undefined; // 使用闭包缓存 Trusted Types 策略

        const _getPolicy = () => {
          if (_policy === undefined) {
            _policy = null;
            if (window.trustedTypes && window.trustedTypes.createPolicy) {
              try {
                _policy = window.trustedTypes.createPolicy('CaliberUniversalPolicy#html', {
                  createHTML: s => s,
                  createScript: s => s,
                });
              } catch (e) {
                if (window.trustedTypes.defaultPolicy) _policy = window.trustedTypes.defaultPolicy;
              }
            }
          }
          return _policy;
        };

        const service = {
          /**
           * [核心] 创建一个TrustedHTML对象(如果策略可用)。
           * @param {string} htmlString - 要处理的HTML字符串。
           * @returns {TrustedHTML|string} 返回TrustedHTML对象或原始字符串。
           */
          createTrustedHTML(htmlString) {
            const policy = _getPolicy();
            return policy ? policy.createHTML(htmlString) : htmlString;
          },

          /**
           * [便捷方法] 安全地设置一个元素的 innerHTML。
           * @param {Element} element - 目标元素。
           * @param {string} htmlString - 要设置的HTML字符串。
           */
          setInnerHTML(element, htmlString) {
            try {
              element.innerHTML = this.createTrustedHTML(htmlString);
            } catch (e) {
              logger.error('setInnerHTML failed due to CSP.', `Error: ${e.message}`);
            }
          },

          /**
           * [便捷方法] 安全地注入脚本。
           * @param {Document} doc - 目标文档。
           * @param {string} codeString - 脚本字符串。
           */
          injectScript(doc, codeString) {
            try {
              const script = doc.createElement('script');
              const policy = _getPolicy();
              if (policy) script.textContent = policy.createScript(codeString);
              else script.textContent = codeString;
              (doc.head || doc.documentElement).prepend(script);
              script.remove();
            } catch (e) {
              logger.error('Script injection failed due to CSP.', `Error: ${e.message}`);
            }
          },

          /**
           * [便捷方法] 安全地注入样式。
           * @param {Document} doc - 目标文档。
           * @param {string} cssString - 样式字符串。
           * @param {string} id - 样式元素的ID。
           * @returns {HTMLStyleElement|null} 创建的元素或null。
           */
          injectStyle(doc, cssString, id) {
            try {
              const style = doc.createElement('style');
              style.dataset.caliberId = id;
              style.innerHTML = this.createTrustedHTML(cssString);
              doc.head.appendChild(style);
              return style;
            } catch (e) {
              logger.error('Style injection failed due to CSP.', `Error: ${e.message}`);
              return null;
            }
          }
        };

        return service;
      },

      /**
       * 网络请求拦截器服务。
       * 创建一个通用的拦截器服务,它通过安全的脚本注入来代理原生fetch,并使用CustomEvent将响应数据传回沙箱。
       *
       * @param {object} logger - 框架的主 logger 实例。
       * @param {Window} hostWindow - 宿主页面的 window 对象。
       * @param {Document} hostDocument - 宿主页面的 Document 对象。
       * @param {string} safeAppName - 当前应用名称,用于生成唯一的命名空间。
       * @param {boolean} isDebug - 是否为调试模式,用于控制注入脚本的日志输出。
       * @param {DOMSanitizerInstance} sanitizer - DOM净化与安全注入服务实例。
       * @returns {FetchInterceptorInstance} 服务对象。   
       */
      createFetchInterceptor: (logger, hostWindow, hostDocument, safeAppName, isDebug, sanitizer) => {
        const namespace = `__CALIBER_${safeAppName}`;
        const patchFlag = `${namespace}_PATCHED`;
        const hooksRegistry = `${namespace}_HOOKS`;
        const responseEventName = `${namespace}_RESPONSE`;

        const responseCallbacks = new Map();
        let listenerRefCount = 0;

        // 事件处理函数
        const _handleInjectedEvent = (event) => {
          const { path, responseData } = event.detail;
          const pathKey = JSON.stringify(path);
          if (responseCallbacks.has(pathKey)) {
            try {
              responseCallbacks.get(pathKey)(responseData);
            } catch (e) {
              logger.error(`[FetchInterceptor] Error in response callback for path [${path.join('/')}]`, e);
            }
          }
        };

        // 统一处理路径验证和转换
        const _validateAndTransformPath = (path, methodName) => {
          if (typeof path === 'string' && path) return [path];
          if (Array.isArray(path) && path.length > 0) return path;
          logger.error(`[FetchInterceptor] ${methodName} failed: path must be a non-empty array or a non-empty string.`);
          return null;
        };

        const service = {
          /**
           * 根据配置对象构建一个 fetch 钩子函数的字符串。
           * 这是一个便捷的“填空题”工具,用于简化 addHook 的使用。
           * @param {object} options - 钩子配置。
           * @param {string} options.targetUrl - 必须完全匹配的目标URL (origin + pathname)。
           * @param {string} [options.method='GET'] - (可选) 匹配的HTTP方法 (大小写不敏感)。
           * @param {string} options.handler - 在匹配成功后,要执行的核心逻辑的函数体字符串。
           *                                   在此字符串中,你可以使用 `urlObject` 和 `config` 这两个变量。
           *                                   它必须返回一个 `{ url: string, config: object }` 或 `undefined`。
           * @returns {string} - 一个完整的、自包含的、可注入的钩子函数字符串。
           */
          createHook({ targetUrl, method = 'GET', handler }) {
            if (!targetUrl || !handler) {
              logger.error(`[FetchInterceptor.createHook] failed: 'targetUrl' and 'handler' are required.`);
              return `() => {}`;
            }
            const template = `
            (url, config) => {
                const TARGET_URL = '${targetUrl}';
                const TARGET_METHOD = '${method.toUpperCase()}';
                try {
                    if (config.method && config.method.toUpperCase() !== TARGET_METHOD) return;
                    const urlObject = new URL(url);
                    if (urlObject.origin + urlObject.pathname !== TARGET_URL) return;
                    
                    const result = (() => { ${handler} })();
                    return result;
                } catch (e) { /* ignore errors */ }
            }`;
            return template;
          },

          /**
           * 添加一个网络请求钩子,并注册一个用于处理响应的回调。
           * @param {string[]|string} path - 钩子的唯一路径。
           * @param {string} hookFunctionString - 修改请求的钩子函数字符串。
           * @param {(responseData: any) => void} responseCallback - 接收响应数据的回调函数。
           */
          addHookWithResponse(path, hookFunctionString, responseCallback) {
            const finalPath = _validateAndTransformPath(path, 'addHookWithResponse');
            if (!finalPath) return;
            if (typeof responseCallback !== 'function') {
              logger.error('[FetchInterceptor] addHookWithResponse failed: responseCallback must be a function.');
              return;
            }

            const pathKey = JSON.stringify(finalPath);
            // 如果是新注册的回调,增加引用计数
            if (!responseCallbacks.has(pathKey)) {
              if (listenerRefCount === 0) {
                hostDocument.addEventListener(responseEventName, _handleInjectedEvent);
                if (isDebug) logger.log(`[FetchInterceptor] Global response listener attached for event: ${responseEventName}`);
              }
              listenerRefCount++;
            }

            responseCallbacks.set(pathKey, responseCallback);
            this.addHook(finalPath, hookFunctionString, true);
          },

          /**
           * 添加或更新一个网络请求钩子。
           * @param {string[]|string} path - 钩子路径。
           * @param {string} hookFunctionString - 钩子函数字符串。
           * @param {boolean} [awaitsResponse=false] - (内部) 标记此钩子是否需要返回响应。
           */
          addHook(path, hookFunctionString, awaitsResponse = false) {
            const finalPath = _validateAndTransformPath(path, 'addHook');
            if (!finalPath || !hookFunctionString) {
              if (!hookFunctionString) logger.error('[FetchInterceptor] addHook failed: hookFunctionString is required.');
              return;
            }

            const pathJson = JSON.stringify(finalPath);
            const injectionCode = `
            (() => {
              const doLog = ${isDebug};
              if (!window['${patchFlag}']) {
                window['${patchFlag}'] = true;
                window['${hooksRegistry}'] = {};
                const originalFetch = window.fetch;
                const RESPONSE_EVENT_NAME = '${responseEventName}';

                const executeHooks = (node, url, config, currentPath = []) => {
                  let matchedPath = null;
                  if (typeof node === 'object' && node !== null) {
                    for (const key in node) {
                      if (Object.prototype.hasOwnProperty.call(node, key)) {
                        const newPath = [...currentPath, key];
                        const hookResult = executeHooks(node[key], url, config, newPath);
                        url = hookResult.url;
                        config = hookResult.config;
                        if (hookResult.matchedPath) {
                            matchedPath = hookResult.matchedPath;
                            break;
                        }
                      }
                    }
                  } else if (typeof node === 'function') {
                    try {
                      const result = node(url, config);
                      if (result && result.url && result.config) {
                        url = result.url;
                        config = result.config;
                        matchedPath = currentPath;
                      }
                    } catch (e) { if (doLog) console.error('[Caliber Hook Error]', e); }
                  }
                  return { url, config, matchedPath };
                };

                window.fetch = async function(...args) {
                  let resource = args[0], config = args[1] || {}, url = new URL(String(resource), window.location.origin).toString();
                  
                  const { url: finalUrl, config: finalConfig, matchedPath } = executeHooks(window['${hooksRegistry}'], url, config);
                  args[0] = finalUrl;
                  args[1] = finalConfig;

                  const response = await originalFetch.apply(this, args);

                  if (matchedPath) {
                    const responseClone = response.clone();
                    responseClone.text().then(text => {
                      let responseData;
                      try {
                        responseData = JSON.parse(text);
                      } catch (e) {
                        responseData = text;
                      }
                      
                      const event = new CustomEvent(RESPONSE_EVENT_NAME, {
                        detail: { path: matchedPath, responseData }
                      });
                      document.dispatchEvent(event);
                    }).catch(e => {
                      if (doLog) console.warn('[Caliber Interceptor] Could not read response body for hooked request.', e);
                    });
                  }
                  
                  return response;
                };
                if (doLog) console.warn('[Caliber] Injected fetch interceptor is active.');
              }

              const setByPath = (obj, p, val) => {
                const last = p.pop();
                let node = obj;
                p.forEach(k => { node = (node[k] = (typeof node[k] === 'object' && node[k] !== null) ? node[k] : {}); });
                node[last] = val;
              };

              setByPath(window['${hooksRegistry}'], ${pathJson}, (${hookFunctionString}));
              if (${isDebug}) console.log(\`[Caliber] Hook at path [${finalPath.join('/')}] added/updated. Awaits response: ${awaitsResponse}\`);
            })();
          `;
            sanitizer.injectScript(hostDocument, injectionCode);
          },

          /**
           * 从注入的钩子注册表中移除一个钩子或分支。
           * @param {string[]|string} path - 要移除的钩子或分支的路径。
           */
          removeHook(path) {
            const finalPath = _validateAndTransformPath(path, 'removeHook');
            if (!finalPath) return;

            const pathKey = JSON.stringify(finalPath);
            // 如果确实存在一个回调被移除,减少引用计数
            if (responseCallbacks.has(pathKey)) {
              responseCallbacks.delete(pathKey);
              listenerRefCount--;

              if (listenerRefCount === 0) {
                hostDocument.removeEventListener(responseEventName, _handleInjectedEvent);
                if (isDebug) logger.log(`[FetchInterceptor] Global response listener removed as no hooks are active.`);
              }
              if (isDebug) logger.log(`[FetchInterceptor] Response callback for path [${finalPath.join('/')}] removed.`);
            }

            const pathJson = JSON.stringify(finalPath);
            const removalCode = `
            (() => {
              const registry = window['${hooksRegistry}'];
              if (!registry) return;
              const path = ${pathJson};
              let parent = registry;
              for (let i = 0; i < path.length - 1; i++) {
                if (typeof parent[path[i]] === 'undefined') return;
                parent = parent[path[i]];
              }
              delete parent[path[path.length - 1]];
              if (${isDebug}) console.log(\`[Caliber] Hook or branch at path [${finalPath.join('/')}] removed.\`);
            })();
          `;
            sanitizer.injectScript(hostDocument, removalCode);
          },

          /**
           * 启动一个链式调用来创建和注册一个拦截器。
           * @param {string|{url: string, method?: string, match?: string|RegExp|Array<string|RegExp>}} urlOrOptions - 目标URL或一个包含URL、方法和页面匹配规则的对象。
           * @returns {object} 一个包含 .onRequest(), .onResponse(), .register() 的构建器对象。
           */
          target(urlOrOptions) {
            const builder = {
              _targetConfig: {},
              _requestHandlerStr: `(url, config) => ({ url, config })`,
              _responseCallback: null,

              _init(targetConfig) {
                this._targetConfig = targetConfig;
                return this;
              },

              /**
               * 定义请求被拦截时要执行的逻辑。
               * @param {string} handlerString - 一个将要被注入的函数体字符串。
               * @returns {builder}
               */
              onRequest(handlerString) {
                this._requestHandlerStr = handlerString;
                return this;
              },

              /**
               * 定义在沙箱中处理响应数据的回调函数。
               * @param {(responseData: any) => void} callback - 回调函数。
               * @returns {builder}
               */
              onResponse(callback) {
                this._responseCallback = callback;
                return this;
              },

              /**
               * 最终确定并注册这个拦截器。
               * @param {string|string[]} id - 拦截器的唯一ID,通常是模块的this.id。
               */
              register(id) {
                const { match } = this._targetConfig;

                const isMatched = _CaliberInternals._checkMatch(match, hostWindow);
                if (!isMatched) {
                  if (isDebug) {
                    logger.log(`[Interceptor.register] Registration for ID '${id}' skipped. Current URL "${hostWindow.location.href}" does not match the rule:`, match);
                  }
                  return;
                }

                if (!id) {
                  logger.error('[Interceptor.register] An ID is required to register a hook.');
                  return;
                }

                const isRequestModified = this._requestHandlerStr !== `(url, config) => ({ url, config })`;
                const isResponseHandled = this._responseCallback !== null;
                if (!isRequestModified && !isResponseHandled) {
                  if (isDebug) {
                    logger.warn(`[Interceptor.register] Registration for ID '${id}' was silently cancelled because both onRequest and onResponse were omitted.`);
                  }
                  return;
                }

                const finalHandler = isRequestModified ? this._requestHandlerStr : `return { url, config };`;

                const fullHookString = service.createHook({
                  targetUrl: this._targetConfig.url,
                  method: this._targetConfig.method,
                  handler: finalHandler
                });

                if (isResponseHandled) {
                  service.addHookWithResponse(id, fullHookString, this._responseCallback);
                } else {
                  service.addHook(id, fullHookString);
                }
              }
            };

            const targetConfig = typeof urlOrOptions === 'string'
              ? { url: urlOrOptions, method: 'GET' }
              : { method: 'GET', ...urlOrOptions };

            return builder._init(targetConfig);
          }
        };
        return service;
      },

      /**
       * @private
       * [原生适配器] 基于Web标准API的默认服务实现。
       */
      _nativeBrowserAdapters: {
        storage: (storageKey) => ({
          get: () => Promise.resolve(JSON.parse(localStorage.getItem(storageKey) || '{}')),
          set: (value) => Promise.resolve(localStorage.setItem(storageKey, JSON.stringify(value))),
        }),
        command: {
          register: (name, callback) => { },
        },
        style: (sanitizer, hostDocument) => ({
          _addedStyles: new Map(),
          add(cssString, id) {
            const styleElement = sanitizer.injectStyle(hostDocument, cssString, id);
            if (styleElement) {
              this._addedStyles.set(id, styleElement);
            }
          },
          remove(id) {
            if (this._addedStyles.has(id)) {
              this._addedStyles.get(id).remove();
              this._addedStyles.delete(id);
            }
          }
        })
      },

      /**
       * @private
       * [服务工厂] 创建一个页面作用域代码执行器服务。
       * 可将任意JS代码字符串注入到宿主页面执行,并异步返回其可序列化的结果。
       * @param {object} logger - 框架的主 logger 实例。
       * @param {string} safeAppName - 应用的安全名称,用于生成唯一事件名。
       * @param {DOMSanitizerInstance} sanitizer - DOM净化与安全注入服务实例。
       * @param {Document} hostDocument - 宿主 document 对象。
       * @returns {{execute: (codeString: string) => Promise<any>}} PageScopeExecutor 服务实例。
       */
      createPageScopeExecutor: (logger, safeAppName, sanitizer, hostDocument) => {
        const namespace = `__CALIBER_PAGE_EXECUTOR_${safeAppName}`;

        return {
          async execute(codeString) {
            return new Promise((resolve, reject) => {
              const requestId = `req_${Math.random().toString(36).substr(2, 9)}`;
              const responseEventName = `${namespace}_RESPONSE_${requestId}`;

              const handleResponse = (event) => {
                const { success, data, errorMsg } = event.detail;
                hostDocument.removeEventListener(responseEventName, handleResponse);
                if (success) {
                  resolve(data);
                } else {
                  reject(new Error(errorMsg || 'Page-scope code execution failed.'));
                }
              };

              hostDocument.addEventListener(responseEventName, handleResponse, { once: true });

              const injectionCode = `
              (async () => {
                const RESPONSE_EVENT_NAME = '${responseEventName}';
                try {
                  const result = await (${codeString});
                  
                  document.dispatchEvent(new CustomEvent(RESPONSE_EVENT_NAME, {
                    detail: { success: true, data: result }
                  }));
                } catch (e) {
                  document.dispatchEvent(new CustomEvent(RESPONSE_EVENT_NAME, {
                    detail: { success: false, errorMsg: e.message }
                  }));
                }
              })();
            `;

              sanitizer.injectScript(hostDocument, injectionCode);
            });
          }
        };
      },

    };

    /**
     * 创建并启动一个基于 Caliber 框架的增强脚本应用。
     * 这是 Caliber 框架的唯一入口点。
     * 
     * @param {object} options - 应用的配置对象。
     * @param {string} options.appName - 应用的名称。将用于日志前缀、UI标题和菜单项。
     * @param {class[]} options.modules - 一个由模块类(必须继承自 Caliber.Module)组成的数组。
     * @param {object} options.services - 一个包含所有平台相关服务实现的对象。
     * @param {object} options.services.storage - 存储服务适配器。必须实现 get() 和 set(value) 方法。
     *   @param {() => Promise<object>} options.services.storage.get - 一个异步函数,返回存储的用户配置对象。
     *   @param {(value: object) => Promise<void>} options.services.storage.set - 一个异步函数,将配置对象写入存储。
     * @param {object} options.services.command - 命令服务适配器。必须实现 register(name, callback) 方法。
     *   @param {(name: string, callback: () => void) => void} options.services.command.register - 一个函数,用于注册一个菜单命令。
     * @param {object} [options.services.hostWindow=window] - (可选) 要操作的窗口对象。默认为油猴环境的`unsafeWindow`或标准`window`。
     * @param {object} [options.services.hostDocument=document] - (可选) 要操作的文档对象。默认为`document`。
     * @param {boolean} [options.isDebug=true] - (可选) 是否开启调试模式,会影响日志的输出。默认为`false`。
     * @param {boolean} [options.settingsPanelEnabled=true] - (可选) 设置面板在首次启动时是否默认开启。默认为`true`。
     * @param {object} [options.framework] - (可选) 用于微调 Caliber 框架内部行为的配置。
     * @param {number} [options.framework.domProcessorBatchSize=20] - (可选) 设置 DomBatchProcessor 在每个渲染帧中处理的最大任务数。
     * @returns {Promise<void>}
     */
    async function createApp(options) {
      const { appName, modules, services, isDebug = false, settingsPanelEnabled = true } = options || {};

      // 初始化基础环境
      const loggerFactory = new LoggerService(isDebug);
      const mainLogger = loggerFactory.createMainLogger(appName || 'CaliberApp');
      const hostWindow = (services.hostWindow || (('unsafeWindow' in window) ? unsafeWindow : window));
      const hostDocument = (services.hostDocument || hostWindow.document || document);

      // 创建上下文
      const context = _CaliberInternals._createAppContext(appName);

      // 执行预检 
      if (!_CaliberInternals.runPreflightChecks(options, context.instanceKey, mainLogger, hostWindow)) {
        return;
      }

      const sanitizer = _CaliberInternals.createDOMSanitizer(mainLogger);
      // 优先使用用户提供的,否则使用框架内置的原生适配器
      const finalServices = {
        storage: services.storage || _CaliberInternals._nativeBrowserAdapters.storage(`CALIBER_STORAGE_${appName}`),
        command: _CaliberInternals._nativeBrowserAdapters.command,
        style: services.style || _CaliberInternals._nativeBrowserAdapters.style(sanitizer, hostDocument),
        ...services
      };

      // 初始化核心服务
      const coreServices = _CaliberInternals.initializeCoreServices(options, context, mainLogger, hostWindow, hostDocument, sanitizer);

      // 组装依赖并运行内核
      const injectedServices = {
        hostWindow,
        hostDocument,
        eventBus: coreServices.eventBus,
        storage: finalServices.storage,
        logger: mainLogger,
        style: finalServices.style,
        framework: {
          ...coreServices.framework,
          ...options.framework,
        },
        IS_DEBUG: isDebug,
        APP_NAME: appName,
        SAFE_APP_NAME: context.safeAppName,
        initialConfig: { settingsPanel: { enabled: settingsPanelEnabled } },
      };

      const kernel = new AppKernel(injectedServices);
      hostWindow[context.instanceKey] = kernel;
      modules.forEach(ModuleClass => kernel.registerModule(ModuleClass));
      await kernel.run();

      // 注册外部接口
      finalServices.command.register(`⚙️ ${appName} 设置`, () => {
        coreServices.eventBus.emit('command:toggle-settings-panel');
      });

      mainLogger.log('Bootstrap sequence complete. Application is alive.');
    }

    return {
      createApp,
      Module // 暴露 Module 基类,以便应用脚本可以继承它
    };
  })();

  // 将Caliber对象挂载到window上
  window.Caliber = Caliber;

})((typeof unsafeWindow !== 'undefined' && unsafeWindow) ? unsafeWindow : window);