Commit Graph

431 Commits

Author SHA1 Message Date
caoqianming 1332139c9e docs: bump 0.59.1 + PROGRESS 汇总 15 commit 重构 + DESIGN §2 架构树同步
- core/__init__.py 0.59.0 → 0.59.1(patch:全部内部重构/测试/修复,对外行为零变化)
- PROGRESS:07-23 条目(七项重构 + 2 bug + 测试 286→351 + 测试库基建)、
  文件清单刷新(loop/llm_transport/tool_registry/storage 三分/web routers 布局)、
  最后更新行
- DESIGN §2:架构树补 llm_transport/tool_registry/storage 三分/web 拆分后布局
  (实施后与代码偏离,按「同步改回」条款修正);CHANGELOG 按「纯内部重构/修复
  不记」规则不动

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:29:19 +08:00
caoqianming c6232d8325 feat(kb): 个人知识库——.kb/ 纯文件机制+/v1/kb* API+前端 modal+注入契约(bump 0.59.0)
- core/kb.py: 状态/视图层(INDEX 单行格式 parse/format、已入库判据=INDEX 有条目、
  删单篇连带原件与索引行、kb_block 注入——有库才注)
- core/kb_ingest.py: 入库管线 markitdown→扫描件 OCR 兜底(方舟)→flash 摘要(失败降级
  不阻塞)→追加 INDEX;编排 to_thread+per-(user,库) 锁,幂等可续跑
- 0022 迁移: usage_events.task_id 放宽可 NULL(kb 入库无 task 上下文);
  记账溯源 kind="kb_ingest"/"vision" + units {"kb","source"}
- web/app.py: /v1/kb* 8 端点(列/建/删库、详情带进度、上传即入库、手动 ingest、看/删单篇),
  不设 HTTP 检索端点(agent 走 fs 工具)
- 前端: kb.js 两栏 modal(上传 XHR/进度轮询/删除确认);rail 左下新增知识库入口,
  四按钮改图标+小字两行布局
- agent_builder 在 memory_block 后注 kb_block(docker/host 路径换算同 .memory);
  documents skill「何时不用」补自建资料路由行
- DESIGN §3.8 机制小节 / CHANGELOG 0.59.0 / PROGRESS

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 15:20:43 +08:00
caoqianming a1db5756aa feat(tools): 扫描件 PDF 直读 read_document——方舟文档理解 base64 内联(bump 0.58.55)
markitdown 只抽文本层,扫描件(老标准/检测报告/红头指南)转出为空=死路。
探针(scripts/probe_ark_doc.py)验证方舟 chat file 内容块直读 PDF 后落地:

- tools/read_document.py:seed_2_lite 同 variant 同 key,记账走
  record_vision_usage;体积/页数双闸(30MB/100 页,pdfminer 软探页数)+
  finish_reason=length 截断提示;多页 OCR save_md 全文落盘只返 1500 字预览
  (防上下文爆),save_md 与读取侧同款三形态路径解析(相对/宿主绝对/容器
  /workspace)+ user_root 边界
- tools/image_ref.py:抽 load_pdf_as_data_url(复用 resolve_in_root;
  data:application/pdf;base64, 前缀为方舟硬校验)
- config/media/doubao.yaml:vision 段加 max_pdf_mb / max_pdf_pages /
  doc_request_timeout_s(probe 实测口径:~1300 token/页、100 页全覆盖、
  17MB 内联可用、单页栅格化 3600 万像素硬限)
- core/agent_builder.py:注册(跟 look_at_image 同 vision 段走)+ 系统提示
  _MEDIA_READDOC_SEG(何时调/何时不调,防对有文本层 PDF 重复花钱)
- 六 skill(paper/patent/standard/proposal/rebuttal/ppt)摄取段加扫描件
  兜底一行(markitdown 转出空/近空 → read_document OCR)
- 冒烟 scripts/smoke_read_document.py 全过(3 页 ¥0.0066,表格→md 表、
  公式→LaTeX,usage_events 记账验证)

选型对比(不接 MinerU 等外部解析 API=新增第三方数据面;不选本地 OCR=
过度投资;不选 file_url/file_id=免认证公网直链/TOS 依赖)见 DESIGN §8.13。
host 侧工具,无需重建沙箱镜像,pull + 重启 web 生效。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 16:57:23 +08:00
caoqianming 2e3a4db4e0 fix(web): 手机端文件预览改悬浮卡片——四边留边距、不压输入区(bump 0.58.54)
移动端预览弹框原为全屏贴边(100vw×100dvh、直角),观感差且糊在消息
输入框上。≤640px 下改为:遮罩层四边 10px 呼吸边距,底部在
--preview-bottom-inset(chat-form 让位)之上再加 10px;卡片尺寸改
100%(相对遮罩内容区,弃 100dvh,旧 WebView 兼容更稳),恢复默认圆角。
输入区仍由 body.fp-open #chat-form z-index 抬升保持可见可用;桌面端不动。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 15:20:08 +08:00
caoqianming c1508d0df0 feat(web,docs): App 套壳 embed app 变体(relogin_url + fragment 注入)+ APP.md 对接文档(bump 0.58.53)
移动 App 方案:原生壳(WebView)+ 原生登录页,登录走 platform 自有接口
(/api/login/token → /api/login/external-login,实测打通,后者服务端换 zcbot JWT)。
顶层 WebView 无父窗口,iframe 的 postMessage 协议失效,泛化企微免登为 app 变体:
?embed=1&relogin_url=<绝对地址>#token=..&user_id=.. —— fragment 注入读完即清,
401/logout 时 location.replace(relogin_url),原生壳拦自定义 scheme 换新 token 重进。

- state.js: EMBED_RELOGIN_URL 解析+消毒(拦 javascript:/data: 等可执行 scheme)
- embed.js: 抽共用 readFragmentToken/gotoInitialTask,加 embedAppInit/embedAppRelogin
- auth.js: logout 分支序 wecom → app → iframe
- APP.md 新增(进入契约 + platform 登录链路 + 原生壳杂活清单 + H5 备选)
- EMBED.md 精简 243→约 120 行,指向 APP.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:59:16 +08:00
caoqianming fb214de960 fix(context): 窗口体量估算实测校准——50%压缩/85%折叠/占用环换 token 实测口径(bump 0.58.52)
diag 实测(scripts/diag_context_pressure.py 留仓):折叠机制有效、零撞硬上限,
但静态 CHARS_PER_TOKEN=2.5 对中文密集窗口低估近一倍——名义 85% 折叠线实际
~155% reliable 才触发(task 9a863424 冲到 40.7 万 tokens);代码密集反向虚高
(fe2d8b73 估 1.2M 实际 616k)。

修法是信号校准、不加新机制:
- context.py: estimate_window_tokens(provider 实报 usage 覆盖窗口主体,仅实测
  点后尾巴按 2.5 估)+ calibrated_chars_per_token(比值夹 [1.0,4.0] 带宽)
- session.py: last_measured_usage() 取窗口内最后一条实报 usage 的 assistant 行
  (best-effort 绝不抛;idx→内存 pos 映射校验 role)
- context_fold.py: maybe_fold 触发判定换 token 实测口径
- loop.py: 压缩门槛与 context_limit_chars(前端环)用校准比值,逐轮以
  sent_chars/prompt_tokens 刷新;校准态挪类属性兜默认,任何异常回退 2.5

已知残余:折叠后 run 在首次 chat 完成前崩掉会多折一次(摘要偏保守,原文全在
DB),不为此加持久化状态。run 中途折叠/超限自愈按 §5 无信号不实施继续搁置。

真实生产 task 只读验证映射与校准(drift 0.95x/0.51x/0.86x),286 测试全绿。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 12:49:02 +08:00
caoqianming 92b8aa4575 docs(progress): 移除 0.58.49 条目里已撤销的 max_tokens 描述
窗口约束 max_tokens 那摊探索后判定不必要、已撤销(glm 空响应是禁 thinking 修的,
max_tokens 防的是没发生过的风险)。PROGRESS 里"max_output 死字段"根因点 + "遗留:
没盲发 max_tokens 需逐档评估"整段随之删除,免误导。纯文档订正,不改代码/不 bump。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:28:22 +08:00
caoqianming 00bbe42823 fix(prompt): 系统提示加护栏——大段中文正文别内联进 .py 源码(bump 0.58.51)
失败面板 run_python/error 语法预检簇(近7天31次/12task)根行为=模型手写
python-docx/pptx 把大段中文正文硬拼进 .py,ASCII 引号/全角标点/缩进崩成 SyntaxError。

定位:现有正规路径充分(出 docx 的 skill 全指向 rendering/render.py md->docx、且写
"别自己手搓"),但 DB 显示 16 涉事 task 中 7 个没 load 任何 skill→够不着 SKILL 指引;
precheck(pysyntax)已 host 侧拦截+回根治 tip+喂 RepeatGuard,浪费已 bound(每任务~2.6次
自愈、无失控循环)。故走最高覆盖最低改动:base 系统提示 general_v1.md(所有任务加载)的
run_python 段加一句原则级护栏——大段中文正文先 write 进 .md/.txt 再 read、有 skill 优先
走其渲染路径(守 recipe-ban,不给可 copy 配方)。纯提示词引导、无法端到端 verify,ROI 本
就低(precheck 已兜),只求少发。遗留:改现有 .docx 无平台路径,需另设计。

顺带补 PROGRESS 缺失的 0.58.50 条目(salvage 提示降噪,并行 session 提交)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 09:05:46 +08:00
caoqianming 62f3ddb658 fix(ui,loop): salvage 成功提示降噪——黄色警告降为灰色 info 行(bump 0.58.50)
用户反馈流内黄字「工具调用参数损坏但已就地抢救」吓人、疑影响后续对话。
核实零影响:salvage 只就地改写 arguments、当轮照常执行,对话历史与正常轮
无异;warn 是纯前端 SSE 展示事件,不入 messages、不回灌 LLM,刷新即消失。

处置取分级降噪而非全静默(上游返脏数据的事实要可见、可与 tool_salvaged
DB 留痕对上):
- core/loop.py:该 emit 加 level:"info",措辞软化为「已自动修复工具调用
  参数…继续执行」;
- web/static/js/chat.js:warn 渲染按 level 分流,info 走灰色 muted 行、
  不带 ⚠;黄色 ⚠ 只留给真打断本轮的路径(丢弃重试/熔断等)。

频发原因研究背书(DB 近7天,bad/chat 轮次):deepseek_v4.pro 14.5%、
flash 8.3%、unifyllm 5.9~7%,glm/sonnet 0 —— DeepSeek wire + unifyllm
网关两链路的流式 delta 乱序病,前缀实为同一调用自身内容的错位碎片
(与 0.58.24 定层一致);社区 opencode/goose/Cline/Roo-Code 同病且多数
closed-not-planned 未修,我方 salvage(85% 救回)+ 非流式重试已是最完整
处置 —— 频繁黄字本质是「救回成功」记录高频刷屏,降灰合理。

顺带:CLAUDE.md 文档节奏调整(bump/PROGRESS/CHANGELOG 改为一次 push
统一更新一次,不逐 commit;DESIGN 仍随触发它的 commit 走)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 08:47:44 +08:00
caoqianming f535dbaa9a fix(llm,loop): glm.pro52 空响应治本——禁 thinking 免推理烧穿输出上限 + loop 区分截断(bump 0.58.49)
失败面板 empty_response 簇 2026-07 主角 glm.pro52。探针 + DB + 代码三重定层根因链:
1. caps.max_output 是全仓死字段(只在 capabilities.py 定义,_build_kwargs 从不作为
   max_tokens 发出)→ 网关放任 glm-5.2 跑到自带 65536 输出硬上限;
2. glm-5.2 thinking 网关侧默认开(线上探针实测 reasoning_content=766>0,尽管 config
   thinking_mode:false —— 那开关是 glm.yaml 未做的 TODO,根本没透传);
3. 重任务(100k 上下文)上思考膨胀烧满 65536 被截断(finish_reason=length)、content 空
   → loop 判空响应整轮丢弃 + 同上下文无效重试(task 35744bea:5 次 empty 全
   tokens_out=65536,事件4=attempt2)。单任务烧 ~327k 输出 token。
与 opus48/deepseek 的网关 wire bug 不同根 —— 这是我方 max_output 死 + 未约束推理模型。

修(禁 thinking + loop 健壮化):
- core/llm.py _build_kwargs 加 family=="glm" 分支,据 thinking_mode 透传
  extra_body={"thinking":{"type":"enabled|disabled"}}(GLM body 级协议,与 OpenAI 的
  reasoning_effort 不同族;litellm zai provider 转发 extra_body)。当前 glm 档均 false=disabled。
  线上探针 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 测试全绿。
