From da468aff13e54f59fa7cb2613e455db9b6f780f9 Mon Sep 17 00:00:00 2001 From: KimiSwitch Dev Date: Thu, 15 Oct 2026 09:31:01 +0800 Subject: [PATCH] =?UTF-8?q?chore(release):=20bump=20to=20v0.8.3=20?= =?UTF-8?q?=E2=80=94=20kimi-code=202.2.0=20=E9=80=82=E9=85=8D=E6=A0=B8?= =?UTF-8?q?=E9=AA=8C=20+=20=E9=85=8D=E7=BD=AE=E4=B8=80=E8=87=B4=E6=80=A7?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=AD=A3=E5=BC=8F=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - kimi-code 2.2.0 新配置键([mcp] default_deferred、[models] upgrade 迁移元数据)往返回归测试钉住 - 适配基线注释更新至 2.2.0;实验开关清单本就同步,无需改动 - 可选后续项记录于 docs/BACKLOG-kimi-code-2.2.0.md - 版本号三处统一 0.8.3;测试 Rust 199 / 前端 146 全绿 --- CHANGELOG.md | 13 +- docs/BACKLOG-kimi-code-2.2.0.md | 34 ++++ docs/release-notes/release-notes-v0.8.3.md | 49 ++++++ package.json | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/commands.rs | 4 +- src-tauri/src/kimi_code_io.rs | 180 +++++++++++++++++++++ src-tauri/tauri.conf.json | 2 +- 9 files changed, 280 insertions(+), 8 deletions(-) create mode 100644 docs/BACKLOG-kimi-code-2.2.0.md create mode 100644 docs/release-notes/release-notes-v0.8.3.md diff --git a/CHANGELOG.md b/CHANGELOG.md index ae21e17..08efb52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,9 @@ --- -## v0.8.3-rc1 (2026-10-10) +## v0.8.3 (2026-10-15) -配置一致性修复预发布版(RC,请先本地验证)。 +配置一致性修复版(含 kimi-code CLI 2.2.0 适配核验)。 - **供应商改名时模型别名同步重键**:此前只改模型的 `provider` 字段,别名键保留旧前缀(如 `opencode-go-copy/...`)形成僵尸条目;现在模型别名、全局默认模型、供应商记住的默认模型、子代理池条目全部同步更新 - **僵尸模型不再复活**:加载时 SQLite 镜像会把 config.toml 中缺失的模型合并回来,导致已删模型保存后重现;现在供应商已不存在的模型不再合并 @@ -16,9 +16,16 @@ - **复制供应商不再清空源供应商**:修复复制复用改名逻辑(移动语义)导致源供应商模型被搬走;副本独立、源完整 - **删除供应商/模型的关联清理**:子代理模型池中引用被删模型的条目一并移除;池默认模型自动回落到剩余条目(池清空则移除整节);`default_model` 置空;SQLite 中该供应商的用量查询配置随之清理,且不误删另一 agent(Pi)的设置 - **防呆:放弃修改不落盘**:复制供应商不再立即静默保存,与其他编辑一致,放弃后配置保持原样 +- **kimi-code 2.2.0 适配核验**:新增的 `[mcp] default_deferred` 与 `[models] upgrade` 迁移元数据经导入/导出往返无损保留(回归测试钉住);实验开关与版本基线无需改动;可选后续项记录于 `docs/BACKLOG-kimi-code-2.2.0.md` - 修复测试基建中 `KIMI_CODE_HOME` / `KIMI_SWITCH_DB_PATH` 的并发竞争(统一 crate 级共享锁) - models.dev 快照同步至 2026-10-10(8,456 模型 / 226 供应商) -- 测试:前端 146 项(新增 16 项)、Rust 197 项(新增 4 项) +- 测试:前端 146 项(新增 16 项)、Rust 199 项(新增 6 项) + +## v0.8.3-rc1 (2026-10-10) + +配置一致性修复预发布版(RC),内容全部并入 v0.8.3,无独有变更。 + +--- ## v0.8.2 (2026-10-09) diff --git a/docs/BACKLOG-kimi-code-2.2.0.md b/docs/BACKLOG-kimi-code-2.2.0.md new file mode 100644 index 0000000..7ac9d3c --- /dev/null +++ b/docs/BACKLOG-kimi-code-2.2.0.md @@ -0,0 +1,34 @@ +# Kimi Code 2.2.0 适配 — 可选后续项 + +> 2026-10-15 适配核验时确认:必改项为零(`[mcp] default_deferred` 与 `[models] upgrade` +> 元数据经 raw_other 透传无损往返,回归测试见 `src-tauri/src/kimi_code_io.rs` +> 的 `kimi_code_roundtrip_*` 两条测试)。以下为**评估后暂缓**的可选增强,按需再排期。 + +## 1. 模型退役角标(UI 增强) + +- **内容**:模型列表读取模型 `raw_other.upgrade.retirement_at`,显示「将于 X 退役, + 建议迁移到 Y」角标 +- **暂缓原因**:上游 2.2.0 才引入迁移元数据,实际写入该字段的 registry 还很少, + 等 CLI 侧普及后再做收益更高 + +## 2. JSON 编辑器透传字段提示(防误删) + +- **内容**:JSON 预览 / 应用处提示「删除 `raw_other` 会丢失 CLI 透传字段 + (如 `upgrade` 迁移元数据)」 +- **位置**:`handleApplyProviderJson`(`src/App.tsx`)是整表重建语义——用户手改 + JSON 删掉 `raw_other` 块时透传字段随之丢失;属用户显式编辑行为,非 bug +- **暂缓原因**:触发条件苛刻(须手动删除 raw_other 块),文案提示即可,优先级低 + +## 3. TOML datetime 类型降级兜底(上游依赖) + +- **内容**:若上游未来把 `upgrade.retirement_at` 写成 TOML datetime(当前是字符串), + `toml_value_to_json` → `json_to_toml` 往返会把它降级为字符串 +- **暂缓原因**:当前上游(kimi-code 2.2.0)写入的就是字符串,无实际影响; + 升级 CLI 时留意 `git diff` config.toml 中该字段类型即可 + +## 背景参考 + +- 适配结论:实验开关(5 flag)、版本基线(≥2.0.2 → current)、前端模型编辑 + merge 语义均无需改动 +- `executor = "kaos"` 检测评估过、放弃:该字段在 mcp.json(KimiSwitch 不管理), + 不在 config.toml diff --git a/docs/release-notes/release-notes-v0.8.3.md b/docs/release-notes/release-notes-v0.8.3.md new file mode 100644 index 0000000..aa29edc --- /dev/null +++ b/docs/release-notes/release-notes-v0.8.3.md @@ -0,0 +1,49 @@ +# KimiSwitch v0.8.3 + +配置一致性修复版:解决「供应商改名后模型别名没跟着改,产生的僵尸条目删了又出现」这一类问题,补齐删除供应商时的关联清理与防呆,并完成 kimi-code CLI 2.2.0 的适配核验。 + +## 修复 + +- **供应商改名时模型别名同步重键**:此前改名只更新模型的 `provider` 字段,模型别名键仍保留旧前缀(如 `opencode-go-copy/...`),形成永远指向新供应商的僵尸条目;现在别名、全局默认模型、该供应商记住的默认模型、子代理池条目全部同步更新 +- **僵尸模型不再复活**:应用加载时会把 SQLite 镜像中缺失于 config.toml 的模型合并回来(迁移保护),导致已删除的模型在保存后重新出现;现在供应商已不存在的模型不再被合并 +- **改名冲突不再静默吞模型**:改名为已存在的供应商名、或遗留裸别名与新名冲突时,目标别名自动加 `-2`/`-3` 后缀,与「应用 JSON」路径的既有规则一致 +- **复制供应商不再清空源供应商**:修复复制时复用改名逻辑导致源供应商模型被整体搬走的问题;副本为独立副本,源供应商模型完整保留 + +## 删除供应商 / 模型时的关联清理 + +- 子代理模型池(`[secondary_model]`)中引用被删模型的条目一并移除 +- 池默认模型指向被删模型时自动回落到池中剩余条目;池被清空时整个 `[secondary_model]` 节移除 +- 全局 `default_model` 指向被删模型时置空 +- SQLite 中该供应商的用量查询配置(`usage_kinds:` / `usage_config:`)随之清理;保留另一 agent(Pi)的设置,读取失败时跳过清理而非误删 +- 模型占位符自动改名为真实模型 id 时,池条目同步跟随 + +## 防呆 + +- **放弃修改不落盘**:复制供应商不再立即静默保存,与其他编辑一致——点「返回」并放弃后配置保持原样 +- 改名/删除/JSON 应用等所有编辑路径统一遵守「必须保存才写盘」 + +## Kimi Code CLI 2.2.0 适配 + +- **兼容性核验通过**:2.2.0 新增的 `[mcp] default_deferred`(MCP 工具延迟披露全局默认)与 `[models] upgrade` 模型退役/迁移元数据,经 KimiSwitch 导入/导出往返无损保留(含「CLI 后写入」与「编辑已知模型字段」两条路径,回归测试钉住) +- 实验开关清单(`notify_user`、`tower`、`wait_for`、`tool-select`、`subagent_fork`)与上游 2.2.0 注册表一致,无需改动;版本适配基线(≥ 2.0.2 判为「完全支持」)覆盖 2.2.0 +- 可选后续项(退役角标、JSON 编辑器提示等)记录于 `docs/BACKLOG-kimi-code-2.2.0.md` + +## 其他 + +- 修复测试基建中进程级环境变量(`KIMI_CODE_HOME` / `KIMI_SWITCH_DB_PATH`)的并发竞争:dashboard 与 oauth 测试此前各自持锁,改为统一的 crate 级共享锁 +- models.dev 快照同步至 2026-10-10(8,456 模型 / 226 供应商) + +## 测试 + +- 前端 146 项(新增 16 项:改名重键、复制保留源、冲突去重、子代理池清理与空表防护) +- Rust 199 项(新增 6 项:删除后双存储持久化与不复活、孤儿配置修剪、跨 agent 修剪保护、kimi-code 2.2.0 新配置键往返兼容) + +## 已知限制 + +- config.toml 中若已存在「供应商已删除」的僵尸 `[models.*]` 段落,升级后不会自动删除(config.toml 是用户权威文件,本版只防复发),需在界面中手动删除一次 +- 删除供应商后 `default_model` 置空时的 kimi-code CLI 回退行为,建议实测确认 + +## 下载 + +- **Windows**:`.exe`(NSIS 安装包)/ `.msi` +- **macOS / Linux**:见 [Releases](https://github.com/billowliu2/KimiSwitch/releases)(由 CI 构建) diff --git a/package.json b/package.json index cde2cbd..937b208 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "kimiswitch", "private": true, - "version": "0.8.3-rc1", + "version": "0.8.3", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b29fff3..c8fcb37 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1978,7 +1978,7 @@ dependencies = [ [[package]] name = "kimiswitch" -version = "0.8.3-rc1" +version = "0.8.3" dependencies = [ "anyhow", "chrono", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 70cd778..8647661 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kimiswitch" -version = "0.8.3-rc1" +version = "0.8.3" description = "Kimi Switch - model config manager" authors = ["codingplan.site"] edition = "2021" diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 18fcc8c..16ef83b 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1934,7 +1934,9 @@ fn semver_triple(version: &str) -> Option<(u32, u32, u32)> { } /// Adaptation baseline against the kimi-code CLI: -/// >= 2.0.2 fully supported ("current") +/// >= 2.0.2 fully supported ("current"; verified through 2.2.0 — the new +/// [mcp] default_deferred and [models] upgrade keys round-trip, +/// see the kimi_code_io roundtrip tests) /// >= 0.43.1 quota parsing moved upstream — upgrade KimiSwitch ("outdated") /// >= 0.43.0 the auto_session_title flag is gone ("partial") /// < 0.43.0 untested ("unknown") diff --git a/src-tauri/src/kimi_code_io.rs b/src-tauri/src/kimi_code_io.rs index 21c9d6f..9cf264e 100644 --- a/src-tauri/src/kimi_code_io.rs +++ b/src-tauri/src/kimi_code_io.rs @@ -1407,4 +1407,184 @@ max_context_size = 128000 assert_eq!(pt.get("api_key").and_then(|v| v.as_str()), Some("sk-keep")); assert!(!pt.contains_key("api_key_env")); } + + #[test] + fn kimi_code_roundtrip_preserves_mcp_default_deferred() { + // kimi-code 2.2.0 adds `default_deferred` (the global default for + // deferred MCP tool disclosure) to the top-level [mcp] section. The + // whole section travels through raw_other, so both orderings must + // round-trip: present at import time, and written by the CLI only + // after we imported. + let toml_str = r#" +default_model = "p1/m1" + +[providers.p1] +type = "openai" +api_key = "sk-test" + +[models."p1/m1"] +provider = "p1" +model = "m1" +max_context_size = 128000 + +[mcp] +startup_timeout_ms = 30000 +tool_timeout_ms = 60000 +default_deferred = true +"#; + let value: TomlValue = toml_str.parse().unwrap(); + let config = kimi_code_to_config(&value); + + assert!( + config.raw_other.get("mcp").is_some(), + "[mcp] must be carried in raw_other" + ); + assert!( + config.imported_section_keys.contains(&"mcp".to_string()), + "baseline must record the imported [mcp] section" + ); + + // (a) [mcp] existed at import time — it must be written back verbatim. + let exported = config_to_kimi_code(&config, Some(&value)); + let mcp = exported + .as_table().unwrap() + .get("mcp").unwrap() + .as_table().unwrap(); + assert_eq!( + mcp.get("default_deferred").and_then(|v| v.as_bool()), + Some(true), + "[mcp] default_deferred must survive export" + ); + assert_eq!( + mcp.get("startup_timeout_ms").and_then(|v| v.as_integer()), + Some(30000) + ); + assert_eq!( + mcp.get("tool_timeout_ms").and_then(|v| v.as_integer()), + Some(60000) + ); + + // (b) [mcp] absent at import, added by the CLI afterwards — not in the + // baseline, so the stale-key cleanup must leave it untouched. + let without_mcp = r#" +default_model = "p1/m1" + +[providers.p1] +type = "openai" +api_key = "sk-test" + +[models."p1/m1"] +provider = "p1" +model = "m1" +max_context_size = 128000 +"#; + let imported: TomlValue = without_mcp.parse().unwrap(); + let config = kimi_code_to_config(&imported); + assert!(!config.imported_section_keys.contains(&"mcp".to_string())); + + let existing: TomlValue = format!( + "{}\n[mcp]\nstartup_timeout_ms = 30000\ntool_timeout_ms = 60000\ndefault_deferred = true\n", + without_mcp.trim_end() + ) + .parse() + .unwrap(); + + let exported = config_to_kimi_code(&config, Some(&existing)); + let mcp = exported + .as_table().unwrap() + .get("mcp").unwrap() + .as_table().unwrap(); + assert_eq!( + mcp.get("default_deferred").and_then(|v| v.as_bool()), + Some(true), + "CLI-added [mcp] default_deferred must be preserved" + ); + assert_eq!( + mcp.get("startup_timeout_ms").and_then(|v| v.as_integer()), + Some(30000) + ); + } + + #[test] + fn kimi_code_roundtrip_preserves_model_upgrade_metadata() { + // kimi-code 2.2.0 adds an `upgrade` sub-table per model + // ({ model, migration_markdown, retirement_at }) describing the + // retirement/migration target. It is not one of the five known model + // keys, so it lives in the model's raw_other and must survive both a + // plain round-trip and an edit of a known field. + let toml_str = r#" +default_model = "p1/m1" + +[providers.p1] +type = "openai" +api_key = "sk-test" + +[models."p1/m1"] +provider = "p1" +model = "m1" +max_context_size = 128000 + +[models."p1/m1".upgrade] +model = "kimi-k3" +migration_markdown = "Move to kimi-k3 before retirement." +retirement_at = "2027-01-15T00:00:00Z" +"#; + let value: TomlValue = toml_str.parse().unwrap(); + let config = kimi_code_to_config(&value); + + let model = config.models.get("p1/m1").unwrap(); + assert!( + model.raw_other.get("upgrade").is_some(), + "upgrade must be carried in the model raw_other" + ); + // The known keys are still extracted normally. + assert_eq!(model.max_context_size, 128000); + + let exported = config_to_kimi_code(&config, Some(&value)); + let mt = exported + .as_table().unwrap() + .get("models").unwrap() + .as_table().unwrap() + .get("p1/m1").unwrap() + .as_table().unwrap(); + let upgrade = mt.get("upgrade").expect("upgrade sub-table").as_table().unwrap(); + assert_eq!( + upgrade.get("model").and_then(|v| v.as_str()), + Some("kimi-k3") + ); + assert_eq!( + upgrade.get("retirement_at").and_then(|v| v.as_str()), + Some("2027-01-15T00:00:00Z") + ); + assert_eq!( + upgrade.get("migration_markdown").and_then(|v| v.as_str()), + Some("Move to kimi-k3 before retirement.") + ); + + // Editing a known field (the UI path) must not drop the passthrough + // `upgrade` metadata. + let mut edited = config; + edited.models.get_mut("p1/m1").unwrap().max_context_size = 262_144; + let exported = config_to_kimi_code(&edited, Some(&value)); + let mt = exported + .as_table().unwrap() + .get("models").unwrap() + .as_table().unwrap() + .get("p1/m1").unwrap() + .as_table().unwrap(); + assert_eq!( + mt.get("max_context_size").and_then(|v| v.as_integer()), + Some(262_144) + ); + let upgrade = mt.get("upgrade").expect("upgrade sub-table").as_table().unwrap(); + assert_eq!( + upgrade.get("model").and_then(|v| v.as_str()), + Some("kimi-k3"), + "upgrade metadata must survive an edit of a known field" + ); + assert_eq!( + upgrade.get("retirement_at").and_then(|v| v.as_str()), + Some("2027-01-15T00:00:00Z") + ); + } } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index cda31b3..c692486 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,6 +1,6 @@ { "productName": "Kimi Switch", - "version": "0.8.3-rc1", + "version": "0.8.3", "identifier": "com.kimiswitch.app", "build": { "beforeDevCommand": "npm run dev",