Releases: Kline-x/commandcode-desktop
Release list
v0.1.1
fix: 修复审计发现的 6 个「单测全绿但机制不工作」缺陷 (#3)
- fix(rotation): 403 不再污染账号池,并补上 402 的换号语义
mark_rejected 在判定 rotates_account() 之前被无条件调用,于是一个
403「模型不在套餐」(请求侧错误,换号无用)也会把该 key 标成不可用。
注释本来就写着「只有账号相关错误才标记」,但代码没做到——判定正确、
副作用没跟上,而标记之后又没有清除路径(见下一条提交)。
实际后果(用户数据库中的真实记录):一次 403 之后,账号连续失败 15 次、
持续 3.5 分钟、0 次成功,只能重启应用恢复。
同时补上 rotates_account() 漏掉的 402:docs/PROTOCOL.md 第 6 节与
docs/PLAN.md 第 8.1 节都把「402 是账号级额度耗尽、必须换号」列为最大陷阱,
实现里却只认 401/429。
PROTOCOL.md #6 / PLAN.md 8.1
- fix(pool): 打通配额探测到账号池的复活路径
AccountPool::apply_probe 是清除 429 标记的唯一路径,但全仓库没有生产
调用方(只有模块自己的测试),而 quota_poller 又刻意不持有账号池。
结果是标记只进不出:账号一旦被 429 标成 Unknown(不可用),在本进程
剩余生命周期内永远不会恢复——只能重启。
这与给用户的错误文案正好相反:文案承诺「窗口重置后请求会自动恢复」。
也与 ARCHITECTURE 第 4 节「重启后由探测重建」的声明不符——连重启前
都重建不了。
新增:
- window_probe_from_snapshot:快照 → WindowProbe,严格保持三态语义。
无窗口数据返回 None(探测失败不携带信息,不得据此复活账号)。 - ProxyState::apply_probe_for_slot:按槽位 id 定位 key 后写池。池的状态
以 key 为键,而配额轮询器只知道槽位 id(它不该碰 key)。 - bootstrap 的配额订阅循环把每轮快照反馈给池。该反馈与既有的配额写库
在同一处无条件执行,因此不会出现「某轮跳过反馈」的静默缺口。
ARCHITECTURE.md 第 3.1 节记录这条链路,避免将来再被漏接。
- fix(store): 让「流水保留条数」真正生效,并真正打开数据目录
两个设置项此前都是假的——UI 显示已保存,实际没有任何效果。
retention:
bootstrap 把保留条数硬编码成 5000,Store.retention 又是只读字段、没有
setter。于是设置页存的值能读能显示,对实际裁剪零影响。
改为 AtomicI64(Store 以 Arc 共享、方法收 &self)+ set_retention,
后者立即按新上限裁剪一次:只改数字的话,要等下一次写入才生效,
而空闲期可能永远不来,UI 与数据库会长期不一致。
启动时从 settings 表读取,而不是写死 5000。
reveal_data_dir:
此前只把路径写进日志就返回 Ok(()),UI 把它当成功,用户点「在访达中
打开」什么都不会发生。改为经 tauri-plugin-opener 真正调起系统文件
管理器,失败如实回报,前端据此提示。
- fix(proxy): 放宽请求体上限,大上下文不再被本地 413 拒绝
axum 的 Bytes 提取器自带 2 MB 默认限制(axum-core 的 DEFAULT_LIMIT),
而编码代理(Claude Code / Cursor / Codex)会把整个仓库上下文塞进一次
请求,几 MB 是常态。这些请求在本地就被 413 拒绝,根本到不了上游
轮换层,用户看到的是「代理完全不工作」。
新增 Config::max_body_bytes(默认 100 MB,与参考实现 proxy.mjs 的
CC_MAX_BODY_MB 一致),并在 router 上应用 DefaultBodyLimit::max。
mock 上游同步放宽:它此前也是 2 MB 默认值,会把大请求体的测试在 mock
那一侧拦掉,掩盖被测代理的真实行为——正是该模块文档注释里警告的
「假绿」风险。
PROTOCOL.md #11 提到上游请求体在内存中有 5.1–7.4× 放大,100 MB 入站
在最坏情况下对应数百 MB 驻留;单机自用(本项目定位)可接受,常量处
已注明。
- test: 补上此前缺失的副作用与恢复路径用例
原有 19 条 e2e 覆盖了「换号成功」这类正向路径,但没有一条覆盖
副作用与恢复路径——上述缺陷正因此而存活于 290 个全绿测试中。
新增 4 条 e2e:
- rate_limited_account_is_revived_by_a_quota_probe
- plan_error_does_not_poison_the_account_pool
- payment_required_rotates_to_the_next_account
- large_context_request_is_not_rejected_locally
新增 5 条单测:
- window_probe_from_snapshot 的三态换算 4 条(无窗口=无信息、
未超限=可复活、超限取最早重置、任一窗口超限即不可用) - set_retention_takes_effect_immediately
PLAN.md 第 8.1 节补充说明:矩阵每格都应同时断言「上游被打了几次」与
「池的状态变成了什么」,只断言「403 不换号」是不够的。
- fix(vite): 忽略 target/,避免 cargo 锁文件时 dev server 被 EBUSY 杀掉
vite.config.ts 的 watch.ignored 只排除了 src-tauri/ 与 crates/,没排除
target/。cargo 构建时会写入并锁定 target 下的 proc-macro 临时 DLL
(如 cssparser_macros-*.dll),chokidar 一旦尝试 watch 这些被独占的文件
就抛 EBUSY 并直接终止 dev server。
实际症状:pnpm tauri dev 跑着跑着 Vite 退出,窗口变成
「localhost 拒绝连接」——看起来像应用崩溃,实际是文件监听器被杀。
target/ 是纯构建产物,前端没有理由监听它。
- docs: 校准文档与实际实现
三处漂移:
-
阶段状态陈旧:README 与 PLAN 顶部仍写「Phase 0 进行中」,而 PLAN
第 7 节早已把 Phase 0–5 全部勾选、PROGRESS 记录了 v0.1.0 发版。 -
宣称的能力不存在(改为陈述实际行为,便于将来实现时对照):
- stronghold 加密 → 实际 keyring + AES-256-GCM,主路径退化为 0600 文件
- GET /events(SSE 事件总线)→ 实际 2s 轮询
- /api/usage/series → 无此端点
- proxy_token 鉴权 → 未实现
- Config::listen_addr 可配 → 实际硬编码 3050
- conformance 测试 → 未实现,third_party/ 目前只是人工参照物
- 前端 Vitest 组件测试 → 未落地(PLAN 8.3 已如实标注为缺口)
-
ARCHITECTURE 模块图按 proxy/routes/* 目录描述,实际是全平铺;
事件总线一节与实现不符。同时新增第 3.1 节记录账号复活链路。
PLAN 新增第 4.6 节:记录本次审计发现的 5 个「能启动、单测全绿、但核心
机制实际不工作」的缺陷。共同教训是单测覆盖率不等于链路完整性——其中
3 个是「模块有测试但没接线」,2 个是「判定写对了、副作用没跟上」。
v0.1.0
Release v0.1.0: Command Code 桌面客户端多协议与账号轮换代理正式版