更新日志

每次版本都写清“改了什么、为什么改、怎么验证”;本页与 /api/changelog 同源,直接读仓库根 CHANGELOG.md,不手抄、不二次编辑。

当前版本 v0.4.5 · 发布于 2026-11-06 · JSON
v0.4.2 2026-10-30

一句话:全项目深度体检即修批次——六路地毯审计(A~J 十阶段)落地修复:向量检索从分钟级到亚秒、note 写入防碰撞、/mcp 不再阻塞全服务、学习链补上成本刹车,并完成生产 202.189.22.187 回迁后的首次树级收敛(生产 master 孤儿线收编本地 main)。

修复(P0/P1 即修即部署)

  • 向量检索 numpy 矩阵快路径(E/C-P0,app/knowledge/coding/store.py):库长到 74.8 万向量后逐行纯 Python 余弦单次检索分钟级(生产台账均值 181.9s / 最大 649.9s,匿名 /kb 检索 60s 超时)。新增内容代次物化索引(f32 矩阵 + 行范数,懒重建 + 60s 节流),检索退化为一次 BLAS 矩阵向量积(亚秒);无 numpy/维度混杂自动回落原逐行路径,行为不变。新测试 test_kb_vector_fastpath.py(快慢路径结果等价)。当晚 v2:重建挪后台守护线程——请求路径绝不付全表扫描成本(任何 kb 写入后的首次检索不再出现 60~70s 重建墙,先用陈旧索引作答、换轨原子替换;冷启动首查仍同步建一次防回落分钟级逐行)。
  • note 写入 sid 防碰撞(C-P1,app/knowledge/coding/service.py):旧制sha1(title)[:8] 仅 32bit,7.7 万文档量级生日碰撞可致「不同标题静默接管同 sid 条目」。新条目一律 16 位;既有条目(8/16 位)仅同标题按原 sid 稳定更新;sid 被不同标题占用即跳过并告警。新测试 test_kb_note_sid_guard.py(同标题=更新定律不破)。
  • /mcp 不再阻塞全服务(B-P1,app/api/mcp.py):工具处理器是同步重活(LLM 30s/能力轮询 900s),原先在 async 端点内直接跑会卡死事件循环(含 /livez)。改 anyio.to_thread.run_sync 丢线程池;顺带 /mcp 接入 RateLimiter(与 REST 同语义)、admin 口令改常数时间比较(secrets.compare_digest,REST deps.py 同步修)。
  • selfexperiment.revert 条件写(B-P1,app/brain/selfexperiment.py):回退只在该技能正文仍等于实验落盘版本(body==after_body)时执行;已被后续推进则拒绝(stale_body)并留审计,杜绝盲目回退抹掉并发改进。新测试 test_selfexperiment_revert_cas.py。
  • 主学习链接入每-topic 成本刹车(B/C-P1,app/api/service.py):learnroi.spend_brake 此前只挂在 relearn 一条路径,goal.advance/backfill/手动入队不查——同 topic 烧 token 无上限。现 _learn_executor 入口在全局额度闸之后立即查询,被刹 topic 返回topic_spend_brake。新测试 test_learn_spend_brake_wiring.py。
  • LLM 兜底通道可观测(I-P1,app/evolution/llm.py):脚本车道兜底切未知价渠道(如未登记台账的中转)此前完全静默。现每通道一行 LLMCHAN 日志(失败/兜底=WARNING、主力成功=INFO,含 usage,绝不落 key),可直接 grep cron 日志。新测试test_llm_channel_logging.py。
  • .env 进 .gitignore(A-P0):根目录 .env 含真实 key,未 ignore 时一次git add . 就会泄漏入库。
  • /learning 管理口令改 sessionStorage(C-P1,app/web/learning.html):与 /console 安全姿态对齐——admin 口令不再持久落盘 localStorage(跨会话留存即泄露面)。

运维

  • 生产收敛(J 阶段):生产 master(103 时代孤儿快照线 5 提交)经 commit-tree 收编本地 main 树(保留双侧历史、零 reset/force);生产 memory-venv 补装 requirements 缺失依赖(msgpack/jinja2 等 5 项);config.yaml 盘外保全后以未跟踪文件还原。
  • AGENTS.md 服务器现状注(H-P1):仓库与工作区两级 AGENTS.md 的迁移公告模板替换错误(两台老服务器 IP 写成同一个)修正为 2026-10-30 回迁现状。
v0.4.1 2026-10-28

一句话:线上代码进 git 了——0.4.0 整批改动此前只存在于工作区(无版本保护),本批补提交入册;顺带清剿快照遗留的测试代次错配与树内杂物,测试体检恢复可用。

修复(运维闭环,闲时批次 2026-10-28)

  • 补提交 0.4.0 线上状态(0e716fe,28 文件 +6310/-3150):pager.js/about 页/kb 强化等自 10-25 上线以来首次进入 git 历史;生产仓从此工作区干净。
  • 测试套件对齐(9af4c98):快照内 12 个测试文件是比 app 模块更老的孤本(导入从不存在的符号,收集即错),用主仓版对齐恢复收集;0.4.0 关键回归子集36 passed / 1 skipped(分页合同、pager.js、版本链一致、kb 告警转发、内联 JS 语法)。
  • 树内归档:树根 5 个历史 patch、2 个 .bak、5 个 _debug 测试 → _patch_baks/;旧 backup 目录 → backups/(只 mv 零删除)。

