Compare commits

..
1 Commits
Author SHA1 Message Date
KimiSwitch Dev da468aff13 chore(release): bump to v0.8.3 — kimi-code 2.2.0 适配核验 + 配置一致性修复正式版
Release / Version consistency (push) Waiting to run
- 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 全绿
2026-10-15 09:31:01 +08:00
9 changed files with 280 additions and 8 deletions

No files matched your search

+10 -3
View File
@@ -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)
+34
View File
@@ -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
@@ -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 构建)
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "kimiswitch",
"private": true,
"version": "0.8.3-rc1",
"version": "0.8.3",
"type": "module",
"scripts": {
"dev": "vite",
+1 -1
View File
@@ -1978,7 +1978,7 @@ dependencies = [
[[package]]
name = "kimiswitch"
version = "0.8.3-rc1"
version = "0.8.3"
dependencies = [
"anyhow",
"chrono",
+1 -1
View File
@@ -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"
+3 -1
View File
@@ -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")
+180
View File
@@ -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")
);
}
}
+1 -1
View File
@@ -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",