后端 update.rs: app_check_update(拉 latest.json+ed25519 签名校验) / app_download_install_update / prefs 读写 / mark_checked / mark_notified 五命令, lib.rs 注册;tauri.conf updater 段(endpoints=app.1216.top/download/devflow/latest.json + pubkey + createUpdaterArtifacts) + tauri-plugin-updater 依赖 + capabilities 权限。 前端 UpdateBadge.vue(顶栏徽章+下拉) / useUpdateCheck.ts(检查/下载/安装状态机) / AboutPanel.vue(设置页关于面板) + AppLayout 接线 + SettingsNav/searchIndex 索引 + i18n zh/en settings 键。 运维 scripts/publish-release.ps1(发版) + swap-install.ps1(换装校验) + 设计文档 版本更新与自动升级-设计-2026-10-09.md。 验收: cargo check -p devflow EXIT 0 + vue-tsc EXIT 0 + build EXIT 0。
362 lines
20 KiB
Markdown
362 lines
20 KiB
Markdown
# DevFlow 版本更新与自动升级 — 专项设计(2026-10-09)
|
||
|
||
> 对象:应用内「版本可见 → 检查更新 → 下载 → 安装重启」全链路。
|
||
> 标准:合理 · 兼容 · 优雅 · 可扩张。
|
||
> 现状勘察:`src-tauri/tauri.conf.json` / `src-tauri/Cargo.toml` / `src-tauri/capabilities/default.json` / `src-tauri/src/lib.rs` / `src/views/Settings.vue` / `src/components/settings/SettingsNav.vue` / `scripts/package-devflow.ps1` / `swap-install.ps1` / u-desk 先例。
|
||
|
||
---
|
||
|
||
## 一、本质定性
|
||
|
||
### 1.1 两条"升级"通道必须分开命名
|
||
|
||
现状只有一个动作叫「换装」,但它其实是两个语义完全不同的事情,混称导致讨论失真:
|
||
|
||
| 通道 | 触发源 | 使用者 | 目标 | 现载体 |
|
||
|---|---|---|---|---|
|
||
| **本地换装** | 本机源码改动 | 开发者(你) | 把刚编译的产物装到自己机器 | `scripts/package-devflow.ps1 -Mode full` / `swap-install.ps1` |
|
||
| **应用内升级** | 远端新版本 | 任何使用者 | 把手上的旧版升到新版 | **不存在** |
|
||
|
||
**本设计只做第二条**。第一条保持不动(且是本设计的发布端依赖)。
|
||
|
||
### 1.2 为什么这件事值得做(而非"自用工具不需要")
|
||
|
||
现状的隐性成本:
|
||
|
||
1. **用户无法得知自己用的是哪版** —— 设置页无版本号展示,issue 反馈时只能靠猜("你那个缓存修复装上了吗?"——本次对话里就真实发生过:凌晨 02:50 装了包,13:02 又改了码,靠猜时间戳判断哪个生效)。
|
||
2. **升级不可达** —— 未来若分发给他人(u-desk 已有对外分发先例),对方拿到 v0.1.1 后永远停在 v0.1.1,只能等新包重装。
|
||
3. **升版本号是手工雷区** —— `swap-install.ps1` 硬编码 `devflow_0.1.1_x64-setup.exe`,改版本号忘改脚本 = 静默装回旧包(**静默伪成功**,最危险的一类故障)。
|
||
|
||
结论:**先做 L0(版本单一源 + 可见),再做 L1(检查→提示),最后 L2(下载安装)**。L0 交付即消除 1、3 两项,成本极低。
|
||
|
||
---
|
||
|
||
## 二、现状勘察(全部实证)
|
||
|
||
### 2.1 三元缺口
|
||
|
||
| 层 | 现状 | 证据 |
|
||
|---|---|---|
|
||
| 插件层 | 无 `tauri-plugin-updater` | `src-tauri/Cargo.toml:18-25` 仅 dialog/opener/window-state/notification/single-instance |
|
||
| 配置层 | 无 `plugins.updater` 段 | `tauri.conf.json` 仅 `bundle.targets: ["nsis"]` |
|
||
| 逻辑层 | 无版本比对 IPC | `grep check_update\|latest_version\|auto_update` 于 `src-tauri/src` → 0 命中 |
|
||
| UI 层 | 无版本号/检查更新入口 | `grep 检查更新\|getVersion\|appVersion` 于 `src/` → 0 命中;SettingsNav 仅 7 分类 |
|
||
|
||
### 2.2 现存缺陷(本设计顺带治理)
|
||
|
||
**D1 — 版本双源不一致**
|
||
|
||
```
|
||
src-tauri/tauri.conf.json:3 "version": "0.1.1" ← 打包器实际用这个
|
||
src-tauri/Cargo.toml:3 version = "0.1.0" ← Rust crate 用这个
|
||
```
|
||
|
||
Tauri 2 中 `bundle` 版本取 `tauri.conf.json`,`CARGO_PKG_VERSION` 取 Cargo.toml。当前两者已经分叉(0.1.1 vs 0.1.0),未来必然出「设置页显示 0.1.0,实际装的是 0.1.1」这类灵异现象。
|
||
|
||
**D2 — 换装脚本硬编码版本号**
|
||
|
||
```powershell
|
||
# swap-install.ps1:8
|
||
$installer = 'E:\wk-lab\devflow\target\release\bundle\nsis\devflow_0.1.1_x64-setup.exe'
|
||
```
|
||
|
||
版本号进入文件名,升版忘改 → 找不到文件 → `Start-Process` 抛错(尚好);若旧版包残留同目录,则**装回旧版**(伪成功)。必须改为 glob 探测(`package-devflow.ps1:44` 已用 `Sort-Object LastWriteTime -Descending | Select-Object -First 1` 的正确姿势,swap-install 未对齐)。
|
||
|
||
### 2.3 可复用的成熟资产
|
||
|
||
**u-desk(同作者的 Wails 应用)已完成全套更新链路**,虽技术栈不同(Go+Wails vs Rust+Tauri),但**方案骨架可直接借鉴**:
|
||
|
||
- 清单托管:`https://img.1216.top/u-desk/last-version.json`(七牛 CDN)
|
||
- 后端:`internal/api/update_api.go` + `internal/service/update_service.go`
|
||
- `CheckUpdate()` / `GetCurrentVersion()` / `GetUpdateConfig()` / `SetUpdateConfig()`
|
||
- `DownloadUpdate()` 异步 + `download-progress` 事件流
|
||
- `InstallUpdateWithHash()` 哈希校验后安装
|
||
- 自动检查定时器(`startAutoUpdateCheck` 可重入,配置变更 `resetAutoUpdateCheck` 免重启生效)
|
||
- 前端:`update-available` 事件驱动弹窗
|
||
|
||
**关键差异**:u-desk 是自研(无签名体系);Tauri 2 官方 updater **内建 ed25519 签名校验**,安全性更好,应优先采用官方插件而非照搬自研。
|
||
|
||
**另有可复用的健壮性经验**:`swap-install.ps1` 里已验证有效的三件套——
|
||
1. `Wait-HandleReleased()`:独占打开目标 exe 探测句柄释放(治 Windows 文件锁延迟释放致 NSIS 静默伪成功)
|
||
2. SHA256 三级判据(等磁盘快照 / 两次确定性输出 / 实变)
|
||
3. SYSTEM 上下文转投交互用户会话(GUI 才可见)
|
||
|
||
这三条应在应用内升级的「安装前等待」阶段复刻(Rust 侧实现等价逻辑)。
|
||
|
||
---
|
||
|
||
## 三、方案选型
|
||
|
||
### 方案 A:Tauri 官方 `tauri-plugin-updater`(推荐)
|
||
|
||
**能力**:`check()` → `Update` 对象(version/notes/date)→ `download_and_install(on_chunk)` → 自动重启。
|
||
|
||
**优点**
|
||
- 内建 **ed25519 签名校验**(防篡改,安全基线)
|
||
- 跨平台统一(NSIS / MSI / AppImage / dmg)
|
||
- 与 Tauri 窗口生命周期原生协同(安装前自动关窗)
|
||
- 官方维护,随 Tauri 2 演进
|
||
|
||
**代价**
|
||
- 需引入签名密钥(`tauri signer generate`,公钥入配置,私钥入打包环境)
|
||
- 打包需开 `bundle.createUpdaterArtifacts: true`(产出 `.nsis.zip` + `.nsis.zip.sig`)
|
||
- 端点必须 HTTPS + 清单格式固定(`latest.json`)
|
||
|
||
### 方案 B:自研(照搬 u-desk 模式)
|
||
|
||
**优点**:零新依赖,可完全复用 `swap-install` 的静默安装动作。
|
||
|
||
**缺点**:签名/校验/回滚/进度全自研;`latest.json` 契约要自己定;安全基线靠自己守。
|
||
|
||
### 选型结论:**A 为基座,B 的本地动作经验作为"安装前等待"的健壮性补丁喂给 A**
|
||
|
||
理由:
|
||
- 官方插件把「签名校验」这件最容易做错的事做对了,没有理由重造。
|
||
- devflow 是**联网分发**场景(未来对外),签名是刚需;u-desk 的裸下载在自用场景够,对外不够。
|
||
- 本地换装通道(脚本)**不废弃**,继续服务开发期高频迭代(它比走远端快得多)。
|
||
|
||
---
|
||
|
||
## 四、分层设计
|
||
|
||
```
|
||
┌─ L3 发布侧(脚本增强)────────────────────────────┐
|
||
│ tauri build → 产出 .nsis.zip + .sig │
|
||
│ → 生成 latest.json(version/notes/pub_date/urls/sig)│
|
||
│ → 上传 app.1216.top/download/devflow/ │
|
||
└────────────────────────────────────────────────────┘
|
||
↑ 拉取
|
||
┌─ L2 前端 ──────────────────────────────────────────┐
|
||
│ SettingsNav 新增「关于」分类(第 8 类) │
|
||
│ AboutPanel.vue:版本号 / 检查更新 / 更新弹窗 / 进度 │
|
||
└────────────────────────────────────────────────────┘
|
||
↑ IPC
|
||
┌─ L1 后端(Rust)───────────────────────────────────┐
|
||
│ tauri-plugin-updater 注册 + capabilities 授权 │
|
||
│ IPC: app_get_version / app_check_update / │
|
||
│ app_download_install_update │
|
||
│ 本地 KV: df-update-auto-check / -interval-minutes / │
|
||
│ -last-check / -notified-version │
|
||
└────────────────────────────────────────────────────┘
|
||
↑ 单一源
|
||
┌─ L0 版本治理 ──────────────────────────────────────┐
|
||
│ 单一版本源 + 设置页展示版本号(零风险,先落) │
|
||
└────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### L0:版本单一源治理
|
||
|
||
**决策**:以 `tauri.conf.json` 的 `version` 为**唯一权威源**(打包器实际使用它)。
|
||
|
||
**动作**
|
||
1. `src-tauri/Cargo.toml` 的 `version` 对齐同名值(人工同步,或加校验脚本);**§2.2-D1 双源问题在此关闭**。
|
||
2. 新增 IPC `app_get_version`:返回 `env!("CARGO_PKG_VERSION")` + 构建时间 + git short hash(option_env! 注入,未注入显示 `dev`)。
|
||
3. `swap-install.ps1:8` 改为 glob 探测(**§2.2-D2 在此关闭**):
|
||
```powershell
|
||
$installer = (Get-ChildItem 'E:\wk-lab\devflow\target\release\bundle\nsis\devflow_*_x64-setup.exe' |
|
||
Sort-Object LastWriteTime -Descending | Select-Object -First 1).FullName
|
||
```
|
||
4. 设置页「关于」展示版本号(L0 即可交付的可见价值)。
|
||
|
||
**扩张性**:未来 `df-im-gateway` 等独立服务走各自版本线(在其自身发布 skill 内治理),本设计不扩范围,但 `app_get_version` 的返回结构预留 `components: []` 数组位。
|
||
|
||
### L1:后端接入(插件注册 + 检查)
|
||
|
||
**Cargo.toml**
|
||
```toml
|
||
tauri-plugin-updater = "2"
|
||
```
|
||
|
||
**lib.rs**(`Builder` 链,插在 notification 之后)
|
||
```rust
|
||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||
```
|
||
|
||
**capabilities/default.json** 补
|
||
```json
|
||
"updater:default"
|
||
```
|
||
|
||
**tauri.conf.json**
|
||
```json
|
||
"bundle": {
|
||
"active": true,
|
||
"targets": ["nsis"],
|
||
"createUpdaterArtifacts": true
|
||
},
|
||
"plugins": {
|
||
"updater": {
|
||
"endpoints": ["https://app.1216.top/download/devflow/latest.json"],
|
||
"pubkey": "<tauri signer generate 产出的公钥>",
|
||
"windows": { "installMode": "passive" }
|
||
}
|
||
}
|
||
```
|
||
> `installMode: passive` —— **不是静默**:弹进度条窗口,用户看得见进度,但无需点击「下一步」,装完自动往下走。
|
||
> 三档对比:`basic`(完整向导,需点下一步)/ `passive`(进度条,无需点击)/ `quiet`(完全无 UI,**失败不可见**,不推荐)。
|
||
> 注:`swap-install.ps1` 用的 NSIS `/S` 是真静默(等价 `quiet`),应用内升级刻意不采用——面向用户自己机器的升级需要失败可见性。
|
||
|
||
**IPC 契约**(新增 3 个,命名沿用 `app_*` 前缀)
|
||
|
||
| 命令 | 入参 | 返回 | 风险档 |
|
||
|---|---|---|---|
|
||
| `app_get_version` | — | `{version, build_time, git_hash}` | 只读 |
|
||
| `app_check_update` | — | `{has_update, version?, notes?, date?}` | 只读(外发网络) |
|
||
| `app_download_install_update` | — | 流式事件 + 成功即重启 | **高风险(需审批)** |
|
||
|
||
**配置 KV**(复用既有 appSettings 体系,零新表)
|
||
- `df-update-auto-check`:bool,默认 `true`
|
||
- `df-update-check-interval-minutes`:int,默认 `15`
|
||
- `df-update-last-check`:ISO 时间戳(节流用,防每次启动都拉)
|
||
- `df-update-notified-version`:string(**同版本只提示一次**,防骚扰)
|
||
|
||
**自动检查策略**:启动后延迟 **3s** → 拉清单 → 有更新则发事件 `app-update-available` 到主窗;此后**每 15 分钟**检查一次,但**同一版本号只提示一次**(用户点「稍后」后不再重复弹)。**只提示不自动装**(安装是高风险动作,必须人点头)。
|
||
|
||
### L2:前端(设置页「关于」分类)
|
||
|
||
**SettingsNav** 加第 8 类:
|
||
```ts
|
||
{ value: 'about', icon: 'ℹ️', labelKey: 'settings.navAbout' },
|
||
```
|
||
`searchIndex.ts` 补对应条目(`SettingsCategory` 联合类型同步扩 `'about'`)。
|
||
|
||
**新增 `src/components/settings/AboutPanel.vue`**(对齐既有 panel 视觉语言)
|
||
|
||
```
|
||
┌─ 关于 DevFlow ─────────────────────────┐
|
||
│ 当前版本 v0.1.2 (a1b2c3d) [检查更新] │
|
||
│ 构建时间 2026-10-09 14:30 │
|
||
│ ─────────────────────────────────────── │
|
||
│ ☑ 启动时自动检查更新(启动后 3 秒) │
|
||
│ 检查间隔 [15 分钟 ▾] │
|
||
│ ─────────────────────────────────────── │
|
||
│ 上次检查 2026-10-09 14:35 │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
**更新弹窗**(复用既有 ConfirmDialog 视觉,含:新版本号 / 更新说明 markdown / 下载进度条 / 「立即更新并重启」「稍后」)
|
||
|
||
**事件订阅**(对齐既有 `useAiEvents` 模式)
|
||
- `app-update-available` → 顶栏小红点 + 弹窗
|
||
- `app-update-progress` → 进度条(`downloaded` / `total` / 百分比)
|
||
|
||
**i18n**:zh-CN / en 双语同步补 `settings.navAbout` / `settings.about*` / `update.*` 系列键。
|
||
|
||
### L3:发布侧脚本增强
|
||
|
||
新增 `scripts/publish-release.ps1`(或扩展 `package-devflow.ps1` 加 `-Mode release` 通道):
|
||
|
||
1. 前置:校验 `tauri.conf.json.version` 与 `Cargo.toml.version` 一致(防 D1 复发)
|
||
2. tauri build(含 createUpdaterArtifacts → .nsis.zip + .sig)
|
||
3. 计算 sha256 + 读取 .sig 内容
|
||
4. 生成 latest.json:
|
||
{
|
||
"version": "0.1.2",
|
||
"notes": "<从 docs/CHANGELOG.md 提取本节>",
|
||
"pub_date": "2026-10-09T14:30:00Z",
|
||
"platforms": {
|
||
"windows-x86_64": {
|
||
"signature": "<.sig 内容>",
|
||
"url": "https://app.1216.top/download/devflow/devflow_0.1.2_x64-setup.nsis.zip"
|
||
}
|
||
}
|
||
}
|
||
5. 上传 latest.json + .nsis.zip + .sig + 裸 exe 到 **app.1216.top** `download/devflow/`(走 publish-app-site 通道:suke-dev / `/var/www/app-site/`)
|
||
6. `app.1216.top` 的 JSON 已配 `Cache-Control: no-cache` → **无需刷 CDN**,发布即生效
|
||
```
|
||
|
||
**密钥管理**
|
||
- 一次性:`tauri signer generate -w ~/.tauri/devflow.key` → 公钥入 `tauri.conf.json` 的 `pubkey`
|
||
- 打包时设环境变量 `TAURI_SIGNING_PRIVATE_KEY`(路径)+ `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`
|
||
- **私钥绝不入仓**(`.gitignore` 补 `*.key`)
|
||
|
||
---
|
||
|
||
## 五、兼容性核验
|
||
|
||
| 既有资产 | 影响 | 结论 |
|
||
|---|---|---|
|
||
| `scripts/package-devflow.ps1` | 不动 verify/full 两通道,L3 新增独立通道 | ✔ 零回归 |
|
||
| `swap-install.ps1` | 仅改第 8 行硬编码 → glob(D2 修复) | ✔ 行为等价,更健壮 |
|
||
| `lib.rs` Builder 链 | 追加一个 `.plugin()`,不触碰既有 5 个插件与 68 IPC | ✔ 零侵入 |
|
||
| `capabilities/default.json` | 追加一行 `updater:default` | ✔ 纯增量 |
|
||
| SettingsNav 7 类 | 追加第 8 类,搜索/防抖/持久化逻辑自动覆盖(`SETTINGS_INDEX` 驱动) | ✔ 架构自洽 |
|
||
| appSettings KV 体系 | 新增 4 个 key,复用既有读写 | ✔ 零新表零迁移 |
|
||
| `tauri.conf.json` targets | 保持 `["nsis"]`,只加 `createUpdaterArtifacts` | ✔ 不扩大构建目标(对齐知识库「targets 收窄需还原」的教训:本项是新增字段非收窄,安全) |
|
||
| devflow.db / devflow-dev.db | L1/L2 只读 KV,不涉 schema | ✔ 零库变更 |
|
||
|
||
**dex 依赖**:无。**P0 依赖**:无(可立即开工)。
|
||
|
||
---
|
||
|
||
## 六、实施批次(全部可独立交付)
|
||
|
||
| 批次 | 规模 | 内容 | 验收 |
|
||
|---|---|---|---|
|
||
| **B1** | S | L0 版本治理:Cargo.toml 对齐 0.1.1 / `app_get_version` IPC / swap-install glob 修复 / 设置页版本号展示 | `cargo check -p devflow` 0 error;设置页可见 `v0.1.1`;换装脚本升版后仍能正确取包 |
|
||
| **B2** | M | L1 插件接入 + `app_check_update` IPC + 自动检查节流 + `app-update-available` 事件 | 手动改远端 `latest.json` 版本号 → 应用内提示可用 |
|
||
| **B3** | M | L2 下载安装:进度事件 + 更新弹窗 + `app_download_install_update` + 重启 | 端到端:旧版 → 提示 → 下载 → 装 → 重启后版本号变新 |
|
||
| **B4** | S | L3 发布脚本 + 密钥生成 + latest.json 生成上传 | 一条命令产出并上传可分发的更新清单(app.1216.top 无需刷缓存) |
|
||
|
||
**建议序**:B1 立即做(零风险、当天可交付、消除 D1/D2 两个真实缺陷);B2→B3 一起做(拆开无独立价值);B4 在首次真实发版前完成即可。
|
||
|
||
**总估算**:B1 0.5 天 / B2+B3 1.5-2 天 / B4 0.5 天 ≈ **2.5-3 天**。
|
||
|
||
---
|
||
|
||
## 七、风险与边界
|
||
|
||
| 风险 | 等级 | 对策 |
|
||
|---|---|---|
|
||
| 私钥泄露 | **高** | `.gitignore` 拦 `*.key`;仅打包机存私钥;公钥入仓无妨 |
|
||
| ~~CDN 缓存陈旧致拉了旧清单~~ | ~~中~~ **已消解** | C1 改走 `app.1216.top`(已配 `Cache-Control: no-cache`),无缓存陈旧问题;可选 `?t=<timestamp>` 兜底 |
|
||
| 更新包下载中断 | 中 | 插件内建重试;失败保留旧版不破坏现状 |
|
||
| 装完起不来(坏包) | 中 | `installMode: passive` 失败可见;保留上一版安装包以备手回滚 |
|
||
| 自动检查打扰 | 低 | 15 分钟周期 + **同版本只提示一次** + 只提示不自动装 + 可关 |
|
||
| 版本双源复发 | 低 | B1 加校验(发布脚本第 1 步)+ 可选 `build.rs` 断言 |
|
||
|
||
**明确不做(本设计范围外)**
|
||
- 增量更新(delta update)——体量收益不值复杂度
|
||
- 灰度/分渠道发布
|
||
- 非主应用组件(`df-im-gateway` 等)的版本线
|
||
- 回滚 UI(保留手动重装路径即可)
|
||
|
||
---
|
||
|
||
## 八、决策点(✅ 2026-10-09 拍板定稿)
|
||
|
||
| # | 议题 | ✅ 定稿 | 依据 |
|
||
|---|---|---|---|
|
||
| **C1** | 清单托管位 | **`app.1216.top/download/devflow/`** | 产品门户语义正确;该站已配 `Cache-Control: no-cache`,发布即生效,无需刷 CDN;通道(suke-dev / `/var/www/app-site/`)现成 |
|
||
| **C2** | 安装模式 | **`passive`**(进度条窗口,无需点击) | 非静默;`quiet` 完全无感但失败不可见(静默伪成功风险) |
|
||
| **C3** | B1 是否先行 | **立即开工** | 零风险、当天交付,消 D1/D2 两个真实缺陷 |
|
||
| **C4** | 自动检查节奏 | **启动后 3s → 周期 15 分钟** + 同版本单次提示 + 关于页手动按钮 | 用户定:3s / 15min |
|
||
|
||
### C1 补充决策:双产物并存(站点规范 vs Tauri 强制格式)
|
||
|
||
app-site 站规为「不打 zip,直接裸 exe」,而 Tauri updater **强制** `.nsis.zip` + `.sig`。故 `download/devflow/` 并存四项:
|
||
|
||
| 文件 | 给谁 | 合规 |
|
||
|---|---|---|
|
||
| `devflow_<ver>_x64-setup.exe` | 手动下载的用户 | ✅ 站点「裸 exe」规范 |
|
||
| `devflow_<ver>_x64-setup.nsis.zip` | updater 拉取 | ✅ Tauri 格式强制 |
|
||
| `devflow_<ver>_x64-setup.nsis.zip.sig` | updater 验签 | ✅ |
|
||
| `latest.json` | updater 清单 | ✅ |
|
||
|
||
### C4 补充决策:体验设计「环境式更新」
|
||
|
||
**核心原则**:检查频率 ≠ 体验。体验由「提醒方式 / 去重 / 过程可见 / 时机可控」四件事决定。
|
||
|
||
| 触点 | ✅ 定稿 |
|
||
|---|---|
|
||
| 平时 | 完全安静,零存在感 |
|
||
| 发现新版 | **顶栏非模态徽章**(不弹窗打断) |
|
||
| 查看详情 | 点徽章展开:版本号 + 更新说明 + 包大小 |
|
||
| 决定 | 「立即升级」/「稍后」双选(**不自动下载安装**) |
|
||
| 下载 | 进度条 + 可取消 |
|
||
| 安装 | `passive` 进度窗口,无需点击 |
|
||
| 完成 | 重启后 toast「已升级到 vX.Y.Z」 |
|
||
| 反复 | **同版本只提示一次**(KV `df-update-notified-version`) |
|
||
|
||
**为什么必须是「环境式」而非「隐形型(全自动)」**:DevFlow 是**有状态工作工具**(可能正跑长任务/开着会话),任何自动重启都会打断工作。时机控制权必须交给用户。
|