已知边界(留给下个版本)

  • 14 个测试文件因主仓↔生产 app API 真分叉(97 文件热修未回流)无法对齐,收敛方案见主仓 docs/部署一页纸-改代码到上生产-20261028.md。
v0.4.0 2026-10-25

一句话:列表页都能翻到底了——生产 /kb 公开来源 33,392 条、文档版本 11,698 条、/capabilities 68 项,原来各自写死一个 limit 就当"分页",用户翻不到底而页面还写着总数。现在统一走一份分页组件,形态按数据量选(长期增长用上下页,卡片流用加载更多),并且"新加列表却忘了分页"会直接让测试变红。

新增

  • app/web/pager.js:全站唯一的分页实现(无依赖,UMD 导出以便 Node 直接断言)。两种形态:renderPages(上一页/页码/下一页 + 第 N–M 条、共 T 条)与renderMore(加载更多 + 已显示/总数);纯逻辑 paginate/pageList/mergeUnique/moreState。窄屏(< 680px)自动收窗:页码窗口 2→1、上一页/下一页换成箭头,464 宽不再折行。
  • /kb 公开来源分页(服务端,每页 50):33,392 条。原来固定 limit=100,页面写着"共 33392 个来源"却只渲染前 100 条——99.7% 的数据在界面上不存在。
  • /kb 管理员来源分页(服务端,每页 100):同 33,392 条,且原来一次性拉全量、渲染 33,392 个 <tr>,直接是内存炸弹。
  • /kb 文档版本分页(服务端,每页 50):11,698 条,原来看不到 11,648 个版本。
  • /kb 问答留痕(每页 50)、近似重复复核队列(每页 100)分页。
  • /kb 复核留痕、草稿留痕:日志型列表,追加式"加载更多",终止条件看 total 而不是"本页是否为空"(审计记录会被清理,本页空但前面还有是正常的)。
  • /capabilities 能力总库(前端,每页 12):68 项,加载更多 + "已显示 12 / 68";改搜索词或换域时把已显示条数重置回第一页,否则从"全部域"切到只有 5 项的域会看到5 项却没有任何"加载更多",像丢了数据。

变更

  • GET /api/v1/kb/sources 加 limit(默认 200,上限 2000,0 表示不限)+ offset + total。count 语义由"总数"改为"本页条数",总数改看 total(唯一调用方是 kb.html,已同步)。
  • GET /api/v1/kb/versions 加 offset + total;/kb/reviews、/kb/qa 加 offset + total;/kb/reviews/actions、/kb/drafts/actions 补 total。统一口径:count = 本页条数,total = 总数,offset / limit = 回显请求参数。
  • 只读接口一律分页:这一版之后,凡"返回列表"的公开读取接口都必须能报出 total 并接受 offset,否则前端无法判断"是不是还有"——分页条只能显示"本页几条",等于没说。

修复

  • loadSources 的 .then(loadSources):删除来源后回调收到的是 DELETE 的响应对象,一旦函数签名收 page 参数就会把它当页码算,直接 NaN。改成显式 .then(function () { … })。
  • loadQA / rateQA 同款问题(.then(loadQA)),一并改成显式调用。

验证

  • tests/test_pager_js.py:Node 真跑 pager.js,45 条边界断言——0 条/1 条/恰好整除/整除多一条、页码越界(过大/0/负数)收敛、pageSize=0 不除零、非数字不产出 NaN、页码窗口(少页全列/多页首尾+窗口/两端不留孤立省略号)、去重合并、加载更多终止条件。变异检验 10/10 全杀(ceil→round、<→<=、去掉除零兜底、序号从 0 起、页码不收敛、gap 判据放宽、去重失效、终止条件放宽、窗口上界算错),复原后 md5 一致。
  • tests/test_pagination_contract.py:登记表 + 反向判据。① 每个登记项必须在页面里真的带 offset 或调分页器,判据做在"URL 出现位置前后窗口"里,不做整页搜索(整页搜索时删掉某一处 offset 依然绿,等于没写);② 服务端分页的接口必须在 OpenAPI 里真的暴露 offset——抓的是"前端传了、后端无视"的假分页;③ 页面里出现的带 limit= 的列表请求,既没登记也没豁免 → 直接红(这是"以后新列表忘分页"的兜底);④ 豁免必须写清"为什么不用分页 + 数据上界",写 TODO/暂时 一律红。变异检验 11/11 全杀(逐页删 &offset=、删后端 offset 参数、新增未登记列表、豁免理由改 TODO)。
  • tests/test_inline_js_syntax.py:把每个页面的内联 <script> 抽出来交给 node --check。这些页面没有构建链,JS 写错只会"整块不执行":页面照样 200、HTML 照样完整、pytest 照样全绿,只有人打开控制台才看得见——分页会整个消失而没有任何报错。

版本号同时出现在 /healthz、/api/version 与控制台状态页;四处读的是同一个常量(app/core/version.py)。