diff --git a/CHANGELOG.md b/CHANGELOG.md index a47d396..5269307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ > 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。 > 工程口径的完整记录见 `PROGRESS.md` / git log。 +## 0.58.50 — 2026-07-21 + +- 对话过程中偶尔出现的黄色警告「工具调用参数损坏但已就地抢救」改为低调的灰色提示,措辞也更平和(「已自动修复工具调用参数」)。这种情况系统会自动修复,对回答质量和后续对话没有任何影响;黄色警告今后只用于真正需要留意的中断重试类情况。 + ## 0.58.43 — 2026-07-19 - 优化文件预览的观感:打开预览时整个页面背景统一压暗(半透明,背景内容仍隐约可辨、不挡死),聚焦到预览内容,不再出现"上半屏变暗、底部输入区一条通亮"的割裂感;底部输入框依然悬浮可用,可以边看文件边打字。 diff --git a/CLAUDE.md b/CLAUDE.md index 7207d4a..d151006 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -45,12 +45,14 @@ PowerShell here-string `@'...'@` **只在 PowerShell 工具里有效**;用 Bash ## 文档维护 -每完成一步实现(commit 前),**必须更新 `PROGRESS.md`**: -- "已完成关键能力" 段加一条 `YYYY-MM-DD / <短标题>:<改了什么>` +**节奏:版本号 / CHANGELOG / PROGRESS 在 push 前统一更新一次,不用每次 commit 都动;DESIGN.md 例外 —— 某次 commit 触发了架构 / 取舍变化就随该 commit 一起改(见下)。** + +**push 前必须更新 `PROGRESS.md`**(覆盖本次 push 包含的所有 commit): +- "已完成关键能力" 段加条目 `YYYY-MM-DD / <短标题>:<改了什么>` - 状态表(§7 B Step 几 / Phase 几)若变化跟着改 - 文件清单若新增 / 删除模块跟着改 -**每次 commit / push 必须 bump 版本号** —— 单一事实源是 `core/__init__.py` 的 `__version__`(web/app.py 的 FastAPI version、`/healthz` 返回、前端左栏底部展示都引这里,改版本只动这一行): +**push 前必须 bump 版本号(一次 push 一 bump,不按 commit 数)** —— 单一事实源是 `core/__init__.py` 的 `__version__`(web/app.py 的 FastAPI version、`/healthz` 返回、前端左栏底部展示都引这里,改版本只动这一行): - patch(`0.8.x`):bug 修复 / 重构 / 调参 / 新加 skill / 样式 - minor(`0.x.0`):成批新功能 / 明显的对外行为变化 - major(`x.0.0`):1.0 正式发版 / 不兼容大重构 @@ -58,7 +60,7 @@ PowerShell here-string `@'...'@` **只在 PowerShell 工具里有效**;用 Bash **bump 版本时若改动用户可感知 → 顺手在 `CHANGELOG.md` 顶部加条目**(格式 `## <版本> — <日期>` + bullet):口径面向用户("能干什么了"而不是"改了哪个模块"),纯内部重构 / 修复不记——不是每个版本都要有条目。这份由 `GET /v1/changelog` 解析、前端点版本号展示,别写内部模块名 / env 变量 / 部署细节。 -**只有以下情况才动 `DESIGN.md`**(避免把工程笔记沉淀成设计): +**只有以下情况才动 `DESIGN.md`**(避免把工程笔记沉淀成设计)。DESIGN 不跟 push 节奏 —— 哪个 commit 触发了下列变化,就随那个 commit 一起改: - 架构 / 心智模型变化(如 §7.1 task-primary 重写) - 取舍决策推翻或新增(§5 / §7.9 类内容) - API / schema 字段语义变化(§7.2 / §7.4) diff --git a/PROGRESS.md b/PROGRESS.md index eb7f38f..e73ac2a 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -23,6 +23,7 @@ ### 2026-07 +- **07-21 / 0.58.50 / salvage 成功提示降噪(黄→灰)**:用户反馈"工具调用参数损坏但已就地抢救"黄字吓人、问是否影响后续对话——核实**零影响**(salvage 只就地改写 arguments,当轮照常执行,对话历史与正常轮无异;warn 是纯前端 SSE 展示事件,不入 messages 不回灌 LLM,刷新即消失)。处置:不全静默(上游返脏数据的事实要可见、可与 `tool_salvaged` DB 留痕对上),改**分级降噪**——`loop.py` 该 emit 加 `level:"info"` + 措辞软化「已自动修复工具调用参数…继续执行」;`chat.js` warn 渲染按 level 分流,info 走灰色 muted 无 ⚠,黄色 ⚠ 只留给真打断本轮的路径(丢弃重试/熔断等)。 - **07-21 / 0.58.49 / glm.pro52 空响应治本(禁 thinking + loop 区分截断)**:探针定层坐实根因链——①`max_output` 是**全仓死字段**(只在 capabilities.py 定义,`_build_kwargs` 零引用,`max_tokens` 从不发)→ 网关放任 glm-5.2 跑到自带 65536 输出上限;②glm-5.2 thinking **网关侧默认开**(线上探针实测 reasoning_content=766>0,尽管 config thinking_mode:false —— 那开关是 glm.yaml 未做的 TODO,根本没传);③重任务(100k 上下文)上思考膨胀烧穿 65536 被截断(finish_reason=length)、content 空 → loop 判空响应整轮丢弃 + **同上下文无效重试**(task 35744bea:5 次 empty 全 tokens_out=65536,事件4=attempt2 铁证);后果单任务烧 ~327k 输出 token。与 opus48/deepseek 网关 wire bug 不同根。**修**:`core/llm.py._build_kwargs` 加 family=="glm" 分支,据 thinking_mode 透传 `extra_body={"thinking":{"type":"enabled|disabled"}}`(GLM 协议,与 reasoning_effort 不同族)——线上探针 A/B 实测 disabled 后 reasoning_content 766→0、正文照常;`core/loop.py` 加 `_finish_reason`,空响应路径区分 length(截断,我方预算烧穿,重试无效)与 wire 吐空,warn 措辞据实 + `record_empty_response` units 记 finish_reason(JSON 免 migration,便于面板区分性质)。修好既坏的 test_loop_empty_response(mock 缺 executor)+ 补截断措辞用例,61 测试全绿。探针 `scripts/diag_glm_empty_probe.py` 留仓(monkeypatch _resolve_executor 绕沙箱池、PROBE_THINKING_OFF A/B 开关)。**遗留**:max_output 对所有模型仍未生效(本次只治 glm thinking 根因,没盲发 max_tokens——怕截断大 write 的 args);要不要让 max_output 全局生效需单独评估各档安全值。 - **07-20 / 0.58.48 / glm.pro52 空响应定层探针**:失败面板 empty 簇(15)主角从 opus48 换成 `glm.pro52`(11/15)。加 `scripts/diag_glm_empty_probe.py`——`build_agent(resume=True)` 复原真实 system prompt + 全工具 schema + **完整多轮上下文**(空响应发生在深层,单条 user 消息复现不了,这是与 2a1bc25d narrated 探针的关键差别),打 glm.pro52,三态判定 EMPTY(真吐空)/NARRATED(工具意图漏成正文)/STRUCTURED。默认锁 task 35744bea(近7天 empty 5 次最可复现)。本机 import litellm 卡 ~20min,交生产机 `.venv/Scripts/python.exe scripts/diag_glm_empty_probe.py` 跑,输出写 `scripts/_glm_empty_probe_*.txt`。定层后再决处置。 - **07-20 / 0.58.47 / 工具失败面板 6 簇巡检 + svg 质量门签名诚实化**:扫近7天失败聚集逐簇定根因。①**write/edit malformed(45/32)**=100% DeepSeek v4 wire 把并行 tool_call 的 arguments 拼成 `{...}{...}`,salvage(0.58.24)复盘提前到 07-20:命中率稳 ~85%(post-launch salvaged 252/malformed 44)、残差涉事 task 全在跑无终态失败=落非流式重试自愈,**决策保持现状不再写代码**(面板绝对数被上线前 07-13/14 尾巴撑高,要看 salvaged/malformed 比)。②**shell/exit "Font issues"(20)**=`svg_quality_checker` 质量门按设计 exit-1,但 `toolfail._classify` 取 `[exit]` 前尾行当签名,而尾行永远是通用 tip「4. Font issues:…」→ 所有门失败(typography/alignment/spec_lock drift)误标成 Font issues 误导排查。修:checker 在 errors>0 时末尾多打确定性 `gate_verdict_line()`「[GATE FAIL] svg_quality_checker: N error file(s) / M total; top issues: …」作为最后一行 stdout(放 main() 里 export 之后),toolfail 现有取尾行逻辑自动拾取诚实签名、toolfail 零改动;`_categorize_issue` 加 `spec_lock/typography` 类避免真错落 "Other"。补 3 测试(GateVerdictLineTests),39+3 全绿。③**run_python 预检(31)**=pysyntax 按设计拦中文正文硬拼进 .py(引号/缩进崩),非平台 bug=提示词治理待办。④**empty(15)**=主角换成 `glm.pro52`(11/15,非旧 opus48 案),需生产跑 narrated 探针定层(本机 import litellm 卡,task e7c2f50b 备用)。⑤**web_fetch(6)**=外部 403 拒抓为主,非平台锅。 diff --git a/core/__init__.py b/core/__init__.py index 554a919..d2d0123 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.58.49" +__version__ = "0.58.50" diff --git a/core/loop.py b/core/loop.py index b319366..a3729e5 100644 --- a/core/loop.py +++ b/core/loop.py @@ -821,7 +821,9 @@ class AgentLoop: 原样走丢弃 + 非流式重试(半抢救 = 一部分执行一部分丢弃,状态不一致,不如整轮重来)。 allowed_keys 从各工具 schema 的 properties 现取(单一事实源,新增工具自动生效)。 改写用 ensure_ascii=False 保留中文原字节,_execute_tool_call 会正常 json.loads 回来。 - 每条成功抢救记一行 kind=tool_salvaged 留痕 + 发 warn 让前端重建占位段。 + 每条成功抢救记一行 kind=tool_salvaged 留痕 + 发 level=info 的 warn:抢救成功当轮 + 照常继续、对后续对话零影响,前端按 info 渲成灰色 muted 行;黄色显著标注只留给 + 真被打断的丢弃+重试路径。 """ try: msg = response.choices[0].message @@ -874,9 +876,10 @@ class AgentLoop: pass # 留痕失败绝不能打断:arguments 已改好,当轮照常执行 self._emit({ "type": "warn", + "level": "info", "msg": ( - f"工具调用参数损坏但已就地抢救 " - f"{[f'{tc.function.name}({o}->{s})' for tc, _, o, s, _h in pending]},本轮继续" + f"已自动修复工具调用参数 " + f"{[f'{tc.function.name}({o}->{s})' for tc, _, o, s, _h in pending]},继续执行" ), }) return True diff --git a/web/static/js/chat.js b/web/static/js/chat.js index 5b6884c..3c54e94 100644 --- a/web/static/js/chat.js +++ b/web/static/js/chat.js @@ -2451,10 +2451,13 @@ function handleSseEvent(ev, asstCard, ctx) { const msg = (ev.data && ev.data.msg) || ""; if (msg) { closeTextSeg(ctx); + // level=info:已自愈、本轮未被打断(如参数就地抢救)→ 灰色低调行,不带 ⚠; + // 黄色显著标注只留给真打断本轮的情况(丢弃重试/熔断等)。 + const info = ev.data.level === "info"; const w = document.createElement("div"); w.className = "run-warn muted"; - w.style.cssText = "margin:6px 0;font-size:12px;color:var(--warn,#b8860b);"; - w.textContent = `⚠ ${msg}`; + w.style.cssText = `margin:6px 0;font-size:12px;color:var(--${info ? "muted,#888" : "warn,#b8860b"});`; + w.textContent = info ? msg : `⚠ ${msg}`; asstCard.appendChild(w); } } else if (t === "cancelled") {