Compare commits
1
Commits
v0.8.3-rc1
..
v0.8.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da468aff13 |
No files matched your search
+10
-3
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "kimiswitch",
|
||||
"private": true,
|
||||
"version": "0.8.3-rc1",
|
||||
"version": "0.8.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
Generated
+1
-1
@@ -1978,7 +1978,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kimiswitch"
|
||||
version = "0.8.3-rc1"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"productName": "Kimi Switch",
|
||||
"version": "0.8.3-rc1",
|
||||
"version": "0.8.3",
|
||||
"identifier": "com.kimiswitch.app",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
||||
Reference in new issue
Block a user