
Cherry Studio 主进程 Core 基础设施解析应用级架构、启动三阶段与核心模块【免费下载链接】cherry-studio Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studioCherry Studio 的 v2 主进程将与业务无关的应用级基础设施收敛到src/main/core/目录形成一套包含预启动preboot、分阶段引导bootstrap与稳态运行running的完整生命周期体系。本文以 core 目录总览 为骨架结合 main.ts、生命周期参考文档与各子模块实现系统讲解 core 的边界判定准则、三阶段启动模型以及 IoC 容器、路径注册表、并发原语、诊断工具、utilityProcess 等核心模块的设计与用法帮助读者快速定位 Cherry Studio 主进程骨架代码并理解新增服务应放在哪一层、如何接入生命周期。core/ 的定位应用级基础设施层src/main/core/存放的是应用级基础设施application-level infrastructure与业务逻辑完全解耦。换句话说这些模块是 Cherry Studio 作为一个 Electron 应用能跑起来所必需的骨架——即使明天把所有业务功能AI 对话、模型、话题、助手、知识库、MCP 等全部替换掉这些模块依然不可或缺。core 目录明确收纳以下能力生命周期管理服务注册、引导、关闭日志基础设施配置管理IPC 通信框架安全原语IPC 发送方来源信任插件/扩展系统的基础管道平台抽象工具边界判定什么不属于 core与上述骨架相对以下内容严禁放进 core/与 Cherry Studio 具体业务相关的东西AI、会话、模型、话题、助手、知识库、MCP 等业务数据 schema、仓库或服务UI 专属逻辑功能专属的工具函数文档给出的经验法则rule of thumb非常直白如果移除某个模块会让应用无论具备什么功能都无法运行它属于core/如果移除它只会破坏某个具体功能它应放在别处如services/、data/。这条准则在目录结构上可以得到印证业务服务集中在 src/main/services/数据层集中在 src/main/data/而 core 下只有 application/、lifecycle/、preboot/、paths/、logger/、concurrency/、security/、window/ 等通用设施目录本身没有出现任何AIconversationtopic这类业务名词。启动三阶段preboot / bootstrap / running整个 v2 主进程的启动被划分为三个顶层阶段这也是全代码库统一使用的标准术语core/README.md 与 preboot/README.md 中反复强调不要随意引入替代命名阶段归属说明prebootcore/preboot/必须在application.bootstrap()被调用之前完成的同步设置BootConfig 加载、logger 初始化、userData 解析、命令行开关、顶层 Electron API。无 DI、无生命周期服务。bootstrapcore/application/core/lifecycle/application.bootstrap()编排函数冻结路径注册表、构建 IoC 容器、执行生命周期阶段Background / BeforeReady / WhenReady。采用 NestJS/Spring 风格术语是全代码库中 bootstrap 的唯一含义。running无显式归属application.bootstrap()返回之后的稳态所有服务就绪、主窗口可见、IPC 与用户事件正常流转。有两个容易混淆的点需要特别澄清生命周期阶段Background / BeforeReady / WhenReady运行在 bootstrap 阶段内部不是三个独立的顶层阶段。遗留文件 src/main/bootstrap.ts 早于这套术语体系目前已无任何导入将在后续清理 PR 中移除——阅读旧代码时不要被它误导。preboot带着时序契约的同步启动段从 main.ts 可以看到 preboot 的实际编排顺序顺序本身即是契约// BootConfig must load before any other import (configures userData path) import main/data/bootConfig // Preboot phase — order matters. See core/preboot/README.md. resolveUserDataLocation() requireSingleInstance() configureChromiumFlags() initCrashTelemetry() protocol.registerSchemesAsPrivileged([CHERRY_MEDIA_SCHEME_DECLARATION, MINI_APP_SCHEME_DECLARATION]) // Freeze the path registry — bootstrap() asserts this completed. application.initPathRegistry()关键时序约束preboot/README.mdresolveUserDataLocation()必须先于单实例锁开发实例可通过 userData 后缀获得互相隔离的锁所有app.setPath(userData, …)必须发生在application.initPathRegistry()之前因为路径注册表在初始化时就要读取app.getPath(userData)等 Electron 路径并生成冻结快照路径注册表必须在application.bootstrap()之前就绪——bootstrap()只会断言注册表已存在拒绝在未初始化时启动Chromium 启动开关与特权 scheme 声明必须在app.whenReady()之前完成。preboot 的成员资格标准五条同时成立才属于 preboot/否则应放进 services/ 或生命周期模块必须在application.bootstrap()之前运行这是时序契约而非目录归属必要条件但不充分不可移除没有它应用就无法正常启动可移除的能力即使执行时机在 preboot也应放在其自然归属地如 services/并从 main.ts 在正确时机调用只依赖 Electronapp顶层 API 与同步加载模块如BootConfigService、loggerService直接对全局状态产生副作用路径、命令行开关或是支撑某个副作用 preboot 操作的纯辅助函数不依赖任何生命周期托管服务任何经由application.get(...)获取的服务——这是最硬性的约束preboot 代码允许在必要时异步例如 v1→v2 迁移门需要 await 一次 DB 探测但绝不能依赖只有在application.bootstrap()之后才存在的服务。preboot 目录刻意保持扁平、无 barrel 导出每个模块有各自的时序契约userDataLocation必须先于initPathRegistrychromiumFlags必须先于app.whenReady消费者必须从具体模块文件导入这样 main.ts 中的启动顺序在任何调用点都清晰可见。另外注意 preboot 文档中的术语提醒这里的userData特指 Electron 的app.getPath(userData)目录它既包含用户内容Data/cherrystudio.sqlite、Data/Files、Data/KnowledgeBase等也混有 Chromium 运行时状态Network/、Partitions/、IndexedDB、Local Storage等Windows/Linux 下还包含logs/macOS 日志在~/Library/Logs。bootstrapapplication.bootstrap() 编排bootstrap 阶段由 application/Application.ts 与 lifecycle/ 共同完成。完整引导流程Application OverviewsetupSignalHandlers() ← SIGINT/SIGTERM → graceful shutdown setupQuitHandlers() ← before-quit (preventQuit gate) will-quit (shutdown) │ ├── startPhase(Background) ← fire-and-forget (非阻塞) │ ├── startPhase(BeforeReady) ─┐ │ ├──── 并行执行 └── app.whenReady() ─┘ │ ├── setupElectronHandlers() ← window-all-closed, preventQuit IPC │ ├── startPhase(WhenReady) ← 需要 Electron API 的服务 │ ├── await Background ← 确保后台服务完成 │ └── allReady() ← 通知所有服务系统已完全就绪如果某个 fail-fast 服务在引导期间抛错会弹出对话框提供 Exit 或 Restart。main.ts 中对应的调用是application.registerAll(serviceList) const bootstrapPromise application.bootstrap() await app.whenReady() await bootstrapPromise注意application.bootstrap()与app.whenReady()是并行发起的这正对应 BeforeReady 阶段与 Electron 自身初始化并行、占用空闲时间的设计。running稳态application.bootstrap()返回后进入 running 阶段所有服务就绪、主窗口可见、IPC 与用户事件正常流转。此阶段没有显式的目录归属属于架构术语层面的命名。生命周期阶段Background / BeforeReady / WhenReady生命周期系统提供三个初始化阶段Lifecycle Overview阶段说明时机是否等待BeforeReady不需要 Electron API 的服务app.whenReady()之前是Background独立服务fire-and-forget立即开始否WhenReady需要 Electron API 的服务默认app.whenReady()之后是阶段选择的判断流程官方决策树┌──────────────────────┐ │ 直接使用 Electron API? │ └──────┬─────────┬─────┘ yes │ │ no ▼ ▼ ┌───────────┐ ┌───────────────────────────┐ │ WhenReady │ │ 在关键启动路径上? │ └───────────┘ │ (其他服务依赖它) │ └─────┬──────────┬──────────┘ yes │ │ no ▼ ▼ ┌─────────────┐ ┌────────────┐ │ BeforeReady │ │ Background │ └─────────────┘ └────────────┘BeforeReady与app.whenReady()并行执行只要在 Electron 就绪前完成就不增加启动延迟。最适合数据库连接、配置加载、数据迁移、schema 校验等 WhenReady 服务将依赖的工作。不能使用任何 Electron API。WhenReady默认阶段省略ServicePhase即落在此处AfterReady 与app.whenReady()都完成之后运行可完整访问BrowserWindow、Tray、screen、nativeTheme、dialog、globalShortcut等。适合窗口管理、托盘、系统快捷键、主题管理、需要 Electron API 的 IPC handler。Background立即启动但完全独立永不阻塞其他阶段错误被捕获并记录但不会中止引导。适合遥测上报、非关键数据预取、后台清理任务需要与其他阶段服务交互时用onAllReady()。依赖规则Dependency Rules阶段可依赖不可依赖BeforeReadyBeforeReadyBackground、WhenReadyBackgroundBackgroundBeforeReady、WhenReadyWhenReadyBeforeReady、WhenReadyBackground关键约定跨阶段依赖是隐式的——可依赖列只表示这些服务保证已就绪不应再通过DependsOn声明例如 WhenReady 服务无需声明依赖PreferenceService、DbService、CacheService、DataApiService这些 BeforeReady 服务跨阶段就绪由LifecycleManager.startPhase()自动保证冗余声明只会污染依赖图。DependsOn只用于同阶段排序。无效依赖会被自动纠正并打警告日志。并行初始化同一阶段内互无依赖的服务按层并行初始化Phase: WhenReady Layer 1: [DbService, ConfigService] - 并行无相互依赖 Layer 2: [PreferenceService] - 串行依赖 layer 1 Layer 3: [MainWindowService] - 串行依赖 layer 2核心模块逐个解析core 目录当前收录的模块及其参考文档如下core/README.md模块说明参考文档application/应用单例、服务注册表、bootstrap 编排Lifecycle Referenceconcurrency/createLatestReconciler——通用 latest-wins 异步副作用调和器single-flight、level-triggeredconcurrency/README.mddiagnostics.ts可选性能插桩CPU profile、事件循环延迟、服务 span由CS_DIAGNOSTICS控制diagnostics.mdlifecycle/IoC 容器、服务生命周期管理、分阶段 bootstrapLifecycle Referencelogger/基于 Winston 的日志服务preboot 单例经logger别名消费logging.mdpaths/路径注册表主进程所有文件系统路径的唯一事实来源paths/README.mdpreboot/预引导同步设置userData 解析等preboot/README.mdutilityProcess/崩溃隔离的 Electron utility 进程注册、类型化客户端、wire protocol、子进程运行时Utility Process Reference从目录结构看core/下还包含 security/如guardedIpc.ts、validateSender.ts——IPC 发送方来源信任的安全原语、window/WindowManager 及其窗口注册表/边界追踪、job/ 与 scheduler/任务与调度基础设施、power/系统电源状态服务、以及 platform.ts 平台抽象它们共同构成 core 的完整拼图。application/顶层编排器application是顶层编排器它不重复实现生命周期逻辑而是内部委托给ServiceContainer与LifecycleManager对外提供干净的应用级 APIApplication Overviewimport { application } from application import { serviceList } from main/core/application/serviceRegistry // 1. 注册所有服务 application.registerAll(serviceList) // 2. 引导内部处理三个阶段 Electron 生命周期 await application.bootstrap() // 3. 获取服务 const dbService application.get(DbService)设计细节值得注意导出的application常量是一个惰性代理在模块顶层导入是安全的真正的Application实例在首次属性访问时才创建。服务注册表 serviceRegistry.ts 是 bootstrap 内部清单注册一个新服务只需加一行NewService,类型自动推导随后即可通过application.get(NewService)获得类型安全访问。application别名直接解析到Application.ts文件本身非目录 barrel因此导入定位器不会拖入整个服务依赖图。关闭流程application.shutdown()是所有优雅退出的汇聚点顺序为bootConfigService.flush()→stopAll()按初始化逆序执行onStop()→destroyAll()按逆序执行onDestroy()→loggerService.finish()必须最后关闭 logger。每个服务在 stop/destroy 阶段有SERVICE_STOP_TIMEOUT_MS5s上限stopAll()/destroyAll()会返回超时/失败摘要Shutdown complete行会标明退出是否干净。此外每个退出入口都会给shutdown()包一层 30s 的SHUTDOWN_TIMEOUT_MSprocess.exit(1)熔断定时器——健康关闭通常在 1 秒内完成远不会触达该熔断。退出/重启 API一律使用application.quit()、application.forceExit(code)、application.relaunch()、application.markQuitting()、application.preventQuit(reason)返回带dispose()的 hold而不是直接调app.quit()/app.exit()——ESLint 规则no-restricted-properties会拦截 src/main/ 下除Application.ts之外的裸调用。relaunch()额外处理了开发模式检测dev 下弹窗优雅退出、Linux AppImageexecPath重写与 Windows Portable 可执行路径修复。lifecycle/IoC 容器与分阶段引导lifecycle/ 是 IoC 容器 服务生命周期管理文件结构清晰lifecycle/ ├── types.ts # Phase, LifecycleState, ServiceMetadata, Pausable, errors ├── decorators.ts # Injectable, ServicePhase, DependsOn, Priority, 等 ├── BaseService.ts # 带生命周期钩子的抽象基类 ├── event.ts # EmitterT, EventT, Disposable —— 类型化服务间事件 ├── signal.ts # SignalT —— 一次性 deferred 值 (PromiseLike) ├── ServiceContainer.ts # 带 DI 与条件激活的 IoC 容器 ├── DependencyResolver.ts # 拓扑排序、分层并行解析 ├── LifecycleManager.ts # 分阶段引导、关闭、pause/resume/stop/start ├── index.ts # Barrel 导出 └── __tests__/ # 各组件单元测试生命周期钩子状态机Created → Initializing → Ready ⇄ Paused以及Stopping → Stopped → Destroyed钩子调用时机可覆写onInit()初始化期间重启时重新初始化也会调用是onReady()onInit()完成后立即是onAllReady()所有阶段所有服务就绪后调用一次是onStop()服务停止时是onDestroy()最终清理服务不可复用是onPause()/onResume()暂停/恢复时需实现Pausable是自动资源清理BaseService统一用 Disposable 追踪机制管理资源——IPC handler、事件订阅、定时器、Signal、清理函数全部登记为 Disposable在 stop 生命周期统一释放。registerDisposable()同时接受Disposable对象和纯() void清理函数ipcHandle()、ipcOn()、registerInterval()的返回值都会通过同一通道自动登记。onStop()即使抛错disposal 仍会执行调用位于finally中。onAllReady 语义系统级就绪onAllReady()在所有阶段的服务都完成初始化后调用一次此时可安全访问任何服务而不必关心DependsOn。它与ALL_SERVICES_READY事件在同一 JS tick 内先后触发——框架先逐个调用服务的onAllReady随后立即发出事件不等待钩子完成fire-and-forget。因此监听该事件的一方不能假设onAllReady的副作用已完成服务自身用钩子响应非服务代码诊断、遥测订阅事件。restart()不会重触发onAllReady由_allReadyCalled守卫钩子内抛错会被框架捕获并作为SERVICE_ERROR发出绝不传播到 bootstrap。服务间通信的四种模式模式机制示例服务 B 必须在 A 之后初始化DependsOnPreferenceService 依赖 DbServiceA 完成运行时工作其他服务响应可重复EmitterT/EventTMainWindowService 触发onMainWindowCreatedA 完成运行时工作其他服务响应一次性SignalT框架已实现并通过单测暂无生产消费者让某服务做某事经application.get()直接调用方法windowService.showMainWindow()paths/路径注册表——主进程路径的唯一事实来源paths/ 是主进程所有文件系统路径的唯一事实来源所有路径在pathRegistry.ts注册、统一经application.getPath()访问import { application } from application const dir application.getPath(feature.files.data) // /Users/alice/Library/Application Support/CherryStudio/Data/Files const file application.getPath(feature.files.data, avatar.png) // .../Data/Files/avatar.png application.getPath(invalid.key) // TS2345: invalid.key is not assignable to type PathKey顶层命名空间分为六类命名空间归属示例cherry.*~/.cherrystudio下的通用基础设施cherry.home,cherry.binsys.*操作系统托管目录sys.home,sys.temp,sys.downloadsapp.*Electron 应用安装目录、userData、数据库、日志、临时根app.userdata,app.database.filefeature.*Cherry 自有的功能数据按功能分组默认首选feature.files.data,feature.mcp.oauthv1.*仅为清理保留的旧版本路径v1.trace,v1.cli.installexternal.*第三方路径Cherry 可读写但不拥有external.openclaw.config使用约束活动数据默认用feature.*Cherry 创建/管理/可删除v1.*只用于旧版本清理目标Cherry 从不创建仅可在显式清理时检查/删除external.*严禁删除。键名格式/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)$/ESLintdata-schema-key/valid-key强制至少两段、点分隔、每段以字母开头多词段用 snake_case。文件与目录的区分靠后缀约定独立文件用_file后缀、带兄弟键的文件用.file末段、无后缀表示目录目录键绝不能以file结尾auto-ensure 据此区分类型。Auto-ensure 与 NO_ENSUREgetPath()首次访问时自动创建目录每键至多一次目录键执行mkdirSync(base, { recursive: true })文件键只创建dirname文件本身不创建。NO_ENSURE列表支持命名空间前缀sys.、external.与精确 PathKey 两种形式内的键跳过自动创建——适用于只读/第三方/由所有者另行执行受控物化/仅清理的旧版本路径通过satisfies做类型检查拼写错误与过期引用在编译期失败。两个重要认知点号是语义而非物理feature.mcp.oauth物理上位于~/.cherrystudio/config/mcp/oauth在config/下而非mcp/下feature.pdf_translation.babeldoc位于{userData}/Runtime/models/babeldoc与其他下载的模型缓存同组。永远不要从键的嵌套推断磁盘嵌套直接查pathRegistry.ts。构建时机buildPathRegistry()在 preboot 期间运行一次app.setPath(userData, ...)之后、app.whenReady()之前因此每个值只能依赖同步 Electron API、process.resourcesPath或 Node 内置模块在initPathRegistry()之前调用getPath()会抛错LoggerService与BootConfigService因为先于注册表存在直接读 paths/constants.ts。concurrency/并发原语concurrency/ 提供事件源无关的通用并发原语不感知 Preference、生命周期、IPC 或任何具体触发器。KeyedMutex按 key 串行化独立条目每个 key 惰性创建一个互斥锁、空闲即释放。共享同一 key 的任务严格 FIFO 串行不同 key 之间并发执行。runExclusive(key, task)接受同步或异步任务并返回其结果对以事件结束而非函数返回的临界区可用await acquire(key)拿到幂等 release 回调必须在所有终止路径上调用。createLatestReconcilerlatest-wins 异步副作用调和器当同一异步副作用可能被高频触发、且只有最新意图有意义时使用。它串行化效果、把突发合并为最新、每轮重读世界状态以确保最终结果与最终意图一致。核心属性属性行为single-flight绝不会同时运行两个applyrunning守卫latest-wins / 合并在途apply期间的请求合并为一次后续 pass中间状态永不重放无逐事件队列level-triggered每轮重读getSnapshot()并向其收敛免疫忙碌 handler 丢失一次订阅触发的边缘触发丢失终止性失败抛错的apply停止循环记录错误而不是无限重试同一目标后续request()会重新收敛使用判断三条件同时满足才用① 异步apply副作用会 await/让出控制权② 重复、可能高速连续触发③ 只有最新意图有意义中间状态是可丢弃的目标态/幂等收敛而非必须逐条执行的累积命令。同步副作用、命令/增量语义每个事件都必须按序执行、按 key 串行化独立条目这三种场景不要用调和器分别改为直接调用、FIFO 队列如 p-queue / async-mutex与KeyedMutex。API 契约request()标记脏并确保循环运行廉价、可重入、可合并、dispose()后为空操作flush()在循环静止时 resolve并不等待isSettled true——失败或无进展的 pass 也会让循环静止直接等待已收敛会挂起调用方需自行断言后置条件getLastError()返回最近一次失败的错误或空dispose()停止接收工作在途apply完成不启动新 pass。关于 dispose 的要点它是停止 apply 的开关而非资源清理——调和器不持有任何 OS 资源关键是判断停止工作后request()是否仍可能到达。构造即字段、触发器随所有者销毁的不需要dispose每周期重建如每次onActivate()新建而触发器更长寿的必须dispose 旧的。文档特别警告不要对构造一次的字段registerDisposable(() reconciler.dispose())——那会在stop时触发而 restart 不会重建字段此后request()将永久失效ApiGatewayService正是刻意不 dispose 的参考消费者。lifecycle/的Emitter/Event/Signal负责通知调和器负责收敛把一串有东西变了的通知变成一个已收敛的异步副作用二者天然组合——Emitter 事件 handler 完全可以作为request()触发器。diagnostics.tsCS_DIAGNOSTICS 门控的性能诊断diagnostics.ts 提供默认关闭CS_DIAGNOSTICS未设置时零开销的主进程性能插桩。启用方式# 开发 CS_DIAGNOSTICS1 pnpm dev # 打包产物须从终端启动以传入环境变量 # macOS CS_DIAGNOSTICS1 /Applications/Cherry Studio.app/Contents/MacOS/Cherry Studio # Windows (PowerShell) $env:CS_DIAGNOSTICS1; $env:LOCALAPPDATA\Programs\Cherry Studio\Cherry Studio.exe # Linux (AppImage) CS_DIAGNOSTICS1 ./Cherry Studio-version-arch.AppImage产出的信号完整清单见 diagnostics 文档每服务初始化耗时[Diagnostics/_doInit]、阶段服务 span 与事件循环延迟[Diagnostics]、whenReady 阶段 V8 采样 CPU profileboot-whenReady.cpuprofile、慢 DB 查询15ms[Diagnostics/slow-query]、慢 IpcApi 请求50ms、慢遗留 IPC handler50ms、窗口创建耗时、慢 DataApi 请求50ms。所有阈值集中在SLOW_THRESHOLD_MS一处定义。慢查询探针包装的是 better-sqlite3 而非 drizzledrizzle 的 logger 不带计时无法标记慢查询。新增诊断的步骤导入DIAGNOSTICS_ENABLED标志 →if (DIAGNOSTICS_ENABLED) { … }守卫禁用路径必须零成本→ 阈值加入SLOW_THRESHOLD_MS→ 日志统一[Diagnostics/name]前缀。utilityProcess/崩溃隔离的子进程运行时utilityProcess/ 提供基于 Electron utility process 的崩溃隔离机制包含注册、类型化客户端、wire protocol 与子进程运行时四层。从目录看其实现分三块host/ProcessHost.ts宿主管理、stdioRelay.ts标准 IO 中继、进程适配器electronProcessAdapter.ts面向 Electron、processAdapter.ts抽象接口、environment.ts环境protocol/frames.ts帧协议、guards.ts协议守卫、remoteError.ts远程错误、constants.tsruntime/serveUtilityProcess.ts与utilityProcessServer.ts子进程侧服务端。配套有 13 组测试ProcessHost.breaker/cancellation/lifecycle/stop、UtilityProcessManager、defineUtilityProcess、protocolGuards、stdioRelay等覆盖熔断、取消、生命周期与协议守卫等关键行为。完整设计见 Utility Process Reference。从源码看 core 的设计原则综合上述模块可以总结出几条贯穿 core 的工程约束无 barrel 原则application/、preboot/、paths/等目录刻意不提供index.ts消费者必须从具体文件导入让时序契约与依赖关系在导入处保持可见遵循 命名规范 中纯聚合独立子模块的目录不加 barrel的约定。单一事实来源路径只有一个注册表getPath()服务只有一个容器application.get()关闭只有一个汇聚点shutdown()避免任何临时抱佛脚的旁路。时序契约即代码preboot 的调用顺序、initPathRegistry()的断言、bootstrap()的注册表检查都把必须在此之前完成固化成可执行代码与类型检查。ESLint 强约束路径键格式、裸app.quit()/app.exit()拦截、pathRegistry.ts内禁对象字面量等都用 lint 规则把架构边界落地为机器可查的规范。小结src/main/core/是 Cherry Studio 主进程的骨架层它以preboot → bootstrap → running三阶段模型组织启动流程用 lifecycle 的 IoC 容器管理服务生命周期用路径注册表统一文件系统访问用并发原语与诊断工具保障运行质量并用明确的归属准则与 lint 约束守住业务无关的边界。对想要理解 Cherry Studio 主进程架构、或者在其中新增通用基础设施的开发者来说先读 core/README.md 建立全局观再按模块对照 生命周期参考、paths 文档、concurrency 文档 与 诊断文档 深入是最快的上手路径。【免费下载链接】cherry-studio Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考