Skip to content

Releases: Kline-x/commandcode-desktop

Release list

v0.1.1

Choose a tag to compare

@Kline-x Kline-x released this 14 Sep 09:34
5a09096

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: 校准文档与实际实现

三处漂移:

  1. 阶段状态陈旧:README 与 PLAN 顶部仍写「Phase 0 进行中」,而 PLAN
    第 7 节早已把 Phase 0–5 全部勾选、PROGRESS 记录了 v0.1.0 发版。

  2. 宣称的能力不存在(改为陈述实际行为,便于将来实现时对照):

    • 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 已如实标注为缺口)
  3. ARCHITECTURE 模块图按 proxy/routes/* 目录描述,实际是全平铺;
    事件总线一节与实现不符。同时新增第 3.1 节记录账号复活链路。

PLAN 新增第 4.6 节:记录本次审计发现的 5 个「能启动、单测全绿、但核心
机制实际不工作」的缺陷。共同教训是单测覆盖率不等于链路完整性——其中
3 个是「模块有测试但没接线」,2 个是「判定写对了、副作用没跟上」。

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 13 Sep 09:26
Release v0.1.0: Command Code 桌面客户端多协议与账号轮换代理正式版