- 探针 diag_glm_empty_probe.py 加 monkeypatch 绕沙箱池 + PROBE_THINKING_OFF A/B 开关。

行为变化(知情):glm.pro52 用户现在拿禁思考的 glm-5.2(config 本就 thinking_mode:false)——
不再卡壳/空转,代价是硬任务少了推理链。遗留:max_output 对所有模型仍未生效(本次没盲发
max_tokens,怕截断大 write 的 args),需逐档评估安全值后另做。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 08:16:54 +08:00
caoqianming 9646f571f3 feat(diag): 加 glm.pro52 空响应定层探针(交生产跑)(bump 0.58.48)
失败面板 empty 簇(近7天15)主角从 opus48 换成 glm.pro52(11/15):assistant 轮
既无 tool_calls 又无正文,被 loop 当正常收尾静默 done。要判 glm 是①真吐空、
②工具意图漏成正文(narrated)、还是③瞬态。

scripts/diag_glm_empty_probe.py:build_agent(resume=True) 复原真实 system prompt +
全工具 schema + 完整多轮上下文(空响应发生在深层,单条 user 消息复现不了 —— 与
2a1bc25d narrated 探针的关键差别),打 glm.pro52,stream/non-stream 各一发,
三态判定 EMPTY/NARRATED/STRUCTURED。默认锁 task 35744bea(近7天 empty 5 次
最可复现),argv 可换 task/user/档做对照。

本机 import litellm 卡 ~20min,只能交生产机跑:
  .venv/Scripts/python.exe scripts/diag_glm_empty_probe.py
输出写 scripts/_glm_empty_probe_*.txt。定层后再决处置。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 16:10:51 +08:00
caoqianming 795161adcd fix(ppt,tools): svg 质量门末尾出 [GATE FAIL] 汇总行——失败签名从误标"Font issues"变诚实(bump 0.58.47)
工具失败面板 shell/exit 第1簇「Font issues x20」是误标:svg_quality_checker
按设计 exit-1(质量门),但 core/toolfail 取 [exit] 前尾行当签名,而尾行永远是
通用 tip「4. Font issues:…」→ 所有门失败(typography/alignment/spec_lock drift)
全塌成一条"Font issues",误导排查方向。多文件门也没有可靠尾行(最后文件可能只有 WARN)。

修在工具侧(toolfail 保持通用零改动):
- svg_quality_checker.gate_verdict_line():errors>0 时在 main() 末尾(export 之后)
  多打一行确定性汇总 [GATE FAIL] svg_quality_checker: N error file(s) / M total;
  top issues: … 作为最后一行 stdout,toolfail 现有取尾行逻辑自动拾取诚实签名,
  模型也多一行有用汇总。
- _categorize_issue 加 spec_lock/typography 类,避免真错(typography px)落 "Other"。
- 补 GateVerdictLineTests(3):无 error 不出行 / [GATE FAIL] 打头带 top issues /
  是最后一行 stdout。56 测试全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 15:59:09 +08:00
caoqianming ab55be4cde fix(ui): 文件预览背景撤回毛玻璃、改回半透明压暗遮罩(bump 0.58.46)
用户反馈 0.58.44 的毛玻璃虚化(rgba(20,22,28,.22) + blur(6px))"不行",
要求还是变暗、且背景仍看得到、注意度适中。改回半透明深色遮罩
rgba(0,0,0,0.45)(比 0.58.43 的 .5 略透、背景内容仍隐约可辨,不挡死),
去掉 backdrop-filter,chat-form 悬浮阴影恢复 0 -10px 28px/.22。
因 0.58.44 之上已叠 0.58.45(工具修复),不走 git revert(会冲突
版本/CHANGELOG),改为前向修复。只动 web/static/dev.html。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 13:42:58 +08:00
caoqianming a02d1aa5d8 fix(tools,ppt): 工具失败聚集查前3——修 glob 绝对路径崩溃 + PPT 页脚漂移治本(bump 0.58.45)
- glob(聚集#2):绝对 pattern(/workspace/**/*.py)先拆「静态前缀目录+相对 glob」再匹配,
  不再抛 NotImplementedError: Non-relative patterns unsupported;补首字符 / 判定兼容 Windows 宿主;
  删掉原 if"**"/else 两分支完全相同的死重构。/sandbox/tools 是 Dockerfile COPY,需重建沙箱镜像生效
- ppt(聚集#1,A+C):15/16 exit-1 是手写坐标偏离 layout_grid 锁定值 2-16px 的近失,footer_y 最大头。
  A:spec_lock 加 ## footer 锁定片段(x/y 用 margin_x/footer_y 预填、{PAGE} 占位),executor 逐字粘贴只换页码;
  C:_ALIGN_TOL 2→3px(手写 2px 过严),同步刷 SKILL/executor-base/spec_lock_reference 表述与测试值;30 测试全绿
- 新增诊断:diag_toolfail_scan(跑 scan_tool_failures 出聚集)/ diag_svgguard_breakdown / diag_malformed_samples

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 13:27:50 +08:00
caoqianming d5854e3337 fix(ui): 文件预览背景改毛玻璃虚化——柔和聚焦替代硬深色遮罩、消除输入条突兀感(bump 0.58.44)
0.58.43 的整屏纯深色遮罩(rgba(0,0,0,.5))仍偏硬,亮着的悬浮输入条
浮在深黑上略突兀。改为毛玻璃:浅压暗 rgba(20,22,28,.22) + backdrop-filter
blur(6px)(带 -webkit- 前缀,老浏览器不支持则退化为纯浅压暗、不崩),
背景虚化把内容推远、聚焦到预览卡片又不刺眼;chat-form 悬浮阴影同步调柔
(0 -10px 28px/.22 → 0 -6px 20px/.12)。只动 web/static/dev.html。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 08:57:56 +08:00
caoqianming 9c39d0ba42 fix(ui): 文件预览遮罩改整屏、消除"上暗下亮"割裂——输入区上浮为悬浮活动条继续可用(bump 0.58.43)
旧实现用 #file-preview-modal { bottom: --preview-bottom-inset } 把遮罩顶到
chat-form 之上以便边看边打字,代价是底部整条(输入区+左栏入口+右栏+状态)
全不压暗,与上方压暗区形成硬边界的通亮带,视觉割裂。

改为遮罩铺满整屏(bottom → padding-bottom,仍让居中卡片停在输入区之上不遮挡),
底部随整屏统一压暗;仅 chat-form 由新 body.fp-open 类 position:relative;z-index:95
(压过遮罩 z90;#app/#pane-mid 均不建层叠上下文,已核)+ 上向阴影上浮为
"悬浮活动条",继续可打字。预览时点左栏/右栏/状态落到遮罩=关预览,符合
"背景不可用"预期。改 dev.html + preview.js(open/close 加/去 fp-open)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 21:05:15 +08:00
caoqianming 20c7512370 feat(ui): 页内弹框替代原生 alert/confirm/prompt——分 dialog/message 两族,复用改密码卡片样式(bump 0.58.42)
新增 web/static/js/dialog.js 两族原语(对标 Element dialog/message):
- dialogConfirm/dialogPrompt:遮罩+卡片 modal,Promise 化以一比一替换同步的
  confirm/prompt;Esc/点遮罩=取消,Enter=确认,删除类走 danger 红键。
- message:顶部居中轻 toast,success/error/info 三态,自动消失、点即消。

dev.html 加 #app-dialog 容器 + #app-messages toast + 对应 CSS。全量替换主应用
8 文件调用点(chat/files/crons/procs/wechat/skills/media/auth,共 30 处)。
admin.js 独立 admin-only 页(自带 <style>、无 CSS 变量)刻意保留原生 alert
避免缺样式。纯前端加法,不动后端/接口。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 09:38:31 +08:00
caoqianming 6efda0a157 feat(task): 任务改名 UI——⋯ 菜单加「重命名」,改完列表+顶栏全同步(bump 0.58.41)
后端 PATCH /v1/tasks/{id} 早支持 name(validate_task_name 校验)但前端无入口。
taskMenuItems 加「重命名」项(列表行 ⋯ 与顶栏 ⋯ 共用此菜单,一处插入两处生效),
renameTask() 走 prompt→PATCH→更 state.tasksById/taskMeta.name + renderChatMeta()
+ loadTaskList() 全同步。刻意只改 DB 显示名,不动 working_dir/磁盘宪法文件
(定位锚是 task_id/short_id,name 设计为自由可变标签)。仅改 web/static/js/chat.js。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 22:42:17 +08:00
caoqianming e53211f9c8 docs(progress): 精简 PROGRESS——2026-07 段压回全文一行式,字符数 84K→43K(bump 0.58.40)
本文件头部自定「每条 1-2 句,细节查 git log」,但 2026-07 条目(尤其 0.58.x)膨胀成
几百字大段落,与 2026-06 及更早的一行式不一致。精简只压 2026-07 段:每条收成 1-2 句
保留版本号/日期/关键判断;mermaid 排查 saga 折叠为终案+一行注(结论沉淀进 memory);
同月细碎小版本按主题聚合。状态表/决策表/文件清单/下一步原样保留,内容零丢失。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 10:56:27 +08:00
caoqianming f7e5a80ed9 feat(auth): JWT 滑动续签——活跃用户临期无感换 token,不再因 7d TTL 掉线(bump 0.58.39)
原状:无状态 HS256 JWT(默 7d TTL)无任何续签路径,过期即 401——platform 靠
PLATFORM_KEY 自行重换,SPA 用户只能重输密码。判断:平台层握长期 PLATFORM_KEY、
login 本就幂等换发,缺的不是 refresh token 而是"无感重换"这层;放鉴权中间件一次
改动同时惠及 platform 层与 SPA,且纯加法契约。

- web/auth.py:AuthConfig 加 refresh_threshold_seconds(env
  ZCBOT_JWT_REFRESH_THRESHOLD_SECONDS,默 TTL 的 1/4,设 0 关闭);verify_token
  拆出 decode_token+_uid_from_payload;require_user/admin 共用 _authenticate,
  注入 Response,临期就地 mint 新 token 放响应头 X-Refreshed-Token /
  X-Token-Expires-At(仅续未过期的,已过期先抛 401)
- web/app.py:CORS 加 expose_headers 放行两头,否则浏览器 fetch 读不到
- web/static/js/api.js:中央 api() 封装 absorbRefreshedToken——任一响应带新
  token 就无感换 state.token+localStorage
- 验证:4 case 全绿(fresh 不续/临期续且 exp 正确延长/已过期仍 401/阈值 0 关闭)

顺带:EMBED.md 收敛为纯 platform iframe 对接文档——删企业微信免登 §9(实质併入
RUN.md 企微段「应用主页免登」的内部链路)、校准 §3 换 token 返回体过期字段、补
滑动续签说明;新 env 进 RUN.md、用户可感知条目进 CHANGELOG。

additive,不改字段语义/URL/DB schema;WS ASR 路径 verify_token 签名不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 10:21:35 +08:00
caoqianming 011aee1334 fix(channel-push): 定时简报产物文件链接点开 404——推送端收敛为相对 rel + 前端识别完整 URL 兜底历史(bump 0.58.38)
task 7f904e5f 复盘:定时任务/企业微信推送写进 DB 的 assistant 消息把产物
文件写成完整下载 API 链接 [名](/v1/files/download?path=<rel>),web 正文链接
拦截器(chat.js 的 .msg .body a[href] click)注释设定是"user_root 相对路径",
于是把整个 /v1/files/download?path=... URL 当相对 rel,去前导 / 后
rel=v1/files/download?path=scheduled-…txt,再塞进 openFilePreview 二次包进
download URL → 请求 path 不存在 → 404(node 复刻拦截器坐实)。

关键调研(纠了初判):一度以为链接得留完整 URL 给企业微信端点下载、改格式会
break wecom——错。push_wecom(service.py:266)企业微信端走 send_text(摘要)+
send_file(文件附件独立通道),压根不碰这个 markdown 链接;_build_push_message
的链接只进 DB 给 web 端渲染。焊死 /v1/files/download?path= 纯属把具体 API
端点这个渲染细节泄漏进数据层,还正好踩拦截器坑。

修(A+B 互补):
- B 源头(分层正解):core/wechat/service.py::_build_push_message 链接改
  user_root 相对路径 [名]({rel}),与 chip/正文相对链接统一契约、走拦截器
  已有相对分支;docstring 记明为何不用绝对 API URL(防后人再焊)。
- A 前端兜底:chat.js 拦截器在相对路径回退前先判 ^/?v1/files/,命中则
  new URL(raw,origin).searchParams.get("path") 抽真正 rel 交 openFilePreview
  (自动解码,txt/docx 复用同 modal)——救 DB 里已存 20+ 条历史简报(改不了)
  + 健壮兜底。

验证:node 复刻拦截器——B 新格式(相对)与 A 历史(完整 URL)都解析成同一
干净 rel;A 另测 URL 编码含空格#号 path / 普通相对 / ./ 前缀 / 外链全对、
外链仍开新标签;node --check chat.js + ast.parse service.py 通过。web 前端
需刷新页面生效。additive,不碰对外 API/DB/企业微信推送(send_file)契约,
仅 DB 内 assistant 文本链接写法由绝对 API URL 收敛为相对 rel。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 22:38:57 +08:00
caoqianming eb45e771fa fix(rendering+chip): 修 task d9c74a5a 三处独立缺陷——chip 粗体路径 404 / docx emoji 缺字 / 单文件被迫建 sections(bump 0.58.37)
一次"今日新闻 md→word"暴露三个不相干的坑,逐个定层修:

1) 前端文件 chip 点开弹框 404 —— media.js::extractArtifactRels 的 tail
   字符集 _TAIL_CLS 没排除 `*`,助手把路径写成 markdown 粗体
   `**<wd>/x.md**` 时前导 `**` 被 lead 边界吃掉、尾部 `**` 被贪婪吞进
   rel(星号既不在 tail 集也不在尾部 strip 集),生成 `x.md**` →
   /v1/files/download 404。修:`*` 加进 _TAIL_CLS 排除集,同反引号一样
   在 emphasis 处干净截断;文件名含 `*` 在 Windows 非法、误伤≈0。

2) 导出 word 图标(emoji)缺失 —— docx_manuscript 正文/标题字体是宋体/
   黑体/Times New Roman,无 emoji 字形,📰🔥🇨🇳📊 写进宋体 run →
   Word 渲染成豆腐块。修:common.py 加 EMOJI_RE + add_fonted_runs
   (按 emoji 边界拆 run,emoji 段走 Segoe UI Emoji、其余走原中西文字体),
   接进 _emit_plain_with_chem(不破坏化学式下标)与 add_inline
   (bold/italic/code);标题走 add_inline 自动覆盖。

3) 过程中冒出 sections/ 文件夹 —— render.py docstring 承诺 src 可为
   "目录或单个 .md",但 docx 三 profile 的 render_sections 全是 is_dir()+
   glob 只认目录(只有 pdf 支持单文件),模型传单 .md 撞
   `sections dir not found` → 被迫 mkdir sections && cp 兜底。修:
   common.py 抽 collect_md_files(文件→[src],目录→glob),docx_manuscript
   + docx_brief 共用,figures 基准取 src.parent;兑现 docstring 承诺。

