Skip to content

Integrate two-day release work for v0.9.11 - #40

Merged
shaohaozhi286 merged 7 commits into
masterfrom
codex/release-v0.9.11
Sep 4, 2026
Merged

shaohaozhi286 merged 7 commits into
masterfrom
codex/release-v0.9.11

Conversation

@shaohaozhi286

Copy link
Copy Markdown
Collaborator

Summary

  • include the two-day local master work and patch-equivalent worktree commits
  • improve PA compaction recovery, context-window convergence, link safety, skill state durability, and local build tooling
  • reference Desktop local files by path, share transcript rendering with background tasks, and fit Word previews on first open
  • remove the obsolete mandatory brainstorming skill

Verification

  • pnpm test
  • pnpm build
  • focused C++ regression set: 120 passed, 1 Windows-inapplicable skip
  • python -X utf8 -m unittest tests.scripts.dev_desktop_test tests.scripts.verify_package_unit_test: 11 passed
  • full CTest inventory: all 3844 runnable tests passed across the initial run plus serial rerun of 20 fixed-port conflicts; expected platform/live/benchmark skips remain
  • bash scripts/code_quality_check.sh

现场(feedback IQSZ-D0423,0.9.9):压缩摘要请求发出后网关卡了 2 分 6 秒,
超时后往 SSE 流里插了一行非 JSON 的 `data:504:Gateway Timeout`,随后流没有
[DONE] 就结束。结果压缩失败、整个回合中止,用户只能重开会话:

  Auto full compact failed; error=Summarization failed: Malformed JSON in
  streaming response ... parse error at line 1, column 4: unexpected ':'

三个独立问题叠加,分三层修:

1. saw_parse_error 的粘性误判(openai_provider)
   流里任何一帧解析失败就永久置位,叠加「没等到 [DONE]」即判为终止性的
   MalformedJson。而同样的流中断若没有那一帧脏数据,会判为可重试的
   MalformedSse 并大概率重试成功 —— 一行网关纯文本把可恢复错误降级成了
   致命错误。改为只有整条流**没有任何一帧解析成功**时才算 MalformedJson;
   对面根本没在说这个协议的情况(现有用例)行为不变。

2. 压缩失败缺兜底(agent_loop)
   摘要压缩要发一次模型请求,于是继承了那次请求的全部失败模式。可上下文
   已经满了,这时放弃等于让回合中止。新增 run_mechanical_compact_fallback:
   摘要失败后改用不调模型的 apply_thread_repair 机械修剪腾出空间,回合
   继续。修剪不动时(历史太短)保持原有的失败行为。

3. 瞬时故障被塞进 4xx(src/pa)
   网关把「模型服务异常,请稍候重试」编成 HTTP 400,撞上 retry_policy
   「4xx 一律不重试」的规则成了永久失败。新增 FaultKind::TransientUpstream,
   由 provider_http_error_is_retryable 在 4xx 规则前兜底。收录标准比上下文
   超限更严:必须是报文自己写着可以再试。刻意不收 LLMRequestError 前缀
   (同时罩着参数错误)与单独的「重试」二字(额度用完写的是「请切换模型后
   重试」,那是要人换模型)。上下文超限优先于瞬时故障 —— 盲目重试超大请求
   只会再撞一次墙。

另注:自适应上下文收敛(0.9.9 引入)把该模型阈值从 126000 降到 61967,这个
收敛本身正确(服务端确实在 72903 处拒收过),但压缩触发频率随之上升,把上面
这个既有缺陷从偶发放大成了常见。

新增 5 个单测(SSE 分类 2 / 压缩兜底 1 / 瞬时故障 4);受影响的 268 个用例
与全部 229 个 provider 用例通过。
原实现是严格单调只降不升:一旦某次在较小规模被拒,该模型在整个进程生命周期
内都被钉在那条线上,唯一复位方式是重启。

这个设计隐含假设了「限制是发现出来的常量」。内网模型的实际容量随负载浮动,
假设不成立 —— 一次低谷期的偶发拒绝会永久拖累之后所有高峰期,用户除了重启
没有别的办法。README 里写着「不落盘是因为限制会变」,结果进程内还是钉死了,
只是把永久缩短成了到重启为止,自相矛盾。

改成 TCP 拥塞控制那套 AIMD:

- 乘性减:撞墙立刻降到被拒规模的 85%,回升计时归零(新的拒绝证实限制仍在,
  之前攒的档数作废)。
- 加性增:每隔 15 分钟没再被拒就把被拒线抬一档(×1.2)。被砍到一半的窗口
  约 1 小时回到声明值,期间最多每 15 分钟撞一次。
- 回到声明值 = 完全退出干预,连 85% 的安全余量也不再扣。否则那个折扣会永久
  留着,明明一小时没撞过墙还在按早就不成立的旧证据克扣上下文。
- 回升不改写 lowest_rejected_tokens。它始终是那条实测到的硬证据,回升只是
  「当前该用多大」的时变视图;写回去会让下一次拒绝的 min() 失去基准。

敢主动试探的前提是撞墙已经不疼了:识别 → 修剪历史 → 重试全自动,摘要压缩
失败还有机械修剪兜底。没有那两层之前这么做是不负责任的。

时间源用 steady_clock(不受系统时间调整影响);now_ms 可注入,单测不依赖
真实时钟。回升是随时间连续发生的、没有可挂日志的事件,因此在算出窗口的那一
刻比对上次记录值,变了才写一行 LOG_INFO,避免每回合刷屏。

新增 4 个单测(逐档回升 / 拒绝重置计时 / 回升不改写观测 / 封顶声明窗口);
受影响的 427 个用例通过。
@shaohaozhi286
shaohaozhi286 merged commit b87a42d into master Sep 4, 2026
1 check passed
@shaohaozhi286 shaohaozhi286 mentioned this pull request Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants