deepseek-harness-desktop 更新生命周期归属重构desktop-updates插件与DesktopUpdateLifecycle模块的职责划分【免费下载链接】deepseek-harness-desktop为 DeepSeek Harness (DSH) 插件生态打造的现代化桌面端解决方案。万物皆「插件」桌面本身也是「插件」。项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness-desktop导读本文基于 deepseek-harness-desktop 仓库中已落地Status: implemented的架构决策笔记 2026-08-19-desktop-update-lifecycle-ownership.md剖析 DSH Desktop 更新功能从单插件闭包到代际generation作用域生命周期模块的归属重构。文中将完整还原问题背景、接口决策、生命周期不变量Lifecycle invariant、保留行为与限制并结合仓库源码updates.ts、update-lifecycle.ts、update-checker.ts 等逐层印证实现细节。读完本文你将掌握该架构模式的核心思想一次 start 建立全部更新行为一次 dispose 释放整代资源以及它如何被测试与构建流程验证。一、问题背景一个插件闭包里的并行所有权1.1desktop-updates插件的职责全景desktop-updates是 DSH Desktop 中负责「定时检查、手动检查、确认、下载交接、提示历史持久化、动态托盘状态、超时、取消与释放」的 Cordis 插件。重构前这些行为的全部可变状态都居住在一个ctx.effect()闭包内位于updates.ts包括两个定时器后台轮询定时器pollTimer与请求超时定时器requestTimer两个AbortController实例版本请求控制器与下载控制器三个单飞任务single-flight task检查任务、手动任务、下载任务持久化状态的就绪标志stateReady与可用性状态托盘项的注册与动态刷新。从源码看重构后的updates.ts只保留了插件门面name desktop-updates、注入声明inject [desktopRuntime, webServer, connection]、Configschema 校验以及一个调用startDesktopUpdateLifecycle的 effect见 updates.ts。这正是决策笔记所描述的最终形态。1.2 旧实现的痛点笔记指出插件接口虽小但实现使生命周期正确性难以审查——要判断某一代 Host 是否释放了全部更新工作必须通读每个嵌套函数并把每个状态变量与它的清理路径一一对应。更新操作本身只有一条自然生命周期因此它的状态理应藏在一道代际作用域接缝generation-scoped seam之后。重构前的依赖关系可用如下示意图表达来自笔记的 Before mermaid可以看到所有状态源都直接汇聚到内联的 disposer任何新增状态都必须同步更新清理逻辑容易遗漏。二、决策保留插件接口抽出私有生命周期模块2.1 核心决策保持不变公开的desktop-updatesCordis 插件及其导出的Config均不改变新增一个私有的DesktopUpdateLifecycleModule接口为startDesktopUpdateLifecycle(options): DesktopUpdateLifecycle DesktopUpdateLifecycle.dispose(): Promisevoid该 Module 拥有prompt-history 的加载、校验、替换与可选持久化后台调度与请求超时定时器共享的手动/后台版本检查确认后的一次全新版本复查recheck一个进行中的下载及其取消控制器可下载/下载中状态的托盘呈现及其注册代际释放包括幂等取消与托盘移除。重构后的updates.ts只负责校验 Cordis 配置、在一个 effect 中启动一个生命周期、并把 effect 的释放委托给返回的句柄它不再检查或变更任何生命周期内部状态见 updates.ts。2.2 重构后的结构图来自笔记的 After mermaid接口比实现小却给了调用方杠杆一次 start 建立全部更新行为一次 dispose 释放整个代际。如果删除该 Module其状态与清理规则将回到updates.ts因此这道接缝是挣来的earns its seam。三、源码级实现update-lifecycle.ts逐层解析3.1 模块接口与生命周期句柄update-lifecycle.ts 定义了三个核心类型DesktopUpdatePolicy只读的调度与请求策略enabled、initialDelayMs、intervalMs、requestTimeoutMsDesktopUpdateLifecycleOptions一代 Host 挂载更新处理时注入的原生能力——adapter、policy、locale、registerTrayItemDesktopUpdateLifecycle生命周期句柄暴露checkNow(): Promisevoid与dispose(): Promisevoid。入口函数startDesktopUpdateLifecycle直接返回一个DesktopUpdateLifecycleOwner实例update-lifecycle.ts实现细节全部封装在 Owner 类内部。3.2 构造函数一次性建立全部状态Owner 构造函数update-lifecycle.ts完成四件事启动loadState()并把其 Promise 保存为stateReady注册 id 为check-for-updates、分组status、顺序order: 10的托盘项其invoke指向checkNow()若releaseChannel beta额外注册order: 11的「Install Stable Edition…」托盘项installStable若adapter.isPackaged policy.enabled以initialDelayMs调度首次后台检查。托盘标签由trayLabel()动态解析update-lifecycle.ts按优先级呈现四种文案文案来自 tray-locale.ts状态英文文案中文文案下载中Downloading DSH Desktop {version}…正在下载 DSH Desktop {version}…有可用更新DSH Desktop {version} AvailableDSH Desktop {version} 已可下载检查中Checking for Updates…正在检查更新…默认Check for Updates…检查更新…3.3 状态文件v2 迁移 v3 与 4 KiB 读取上限笔记强调更新状态保持 version 2且保留 4 KiB 读取上限与原子尽力持久化。源码显示实现已升级为UpdateStateV3version: 3字段lastNotifiedVersion?并通过parseState支持从 v2字段lastPromptedVersion自动迁移update-lifecycle.ts。读取readState分配4 * 1024 1字节缓冲区若读取字节数超过 4 KiB 直接抛错update-lifecycle.ts写入persistState使用writeFileAtomicmode: 0o600、dirMode: 0o700失败静默忽略——因为更新状态是可选数据失败不得影响应用启动或用户活动update-lifecycle.ts版本校验isSupportedVersion只接受纯稳定版或beta.数字形式的预发布版并调用parseSemVer做规范化比对update-lifecycle.ts。3.4 后台轮询与提示去重scheduleBackgroundCheck使用setTimeout递归调度首次延迟initialDelayMs此后每次完成后延迟intervalMs并在disposed后停止update-lifecycle.ts。runBackgroundCheck先做单飞判断checkTask ! undefined || disposed则跳过检查结果通过observeResult记录可用版本若发现新版本则调用announceBackgroundUpdateupdate-lifecycle.ts。去重逻辑在announceBackgroundUpdate等待stateReady后若lastNotifiedVersion version则不再重复通知否则先持久化再弹出原生通知通知文案见 update-lifecycle.tslocale zh时标题为「DSH Desktop 有可用更新」。3.5 单飞版本检查与请求超时startCheck是核心单飞实现update-lifecycle.ts若已有checkTask同 channel 直接复用不同 channel 则等待当前任务结束后按新 channel 重查每次发起新请求时新建AbortController存入requestController并设置requestTimeoutMs定时器触发controller.abort()finally中清理定时器、控制器、任务与 channel并刷新托盘。真正发起的 HTTP 检查由 update-checker.ts 的checkForDesktopUpdate完成GET 请求固定端点https://www.dshdesktop.cn/api/desktop/version带X-DSH-Desktop-Version、X-DSH-Desktop-Channel头可选附带安装 ID响应体读取上限 4 KiBMAX_VERSION_RESPONSE_BYTES非 200 状态、JSON 解析失败或 channel 不匹配均返回null。SemVer 比较采用字符串化的数值比较以避免数字溢出update-checker.ts。3.6 手动检查、确认与下载交接runManualCheck是托盘「检查更新」与 Web 路由调用的共同入口update-lifecycle.ts若已有availableVersion直接offerDownload否则发起一次startCheck()通过observeResult记录结果有新版本则offerDownload否则调用adapter.showManualCheckResult呈现原生结果对话框。startDownloadupdate-lifecycle.ts体现确认后复查不变量先confirmDownload原生确认对话框不可取消确认后再次发起startCheck复查只有复查仍指向同一版本时才继续用独立的AbortController管理下载成功后调用downloadAndOpen交给平台安装器网络、文件系统、安装器打开失败一律静默catch空实现。3.7 释放幂等 disposedispose()update-lifecycle.ts是整篇文章的落点幂等disposeTask已存在则直接返回同一个 Promise先标记disposed true再清空两个定时器、abort 请求与下载控制器、移除托盘注册只等待stateReady与可中止的checkTaskPromise.allSettled——原生对话框不可取消因此不被等待也不会阻塞 Host 释放返回的 Promise 供 effect 清理函数await见 updates.ts。四、生命周期不变量Lifecycle invariant对一代更新生命周期源码与笔记共同保证了以下七条不变量至多一个版本检查请求在途手动与后台调用共享同一checkTaskupdate-lifecycle.ts至多一个确认/下载任务在途downloadTask非空即复用update-lifecycle.ts确认的版本在下载交接前必须复查startDownload中的二次startCheckupdate-lifecycle.ts后台提示在打开确认前记录版本且同一持久化版本不重复提示announceBackgroundUpdate的lastNotifiedVersion去重update-lifecycle.tsdispose 先标记代际失效再清理定时器、中止请求/下载、移除托盘disposed true位于清理之前update-lifecycle.tsdispose 只等待状态就绪与可中止的版本请求原生对话框不可取消不阻塞 Host 释放update-lifecycle.ts重复 dispose 返回同一任务、托盘只移除一次、轮询无法重启disposeTask幂等 scheduleBackgroundCheck检查disposedupdate-lifecycle.ts。五、保留行为与明确边界笔记「Preserved behavior and limits」部分逐条界定了重构不改变与不新增的内容更新状态持久化语义不变4 KiB 读取上限、原子尽力写入v2 字段lastPromptedVersion被自动迁移为 v3 的lastNotifiedVersion手动失败仅通过既有原生结果对话框可见定时调度、文件系统、下载、安装器打开失败维持静默行为下载端点、产物校验、安装器交接、更新发现逻辑全部不变本重构不引入加密产物身份cryptographic artifact identity、断点续传、自动重试、远程遥测原生确认/结果对话框仍不可取消Owner 会阻止对话框的迟到结果在 dispose 后开启新工作if (this.disposed) return散布在各异步流程。这些边界意味着生命周期模块只负责组织与释放而下载端点与平台安装器适配器仍是独立模块对应仓库中的update-checker.ts与desktop-runtime-environment.ts等平台层。六、验证与后果6.1 验证方式笔记记录既有更新测试继续覆盖调度、提示持久化、手动/后台检查共享、确认与复查、下载单飞、取消、超时、平台能力与非阻塞原生对话框另有一个生命周期测试验证幂等 dispose 与 dispose 后轮询不重启。同时Desktop 包的构建、类型检查、完整测试套件、runtime-closure 检查与 license 检查全部通过。仓库中这些能力对应的实现证据集中在 update-lifecycle.ts 与 update-checker.ts且updates.ts中 effect 的 dispose 即unregister()之后await lifecycle.dispose()二者顺序保证 Web 路由先摘除、再释放生命周期updates.ts。6.2 架构后果未来对更新定时器、操作任务、提示历史、托盘状态或释放行为的改动应落在update-lifecycle.tsupdates.ts保持为 Cordis 适配器与配置面Configschema 见 updates.ts配置项如下配置项类型默认值校验enabledbooleantrue—initialDelayMsnumber60_000整数0 ~ 2_147_483_647intervalMsnumber6 * 60 * 60 * 10006 小时整数1 ~ 2_147_483_647requestTimeoutMsnumber15_000整数1 ~ 2_147_483_647新更新能力只在共享这一代生命周期时才应扩展该 Module产物校验与平台安装器适配器保持为独立 Module与desktop-updates的「编排」职责解耦。七、小结DSH Desktop 的更新功能重构给出了一个可复用的架构范式把一条自然生命周期的全体可变状态收拢进一个拥有 start/dispose 句柄的私有模块让 Cordis 插件退化为纯配置与装配层。其价值在于可审查性代际边界唯一释放路径只有dispose()一处幂等性重复释放、迟到对话框、轮询重启等竞态在单点内被统一防住可测试性生命周期测试可以独立于 Cordis 树验证 dispose 语义。对于需要管理定时器、并发任务、取消控制器与原生 UI 注册的桌面端插件而言这套代际作用域生命周期所有权设计是一份可以直接借鉴的实现蓝本。【免费下载链接】deepseek-harness-desktop为 DeepSeek Harness (DSH) 插件生态打造的现代化桌面端解决方案。万物皆「插件」桌面本身也是「插件」。项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness-desktop创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考