验证(实跑非仅单测):emoji 满满的今日新闻 md 跑单文件 --profile report
退出 0(不再报 sections 缺失),解包 docx 校 run 字体——emoji 全
Segoe UI Emoji、中文仍宋体、SiO2/CaCO3 下标未被破坏;正则修复 4 组样本
(粗体/反引号+间隔号/裸路径+中文句号/斜体)全绿;目录多文件+brief+
不存在输入三路回归无破坏。三处互不相干可独立修,均 additive,不碰对外
API/DB 契约。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 22:15:34 +08:00
caoqianming 1581b69bfa fix(run_python): 超时返回超时前的部分输出 + 无缓冲(-u),模型据此续跑不整批重来(bump 0.58.36)
失败面板 #8(command timed out 19 次/7 task,累计最多)。定性(查 19 条超时的原始
tool_call 代码):不是死循环/chromium 老坑,全是真慢的批量 I/O——① 论文检索
skills.research.paper.search() 循环搜多期刊/关键词(最多,每次打 OpenAlex 累计爆超时)
② pdfplumber 多 PDF extract_text ③ 大文件下载 ④ pandas 大 Excel。超时机制正常,真正
浪费在 _run_subprocess 超时分支把已捕获的部分输出全丢了(kill 后 communicate() 已续读
超时前 stdout,却只回一句 command timed out → 模型看不到"搜到第6个"、整批重搜)。

修:
1. tools/base.py::format_timeout_result 把超时前 stdout/stderr 一并返回 + 续跑提示
   (据已完成部分续跑/每项落盘/大操作用 background=true/单次别塞太多),docker
   (_run_subprocess)+ host(run_python TimeoutExpired.stdout/stderr)两执行器共用。
2. -u 无缓冲:Python 管道下 stdout 块缓冲,不 flush 的 print 超时被 kill 时缓冲区就丢,
   故 run_python 的 python 调用全加 -u(docker 前台 inline/script_path + host 前台 +
   两处后台 procs.launch_host),让部分输出真正落管道被捕获(不碰 shell)。

提示指路(background/续跑)与语法预检不同、恰当:超时是"你这段太慢"的确定信号,
建议对任何超时都可靠。不做:不抬默认 timeout(120s 合理);不单独改 prompt。

测试 tests/test_timeout_partial.py(format 单测 + host 端到端:print 两项后 sleep
timeout=1 → 结果带两项 + 续跑提示)+ test_executor_docker argv 断言更新为 python -u,
全 34 相邻测试绿。additive(超时结果加信息不破坏 schema),不碰对外契约。至此失败面板
top 全部处置(② edit streak/③ 畸形/① SyntaxError 预检+渲染器/#8 超时)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 10:53:14 +08:00
caoqianming 5cf92ec71a feat(rendering): 通用报告走平台渲染器 report profile + 基座引导,少让模型手撸 python-docx(bump 0.58.35)
接 0.58.34(语法预检=止血),这条根治那一头。关键发现:平台早有成熟 md→docx(+pdf)
渲染器 rendering/(bind-mount /sandbox/rendering:ro,任何 run_python 可达,与 skill
无关),标题/正文/列表/表格/图片图题/mermaid/化学式下标/目录/house 字体全有,
paper/proposal/brief 三 profile 在用。烧 token 的验收报告没走它、在裸手撸 python-docx,
唯一原因是没引导把"随手写 Word 报告"指向它。故不造轮子,只两件小事:

1. rendering/docx_manuscript.py 加 report profile(复用全套渲染;差异=报告不是申报书:
   目录默认无[--toc 才带]/章节不强制分页/页边距 2.5-2.5/列表含"第X章节"不含"条"),
   render.py --profile 加 report 选项 + 路由。
2. core/agent_builder.py 基座 prompt 加软引导:出 Word 报告→正文写成 sections/*.md
   调 render.py --profile report,别手撸 python-docx 内联中文。

取舍红线(与用户对齐):渲染器是首选加法不是替代——绝不硬拦 run_python 生成文档,
确需精细定制版式/改写已有 docx/处理 xlsx 等渲染器覆盖不了的照样自由写代码;软引导
让位于更具体的 skill render 指引(proposal/paper)。刻意不把渲染器指路塞进语法预检
hint(会 overload 只该管语法的检查、误导合法手撸),预检保持只诊断语法。三层叠加:
渲染器(根治)> 预检(0.58.34)> RepeatGuard err-streak(0.58.33),run_python 能力不削。

测试 tests/test_rendering.py(四 profile 端到端 + report 目录可选)+ CLI e2e
(render.py --profile report 出 37KB docx)全绿;py_compile 校三文件无语法错。
DESIGN §8.6 记该 profile + 并存红线。additive,不碰对外 API/DB 契约。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 09:32:02 +08:00
caoqianming 2a60243c02 feat(run_python): 宿主语法预检——中文正文硬拼进源码的语法坏码提前拦下+锐化诊断(bump 0.58.34)
工具失败面板最大头(run_python/exit SyntaxError,20 条真实样本):模型手写
python-docx 生成 docx/报告时把中文正文内联进 T("中文…"),引号/标点崩坏 →
SyntaxError → 改 → 再崩,同一文件一分钟连撞 6 次烧 token。三类:
- A 引号提前闭合(最多):中文串里 ASCII 引号 " 提前闭合外层字符串,Python 却报
  "forgot a comma?"/"unterminated string" 主动误导,模型照着加逗号越修越错。
- B 全角标点漏进代码位(center=True、/采购():invalid character U+XXXX。
- C 括号/引号结构崩。

根因不是缺写长文工具(write 早能写长文进文件),而是 docx/报告无渲染管线——
documents skill 只做检索、不管 docx 生成,模型裸手撸 python-docx 内联中文;ppt 有
SVG→pptx 管线故崩得少。本轮先落机检止血(硬约束靠平台机检,不塞菜谱):

新增 core/pysyntax.py(不 import litellm,本机可测)precheck_python(code):host 上
compile()(host 3.12 ≡ sandbox python:3.12-slim,零版本偏差;只解析不执行、与依赖
无关),命中 SyntaxError 返锐化中文诊断(出错行+光标+按错误类型定向:invalid-char→
全角改半角、forgot-comma/unterminated+含中文→ASCII 引号提前闭合三改法、含中文统一
追加"正文别拼进 .py,write 进 .md/.txt 再 read"根治提示)。接进 run_python 所有派发
路径(inline/script_path/background × docker+host 两执行器,_prepare_script 内嵌覆盖
script_path 全路径 + inline 分支各加一处),命中即早返、跳过 docker 往返。

零误伤:只拦本就跑不了的码(compile 不过=运行必失败),通过原样放行、行为不变;
既有 30 个 executor/run_python 测试(合法码 fixture)全绿印证。白捡协同:预检把语法
失败从 [stderr]…[exit 1] 改成 [Error] 前缀+固定首行,loop._RepeatGuard 的 err-streak
(0.58.33)得以兜住"反复交语法坏码"的循环(旧 [exit 1] 形态它抓不到)。

测试 tests/test_pysyntax.py 11 用例(A/B/C + 合法中文串/中文引号不误伤)全绿。
下一步(未做,单列选型):docx 渲染管线,正文 write 进纯文本文件→渲染工具 read 生成
docx,正文永不进 Python 源码、也不走大正文当工具入参(避免触发 0.58.33 修的 wire
乱序畸形)。additive 内部机制,不碰对外 API/DB 契约。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 08:55:55 +08:00
caoqianming 3c60c5c736 fix(loop): 堵失败聚集两裂缝——必填key被吞畸形走非流式重试 + RepeatGuard连续同错streak拦(bump 0.58.33)
扫近14天工具失败面板 top13 定位两条反复烧 token 的裂缝:

① 必填 key 被吞畸形(面板 #3 edit 缺 path,14次/13task/9用户,最广):
定层坐实是流式 wire 乱序(path 值碎片被吞进 old_str 尾部,独立 path 键消失),
跨 provider,非模型漏参。关键区别是 JSON parse 成功 → 既不命中
_malformed_tool_calls(只抓 parse 失败)、salvage 也救不了,一路漏到 executor
才报错反复烧。新增 _toolcalls_partial_args(非空 dict 且 0<缺失必填<全部必填)
接进 _stream_llm attempt 循环走非流式重试(服务端一次拼好绕开乱序),
_log_partial_args 落 usage_events(kind=tool_malformed,签名 missing required keys)
纳入面板可观测(此前伪装成普通 [Error] 完全是盲区)。

② 换参数撞同一堵墙(面板 #2 edit old_str not found,单task 9次/24h 7次):
模型微调 old_str 重试,精确 args 指纹每次不同 → RepeatGuard arg 判据不累计。
补第二道判据:按工具的连续同类错误 streak(_norm_err 抹平路径/数字后签名相同、
跨≥2 不同 args),SOFT 注入定向提示、HARD 拦截一次并重置到 SOFT(非永久封死),
任一成功立即清零。

测试 test_loop_repeat_guard.py +12(err-streak 5 + partial-args 7),20/20 通过;
相邻 salvage/toolfail_malformed 23/23 通过,未碰坏既有行为。additive 内部机制,
不碰对外 API/DB 契约。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 08:22:42 +08:00
caoqianming 1685004f96 feat(loop): 空响应防御——provider 吐空自动重试 + warn 自停不静默 done + 面板留痕(bump 0.58.32)
task 2a1bc25d 复盘:unifyllm 网关对某档 Claude 偶发把 tool_use 漏成正文/直接吐空,回来的轮
tool_calls=[] 且正文空,loop 当"模型答完"静默 done(run_status=idle、无报错、无终态失败),
表现为"自己中断",只能人肉挖 DB 才发现。复测该 bug 现已消失/瞬态,但"空 tool_calls 直接
done"这个失败模式本身太隐蔽,任何 provider 未来吐一次空都会静默卡死,故加防御(对称既有畸形
salvage 链)。

- _is_empty_response(tc 空且 content 去空白为空;纯 tool_call 轮不误判)挂进 _stream_llm 的
  attempt 循环:空-空丢弃本轮走非流式重试(多数瞬态重发一次即恢复,用户无感)
- run() 收尾点分空-空分支:重试耗尽仍空不发静默 done,改发可见 warn「模型返回空响应,已停止,
  回复继续可重试」+ done(复用 stall 熔断自停话术,run_status 落 idle 可续),不引入终态 error
- _log_empty_response stdout + usage_events(kind=empty_response,cost 0)双写留痕;core/toolfail
  加第四段扫描聚成 tool=(empty)/kind=empty cluster(sample=model_profile 看哪个网关档在吐空),
  admin 面板 + 巡检邮件零改动自动带上(单次瞬态 <阈值不触发,跨 task 系统性吐空才冒头)
- 刻意不做内容嗅探式"narrated tool_call 特征→重试":marker 每次变且与正常正文高度重叠,假阳性
  (合法回答判坏反复重试)比罕见静默停更糟,那类只靠面板留痕兜、不改热路径行为
- tests: test_loop_empty_response.py 新增 7 件 + test_toolfail_malformed.py +2;顺手补回
  test_loop_malformed_retry.py 因 salvage(0.58.24)落地后失修的 loop.executor 桩。全 247 测试绿

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 15:29:49 +08:00
caoqianming 7500b3b134 chore: 撤回 §8.11 delegate,只留验证过的批量工具(bump 0.58.31)
0.58.30 曾整体落地 delegate(隔离检索子循环),现 revert + force-push 抹除。
机制没错、对标 Claude Code subagent 也成立,但触发信号没坐实:自评回看
diag_search_args.py,motivating 案子 document_search 122 次呈"一批批不同材料
体系并行搜"形态,更像批量扇出而非结果驱动探索——若属实正解应与 mp_search 同为
批量工具 document_search_batch,delegate 是过度设计;叠加子循环 transcript 不落盘
(诊断驱动的功能反不可诊断)、20 轮上限对 122 次偏低、强制 flash 对难检索降质等
未验证坑。

重建前置收紧:先用 diag 确认某检索 task 的 query 是真探索(依赖前序结果、无法
一次列全)而非可枚举批量,是批量走批量工具、只有真探索才 delegate。批量工具
(0.58.28)验证过、保留。PROGRESS/DESIGN §8.11 记该教训免日后盲目重建。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:58:21 +08:00
caoqianming d3bd40fad7 fix(web): 对话正文文件锚点两处回归——含·的 pptx 丢 chip + 链接点击整页404(bump 0.58.29)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:40:12 +08:00
caoqianming b5ac175ccd feat(mp): mp_search_summary 批量入参 formulas[] 定点解检索烧 token(bump 0.58.28)
诊断 diag_tool_repeat.py 全库扫:pymatgen task fe2d8b73 里 mp_search_summary
占 562/687 次 tool_call、重复率 91%——逐化学式一 formula 一轮走 agent loop,
562 份中间 JSON 全流经主上下文且每轮重发。根因是工具只收单 formula + host-side
持 MP key(禁在 run_python 里循环)被迫一式一调。

这类"已知清单批量扇出"的正解是工具批量化而非 delegate 子循环(delegate 只把
562 轮搬进子上下文、基础 token 不省)。新增 formulas: list(≤300/次)触发 batch:
复用单个 MPRester 会话逐式查(num_chunks=1 fair-use 护栏不变)、每式按
energy_above_hull 升序、单式出错不连坐整批;结果落 materials/mp_search_batch_
<hash8>.json(内容寻址幂等),只回紧凑摘要+路径,中间数据不进对话。

单查询路径字节不变保向后兼容;working_dir 设可选(裸构造/无 key 测试降级内联)。
agent_builder 注入 working_dir_path;pymatgen SKILL.md 加批量引导(WHY+WHAT)。
tests +1(去重/落盘/稳定相排序/单式错误隔离/无 key 泄漏),原测试补回 chunk_size 断言。
additive 内部工具能力,不碰对外 API/DB 契约。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 10:49:35 +08:00
caoqianming 54db4fc348 feat(web): 对话卡片悬停「复制」按钮,取正文 Markdown 原文(bump 0.58.27)
renderMessages 给 user 卡与有正文的 assistant 卡挂 .msg-actions 栏
(纯 tool_calls 轮/error 卡不挂),默认隐、hover/focus-within 现、
触屏常显淡态。复制经 #chat-stream 既有 click 委托据 data-idx 回
state.loadedMessages 取 payload.content(非渲染 HTML),Clipboard API
主路径 + execCommand 降级,按钮短暂反馈「✓ 已复制」。直播卡不带此栏
——流结束 loadMessages 整屏重渲成持久卡自然带上。

本期只做零后端耦合的复制;重新生成/编辑重发需后端截断语义,另立项。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 09:51:28 +08:00
caoqianming 2066947061 fix(web): 用户停止后补持久「已停止」提示,cancel 落一等终态(bump 0.58.26)
点停止中断回复后对话底部什么都不剩:live-append 的「已停止」badge 被收尾
loadMessages 整屏重建冲掉(同 2a1bc25d 案)。补齐 cancel 终态,与 error 卡对称:

- 后端: agent.run 取消路径 return "[cancelled]",_run_agent_bg 据此收尾写
  run_status="cancelled"(否则 idle)。run_status 是 Text 列,加新值零 migration。
- 前端: renderPersistedRunError → renderPersistedRunTerminal,同时认 error/cancelled;
  cancelled 渲居中 muted 虚线「已停止 · 你中断了上次运行」(良性态,不用错误卡样式),
  刷新/切任务仍在,下次起新 run 覆盖清掉。删掉会被冲掉的 live badge 及死 CSS。
- 兼容: run_status 加新值纯增量,蓝绿旧实例读到 cancelled 当良性态优雅退化不崩;
  全部 run_status 消费点(409 门/reaper/SSE is_active/cancel 端点/clear)已核对。
- 同步 DESIGN §7.4 / models.py 注释 / filter allowlist / CHANGELOG / PROGRESS。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 09:38:54 +08:00
caoqianming c655ce0a76 feat(skill): 已选国际旗舰模型时豁免 skill 定向模型切换(bump 0.58.25)
ppt 等内置 skill 会 pin glm.pro52,但用户若已主动选了国际旗舰模型
(unifyllm 网关族:Claude/GPT/Gemini)不应被切走。_skill_model_switch
在算出目标 profile 后加判据:current profile 的 family==unifyllm → 返回
None 不切。定向 pin 本意是给较弱的国产默认模型托底产物质量,已在旗舰
模型上的用户尊重其选择、不降级。判据在 DB 写入前(纯字符串检查,无副作用),
其余取舍(只信内置 skill / 跳档位门控 / 不自动切回)不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 09:22:02 +08:00
caoqianming e1d5bf6788 feat(loop): 畸形 tool_call arguments 就地抢救(salvage)省一次非流式重试(bump 0.58.24)
失败聚集面板同形态跨 write/edit/run_python/shell 复发(err=Expecting value char 0):
char-0 是 provider 在 wire 上把别处正文字节错标成该 tool_call 的 arguments delta 粘的
垃圾前缀,尾部真 JSON 完好并跑到串尾(定层已证 provider-wire 脏、拼接层无辜)。

命中畸形后先试就地抢救,而非直接整轮丢弃 + 降级非流式重 roll:
- core/salvage.py::salvage_tool_arguments(raw, allowed_keys) 纯函数,左→右扫每个 '{'
  试 json.loads(raw[i:]),第一个 parse-to-end 成功 + 非空 dict + 顶层 key ⊆ 该工具
  schema 参数名的即采纳。双护栏:parse-to-end 挡尾部截断的半个 JSON,key 白名单挡前缀
  里自洽的旁支 JSON。工具无关——allowed_keys 每工具从 executor.schemas() 现取。
- AgentLoop._try_salvage_response 全有或全无:本轮所有畸形都能抠出才就地改写 .arguments
  当轮继续,任一抠不出则一个都不动、原样走既有 _log_malformed_args + 非流式重试(零回退)。
- 抢救成功记 usage_events kind=tool_salvaged(record_salvaged_tool_call,cost 0),与
  tool_malformed 成对;toolfail 精确匹配 tool_malformed/run_error,salvaged 不进失败面板。

纯函数单列 core/salvage.py(不 import litellm)以便 tests/test_salvage.py 无依赖本机可跑
(loop.py 顶层 import litellm 本机导入期卡死);14 用例含 write/edit/run_python/shell 四工具
真前缀 + 截断/多键/旁支/尾残渣/空{}/跨工具白名单隔离等边界。盲区:provider-wire 抖动本机
复现不出、/verify 端到端跑不了,靠单测 + 生产灰度观察 tool_salvaged/tool_malformed 比。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 09:20:38 +08:00
caoqianming 72418a5321 feat(sandbox): 补 olefile/xlrd/extract-msg 老格式解析依赖(bump 0.58.23)
toolfail 巡检:run_python/exit ModuleNotFoundError olefile 3 用户/3 任务,
模型在 run_python 里手撸解析老 Office/Outlook 二进制件(OLE 复合文档)。
markitdown 只吃现代 xlsx/docx/pptx,盖不到 OLE;建材院高频收老 .doc/.xls/.msg。
按 Dockerfile「装齐免 ModuleNotFoundError 摩擦」策略补 olefile+xlrd+extract-msg。
纯 Python 无重依赖;要生效需重建 sandbox 镜像。additive 内部依赖,不碰对外契约。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 08:16:18 +08:00
caoqianming 732420a3b7 fix(exec): run_python script_path 宿主预检+前缀容错+code 落盘回退(bump 0.58.22)
失败聚集 run_python/exit「can't open file」15 次/7 天/13 task,DB 逐条核对三形态:
- 没写就跑(13/15):Docker 执行器补宿主侧 is_file 预检,返带纠偏提示的
  [Error] script_path not found,与 host 执行器对齐,不再进容器撞裸 stderr
- 双重拼接:fs 工具输出渲 user_root 相对路径而解析按 workdir 相对(展示/解析
  不对称),模型回传即踩坑;workdir 相对不存在时按 user_root 相对再试
- code+script_path 同给:文件不存在时 code 落盘到 script_path 再执行,
  不再扔掉完好的 code(schema 描述同步注明)

_container_script_path 重写为 _prepare_script(前台+background 共用,code
回写在宿主侧故加 user_root 越界防护);tools/run_python.py host 路径同语义。
测试 +8;诊断脚本 scripts/diag_run_python_noent{,2}.py 留档。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 16:41:43 +08:00
caoqianming 859c02cf65 feat(ops+web): run 级错误持久提示+失败聚集+provider 致命错误即时告警(bump 0.58.21)
反 2a1bc25d 教训(Zai 余额不足连挂 3 次续跑,前端零提示、admin 零感知):

- chat.js: renderPersistedRunError 挂 renderMessages 末尾——run_status=error
  且无 live run 时消息流末尾补「上次运行出错」持久卡(live 错误卡被收尾
  loadMessages 整屏重建冲掉是"发了继续啥也没发生"的根因);下次 run 起跑
  后端清 run_error 自然消失
- usage.py: record_run_error(kind=run_error, cost 0)——tasks.run_error 只留
  最后一次,此行才是聚合的完整数据源;app.py _run_agent_bg except 落库
- toolfail.py: 第三段扫描聚成 kind=run / tool=(run),admin 面板+巡检邮件
  零改动带上;alert_provider_critical 余额/配额/认证关键词命中即时邮件
  ZCBOT_DEVELOPER_EMAIL(同签名 6h 进程内冷却,全路径静默失败)
- tests/test_toolfail_malformed.py +3 用例;PROGRESS/CHANGELOG/RUN 同步

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 14:06:29 +08:00
caoqianming b2d665a100 docs: 补记 0.58.19 重试期思考中指示+停止即时生效(bump 0.58.20)
非流式重试 UX 修复(llm_start 每 attempt 重发 + _nonstream_once 线程级
poll cancel)的代码已随 da289b2(0.58.19)一并入库,但该提交信息与
PROGRESS/CHANGELOG 只覆盖了 toolfail 留痕部分 —— 本条补齐文档:

- PROGRESS 加 0.58.20 条目(根因:warn 关占位段后无人重建 + 非流式
  同步阻塞吞掉停止按钮;含弃养线程的 token 成本说明)
- CHANGELOG 加 0.58.19 用户口径条目(不再假死 + 停止立即生效)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 12:42:48 +08:00
caoqianming da289b2ce2 feat(ops): 畸形 tool_call 进工具失败聚集 + 巡检邮件只发活跃聚集(bump 0.58.19)
畸形轮整轮丢弃、messages 无痕,admin「工具失败聚集」原本看不见这类失败
(toolfail 只扫 role=tool 报错);且巡检邮件去重集是内存态,每次部署清零,
高频部署期每次重启都把 7 天窗口内早已安静的存量聚集重发一遍。

- 新增 record_malformed_tool_call:丢弃畸形轮时写 usage_events 一行
  kind=tool_malformed(units={tool,len,err,head,tail,tokens_in/out}),
  cost_cny 恒 0 —— cost 全 kind 合计且随任务展示,provider 抖动不算用户
  花销;真实 token 快照进 units 供反推浪费。DB 写失败静默不阻塞重试
- core/toolfail.py 加第二段扫描(usage_events kind=tool_malformed),聚成
  kind=malformed 同构 cluster,admin 面板/巡检邮件零改动自动带上;聚合
  逻辑抽 _add 闭包双数据源复用
- 巡检邮件发信前加 count_24h>0 过滤:已安静的聚集重启后不再重发,还在
  烧的重启后再提醒一次(刻意保留)
- 新增 tests/test_toolfail_malformed.py 4 用例(mock session_scope);
  RUN.md env 巡检段 + 故障兜底同步;PROGRESS 更新

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 12:04:22 +08:00
caoqianming 3c714b34c4 fix(loop): 畸形 tool_call 首败即降级非流式 + 损坏原文留痕(bump 0.58.18)
task 716ed3be 实测:deepseek-v4-pro 大参数中文 write(3~4k 字符)流式重 roll
同轮连挂 3 次全畸形,失败强相关而非独立随机,每章拖 3 分钟+,最后总靠非流式
兜底救场,3 次流式重试纯烧 token。

- _MAX_MALFORMED_RETRIES=3(3 流式+1 非流式)→ _MAX_MALFORMED_ATTEMPTS=3
  (1 流式+2 非流式),首败即降级;修掉旧 warn 计数可显示 "4/3" 的 off-by-one
- 新增 _log_malformed_args:丢弃前把损坏 arguments 首尾各 300 字符(ascii()
  转义)+ JSON 报错位置打 stdout [malformed] 标签 —— 畸形轮不入库,这是唯一
  留痕,供定性 provider delta 错位 vs 本地拼接 bug 及报 case 取证
- 新增 tests/test_loop_malformed_retry.py 4 用例;RUN.md 故障兜底同步
  (v4-pro 也中招 + [malformed] 取证口径);CHANGELOG/PROGRESS 更新

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 11:02:27 +08:00
caoqianming 4a591e1a90 fix(web): 历史消息丢图片预览/chip——taskMeta 竞态 + 中文路径抽取(bump 0.58.17)
两根因:
1. selectTask 并发化(0.12.16)后 renderMessages 在 Promise.all 内执行,
   state.taskMeta 必然还是旧值/null → wd 错 → chip+inline 图整体丢失。
   loadMessages 加 {render} 开关,首屏渲染延后到 taskMeta 赋值后(零额外 RTT)。
2. extractArtifactRels lead 边界不认汉字("已保存到<wd>/xxx.png"不命中),
   tail 会把紧贴的中文标点连后文吞进文件名。lead 改任意非 [A-Za-z0-9._-],
   tail 排除集抽 _TAIL_CLS 常量补中文标点(主规则+legacy 双处)。

node 离线回归 12 用例全绿;CHANGELOG 面向用户补记。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 10:29:23 +08:00
caoqianming 4d7075aeb8 feat(web): 有 gpt_image 权限的用户生图默认 GPT 生图(bump 0.58.16)
- /v1/image_models 的 is_default 改为 _default_image_variant:档位里有
  gpt_image(pro/admin)→ 默认它,否则仍过滤后第一个(seedream)
- _resolve_image_model 空串时按同一 helper 解析该用户默认,下拉默认选中
  与实际起 run 挂的图像工具保持一致;用户仍可下拉切回豆包 Seedream
- 无 user_id 内部路径(定时任务/messenger)行为不变

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 10:26:31 +08:00
caoqianming 61137611a8 feat(prompt): 结构图 mermaid vs 生图改 ask_user 让用户点选(bump 0.58.15)
用户反馈画流程图/架构图时模型直奔 mermaid,想点名用生图没入口。根因:
岔路引导只在 imagegen SKILL.md 里,mermaid 路径不触发 load_skill,模型
看不到岔路口;且 SKILL.md 把"流程图/架构图"硬性划给 mermaid。

- agent_builder: 新增 _MEDIA_DIAGRAM_FORK_SEG(挂任一图像工具时进媒体段)
  —— 结构图先 ask_user 二选一(mermaid 矢量 vs 生图视觉版);免问例外=
  已点名工具 / 本对话已选过 / skill 管线内部要求 mermaid
- imagegen SKILL.md: 岔路段同口径重写 + 补"生图画结构图取舍"段(中文标签
  易乱码/位图不可改字提前说清,prompt 按风格化示意少文字装配)
- SKILL_LIST / PROGRESS / CHANGELOG 同步

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 10:18:48 +08:00
caoqianming dadcc8e7e7 feat(ops): toolfail 聚集加时间分布,活跃/已安静一眼分(bump 0.58.14)
修掉的问题旧记录挂满 7 天窗口,和还在发生的没视觉区别。聚合加
daily(24h 分桶)/count_24h/first_at,活跃在前安静沉底;admin 表
加近24h+趋势列,安静行淡化;告警邮件带近24h。API 只加字段。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 09:27:40 +08:00
caoqianming 073d9ef025 feat(prompt): look_at_image 引导改"慢、谨慎调"口径(bump 0.58.13)
tool description 与 system prompt 媒体段原本强调"便宜(< ¥0.01)",等于鼓励随手调;
实际痛点是慢(一次几十秒)。两处改为:SLOW / 谨慎调用 / 只在确实需要图内容时调 /
想问的一次在 question 里问全 / 绝不对同一张图反复看;"何时不调"补"图内容可有可无"。
纯 prompt 调整,无行为代码变化。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 09:23:42 +08:00
caoqianming 4cdaf9a5d3 feat(sandbox): 镜像补 file 命令(bump 0.58.12)
toolfail 巡检首批产出:7 天 13 任务 9 用户撞 command not found——
模型验产物类型的高频动作,slim 基底不带。独立小 apt 层,不打穿
pip/chromium cache。olefile/cairosvg 评估后不装(可 pip 自愈,
cairosvg 进共享 requirements 会坑 Windows host)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 08:50:55 +08:00
caoqianming 62bfb07312 feat(ops): 工具失败聚集巡检,同签名报错自动冒头(bump 0.58.11)
反 9dcae061 结构性缺口(mmdc 挂 90 天 0 成功无人知,靠人工扫 DB 才发现):
- core/toolfail.py:扫近 7 天 role=tool 错误([Error/timeout/exit!=0]),
  按 工具名+归一化签名(路径/数字/uuid 抹平) 聚合,>=5 次且跨 >=2 task 判聚集
- web/app.py _toolfail_scanner:每天跑(仿 _disk_scanner),聚集发
  ZCBOT_DEVELOPER_EMAIL(复用 SMTP_*,未配则日志);签名进程内去重
- GET /v1/admin/tool-failures + admin 页"工具失败"表(低阈值看全量)

生产 DB 冒烟:一次扫出 8 类真聚集(沙箱缺 file 命令 x13 跨 13 task、
web_fetch 断网 x7、mmdc 超时批次 x6 等)。无新表无 migration。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 08:36:31 +08:00
caoqianming 73d07c9560 fix(sandbox): init.sh 放行容器自身 loopback,mermaid 渲染真·终案(bump 0.58.10)
90 天 67 次 timeout 真凶:127.0.0.0/8 DROP 掐死 puppeteer 连 chromium 的
127.0.0.1 DevTools WebSocket(SYN DROP -> TCP 重试 ~127s,恒定 2m15s 超时
+ [object Object] + 零 CPU)。容器 127.0.0.1 是自己 netns 的 lo、到不了宿主,
DROP 无安全收益纯误伤。apply_blocklist 首条 -o lo ACCEPT(v6 同款),
127.0.0.11:53 豁免收编,127/8 与 ::1 DROP 降为 belt-and-suspenders。

服务器全款复刻容器实测:放行前小图也挂 2m14s;放行后小图 3.9s、
真实大图 -w 2800 -s 2 仅 5.2s(1 vCPU 渲大图慢亦为误判)。
之前 probe 全因 --entrypoint bash 绕过 init.sh 无 iptables 而全绿误判。

生效:镜像重建(init.sh COPY 层秒过)+ 回收存量容器。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 10:40:48 +08:00
caoqianming b4931418f3 fix(sandbox): probe_mermaid.sh 临时容器用法补 -i 注释(bump 0.58.9)
不带 -i 时容器 stdin 为空,bash 读 /dev/stdin 静默退出零输出,
探针看似跑完实则没跑(0.58.9 服务器验证实测踩中)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 10:14:20 +08:00
caoqianming d7add3926c fix(sandbox): mermaid 渲染必崩定案:chromium 坏点版本;refresh 旋钮 + build canary 根治(bump 0.58.8)
9dcae061 三轮排查收官。真凶 = 7-08 镜像重建时 apt 烤进的 chromium 点版本
150.0.7871.46 自带启动即崩 bug(int3 自杀、零日志、strace 无失败 syscall;
上游 .100 已修,容器内 --only-upgrade chromium 实测复活出图)。被洗清的:
hardening 六件套 / seccomp / AppArmor / overcommit / ulimit / max_map_count /
QEMU EPYC vCPU / 镜像环境(纯净 trixie 对照 rc=0 反转定位)。

修复:
- Dockerfile ARG CHROMIUM_REFRESH cache-bust 旋钮:chromium 出问题改日期
  重建,只刷 chromium 层,pip 大层不动
- Dockerfile build 末尾渲染 canary:真渲一张中文最小图,chromium/字体链坏
  则 build 当场失败(有它 7-08 就不会把坏版本带上线)
- RUN.md 故障兜底条目改写最终版(两根因 + 判别特征);CHANGELOG 面向用户记

部署:服务器重建镜像(新 ARG 自然打穿 chromium 层 cache)+ 回收存量容器。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 12:19:31 +08:00
caoqianming 9f78b151c1 fix(sandbox): round-3 探针改 pinned 版本直下,绕开被墙的版本解析(bump 0.58.7)
@stable tag 要先访问 Google last-known-good 版本解析端点(被墙)才换到具体
版本号,--base-url 只管二进制下载不管解析 —— 这是上一轮三源全败的真因。
改从镜像内 puppeteer-core revisions.js 读 pinned chrome-headless-shell 版本
直下阿里 binary 源(且正是 mermaid-cli CI 实测版本);错误输出 grep Error 行;
加 [步骤] 参数支持只跑第 3 步(1/2 已有结论:静默 CHECK 无 FATAL、栈无符号、
CPU 无 pku/shstk/ibt)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:04:23 +08:00
caoqianming 0631aa4a4f fix(sandbox): round-3 探针下载源改阿里(bump 0.58.6)
npx 拉 @puppeteer/browsers 包显式 npm_config_registry=registry.npmmirror.com;
chrome-for-testing 二进制按 阿里 CDN -> 阿里 registry binary -> 官方 三级
fallback(服务器实测默认源下不动)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:58:02 +08:00
caoqianming 6ce2a0b70d feat(sandbox): chromium 秒崩 round-3 探针:FATAL 抓取 + headless-shell 修法验证(bump 0.58.5)
bisect 全 case FAIL(含无 hardening baseline 与 seccomp=unconfined)+ 宿主
overcommit/ulimit 数据干净 → 嫌疑收敛为 trixie chromium 150 构建 × 本宿主
组合本身跑不了。round-3 脚本:
1/2. 抓崩溃真实 FATAL 行(此前被 head 截断)+ gdb backtrace(best-effort)
3.   实测 Google 官方 chrome-headless-shell(npmmirror 下载)+ mmdc 接上出图,
     直接验证「镜像弃发行版 chromium、改装 puppeteer pinned headless shell」
     修法可行性,PASS 即定案动 Dockerfile

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:54:16 +08:00
caoqianming 6c3122eb4b feat(sandbox): chromium 秒崩第二根因 bisect 探针(bump 0.58.4)
pids-limit 1024 部署后复测(task 9dcae061,green 实例全新容器):mmdc 仍
首调秒崩 "Failed to launch the browser process: Code: null" —— 排除 pids
打满,第二根因在 hardening flag × chromium 150(trixie)交互。

新 deploy/sandbox/probe_chromium_bisect.sh:线上 docker run 全款 hardening
逐 flag 开关(no-new-privileges / read-only / cap-drop / mem·cpu /
seccomp=unconfined),每 case 跑 mmdc + 裸 chromium 两条,--user zcbot +
HOME=/tmp 忠实复现 exec 路径,尾部带判读表。RUN.md 故障兜底条目同步。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:35:10 +08:00
caoqianming 0d7725b010 feat(admin): 用量表默认时间范围 all→近7天(bump 0.58.3)
admin.js「按模型」「各用户用量」两表初始 range 从"全部"改"近7天",
下拉仍可切全部/30天;API 端 range 默认值不动。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:09:49 +08:00
caoqianming 38f6157b3a fix(sandbox): pids-limit 256→1024 修容器内 mermaid 渲染必崩(bump 0.58.2)
task 9dcae061 排查:近 90 天 mmdc 渲图 0 成功(26 launch fail + 67 timeout)。
根因:--pids-limit 线程也计数,chromium headless 一次 ~150-200 线程,叠加
shell 超时残留的 mmdc+chromium 树(超时只杀 host 侧 docker CLI),256 打满
→ pthread_create EAGAIN(06-23 c4f03d56 实锤)→ chromium SIGTRAP。
dbus 报错是干扰项(成功时也打)。

- agent.yaml sandbox.pids_limit + pool.py DEFAULT_PIDS_LIMIT 两处 256→1024
- RUN.md 故障兜底补条目;PROGRESS 记录
- 附诊断脚本 scripts/diag_task_9dcae061.py / diag_mmdc_history2.py / diag_mmdc_ok_case.py
- 待服务器验证:重启 web + 容器回收后跑 probe_mermaid.sh;若 chromium 150
  仍秒崩另查宿主 docker/seccomp;彻底修法是 Step 3b PGID kill 协议(未动)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:02:45 +08:00
caoqianming 86f50ff133 feat(models): unifyllm 再加 gpt-5.5 / gpt-5.4-mini 进 pro 档(bump 0.58.1)
- config/models/unifyllm.yaml 新增 gpt55 / gpt54_mini variant(照 gpt56_sol
  模板,temp 硬约束 1.0;mini 档 quality 记 good、max_iterations 100)
- agent.yaml model_tiers.pro 加 2 条
- diag_unifyllm.py 默认冒烟清单 5→7 个模型(RUN.md 同步),两模型
  tool-calling 实测均过

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 21:06:37 +08:00
caoqianming 5a8f5bd930 feat(media): 第二图像后端 gpt_image(unifyllm 网关,pro 档)(bump 0.58.0)
- config/media/unifyllm.yaml + tools/gpt_image.py:仅 prompt 参数(网关忽略
  size/quality,尺寸上游自动定,不支持 i2i),b64 直落 figures/,banner 协议
  同 seedream,价目未知期 cost 记 0、usage tokens 进 units 留对账
- 媒体装配泛化:ArkConfig 通用键名(api_key_env/base_url,ark_* 兜底);
  _choose_image_variant 跨 provider 选 image variant(默认仍 seedream_5,
  本 run 只挂一个图像工具);_MEDIA_TOOLS_BLOCK 拆分段按实挂工具拼装
- web:_list_media_variants 扫 config/media/*.yaml(image/video 清单 +
  admin 门控 catalog);media.js 三处白名单加 gpt_image;prompt 润色 hint
  无 default_size 不再谎报 2048
- gpt_image 进 pro 档,与 seedream 共享 images_per_day 配额
- 手工验证 scripts/test_gpt_image_manual.py 全过(含真调网关落图),
  unittest 201 过

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 20:47:06 +08:00
caoqianming ba2afa9e98 feat(models): unifyllm 网关接入,5 个国际旗舰模型进 pro 档(bump 0.57.0)
- config/models/unifyllm.yaml:Claude Fable 5 / Opus 4.8 / Sonnet 4.6 /
  GPT-5.6 Sol / Gemini 3.1 Pro,litellm openai/ 前缀 + api_base 覆盖,零代码
- 实测:gpt-5.x litellm 硬拦 temp!=1、gemini-3.1-pro temp=0.3 返回空,
  两者 optimal_temperature=1.0;流式+tool calling+parallel 5 模型全通
- scripts/diag_unifyllm.py 冒烟脚本(--stream/--temp)
- 服务器需代理出口(直连 unifyllm.ai TLS 失败),RUN.md env 段已注明

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:16:46 +08:00
caoqianming daf0db854a feat(procs): 后台进程 bg proc + 对话锁/完成通知 + seedance 续查(bump 0.56.0)
- shell/run_python 加 background=true(模型判断,>~1min 走后台):进程 detach 独立运行,
  不受工具超时/服务重启/蓝绿切换影响;新 check_process 工具查状态/日志尾部/终止
- 状态协议纯文件(<user_root>/.zcbot_procs/<task>/<proc>/,exit_code 出现即终态,
  无 DB 无队列组件):host=stdlib wrapper detach(限时+杀树+日志截尾 10MB);
  docker=专用容器 zcbot-proc-*(同款硬化+iptables,product=proc 与 sandbox
  reaper/shutdown_all 生命周期解耦);web lifespan 每小时 sweep(7d TTL+孤儿容器)
- web:GET /v1/procs(用户级)+ POST /v1/tasks/{id}/procs/{pid}/kill;前端
  [Background] 工具卡活化(spinner/跳秒/停止,历史重渲恢复)+ 完成 toast(跨 task
  可跳转)+ 对话锁(bgproc 运行期 composer 发送→停止/Enter 拦截,先锁后放消
  刷新时序窗口;润色/语音与 streaming 期一致可用)
- seedance:resume_task_id 续查(轮询超时/中断后取回结果,跳过提交不重复计费),
  fast/pro poll_timeout 统一 1200s;取消/超时文案引导续查
- core/llm.py:litellm timeout 显式化(默认 600s 不变,ZCBOT_LLM_TIMEOUT_S 可调)
- DESIGN 新增 §8.12(含边界:不做 job 链/自动续跑/不引 Celery);RUN/PROGRESS/
  CHANGELOG 同步;scripts/test_bgproc_manual.py 手工验证;unittest 全量 201 过

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:47:11 +08:00
caoqianming 3ef472c112 feat(files): 文件面板根目录隐藏定时/渠道系统工作目录(bump 0.55.2)
- /v1/files 根层与 /v1/folders 候选过滤 scheduled-*/wechat-*/wecom-* 工作目录
- 判定走 DB 回查(_system_wd_names:scheduled_job_id / channel),孤儿目录不隐藏
- 仅列表降噪非权限拦截:带 path 访问照常,点任务跳入目录的前端行为不变

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 12:12:22 +08:00
caoqianming 9e59cf9e44 chore(quota): per-user 磁盘配额 5GB -> 20GB(bump 0.55.1)
应用层软配额上调(agent.yaml quotas.disk_bytes_per_user,重启 web 生效);
RUN 故障兜底行 + CHANGELOG 用户条目同步。OS 层 prjquota 仍留待外部开放前。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 08:35:52 +08:00
caoqianming 5b6adb3746 feat(changelog): 用户版更新日志 CHANGELOG.md + /v1/changelog + 点版本号弹层(bump 0.55.0)
- 新增仓库根 CHANGELOG.md:面向用户口径(只记可感知变化),回填至 0.30.0 共 25 条
- GET /v1/changelog:公开无鉴权,`## <版本> — <日期>` 正则切段,mtime 缓存热改即生效,无 DB 零 migration
- 前端 changelog.js:右栏底部版本号可点弹更新日志;localStorage 记 last-seen 版本,
  与 /healthz 版本不一致亮红点提示,打开即清;接入 Esc/遮罩关闭栈
- 单测 tests/test_changelog.py 3 项(含真文件格式校验);全量 201 过
- CLAUDE.md 加维护约定:bump 版本若用户可感知顺手补 CHANGELOG 条目

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 16:49:26 +08:00
caoqianming 8aa6bed79c docs(models): 清理 deepseek-chat/reasoner 下线残留描述(bump 0.54.2)
旧模型 2026-07 已下线:删 deepseek_v4.yaml 里"可退回 deepseek-chat"的
误导注释(照做会打不通),DESIGN 附录迁移提醒改为已完成陈述。
全库 grep 确认代码/配置本体无旧 model id 引用。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 13:28:26 +08:00
caoqianming 66a515aa3d feat(context): §8.8 Phase 2 长会话中段折叠摘要 + 头部压缩指示环(bump 0.54.1)
- 折叠(0.54.0):run 起点窗口体量达 reliable_context×85% → core/context_fold.py
  把中段折叠为固定模板结构化摘要(路径/ID/数值原文保留),写 tasks.context_summary
  (migration 0021)+ 推进 context_base_idx;Session.load 注入仅内存「前情摘要」
  (_n_head/_base_idx 维护 idx 映射);摘要调用复用会话 prepare 后前缀 + 末尾追加
  指令,与上一轮 chat 缓存字节一致近全程 hit;失败零阻塞(warn+跳过);「新话题」
  硬重置 / web 清空对话顺带清摘要,gap 软重置保留
- 前端(0.54.0):新 SSE context_fold 事件(start 状态行提示 / done 流内分隔条);
  补 warn 事件渲染 —— 此前 loop 熔断/重复拦截提示在 web 端被静默丢弃
- 压缩指示环(0.54.1):头部金额撤下明面(挪 hover tooltip),换 SVG donut
  (绿<50% 未压缩 / 琥珀 50-85% 压缩区 / 红≥85% 达折叠阈值)+ 已整理×N;
  GET /v1/tasks/{id} 详情新增 context_window_chars/limit_chars/pressure/folds
  (additive,列表端点不加);折叠计费 kind="context_fold"(record_chat_usage
  加 kind 参数,折叠次数 = count 该 kind,零 migration);llm_start 事件补
  context_limit_chars,run 中实时刷环
- DESIGN:§8.11 新增「最小子循环 delegate」预留(上下文隔离而非多 agent 编排,
  按 diag 数据触发,无信号不实施);§1/§6 "不做 subagent" 收窄为编排型;
  §8.8 Phase 2 标  沉淀六条取舍;§7.2 SSE 清单补 warn/context_fold
- tests/test_context_fold.py 12 项(切点/映射/apply_fold/maybe_fold 打桩),
  全量 198 过;本地 DB 已升 0021

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 12:39:31 +08:00
caoqianming f1c7f70e3c feat(skills): 新增 rebuttal 审稿回复 + paper/review 移植 nature 系增强(bump 0.53.1)
调研 GitHub nature skill 生态(Yuan1z0825/nature-skills Apache-2.0、
Boom5426/Nature-Paper-Skills MIT)后移植三块:

- 新 skill rebuttal:决定信/审稿意见 → 逐点回复信 + 修改清单 + cover
  letter;拆条分诊(8类×4档×12动作标签)、零编造铁律、先改稿后写信、
  readiness 四档;渲染复用 --profile paper,中英双语
- paper:figure_discipline.md(一图一论断/panel 角色/图例规则,阶段三)
  + submission_audit.md(六步通读审计 + 数据可用性声明模式,阶段六)
- review:sci_polish.md 英文 SCI 润色子模式(四层递进诊断 + 中译英
  先抽命题不逐句翻)

SKILL_LIST 刷新(18 个)+ PROGRESS 记录。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 16:18:47 +08:00
caoqianming 5d1da0672e feat(asr): 录音文件转写工具 transcribe_audio(讯飞 LFASR,bump 0.53.0)
用户上传录音(会议/访谈/语音备忘)要"转文字/总结"的需求落地:
- core/asr_lfasr.py:讯飞录音文件转写标准版客户端(raasr v2 signa 签名 +
  upload/getResult 异步订单 + lattice 解析 + roleType=1 说话人分离格式化);
  单文件 5h/500MB,服务端解码,本地不需要 ffmpeg
- tools/transcribe_audio.py:host-side tool(look_at_image 同形态,路径复用
  image_ref.resolve_in_root);全文落 <音频名>.transcript.txt 只回预览+路径;
  轮询期响应停止按钮;凭据不齐不挂工具
- 新 env XFYUN_LFASR_SECRET_KEY(APPID 与 IAT 共用,SecretKey 独立)
- 实测:TTS 合成 12.6s 中文语音 13s 出稿逐字全对;originalDuration 是上传参数
  回显,realDuration 才是真实时长
- smoke_transcribe_audio.py(离线 12 项全过)+ diag_lfasr.py 诊断脚本

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 15:33:14 +08:00
caoqianming f59b4a6ce1 docs(run): 企微回调平台侧失效坑 + 语音落地收尾(bump 0.52.6)
企微文字/语音全无反应的根因:回调注册在企微平台侧失效(服务器/代码/env
全正常,消息不投递),后台重新保存「接收消息」API 配置即恢复。故障兜底
补该行(判据:`[wecom] inbound` 行没有=平台没投递,与代码无关)。
两渠道语音全链路用户实测跑通:个人微信 SILK->pilk / 企微 AMR->ffmpeg
-> 讯飞 IAT -> 回显已识别 -> 进对话。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 10:58:02 +08:00
caoqianming 5b09ec5c64 fix(wecom): 最终回复推送失败不再静默(bump 0.52.5)
_bg 最终 push_wecom 的返回值一直被忽略,message/send 失败(如企微
「企业可信 IP」拦截 60020)完全无痕。统一走 _push_checked,失败落
`[wecom] push failed: <reason>` 日志(带企微 errcode/errmsg)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 10:49:50 +08:00
caoqianming 52db2ef939 feat(wecom): 回调加入站留痕日志,断定语音回调是否被投递(bump 0.52.4)
企微发语音无反应且 [wecom-voice] 日志零出现(该分支被调用必打),疑企微
侧未投递语音回调;text 路径原先零日志无法对照。解密成功后每条打
`[wecom] inbound msgtype=... from=...`:text 行出现而 voice 行不出现
即可断定平台侧未投递,与处理代码无关。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 10:38:16 +08:00
caoqianming 386a9ee048 fix(sandbox): npm 步骤加重试,抗境内 mirror 断流(bump 0.52.3)
腾讯 npm 源拉 cytoscape 偶发 ECONNRESET 崩整个镜像 build(部署实测)。
fetch-retries 2->5 + fetch-retry-maxtimeout 120s;RUN 故障兜底补该行,
并写明部署不涉及镜像变化时用 update_bluegreen.sh --skip-build 跳过重建。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 10:25:37 +08:00
caoqianming 2ca210dbad fix(sandbox): 镜像 build 剔除 host-only 依赖,修 pilk 编崩(bump 0.52.2)
pilk 进 requirements 后 sandbox 镜像也去装它,python:3.12-slim 无 gcc
整个 build 编崩(部署实测)。容器内 run_python 用不到语音解码:
- requirements 行尾标 `# [host-only]`(通用机制,后续 host-only 依赖复用)
- Dockerfile 装包前 `grep -v '\[host-only\]'` 过滤
- 不往镜像塞 build-essential(+~250MB 无收益)
- RUN 故障兜底行补 sandbox 变体处理

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 10:05:46 +08:00
caoqianming 98a40c2971 docs(run): 故障兜底补 pilk 源码编译缺 gcc 坑(bump 0.52.1)
服务器 py3.12 无 pilk 预编译 wheel,pip 退源码编译报
`command 'x86_64-linux-gnu-gcc' failed: No such file or directory`。
处理:apt install build-essential python3-dev 后重跑 update(部署实测踩到)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:52:31 +08:00
caoqianming abe2f6b9f2 feat(wechat): ClawBot 语音接入 + 企微语音路径补日志(bump 0.52.0)
- ilink: 解析 voice_item(type=3,真机实测)-> InboundAttachment(kind=voice),
  CDN 下载/AES 解密复用现有 download_media
- core/audio.py: 统一入口 transcribe_voice(bytes) -- 按字节头分流:
  SILK v3(微信,\x02#!SILK_V3)-> pilk 解 24k PCM + ffmpeg 重采样 16k;
  其余(企微 AMR 等)-> ffmpeg 直解;两渠道共用
- inbound: 语音附件就地转写,回执「已识别:...」,文本并入对话;失败回可读提示
- wecom 回调补日志: voice 入站行 / transcribe 异常行 / push_wecom 失败 reason
  (原返回值被忽略纯静默)/ 未处理 msgtype 行 -- 排查"发语音无反应"
- 新依赖 pilk(requirements);RUN/PROGRESS 同步
- 已实测: pilk 造微信同款 silk 全链转写逐字正确;AMR 路径回归通过

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:47:22 +08:00
caoqianming 3579e38df5 feat(wecom): 入站语音消息转写进对话(bump 0.51.0)
- 回调新增 msgtype=voice 分支:media/get 下 AMR -> core/audio.py(ffmpeg
  子进程解成 16k PCM)-> 讯飞 IAT 整段转写 -> 当用户消息走同一条对话链路
- 下载/解码/转写全放后台任务,回调窗口内只登记 MediaId(防 5s 超时重推)
- 识别结果先推「已识别:...」回显;缺 ffmpeg/讯飞未配/识别为空各回可读提示
- ClawBot getupdates 对未识别 item 打结构日志(个人微信语音探协议准备)
- 服务器新增系统依赖 ffmpeg(RUN.md env 段;可选 FFMPEG_PATH 指定路径)
- 已实测:TTS 中文 4.5s -> AMR-NB 8k -> 解码 -> 转写逐字正确

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:01:37 +08:00
caoqianming 09db2c9c93 feat(ppt): 验收改纯代码几何质检,撤除渲图 vision 验收闭环(bump 0.50.0)
真实会话复盘:沙箱 chromium "找到但渲染崩"无回退,硬验收门+死路逼模型
pip install cairosvg 手写渲染;look_at_image 逐页 26-42s 纯烧 token。

- pptx_cli.py:删导出渲图验收门(_acceptance_problems)+ --allow-unreviewed;
  几何硬错误质检门(check13/14 越界/压字/错位)原样保留并继续在导出边界复跑
- 删 accept_pages.py 及 acceptance.json 登记机制
- svg_preview.py 保留为手动工具:chromium 渲染失败按页自动回退 cairosvg
  (原来只在"完全没浏览器"时才回退,找到但崩=死路)
- SKILL.md:阶段五简化为 finalize 单步;渲图预览零提及(提示面不给渲染
  入口);强调导出唯一入口 svg_to_pptx;Geometry warning 改"回源核对坐标"
- DESIGN §8.10 记录取舍推翻(0.36.0 渲图验收闭环);PROGRESS/SKILL_LIST 同步

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:41:28 +08:00
caoqianming e9e549b606 feat(web): 语音输入流式化:边说边出字,松手秒出最终文本(bump 0.49.0)
根因:整段模式按 40ms/帧回放音频给讯飞 = 按录音时长等比例白等(16.8s 实测等 4.6s)。

- 新增 WS /v1/asr/stream 流式代理:首消息 JWT 鉴权(不走 query 防进 access log),
  二进制帧实时转发讯飞(dwa=wpgs),动态修正(sn/pgs/rg)在服务端 XfyunStream 合并,
  回推 {"text","final"} 增量全文;引擎提前收尾(静音/60s)推 final 前端自动结束
- 前端:录音即连 WS,增量降采样(carry 跨片连续)实时上行,录音面板实时字幕;
  WS 失败静默退回整段 POST 兜底。实测 16.8s 音频 end→final 179ms(原 ~2.5s+)
- 整段兜底路径帧间隔 40ms→8ms(实测 4.6s→1.75s,识别文本一致)
- nginx 示例加 /v1/asr/stream WS Upgrade 段;RUN 故障兜底补讯飞 IP 白名单 403 与
  反代挡 WS 两条

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:42:20 +08:00
caoqianming 7b20cae85b feat(web): 语音手势两端统一:按下时长判别,PC 鼠标也可按住说话(bump 0.48.1)
- 不再按 pointerType 分流:按住 ≥300ms = 按住说话(松开转写,上滑取消);
  快速点一下 = 连续录音免持(说完 Enter / 再点结束,Esc 取消)—— PC 手机同一心智模型
- press 发起的录音,松手后合成 click 全压掉;"点按钮停止连续录音"的 press 不压(_pressActive)
- getUserMedia 完成前松手按同一时长规则收尾;键盘激活按钮兜底走 click 起录

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:13:26 +08:00
caoqianming e911eb2c6b feat(web): 语音输入手势升级:触屏按住说话 + 桌面 Enter/Esc 收尾 + 波浪反馈(bump 0.48.0)
- 触屏(微信/DeepSeek 式):按住录音,松开转写,上滑 60px 松开取消;pointer capture + 合成 click 压制
- 桌面:点击开始,说完 Enter 结束转写 / Esc 取消(capture 拦截防触发发送);不做 VAD 静音自动停
  (口述需求句间停顿 3~5s 常态,自动截断更伤;业界听写类全是手动收尾)
- 录音面板替换 textarea:实时波浪(采集 chunk 顺手算 RMS)+ 计时 + 手势提示;iOS 手势内 resume
- 纯前端(chat.js + dev.html),后端零改动

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:03:47 +08:00
caoqianming a36a8f1da4 feat(web): 语音输入(讯飞语音听写 IAT)(bump 0.47.0)
- core/asr_xfyun.py:IAT wss 客户端(HMAC-SHA256 签名 / 9600B 帧 / vad_eos=10000),整段 PCM→文本
- web/app.py:POST /v1/asr/transcribe(裸 PCM 16k/16bit/mono body,>60s→413,未配 env→501,讯飞错→502)
- 前端「🎙 语音」按钮:Web Audio 采 PCM 降采样 16k → 转写 → insertText 填输入框(接 undo 栈,不自动发送);60s 自动停;渠道只读镜像锁定
- 凭据走 env XFYUN_APPID/API_KEY/API_SECRET(.env 当前为测试 key,待换正式)
- scripts/diag_asr.py 链路诊断;requirements 显式加 websockets
- 已实测:TTS 合成 5.2s 中文经端点转写逐字正确;企微语音(AMR 需 ffmpeg)留二期

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 14:16:58 +08:00
caoqianming 9ae1040faf fix(web): 消息分页窗口对齐 user 轮次边界(bump 0.46.2)
原按固定 30 条翻页/跳转,窗口边界常切在 assistant 工具调用序列中间
(顶部孤儿 tool 消息);圆点跳转 before_idx=idx+11 启发式长轮拉不全。
业界调研(Discord/OpenAI):分页单位保持消息条数,轮次归组属前端职责。

- 向上翻页 alignedEarlierLimit:按整轮累加攒够 30 条,窗口顶恒为 user 消息
- 圆点跳转:after_idx=起点-1 + 相邻 outline 边界差精确拉整轮
  (短轮补到 30,超大轮按 MSG_FETCH_MAX=100 截断,后续加载自愈对齐)
- outline 未加载/idx 缺失 → 回退旧按条行为;后端 API 零改动

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:07:40 +08:00
caoqianming a47a7799e7 refactor(wecom): 撤除欢迎推送机制,聊天优先布局下不需要(bump 0.46.1)
实际部署改为聊天优先布局(不配应用主页 + 自定义菜单「工作台」按钮指向
/v1/wecom/entry),点应用直进会话,冷启动问题不复存在,0.46.0 的欢迎
推送成了死重:

- 删 wecom_welcomes 表(migration 0021 撤下;DB 已 downgrade 回 0020,
  表建后无一行写入)、maybe_send_wecom_welcome、免登回调欢迎钩子、
  scripts/wecom_welcome_broadcast.py、wecom.list_visible_userids
- 保留:未绑定入站回绑定指引(聊天优先下必需)、core/branding.py 品牌单一源
- RUN.md 企微段只留推荐布局 + 未绑定指引;PROGRESS 同步

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:26:13 +08:00
caoqianming f3be3d5016 feat(wecom): 会话冷启动欢迎消息 + 未绑定入站回绑定指引(bump 0.46.0)
背景:应用配「应用主页」后点应用只进主页,应用会话要等应用发过第一条消息
才出现在成员消息列表,首次登录的员工没入口和 bot 打字。最终采用聊天优先
布局(不配主页、自定义菜单放「工作台」按钮指向 /v1/wecom/entry)。

- 回调:未绑定成员的 text/图片/文件消息不再静默,每条回绑定指引
  (聊天优先下新员工上来就打字,静默会被当成 bot 坏了;事件不回)
- 新表 wecom_welcomes(0021)+ service.maybe_send_wecom_welcome:
  免登入口回调给首次进入的成员推欢迎语"种"会话入口;两层去重
  (已推过 / 已绑定且有 chat task),claim-first 并发安全
- scripts/wecom_welcome_broadcast.py:配主页布局下的存量批量种入口
  (可选工具,--dry-run 先看名单);wecom.list_visible_userids 枚举可见范围
- 品牌名单一事实源挪 core/branding.py(app.py BRAND 与欢迎语共用)
- RUN.md 企微段补推荐布局;PROGRESS 同步

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:11:10 +08:00
caoqianming 7f77e5aca9 feat(web): 移动端显示修复 + 对外品牌名 ZCBOT_BRAND_NAME(bump 0.45.0)
移动端:
- 浮层菜单(⋯ 任务菜单 / ⚙ 媒体模型弹层)原只右对齐触发按钮,手机上触发钮
  靠左时菜单冲出屏幕左缘盖住正文;改为放不下时切左对齐 + 双向钳视口内
- 手机断点:菜单项加大触控目标、模型下拉限宽 48vw、header 品牌名单行省略
- 「管理」入口挪到 任务/对话/文件 tabs 行右侧小按钮(CSS order 实现,
  embed 与普通模式同款,embed 专属覆盖规则随之删除)

品牌名:
- 新 env ZCBOT_BRAND_NAME(默认「总院科研辅助助手」),zcbot 只是内部代号
- /healthz 返 brand 字段,前端 boot 拉取覆盖页面标题/顶栏/登录卡
- 微信/企微测试推送、OAuth 提示页、绑定页等用户可见文案改品牌名或中性「助手」

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:44:26 +08:00
caoqianming b0a8802d35 feat(wecom): 企业微信应用主页免登进 embed 控制台(bump 0.44.0)
- 新端点 GET /v1/wecom/entry:企微客户端内走网页授权 snsapi_base 静默
  无感,外部浏览器退 wwlogin 扫码,同一 URL 两端可用;state 签 login
  哨兵防 CSRF(绑定回调显式拒绝该哨兵,两条流程不串)
- 新端点 GET /v1/wecom/entry/callback:code→userid→反查绑定→签 JWT→
  302 dev.html?embed=1&wecom=1,token 走 URL fragment 不进日志;
  未绑定成员提示先在控制台绑定,不自动建号(避免重复账号)
- 前端 wecom 变体:fragment 吃 token 即清 hash,不需要 parent_origin;
  401/logout 回 entry 静默重签;iframe embed 与 postMessage 协议零改动
- wecom.py 新增 oauth_inclient_url();提示页 helper 提出共用
- EMBED.md §9 + RUN.md 企微段(应用主页 + 网页授权可信域名两项设置)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:09:52 +08:00
caoqianming 50e1d57ed5 fix(web): 跳秒指示覆盖刷新重连 + 发送后空窗(bump 0.43.1)
- 刷新/重连接管:phaseSince 随页面内存丢失,原先要等下一个 llm_start/
  tool_call 才恢复跳秒——恰是用户因疑似卡死而刷新的场景;重建直播卡时
  立即按"思考中"起跳,后续阶段事件校正(秒数从重连时刻起,非真实累计)
- 发消息 POST 返回即起跳:覆盖 build_agent + 首轮 TTFT,原先等 llm_start
  才开始计时

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 09:16:00 +08:00
caoqianming 463e282afd feat(web): run 长耗时可视化——reasoning 事件流 + 阶段跳秒指示(bump 0.43.0)
修"前端一直静止显示思考中被当成卡死":

- core/loop.py:新增 SSE reasoning{delta} 事件——thinking 模型分钟级推理
  原先 delta.reasoning_content 整段丢弃,前端零反馈;流式/非流式兜底两路都发
- chat.js:reasoning 渲染成灰色可折叠"思考过程"卡(直播态随流展开、正文
  开始折叠;历史消息里持久化的 reasoning_content 同款渲染)
- chat.js:活跃状态指示——TTFT/推理期占位段每秒跳"思考中/深度思考中 · Ns"
  (data-status + CSS attr),工具卡运行中 spinner + 跳秒、tool_result 定格
  为执行耗时;每轮 llm_start 重建占位段,工具轮之后不再空窗
- DESIGN §7.2 SSE 事件清单补 reasoning

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 09:02:31 +08:00
caoqianming de53629573 docs(deploy): 497 跳转 301→308——修平台 POST 经跳转被降级成 GET(bump 0.42.5)
平台配 http:// 入口调 POST /v1/auth/login:497→301 后 requests 按规范把
POST 降成 GET 重发,只挂 POST 的路由回 405。示例改 =308(保留方法和
body,浏览器行为不变);治本是调用方直配 https(明文首跳会把
platform_key/JWT 暴露在网络上,重定向救不了已发出的那次)。进故障兜底。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:11:46 +08:00
caoqianming db449b1723 docs(deploy): IP 访问证书警告的兼容边界(bump 0.42.4)
497 跳转目标示例改写死域名——明文 http://IP 访问被 301 带回域名,无
证书警告;https://IP 直连的警告是 TLS 固有行为(握手先于跳转)nginx 层
无解,入口统一域名 / 内网 DNS 补记录。进故障兜底表。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:03:42 +08:00
caoqianming 7db97f11c7 docs(deploy): nginx TLS 写法示例 + 497 跳转坑进故障兜底(bump 0.42.3)
实际部署上 SSL 踩出两坑:①同端口双 listen(ssl+明文)nginx -t 报
duplicate listen;②`if ($scheme = http)` 在 ssl-only 端口永远不触发,
同端口 HTTP→HTTPS 跳转要用 error_page 497 =301。示例配置补注释模板,
另提醒企微回调/可信域名只认 80/443 标准端口。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:25:19 +08:00
caoqianming e586ffb79e chore(deploy): pip install 加 -q 精简部署输出(bump 0.42.2)
日常部署依赖基本没变,全量 "Requirement already satisfied" 纯刷屏;
装新包 / 报错仍会输出,不影响排障。RUN.md SOP 说明同步。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:07:40 +08:00
caoqianming 700bb56ba3 fix(deploy): 蓝绿脚本 wait_health unbound variable(bump 0.42.1)
`local color="$1" url="...${PORT[$color]}..."` 同行多赋值:bash 先展开整行
参数再执行 local,url 里的 $color 展开时还没被赋值,set -u 下报
"color: unbound variable"。拆成逐行 local。服务器上重跑
update_bluegreen.sh 即可(会自拉本修复重执行;上次失败停在 green 已起、
nginx 未切,重跑幂等续走)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:05:48 +08:00
caoqianming 8b482e7a30 feat(broker): 外置 Redis pub/sub——蓝绿跨实例 SSE/cancel 可达(bump 0.42.0)
用户量上升重评落地(DESIGN §7.0):web/broker.py 拆 LocalRunBroker(原进程内
实现,默认)/ RedisRunBroker 同接口双实现,ZCBOT_REDIS_URL env 开关。

- event:PUBLISH zcbot:ev:<tid> + 单条 async pubsub reader 路由本地订阅 queue
  (Redis 只管跨进程一跳,fan-out 最后一公里仍在进程内)
- done:SETEX 60s key + publish 双通道;订阅先挂 channel 再查 key,晚到不漏
- cancel:SETEX/GET/DEL key,loop chunk 间 poll,跨实例可达——蓝绿切换窗口
  「刷新丢直播 / 停止失灵」两个边缘消失;稳态双实例分流只差 nginx 配置
- 容错:启动 ping 不通 fail-fast(同 sandbox init 纪律);运行中失败 10s 节流
  log + 降级丢帧,run 不受影响;reader 1s 退避重连并重挂活跃 channel
- lifespan finally 加 broker.shutdown();sinks.py 解开对旧类名的 import
- tests/test_broker_redis.py 9 例(fakeredis:跨实例 emit/cancel、晚订阅 done、
  降级不抛、fail-fast);requirements 加 redis/fakeredis
- RUN.md:env 段 + 蓝绿节 + bootstrap(apt install redis-server)+ 故障兜底 3 行

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:51:39 +08:00
caoqianming bb6fd11dbb docs: 规模设定重评——broker 外置 Redis 由搁置转推荐实施(bump 0.41.2)
用户量已非个位数,DESIGN §7.0/§8.4 与 RUN.md 蓝绿节的"当前规模不值得上
Redis"设定过时:①部署时总有 in-flight run,蓝绿窗口边缘常态化;②稳态双实例
分流是最近的扩容手段,broker 外置是硬前提。选型排除 PG LISTEN/NOTIFY(token
级 delta 耦主库)与 nginx sticky(upstream 变更时 hash 重排恰在部署窗失效)。
实施待确认,方案要点已记 DESIGN §7.0。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:28:17 +08:00
caoqianming 242548ea84 fix(deploy): 蓝绿端口反转——nginx 接管对外 8765,实例挪 8766/8767(bump 0.41.1)
对外契约(URL:8765)保持不变:客户端 / platform 嵌入 / EMBED 对接方零改动;
blue=8766、green=8767 只绑 127.0.0.1。bootstrap 顺序同步调整(停旧服务释放
8765 后 nginx 才能 reload 接管);已知边缘补文档:切换窗口内对旧实例 run
点「停止」送达不到(cancel 与 event 同为进程内 broker,根因一致)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:16:46 +08:00
caoqianming 1d62670366 feat(deploy): 蓝绿双实例无感部署落地——零 503 窗口 + SSE 不断流(bump 0.41.0)
- deploy/update_bluegreen.sh:复用 update.sh --prepare-only 做准备 → 起空闲色
  → healthz 验 instance → nginx upstream 切流 → 轮询 DB 等旧色 run 自然清空
  (DRAIN_WAIT_SECONDS 默 600s,期间旧 SSE 继续直播)→ stop 旧色 → 残留
  running 行标 error → enable 新色/disable 旧色;--switch 半途恢复调流
- deploy/zcbot@.service 模板 unit + deploy/instances/{blue,green}.env +
  deploy/nginx/zcbot.conf.example(upstream 单行文件由脚本改写)
- tasks.run_owner(migration 0020):起 run 写 ZCBOT_INSTANCE,启动 reaper
  只收自己色的孤儿,不误杀另一实例在跑的 run
- sandbox 容器名/label 带实例色,shutdown_all 只清自己色,并存期互不清扫
- 微信 ClawBot 长轮询 PG advisory lock 选主(detach 连接持锁 + 30s keepalive,
  断连即让位),蓝绿并存不抢消息;定时任务 claim 本就 SKIP LOCKED 免改
- /healthz 加 instance 字段;update.sh 加 --prepare-only
- 单实例部署(ZCBOT_INSTANCE 未设)行为全部不变;RUN/DESIGN/PROGRESS 同步

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:03:10 +08:00
caoqianming c52f399f07 fix(guard): ppt 伪导出根治三层——平台机检门 + 禁令去菜谱 + 脚本 hint 纪律(bump 0.40.0)
0.35.1 复发复盘:模型照 SKILL 禁令里的"配方"装 cairosvg 手搓 SVG→PNG
整页贴图导出,文档层禁令两次被证不够。三层治理:

- 平台层:core/pptx_guard.py + loop 钩子——shell/run_python 本步新产出
  .pptx 命中"≥80% 页整版位图 + 全 deck 零原生文本"即在 tool 结果注入
  [产物机检 ERROR] 当场逼返工;判定刻意保守(≥2 页、表格/图表算原生、
  解析失败放行),tests/test_pptx_guard.py 8 例全过
- 禁令去菜谱:ppt SKILL.md:185/213、brief SKILL.md:26/103 否定式指令
  改"正面窄门"(唯一入口 + 报错就修/如实上报),删掉可照做的违规配方
- 脚本 hint 纪律:pptx_media / svg_preview / crop_images /
  fix_image_aspect / pptx_cli 共 5 处 pip install 提示改环境说明——
  打印给 agent 的安装指令就是执行指令

DESIGN 新增 §8.9(推翻 0.35.1"平台层暂缓"拍板);PROGRESS 同步。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:40:54 +08:00
caoqianming 022eade18d feat(research): paper_server 接口对接 API Key(bump 0.39.2)
paper_server GET 接口收紧要 Key:paper.py 全部请求带 ?api_key=
(env PAPER_SERVER_API_KEY),认证失败(服务端异常处理器压成
HTTP 400 + err_code)转带指引的 RuntimeError。这把低价值只读 key
是唯一刻意进 sandbox 的凭证:run_python env 过滤器加 allowlist
放行,docker 模式 _sandbox_env() 透传进容器(顺带修掉
PAPER_SERVER_URL 覆盖不进 docker 的缺口)。SKILL/RUN/PROGRESS
同步更新。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:15:51 +08:00
caoqianming 7533119039 docs: 精简 PROGRESS(865→188 行)与 DESIGN(797→218 行)(bump 0.39.1)
- PROGRESS 按自身契约压缩:每条收成一行(做了啥+关键判断),细节查 git log
- DESIGN 保留全部决策/取舍/硬协议清单/升级信号,砍实现复述;协议细节指向代码与 probe 脚本;§2 架构树补齐实际模块

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:52:12 +08:00
caoqianming 43775d531a feat(skill): skill 定向模型——ppt 触发自动热切 GLM-5.2(bump 0.39.0)
内置 skill 可在 frontmatter 声明 model: <family.variant>(ppt → glm.pro52)。
单一执行点:load_skill 命中 → run 内热切 self.llm/self.caps(下一轮生效,
记账/压缩阈值自动跟上)+ 持久化 tasks 双列 + emit model_switch 事件;切失败
降级 warn 原模型继续。send 侧档外静默降级对定向模型豁免(免费档第二条消息
不被降回 flash)。跳档位门控(产品决策);只信内置 skill(用户 skill 的 model
字段忽略,防绕门控);不自动切回。"开关"= frontmatter 那一行本身(registry
每 run 现扫,删行即停,per-skill 粒度)—— 曾实现 app_settings 表 + admin
总开关 + 建 task 预切,架构评审后砍掉(第二事实源/第二执行点),详 DESIGN §3.5。
前端:直播流 model_switch 插模型分隔线 + 顶栏下拉同步(档外定向模型临时补
option 如实显示)。新增 tests/test_skill_model_pinning.py。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:57:32 +08:00