Commit Graph

365 Commits

Author SHA1 Message Date
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 9ce992597b docs(changelog): 0.57.0 条目去掉具体模型名,泛称国际旗舰模型(bump 0.57.1)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:18:29 +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
caoqianming d24165a2fe style(web): 列表状态灯挪到文件夹行左侧,数据行 space-between 均匀分布(bump 0.38.8)
终态徽章 + 运行圆点放进文件夹行行首(无文件夹行回落数据行,patch 逻辑同规则
找 host);底部数据行剩纯数据均匀铺开,时间自然落行尾。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:58:56 +08:00
caoqianming 259dde502d fix(web): 列表 meta 行数字组靠左跟排——修 active 静默后的左侧缺口(bump 0.38.7)
active 徽章静默后,无 skill 行的 meta 左槽空置,条/tok 整组右挤留出一块
"缺了东西"的空白。数字组改靠左填槽,仅时间锚行尾;删无意义的 right-group。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:55:32 +08:00
caoqianming 2937b75143 style(web): status 徽章默认态静默——active 不挂徽章,终态行淡化(bump 0.38.6)
「进行中」徽章与运行圆点语义撞车,且列表主体都是 active,重复徽章是零信息
噪音。改为:active 不渲染徽章(列表 + chat-meta 同规则),completed/abandoned
保留徽章且整行淡化(st-* class,hover 恢复),脉冲圆点成为唯一动效信号。
删不再渲染的 .badge.active CSS。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:49:44 +08:00
caoqianming 7e6159af48 style(web): 运行态标识精简为纯脉冲圆点——文案收进 hover title(bump 0.38.5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:43:58 +08:00
caoqianming 640bd0a1a3 feat(web): 后台 running task 自动挂 SSE——运行态标识刷新后也实时(bump 0.38.4)
loadTaskList 收尾 subscribeRunningRows:列表带出的 running/cancelling 行本地
未订阅的自动挂事件流(上限 4 条防同源连接占满),done/error 走现有收尾清标识 +
重拉列表,零轮询。ensureRunningTaskSubscribed 的 cancelling/workingDir 改由
调用方传 seed(后台 task 媒体 rel 解析要用各自 working_dir);后台订阅不再调
renderLiveRunIfVisible(避免重挂卡强制滚底误伤当前对话)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:41:26 +08:00
caoqianming 0ad7d08242 feat(web): 任务列表加运行态标识——多 task 并发时可见哪些在跑(bump 0.38.3)
列表行 run_status(后端本就返回,前端一直没用)渲成状态徽章旁的标识:
running 绿脉冲点/cancelling 橙/error 红点(hover 出 run_error)。取值叠加本地
liveRuns;run 开始与点停止时就地 patch 行 DOM(不重拉列表保分页),run 结束
沿用收尾 loadTaskList() 重拉。⋯ 菜单"清空对话"的 running 判断同源修正。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:25:59 +08:00
caoqianming 941554f9d7 feat(ppt): zongyuan_red 逆向重建为真实中国建材总院模板 + 主动提示(bump 0.38.2)
按官方 总院模板.pptx(中国建筑材料科学研究总院)把手搓的 zongyuan_red
重建为真实品牌模板:PowerPoint COM 渲真页 + 解 pptx 抽实测色/字/资产。

- 打包 logo.png(八边形字标,EMF→PNG)/ cover_bg.jpg(总部大楼灰度)/
  ending_bg.jpg(材料马赛克);TIFF→压缩 JPG、EMF→透明 PNG
- 重写 5 页 SVG 忠实还原:封面(实景铺底+红块)/目录(红斜三角)/
  章节(八边形水印,原件缺按 DNA 合成)/内容(灰底红顶条卡片+底部红条)/
  尾页(材料创造美好世界+Thanks)
- 实测身份:主红 #D7000E、目录红 #D52C24、近黑 #181717、辅灰 #6F6F6F/#BCBDBD;
  微软雅黑+Arial+方正兰亭黑
- 改写 design_spec.md;补登记 layouts_index.json(此前 dir 在但未注册)
- 质检 --template-mode 5 页零 error;finalize 内嵌 8 图 + 全量渲图逐页确认

主动提示:strategist.md §e + SKILL.md 默认主题段各补一条 —— 指向
中国建材总院·CNBM 系汇报(含职称评审)时策略阶段主动把 zongyuan_red
整套模板作为候选点名给用户,点头再按明确路径套入;唯一鼓励主动提模板的
场景,其余仍等明确路径。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 15:18:06 +08:00
caoqianming dc721ba8a3 fix(web): 进度 dock 展开遮挡最新内容——task_progress 后补触底(bump 0.38.1)
#task-progress-dock 是 #chat-stream 上方的 flex 兄弟(flex-shrink:0),dock 一涨高就
从顶部挤掉 chat-stream 的可视高度,scrollTop 据置不变 → 原本贴底的最新内容被推到视口
折线以下看不见。直播态 task_progress 事件重渲 dock(=涨高)后早 return,跳过了末尾的
贴底兜底,故底部不自动回滚。修:在 task_progress 分支重渲 dock 后补一句
if (nearBottom) stream.scrollTop = stream.scrollHeight(与其余事件分支同款)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:59:34 +08:00
caoqianming 346930449a feat(ppt): 反纯文字页+图表落地硬门(7aa49195 二代陶瓷 deck 复盘,bump 0.38.0)
0.37 网格锁生效后复评仍存两盲区:两栏裸文字页 x4(指纹看不见)、
全本零数据图表;另有内容被页脚裁掉、CJK 文字叠压两硬缺陷。修五处:
- 指纹加 text-columns 原型(0 卡片+<=3 图标+<=2 图形基元+左对齐文本
  聚 >=2 列),裸文字页进单调门,4 页同指纹 error
- spec 指派图表落空检测:page_charts 指派了图表但该页 <3 图形基元
  且 <4 卡片 -> error;executor 硬规则"不许把指派图表降级为文字"
- CJK 叠压升级:两 run 均 >=70% CJK 且互叠 >=50% -> error
  (表意字宽 1.0em 估宽近精确,其余情形保持 warning)
- layout_grid 加可选 content_bottom,正文 baseline 越过 -> error;
  executor 加"写页前垂直空间预算"纪律
- 策略层数据图表下限:素材含 >=3 组可比数值 -> 全本至少 1-2 页
  真数据图表,零图表需在 spec 写理由

测试 +9(30 项)全过,全量 162 过;charts/decks 模板回归零新增噪音。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 13:34:51 +08:00
caoqianming d30f6089bb fix(web): 直播流式文字按轮次分段——修工具刷屏时文字被推出视口(bump 0.37.2)
一次 run 把整段(含几十轮 LLM)塞进一张 assistant 卡:文字全累顶部单块、
工具卡全追加其下,工具多时文字被越推越高滚出视口看不到。根因是直播态(单卡合并)
与历史态(每轮 LLM 一条独立消息、天然穿插)结构不一致。

方案 A(只动 chat.js live-run 路径,历史渲染不动):文字按轮次分段——
ensureTextSeg/closeTextSeg 维护当前打开的文字段,每个可见工具/选项卡(非隐形
task_progress)先关掉当前段(空占位段移除、有内容段定稿去光标+高亮),之后新文字
在卡片底部另起新段。流式文字始终在底部可见,且与历史结构一致,run 结束 reload 无跳变。
rAF 节流改闭包捕获 seg 防错渲;ctx.body/ctx.pending 单块模型换成 ctx.curSeg。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:22:15 +08:00
caoqianming 6f27b7cc5a fix(seedream): size 面积钳制——修 1920x1080 被 ARK 400 打回(bump 0.37.1)
模型自选 16:9 出图(1920x1080=2.07M px)触发 ARK 硬门
`image size must be at least 3686400 pixels`(=1920²,卡总面积非单边),
整次文生图 400 失败。

- tools/seedream.py: 新增 _normalize_size(),出图前把 size 钳进
  [min_pixels, max_pixels]:面积不足按 sqrt(min/area) 等比放大、
  取整到 8 的倍数并复核达标(1920x1080→2560x1440);超上限等比缩小;
  已合规原样透传(向后兼容)。归一化时返回串附 [note]、meta 记
  requested_size,记账按真实出图尺寸。
- config/media/doubao.yaml: seedream_5 加 min_pixels/max_pixels
  (旧 yaml 缺键=不设该侧,行为不变)。
- bump 0.37.0→0.37.1;PROGRESS 加一条。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 12:41:46 +08:00
caoqianming 0e02cff6c6 feat(ppt): 对齐网格锁+错位/单调质检(d1285247 陶瓷 deck 复盘,bump 0.37.0)
复盘 25 页陶瓷 deck 三类缺陷:跨页左基线漂移+并排块顶差 2-12px 的
"想对齐没对齐"、5 页同为图标卡网格的单调、标题语义不兑现(架构画成
横条列表)。修四层:
- spec_lock 新增 layout_grid 锁段(margin_x/content_top/footer_y/gutter),
  strategist 派生、executor 每页吸附、checker 强制
- executor-base §3 网格对齐纪律(同 top 同高等 gutter、打破网格 >=16px、
  同行文字 >=0.3em 禁贴字)
- svg_quality_checker 新增 check 14:兄弟卡片近失对齐 2-12px error
  (底对齐/中心对齐/chart-plot-area 内数据柱三类豁免,71 charts 回归
  误报清零)、layout_grid 偏离 2-15px error、gap 不等 warning、无锁
  项目跨页左缘聚类漂移 warning、版式指纹单调门(>=3 同指纹 warn、
  >=4 或过半 error;仅对 NN_ 编号 deck 页聚合)
- 策略纪律:同一版式原型整本 <=2 次 + 标题语义必须被图形兑现

顺手修 comparison_columns 模板胶囊 5px 错位。
新增 tests/test_svg_alignment_check.py 21 项;全量 153 过。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:16:42 +08:00
caoqianming a89c7386fd fix(web): 进度条自愈——回放层强制单调完成(d1285247 复盘,bump 0.36.2)
task_progress 回放非渲染 bug:模型跳步推进时漏给上一步补 completed,
导致"下面绿勾、上面红圈"。progress.js 加 enforceMonotonicProgress:
某步 completed 则其之前所有步自动 completed,set_plan/update_step 出口
各过一遍,漏发自愈。前端单测 +3(含复刻 d1285247 跳步序列→6/6)。
诊断脚本 scripts/diag_progress_d1285247.py。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 11:16:34 +08:00
caoqianming fcc158dff6 fix(ppt): 门体系二轮硬化——逃生口收紧+导出自动质检+svg_final 嵌图修复(bump 0.36.1)
0.36.0 重跑复盘:门都触发了,但弱模型 8 秒内连按 --allow-iconless +
--allow-unreviewed 绕过,质检/渲图验收仍 0 调用,4/25 页错位漏出。修五处:

- A 验收门分层:"从没渲过/渲后又改/finalize 前渲的"= 硬问题,任何 CLI
  flag 不豁免;--allow-unreviewed 只豁免"渲过但没标 pass";运维兜底走
  ZCBOT_PPT_FORCE_EXPORT=1 环境变量(不进 --help/SKILL)
- B 拔 -s final 雷:图标门永远对 svg_output 源检测(消除 svg_final 展开
  后误报"零图标"),wrapper docstring 老示例删除
- C 导出自动质检门:svg_to_pptx 导出前内嵌复跑 quality checker 逐页硬
  错误,error 拒绝导出、无豁免参数
- E 几何质检加"文字骑卡片边缘"检测(warn 带坐标,P12/P14/P18 类命中)
- F 修 svg_final 嵌图失效:copytree 后 ../images/ 解析必落空,所有 deck
  的 svg_final 一直嵌不进外链图(验收 PNG 图片为空);resolve 加 rebase
  回 svg_output 兜底

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 08:58:49 +08:00
caoqianming 3c712031d5 feat(ppt): 渲图验收闭环+导出验收硬门+几何质检(139a59c5 错位复盘,bump 0.36.0)
复盘 25 页 deck 错位交付:阶段六全量渲图验收被整个跳过(svg_preview 0 调用,
进度步骤只跑了 echo),图标 regex 盲插压字、大字压说明、目录溢出页底全部漏出。
文档要求过但无机制强制,三层补齐:

- A 机制:svg_preview 渲图登记 .build/acceptance.json(源 sha1+verdict);
  新增 accept_pages.py 标 pass/fail(校验渲过+源未改);svg_to_pptx 导出
  边界加验收硬门(每页 pass 且 sha1 未变,--allow-unreviewed 逃生)
- B 提前拦截:svg_quality_checker 新增几何检测(估宽包围盒):图标压字/
  基线出画布=ERROR,文字重叠=WARN 带坐标(密排设计误伤权衡,判断交渲图
  验收);tspan 按视觉行归组续排,71 charts 模板 0 error 误报
- C 文档:SKILL.md 管线改"后处理→渲图验收→导出",反模式加"没看 PNG 就
  --pass-all""为消警告批量盲插元素";SKILL_LIST 同步

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 13:37:59 +08:00
caoqianming d79c28de06 fix(ppt): 禁自搓 SVG→PPTX 导出器硬约束(966041e5 复盘,bump 0.35.1)
复盘 陶瓷资源节点建设方案 (3).pptx:25 页全是整页 PNG 贴图、零原生
文本/形状。根因是模型整条绕开官方管线(svg_quality_checker/finalize_svg/
svg_to_pptx/svg_preview/total_md_split 调用次数全 0),自搓 cairosvg
export_pptx.py 逐页光栅化贴图,连带图标空方框、外链配图丢失、文字溢出。

上一条(0.34.7)硬化的是官方工具内部的门,只在模型用官方工具时生效;
本次证明模型可完全另起平行管线,内部门无从触发。改动仅在文档层:
- SKILL.md 阶段五:加「导出唯一入口=官方 svg_to_pptx.py,默认原生可编辑、
  纯 Python 无需外部渲染器,渲染器没装不是自搓借口」
- SKILL.md 反模式:加「绕开官方管线自搓导出器 → 不可编辑贴图、价值作废」

不改线上跑法/官方脚本行为。残留风险(平台层自动检测整页贴图)按用户
选择暂缓,已记入 PROGRESS。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 09:25:14 +08:00
caoqianming e46eb01766 feat(shortcuts): 加快捷指令(触发词→完整指令,入口层确定性展开)(bump 0.35.0)
预定义"简报 → 给我输出一份昨日的 AI 新闻简报",任意入口整条打"简报"就展开执行。

关键设计:快捷指令 ≠ memory。memory 是注上下文给模型概率召回的软上下文;快捷词是
入口层、模型跑之前的确定性替换(命中即换、零歧义)。性能上 shortcuts.md 内容永不注
上下文,存再多条平时也是 0 token;触发时进上下文的就是那条完整指令本身。

- core/shortcuts.py(新):shortcuts.md(| 触发词 | 指令 | 两列表)解析 + expand()
  整条 strip()+casefold() 精确匹配展开(与「新话题」魔法命令同风格,不部分匹配)
- web/app.py 两处共用同一 expand:渠道核心 _run_channel_conversation(微信/企业微信)
  + 网页 post_message,起 run 前展开,任意入口行为一致
- core/memory.py memory_block:加一行契约让模型可维护 shortcuts.md;内容不注上下文
- tests/test_shortcuts.py(新):解析 + 展开全覆盖
- DESIGN §3.7 加"快捷指令 ≠ memory"取舍段 + 文件树;PROGRESS 加条目

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:58:55 +08:00
caoqianming c2d24b20b4 fix(ppt): 导出图标门升硬 + 修 svg_to_pptx CLI 退出码不传播 + 验收改全量(bump 0.34.7)
诊断 ppt生成2(966041e5)真实产出的两个缺陷——23 页零图标、多处错位——
根因不是缺 gate 而是 gate 被打穿:

- svg_to_pptx.py 只 main() 不 sys.exit(main()),main() 里所有 return 1
  (图标门/无 SVG/坏路径)全被吞成退出 0(最致命)
- 导出侧图标检查按设计只软 WARN、照常产出
- 模型质检用 `| head` 截断,吞非零退出码 + 截掉打在最后的零图标 [ERROR]
- SKILL.md 验收本就只要求抽查 3 页,错位藏在没看的页里;差评也未阻断

改动:
- svg_to_pptx.py: sys.exit(main()) 传播退出码
- pptx_cli.py: 导出图标门从软 WARN 升为硬门(锁图标却全 deck 零
  <use data-icon> → [ERROR] 退非零、不产出 pptx),加逃生口 --allow-iconless
- SKILL.md: 阶段六验收改「默认渲整本 + 逐页过目 + 差评即阻断返工」,
  阶段四/五/反模式补「别用 | head 截断」「别只看几页」「差评必返工」

合成测试三例(默认拒 / --allow-iconless 放行 / 有图标正常)全过。
仅改 skill 侧,不改动线上跑法;导出门只兜「锁了图标却零引用」,正常 deck 不受影响。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:16:49 +08:00
caoqianming 641c7d58aa fix(tools): look_at_image/seedream 接受容器 /workspace 绝对路径(bump 0.34.6)
docker backend 下系统提示告知主模型一切在 /workspace 下,模型自然产出
/workspace/<wd>/x 绝对路径,但 image_ref.resolve_in_root 不翻译该前缀,
报「图片找不到或越界」。加容器根前缀翻译(与 send_email 的 _resolve_user_file
一致),按字符串前缀判断而非 is_absolute()(Windows 上 /workspace 缺盘符不算
绝对);越界仍靠 relative_to(root) 兜住。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:07:44 +08:00
caoqianming eb9ffd654f feat(admin): 各用户用量表加「最近使用」列(bump 0.34.3)
后端 _user_usage_page 加全量(不随 range 筛选)相关子查询
max(created_at) → last_used_at;前端 renderUserUsage 加列,
fmtTimeAgo 显示 + 全时间戳 title,无用量显示「—」。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:28:34 +08:00
caoqianming d8f71aa7b2 feat(ppt): 页数改为用户必须显式拍板的 gate(bump 0.34.2)
页数原先只给「常 8–15 页」区间又被打包进 a–h 批量确认,用户一句
笼统「OK」就整批过、模型自取区间中位数(~12)。改(纯文档):
- SKILL.md b 项 → 推一个具体数字 + 标为「独立拍板项」
- SKILL.md 新增「🔒 页数 gate」:没给/没显式认可具体张数必须单独
  追问「就定 N 页?」拿到明确整数才写逐页大纲;唯一例外是用户明说
  「页数你随意」时按推荐数走、仍在预览写出供否掉
- strategist.md §b 同步补 Non-defaultable gate 硬约束 + 例外

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:01:51 +08:00
caoqianming 4b1dce6df9 fix(web): 清空对话时同步清空右侧导航条(bump 0.34.1)
clearMessages 成功分支只 renderMessages([]),漏了重置 outline;
切 task 路径有 state.outline=[]; renderOutlineRail(),清空路径补齐。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 12:13:51 +08:00
caoqianming 5bde2445a0 refactor(ppt): 工作目录收进隐藏 .build/ + 反卡片映射 + svg_preview 兜底/gate(bump 0.34.0)
累积一批(承接 ppt生成2 验证 + 用户"缺图形/卡片阵太多/文件夹过多"反馈):

- 工作目录重构:<project_dir> 根原本把"持久源 / 交付物 / 可再生构建产物"混摊。
  新增 project_utils.build_dir/svg_final_dir/preview_dir/backup_dir 单一事实源,
  把 svg_final→.build/svg_final、preview→.build/preview、backup→.build/backup/latest
  (只留最新,不再堆时间戳)。.build 是 dotfile → /v1/files 自动隐藏 → 用户可见面
  收敛到 源(sources/images/svg_output/notes/两个 spec)+ 交付物(exports)。改动:
  finalize_svg / svg_preview(_collect)/ pptx_discovery('final'→.build/svg_final)/
  pptx_cli(backup 路径 + rmtree 清旧)+ SKILL 工作目录约定/命令。端到端实测:根目录
  只剩 exports/+svg_output/,.build/ 三子目录就位,导出/预览/backup 全正常。

- 反卡片映射(治"大段大段卡片阵"):executor-base §page_rhythm 的 dense 行去掉
  "card grid 是 baseline"的背书;加一段硬映射「先看内容关系再选图形」(系统→
  hub_spoke/分层、流程→flow、层级→树/金字塔、循环→环、互依→mind_map、对比→象限、
  ≥3数据→图表),卡片阵封顶 ~1/3 页、连画两页网格下一关系页必须上示意图,指回 page_charts。

- svg_preview 加 cairosvg 兜底:find_browser 改返回 None 不抛错;无 chromium 时回退
  cairosvg,渲前用 embed_icons 预展开 <use data-icon> 成真 path(避 INVALID_MATRIX);
  修 --screenshot 相对路径静默失败(改绝对路径 + 暴露 chromium stderr)。

- 扁平 gate 计入 circle/polyline:svg_quality_checker 图形图元加 <circle>(node/venn/
  timeline 是真图,修 21-circle roadmap 误判);文字密集 deck ≥60% 页无图形 → ERROR。

架构结论(svg 目录):svg_output(可编辑源)与 svg_final(自包含编译产物)是两态、不能
合并成一个文件,但只暴露一个——现 svg_output 可见、svg_final 进 .build。终态(下一议题)
干掉持久化 svg_final、finalize 内存化 + web 按需预览,牵涉 web 层,本次未做。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:12:57 +08:00
caoqianming 13835a315a feat(ppt): 加商务红品牌预设 + 配图默认主动提议(bump 0.33.5)
用户两个需求:(1) 加一款红色主题;(2) 用户没给图时在需要处主动配图。

- 商务红品牌预设:新增 templates/brands/business-red/design_spec.md(同 anthropic
  格式:#C00000 全色表 + primary-deep/gold/info/positive/alert/surface/border/muted
  派生色 + 宋体标题/黑体正文字体栈(栈尾收预装字体)+ 实心图标偏好 + 政企口吻;无
  logo,注明用文字 wordmark / 可后补)+ brands_index.json 加条目。红色承载在 brand
  而非 visual-style(后者不带色)。同时把商务红设为 strategist §e 默认配色候选:中文
  政企/集团/科研商务汇报默认列入 ≥3 候选(红金 #BF9B5F / 红蓝 #2B4C7E 二选一点缀,
  纯红只压标题/关键数据)。SKILL §默认主题 + 八条对齐 h 行同步指向。

- 配图默认主动提议:strategist §h + SKILL h 行改——用户没给图时不再默认整本 A
  (no images);封面/分节/概念/breathing/氛围页主动把 ai 配图作为候选提给用户(数据/
  列表/流程页仍走图表→§VII,不配装饰图)。仍全程 gated:用户在 h 确认 + imagegen
  自带成本门(提议免费,确认才花钱)。

附:scripts/config.py 的 INDUSTRY_COLORS 未移植(ppt-master 残留引用),strategist
文档表是实际依据,已直接在表里加商务红行。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:57:52 +08:00
caoqianming 4a6182a76a fix(ppt): 修生成 PPT 缺图形(扁平 deck 质检 gate + 策略层视觉下限)(bump 0.33.4)
延续缺图标排查,统计最近 ppt生成 任务 24 页 SVG 的元素构成:<path>=0、
<image>=0,整本是 <text> 摞 <rect>(文字方块),零示意图/图表/配图。根因同
图标——71 个 charts/ 模板没用、content→版式映射形同虚设,且策略层把"Not every
page needs a chart"当跳过口子(spec_lock 实际 page_layouts: free design、无
page_charts 段),输出层又无 gate 拦扁平 deck。两层修(用户选定):

- A' 输出 gate(svg_quality_checker):统计每页图形图元 <path>/<polyline>/
  <polygon>/<image>(rect/line 是版面脚手架不算);≥6 页且文字密集(avg <text>
  ≥10/页)却全 deck 0 图元 → deck 级 error 退非零(逼回执行重写);多数页无图元
  → INFO;<6 页豁免(不误伤极简/teaser)。实测:8 页文字方块→exit 1;任一页带
  path→放行;4 页→豁免。

- B' 策略层视觉下限(strategist.md GATE):把 §633「Template Match」从纯建议升为
  硬下限——内容 deck(≥6 页)每个能结构化的内容页必须分配视觉处理(page_charts
  模板 / page_layouts 结构模板 / §VII 自绘示意图),spec_lock 不许 page_charts +
  page_layouts 同时空着;给出 content→图形映射速查;明示下游 A' 会硬卡。同步改
  SKILL §大纲映射纪律 + §阶段四质检清单 + spec_lock_reference page_charts 段。

诚实边界:prompt+gate 抬下限(逼别交全文字 deck),执行模型设计功力是上限;gate
守"零图形"底线而非"每页必图表",避免误伤极简风。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:37:28 +08:00
caoqianming 5d23ee682b fix(ppt): 修生成 PPT 缺图标(图标管线四层断点)+ 沙箱 SVG 预览渲染(bump 0.33.3)
查真实用户两个「ppt生成」任务的 DB 执行轨迹:24 页 SVG 共 0 个 <use data-icon>。
根因是图标管线四环节无一强制图标落地——策略层(有时)锁图标,执行层不放、
质检层不拦、工具层还断着。四层一起修:

- B 工具断点:references/SKILL 23 处路径仍指向已不存在的 skills/ppt-master/
  (zcbot 是 skills/ppt/)→ 模型 `ls .../icons/<lib>/|grep` 验名得空集 → 放弃图标;
  且 strategist 强制用的 icon_sync.py 在 zcbot 根本没有(GATE 空转,正是某任务连
  图标都没锁的原因)。修:全量改路径(保留上游署名)+ 新建 icon_sync.py(复用
  embed_icons 解析,验名+拷进 project/icons,缺名非零退出)。
- A 质检兜底(硬门):svg_quality_checker 加图标校验——锁了 icons.library + 非空
  inventory 但全 deck 0 图标 → deck 级 error 退非零(逼回执行重写);单页 0 图标 →
  warning(封面/分节/breathing/尾页豁免)。
- C 执行强制:executor-base §4 + SKILL 执行纪律改为"内容页必须放 1–3 个 inventory
  图标"(自由设计无模板可继承图标,只能逐页手写)。
- D 导出兜底(纵深):svg_to_pptx 导出前预扫,锁了 inventory 却 0 图标 → stderr 大声
  [WARN](非致命,防跳过质检直接导出)。核实 native 转换器本就自己从图标库展开
  <use data-icon>,故原设想的"finalize 硬前置"前提不成立,D 改成与 A 同源的导出层警告。

同版附带修 svg_preview.py 在沙箱里渲不出 SVG(报"未找到 Chrome / Edge"):移植自
ppt-master 的 find_browser() 只认 Windows chrome/msedge,不认镜像自带 /usr/bin/chromium
(给 mermaid 装的)→ 视觉验收这关在容器里全程失效。对齐 rendering/pdf.py 发现逻辑
(认 chromium/chromium-browser/google-chrome + $CHROMIUM 覆盖);render() 补容器必需的
--disable-dev-shm-usage + 临时 --user-data-dir;并修一个静默已久的 bug——--screenshot
传相对路径 chromium 写不出文件(原代码吞 stderr,看着和"没浏览器"一样),改传绝对路径
并暴露 chromium stderr。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 13:59:00 +08:00
caoqianming 001f9af96f fix(vision): look_at_image 超时透明重试 + 超时 60→120s(bump 0.33.2)
Seed 2.0 Lite 非流式,长 OCR 首字节可能逼近 60s read timeout → 偶发超时;
且返 [Error] 会触发主模型重发整个 tool call(图 base64 重传、输入 token 再付一次)。

- core/ark_client: 新增 ArkTimeoutError(ArkError) 子类,仅超时/网络抖动抛它;
  HTTP 4xx/5xx 业务错误仍抛普通 ArkError 不重试。子类仍是 ArkError,seedream 等
  现有 except ArkError 不受影响。
- tools/look_at_image: 对 ArkTimeoutError 退避重试(timeout_retries 默认 1 次,
  2^n s),tool 内消化掉不抛给主模型,避免重传图烧 token。
- config/media/doubao.yaml: vision request_timeout_s 60→120,新增 timeout_retries。

smoke_look_at_image 通过(OCR 命中 + 记账正确)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:02:40 +08:00
caoqianming ff276eb9b3 fix(web): SVG 预览强制 image/svg+xml(前端 blob mime + 后端 download)(bump 0.33.1)
SVG 在 <img> 里必须 Content-Type=image/svg+xml 才渲染。前端 preview.js 的
_showImage / mini 图片分支据扩展名强制 blob mime;后端 download 接口对 .svg
显式回 image/svg+xml(部分部署环境 mimetypes 未注册 svg → FileResponse 会猜成
octet-stream → 不显示)。双保险。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 08:28:11 +08:00
caoqianming e3a432dcdd feat(ppt): skill 重构为 SVG-first(移植 ppt-master,弃 python-pptx 版式件)(bump 0.33.0)
旧 python-pptx 固定组合版式件是版面单调/AI 味的架构天花板。改为 SVG-first:
AI 逐页手写 SVG 设计稿 → 纯 Python 转换器逐元素译成原生可编辑 DrawingML。

- 搬引擎:svg_to_pptx/ 转换器 + finalize_svg/svg_finalize + svg_quality_checker + total_md_split + update_spec(依赖闭包干净,只需 python-pptx)
- 搬知识:references(shared-standards/executor-base/strategist/image-layout-*/canvas-formats)+ 5 叙事骨架 + 19 视觉风格
- 搬模板:templates(layouts/decks/brands/charts + 图标库 1.1w+ + spec 骨架)
- 换 GUI:浏览器 Confirm UI → 聊天 BLOCKING 八条确认;live preview → svg_preview.py(无头 Chrome 渲 SVG→PNG);配图走 zcbot imagegen skill
- 默认主题改自由设计(商务红降为候选之一)
- 修 Windows GBK 控制台 UnicodeEncodeError:6 个入口脚本加 sys.stdout.reconfigure(utf-8) shim
- 端到端验证通过:4 页材料领域 deck,质检 0 error → finalize 嵌图标 → 导出原生 pptx → 渲图肉眼验收(swiss-minimal 设计级,非 AI 味)

移植自 github.com/hugohe3/ppt-master (MIT),适配 zcbot task_dir/聊天确认/imagegen 工作流。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 16:38:58 +08:00
caoqianming d4aa5ccbec docs(prompt): system prompt 加通用 context 纪律,堵大块输出滚雪球(bump 0.32.5)
反复 dump 全文 abstract 烧 2.5M token 不是 brief 专属——任何 skill 让弱模型
处理一批长文本都可能踩。在 system prompt 单一事实源 general_v1.md「工作原则」
段、紧挨「少来回」加一条全局铁律:大段 run_python/shell 输出会进对话历史每轮
重发,中间数据落文件、只 read 用得上的片段、别整批重复打印,否则烧 token 还
可能撑爆窗口/拖到超时被掐断。

与既有规则互补:行7(源码落 .py)管代码、行42(少来回)管轮数、本条管"大块
数据输出"。brief skill 0.32.3 的场景化版本保留做细化。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 14:56:44 +08:00
caoqianming b5d75d2a7b feat(scheduler): 定时任务默认单次超时 0→1800s(bump 0.32.4)
超时此前默认 0(不限),配合"超时被吞成 ok"的旧 bug,跑飞的 job 能无限拖。
改默认有限值 1800s(30min):新建 job 不指定 timeout_seconds 时给 1800,
显式 0 仍保留为"不限"逃生口。

- 单一事实源 core/scheduler.DEFAULT_TIMEOUT_SECONDS=1800;create_job 与
  tools/schedule.py(agent 建 job 的工具)默认都引它,JSON schema 描述同步。
- create_job 里 int(timeout_seconds or 0) 保留显式 0=不限语义。
- 存量:线上 job e621c8a6「每日水泥科研简报」timeout 600→1800(直接 SQL,
  未动其它 job)。
- RUN 故障兜底行同步默认值。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 14:52:09 +08:00
caoqianming 700176a0c6 docs(brief): 加 context 纪律,堵反复 dump 全文 abstract 烧 token(bump 0.32.3)
承接定时任务超时复盘:同一 job 的 agent 把 38 篇全文英文 abstract 用
run_python/print 反复灌进上下文(≥3 次),工具输出每轮重发 → 48 次 LLM
调用累计输入 2.5M tokens(输出仅 28K),既慢又贵还顶满 600s 超时。根因
brief skill 虽要求证据落 evidence.md 文件,却没明令"别反复 print 进上下文"。

skills/brief/SKILL.md 三处加指示文:
- 阶段二「context 纪律」:落文件、按需 read、别整批重打
- 阶段三:一次成稿别重复 dump + 论文多时按期刊分批 write
- 反模式加一条:反复 print 全文 abstract 让 context 滚雪球

纯指示文,frontmatter/description 不变 → SKILL_LIST 无需更新。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 14:38:32 +08:00
caoqianming 1646205364 fix(scheduler): 定时任务超时被掐断时记 error 而非误吞成 ok(bump 0.32.2)
实测 bug:isolated 定时 job 跑满 timeout_seconds 被协作式 cancel 后,
_run_agent_bg 对 ok/cancelled 都把 run_status 收回 idle(DB 不可区分),
而 _execute_scheduled_job 收尾只判 run_status=="error",于是超时中断被落成
last_status="ok" —— 掩盖"跑到一半没写 sections/没推送",且不计连续失败、
不触发兜底。复盘 job e621c8a6「每日水泥科研简报」:timeout=600s,task
创建→last_run_at 正好 600.0s,agent 停在"按期刊打印 38 篇摘要"(还在取数)。

修:超时分支置 timed_out 标志,run 收尾后若 timed_out → record_result(
status="error", 半成品不投递 notify)并直接返回。复用既有 error 语义(计入
consecutive_failures、到阈值自动停用、前端 crons 显示「上次失败」)。不动
_run_agent_bg 的 idle-on-cancel 共享语义(HTTP cancel/drain 也依赖)。

配套:PROGRESS/RUN 故障兜底各加一条;诊断脚本 scripts/diag_sched_e621.py
(dump 输出 scripts/_*.txt 入 gitignore)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 14:35:40 +08:00
caoqianming 89062d99b3 docs(design): 新增 §8.8 channel 长会话上下文治理(Phase 1 / Phase 2-3 design)(bump 0.32.1)
记 channel 常驻会话上下文软重置的设计:根因、业界对照(OpenClaw/Hermes/
Claude Code)、「边界而非删除」心智、Phase 1 已落地(context_base_idx 软重置
+ gap 自动分段 + 新话题命令 + 否决的替代方案)、Phase 2(阈值结构化摘要,
对齐 Hermes 阶段③)/ Phase 3(sqlite-vec/FTS5 持久检索)design。
回链修 §8.7、§8.2 两处引用。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 10:47:02 +08:00
caoqianming b27cc9cd5b feat: channel 长会话上下文软重置(gap 自动分段 + 新话题命令)(bump 0.32.0)
微信/企业微信常驻会话不再无限膨胀。tasks 加 context_base_idx,
Session.load 只把 idx>=base 的消息喂模型,base 之前历史全留 DB
(网页端照旧翻完整记录,一条不删)。

- 自动 gap 分段:入站距上次消息超 channel.session_gap_hours(默 6h)
  → 软重置,base=最后一条 user 消息 idx(保留上一轮做续聊锚点)
- 手动新话题:发「新话题/新会话//new/清空上下文」→ 硬重置 base=总数
- clear_messages 全删后归零 base;_db_idx 取真实总数避免 append 撞 idx

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 10:05:07 +08:00
caoqianming e49ff641f9 feat(web): 消息框支持拖拽文件 + 修多次粘贴互相顶掉(bump 0.31.3)
附件 chip 拆出独立托盘 #chat-attach,与状态文字解耦:append+按 rel 去重,
上传进度只写 #chat-hint,不再互相覆盖。整个 #chat-form 加 dragenter/over/
leave/drop(计数防闪烁,只认文件拖拽,微信镜像只读不接收),复用 uploadFiles。
takePastedRels/删除/预览改查托盘;切 task 清残留未发送 chip。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 14:38:58 +08:00
caoqianming d235cb7564 fix(web): 消息目录圆点错位再修(点击竞态 + 触底兜底)(bump 0.31.2)
- 点圆点不变红/点#1跳到#2:scrollIntoView 平滑滚动途中的 scroll 事件
  抢走显式点选 → 加 _outlineJumpLock,跳转期间不重算,700ms 兜底解锁
- 点最后一个/滚到底倒数第二个变红:末项永远顶不到顶线(容器先到底)
  → updateActiveOutlineDot 加触底分支,判最后一个已加载轮为当前

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 14:27:50 +08:00
caoqianming 1352f092a3 feat(web): admin 近7天用量表加合计行(bump 0.31.1)
renderByDay 在 by_day_7d 表底加 tfoot 合计行,汇总 7 天
cost_cny/tokens_in/tokens_out;无数据时不渲染。后端无改动。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 14:21:40 +08:00
caoqianming b4808b0370 feat: per-account 模型访问控制(档位制,复用 plan 列)(bump 0.31.0)
- core/model_access.py(新):档位制访问控制。users.plan 存档位名,
  「档位→模型集合」配在 config/agent.yaml model_tiers;plan 空/未知→default 档,
  role=admin 全开。无需 migration(plan 列 0001 起就有,之前休眠)。
- 两档:default(deepseek+local+seedream+seedance)、pro(+doubao+glm)。
- web/app.py:三个 list 端点按档过滤(用户只看到本档模型);三个 resolve 加
  user_id 门控 —— 显式选档外模型 403;老 task 下次发消息模型已不在档位内→
  持久落回 deepseek_v4.flash;定时任务执行 grandfather 不门控。
- web/admin.py:GET /v1/admin/tiers + PATCH /v1/admin/users/{uid}/plan;
  用户行补 plan 字段。
- web/static/js/admin.js:各用户用量表加「档位」列(内联下拉)+ 档位图例 + apiSend。
- DESIGN.md plan 列语义 / RUN.md model_tiers 配置说明。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 14:11:22 +08:00
caoqianming 8263382fd1 feat: 新增豆包 Seed 2.1(turbo/pro/evolving)+ GLM 5.2 文本模型档案(bump 0.30.0)
- config/models/doubao.yaml(新建):Seed 2.1 turbo/pro + 自进化 evolving,
  走 Ark OpenAI 兼容端点(openai/ 前缀 + ARK_API_KEY,同 local.yaml 范式)
- config/models/glm.yaml:加 pro52(GLM 5.2,zai/glm-5.2,1M 上下文),与 glm.pro(5.1)并存
- thinking_mode 均 false(深度思考走 body 协议,非 reasoning_effort 等级,留 TODO)
- 单价按火山/智谱 2026-06 发布价;evolving 单价未公布暂按 pro 估值兜底
- RUN.md 更新 ARK_API_KEY 说明(文本+图像+视频三处共用)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 13:05:07 +08:00
caoqianming d633949a66 feat(web): 定时任务执行历史列表(右栏 Tab + 分页)(bump 0.29.0)
isolated 模式每次触发新建 task,旧的带 scheduled_job_id 被普通列表过滤、
UI 够不到,原来只有「打开它跑的任务」单按钮指向 last_task_id(最近一次)。

- 后端新增 GET /v1/schedules/{job_id}/tasks?page=&page_size=:按 scheduled_job_id
  归属 + user_id 隔离,created_at desc 分页,复用 _task_dict,标准分页壳返回。
- 前端定时弹框右栏改 Tab(详情 / 执行记录),动作按钮提到顶部 head;
  执行记录是分页列表,点某条打开那次对话。await 后重查 #cr-hist 防切换串显。
- 决策(与用户对齐):历史全部保留不剪枝;布局选 Tab 而非三栏。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:53:56 +08:00
caoqianming a6d00b24ff feat(web): 渠道卡片收拢绑定管理 + 删 rail 按钮 + bump 0.28.1
把渠道绑定/对话/管理全部收进「新建任务」下方的卡片,删掉左下角
rail「微信」按钮(精简页面)。后端 /v1/channel_tasks 返回
{ wechat: { bound, task }, wecom: { bound, task } },前端渲染三种卡片:
未绑定(点绑定)/已绑定无对话(占位)/已绑定有对话(点进+⚙管理)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 11:19:45 +08:00
caoqianming e66fdd0ffc feat: 定时任务对话归属 + push 统一记录到渠道对话(bump 0.28.0)
问题1:定时任务产生的 task(isolated 每次新建)混进普通对话列表。
- tasks 加 scheduled_job_id(nullable FK→scheduled_jobs,migration 0017 + backfill
  persistent/isolated);列表 WHERE scheduled_job_id IS NULL 排除(+working_dir LIKE 兜底)
- ensure_local_task_row 加参数,_execute_scheduled_job 建任务时填
- mode 语义澄清:只管对话是否延续,文件夹两种模式都按 job 复用

问题2:任何 push(定时 deliver_notify / agent wechat_push 工具)推到微信渠道,
web 端渠道对话看不到、没法基于推送追问。
- 记录下沉到 send_to_user(两调用方统一入口):投递成功后对每个成功渠道
  ensure_channel_chat_task(不存在自动建,与入站对话共用)+ 写 assistant 消息
  (摘要+文件下载链接+../rel read 路径)
- Unified 进 agent 上下文(基于推送追问);source_task_id 去重(chat task 内调
  wechat_push 时不重复插摘要);不塞正文,agent 按需 read 产物文件
- _run_channel_conversation 复用 ensure_channel_chat_task,消除建 task 重复逻辑

messages.kind 列(migration 0018):push 记录标 kind="push"(独立列不进 payload),
extract_last_assistant_text 加 WHERE kind IS NULL 跳过,避免 wecom 入站取回复
误取 push 摘要当回复。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-26 10:51:06 +08:00
caoqianming 133e350428 style(web): 渠道卡片改并排省纵向空间 + bump 0.27.4
接 0.27.3:两张渠道镜像对话卡片(微信/企业微信)从竖排改并排
(#channel-cards flex row,各 flex:1);窄栏内图标左、名称 + 条数·时间
堆两行(新增 .cc-body 列容器)。绑定弹框(左下角「微信」rail 按钮)保留
不动 —— 它是绑定/解绑/测试推送唯一入口,与卡片职责互补不重复。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:39:25 +08:00
caoqianming 7dfdf4c73b feat(web): 微信/企业微信对话改成左栏固定卡片 + 企业微信也只读 + bump 0.27.3
把渠道镜像对话(每用户每渠道唯一的常驻只读对话)从「任务列表置顶行 +
绿徽章 + 绿边」改成「新建任务下方两张固定卡片」,与可滚动任务列表分离、
常驻可见;顺带补企业微信对话的 web 端只读锁。

- 后端 /v1/tasks 用 coalesce(channel,'web').notin_(CHANNEL_MIRROR_KINDS)
  排除渠道任务并删掉 case() 强制置顶;新增 GET /v1/channel_tasks 返回
  {wechat, wecom} 摘要(复用 _task_dict,无则 null)
- 前端加 #channel-cards 卡片块(:empty 自动隐藏)+ loadChannelCards/
  syncChannelCardActive;移除列表行已失效的绿徽章逻辑
- applyChannelComposerLock / sendMessage 守卫从硬编码 channel==='wechat'
  改读 CHANNEL_BADGE,微信 + 企业微信都 readonly,提示文案按渠道动态

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:32:12 +08:00
caoqianming 474597cfc6 feat(wecom): 企业微信入站对话支持图片/文件附件(media/get 下载 + 复用渠道无关核心)+ bump 0.27.2
接续 0.27.0 企业微信入站(此前只收文本)。

- wecom.download_media(media_id):走 media/get,成功回二进制流 + Content-Disposition
  文件名,出错回 JSON errcode(40014/42001 重取 token);_filename_from_disposition 解
  filename / filename* 两种形式。
- 回调按 MsgType 分支:image/file 下载后构造 InboundAttachment(kind/file_name/data,与
  个人微信同结构)→ 喂同一 _run_channel_conversation,复用其落盘 + 拼 [用户上传的...] 行
  (图片 agent 自调 look_at_image,文件走 Read)。纯图片/文件消息无文本时据附件行生成 text。
- 语音/视频/位置/链接/事件暂回 success 不处理;附件下载失败静默跳过(打日志)。
- dev.html「企业微信(仅推送)」文案纠正为「推送 + 对话」。

文件:core/wechat/wecom.py、web/app.py、web/static/dev.html。_filename_from_disposition
+ import 自测过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:17:44 +08:00
caoqianming d1aa2b12e2 fix(wecom): wechat_push 支持按渠道定向投递,修「点名企微仍推到个微」+ bump 0.27.1
用户说"推送给我的企业微信",消息却同时进了个人微信。根因:send_to_user
是无差别广播(for ch in active_channels() 逐个推),且 wechat_push 工具
没有指定渠道的参数 —— 部署同开 clawbot+wecom 时一条推送两边都到。

- send_to_user 加 channel=None:None 保持广播(定时任务/不点名沿用,向后
  兼容);指定 wecom/clawbot 时只投那一条,该渠道未开返回单条 no_binding,
  不静默回退到别的渠道。
- WechatPushTool 加可选 channel(enum wecom/clawbot)+ 描述教 agent
  「用户点名某微信就传对应 channel」,execute 做渠道白名单校验。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:15:36 +08:00
caoqianming d16297e556 feat(wecom): 企业微信支持入站对话(回调 webhook + AES 解密 + 复用渠道无关对话核心)+ bump 0.27.0
入站方式与 ClawBot 本质不同:ClawBot 走长轮询(getupdates + 常驻 run_inbound_manager),
企业微信走回调 webhook(企微服务器主动 POST 加密 XML)→ 无需后台轮询 task,只加 HTTP 端点。
agent 跑 >5s 超被动同步窗口 → 回复走 message/send 主动推回(复用 push_wecom),被动回 success 防重试。

- 抽 _run_wechat_message 为模块级 _run_channel_conversation(app, uid, text, atts, channel):
  个人微信(wechat)与企业微信(wecom)同核心、各一张会话 task(企微 binding 也存 chat_task_id)。
- 新增 core/wechat/wecom_crypto.py:WXBizMsgCrypt 等价(SHA1 验签 + AES-256-CBC 解密 + corpid 校验);
  与 crypto.py 的 Fernet 列加密、wecom.py 出站 API 全无关。
- service.py:get_user_by_wecom_userid 回调反查身份 + get/set_wecom_chat_task;
  upsert_wecom_binding 改成合并 config(不再覆盖 chat_task_id)。
- web/app.py:GET/POST /v1/wecom/callback(无 JWT,身份从加密 XML FromUserName 反查)。
- env:WECOM_CALLBACK_TOKEN / WECOM_CALLBACK_AESKEY;暂只收文本,未绑定/空消息静默。
- 文档:PROGRESS/RUN/DESIGN/wecom 同步(DESIGN 把「只做推送不做对话」旧决策标为演进)。

crypto round-trip 自测过;create_app + 路由注册 + 全量 import 通过。端到端待企微后台配回调 URL(需公网 HTTPS)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:07:47 +08:00
caoqianming 5d3cd88e2c fix(wecom): 扫码绑定改用扫码授权登录端点,修复「请在企业微信客户端打开链接」+ bump 0.26.10
oauth_authorize_url 原用 open.weixin.qq.com/connect/oauth2/authorize(网页授权,
只能在企业微信客户端内打开),桌面浏览器 window.open 它 → 企业微信报「请在企业微信
客户端打开链接」,扫不了码。

改用扫码授权登录端点 login.work.weixin.qq.com/wwlogin/sso/login(login_type=CorpApp),
桌面浏览器渲染二维码,企业微信 App 扫码确认后回跳带 code,verify_state / get_user_id
逻辑不变。前置:redirect_uri 域名须配在应用「企业微信授权登录」可信域名(另一项设置)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 11:21:30 +08:00
caoqianming 8ab1805df4 fix(wechat): wechat_push 工具漏挂企业微信 + 提取 active_channels 单一真相源 + bump 0.26.9
根因:wechat_push_available() 只看 clawbot_enabled(),没算企业微信。线上若只开
企微渠道(ClawBot 开关没开)→ 工具压根不注册到 agent → zcbot 照实回"没有直接
发企业微信的工具",用户已绑企微仍推不出。底层 send_to_user 早支持 push_wecom,
纯属注册门槛漏判。

修:提取 service.active_channels() 作渠道清单唯一真相源,门槛(wechat_push_available)
与投递(send_to_user)都引它,加渠道只改一处,根除"两处各列各的"这类偏差。
工具描述把 ~24h 窗口注明为 ClawBot-only(企业微信无窗口约束)。

纯内部重构,对外契约不变;test_secret_host_tools 8/8 过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 11:09:21 +08:00
caoqianming 23c5ab20e0 feat(web): main.py web 支持 --ssl-certfile/--ssl-keyfile(uvicorn 原生 TLS,免 nginx)+ bump 0.26.8
两者同时给即在本端口跑 HTTPS,只给其一报错;都不给=明文(向后兼容)。
适配「只有 8765 对外」场景:zcbot 直接在 8765 上 HTTPS,不用 nginx/不挪端口。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:43:18 +08:00
caoqianming 0cf6e3e61e chore(wecom): 加企业微信可信域名校验文件 WW_verify_THssshZfneJwIG5Y.txt(放 repo 根)+ bump 0.26.7
bot.ctc-zc.com 的可信域名归属校验;配合 /WW_verify_{token}.txt 路由在域名根 serve。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:25:06 +08:00
caoqianming 36964d9920 feat(wecom): 域名根 serve 企业微信可信域名校验文件 WW_verify_*.txt + bump 0.26.6
GET /WW_verify_{token}.txt 从 ZCBOT_WECOM_VERIFY_DIR(默 repo 根)读同名文件返回,
公开端点 + token isalnum 防穿越。解企业微信「网页授权可信域名」归属校验
(zcbot 根路径原是 302 跳 SPA,验证文件 404)。配好可信域名才能配可信IP(修推送 60020)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:22:34 +08:00
caoqianming ed2ff52bf4 fix(wecom): diag_wecom 加 sys.path 仓库根 + 手动 .env 兜底(直跑不再 ModuleNotFoundError)+ bump 0.26.5
诊断已定位线上 60020:应用「企业可信IP」白名单未含服务器出口 IP。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:16:17 +08:00
caoqianming 6f7c904cca fix(wecom): 推送失败透出真实 errcode/errmsg + 加 diag_wecom 诊断脚本 + bump 0.26.4
之前推送失败只回 error:RuntimeError,吞了企业微信的 errcode。改成 reason=str(e)
(含 gettoken/message_send 失败的 errcode+errmsg);scripts/diag_wecom.py 分步查
gettoken vs send 的确切 errcode,服务器上直跑即可定位(可见范围/userid/凭据)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:10:56 +08:00
caoqianming c79fc8ef0c feat(wecom): 企业微信加「手填 userid」绑定(无 HTTPS 域名也能推)+ bump 0.26.3
企业微信推送是出站调用(gettoken/message_send 直连 qyapi),不需要域名;只有
OAuth 扫码拿 userid 那步要 HTTPS 可信域名。用户暂无域名 → 加第二条绑定路:
手填成员 userid(管理后台→通讯录→成员→「账号」)即可推送。

- web/app.py:`PUT /v1/wecom/bind/userid`(写绑定,wecom_configured 才允许)
- 前端 rail「微信」modal 企业微信段加输入框 + 保存(与扫码并列,已绑回填);
  refreshWecom 提示两路并存
- service/推送/send_to_user 不动(userid 来源换了,绑定数据结构一样)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:42:53 +08:00
caoqianming 9381655210 fix(admin): 近 7 天用量按日期倒序(最新一天在最上)+ bump 0.26.2
_usage_section 的 by_day_7d 排序 order_by(day) → order_by(day.desc())。
overview 趋势表 + PDF 报告共用此数据,两处都生效;前端纯按行渲染、不依赖升序,无需改 JS。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:33:54 +08:00
caoqianming f17da6a6e1 feat(auth): 平台登录注入 name/user_name + 监控页/dev 顶栏用户名展示 + bump 0.26.1
平台登录档案注入(0.26.0):
- users 加 name/user_name 两列(migration 0016,纯加 nullable 列,平滑兼容存量行)
- /v1/auth/login body 可选收 name/user_name,ensure_user_row 升级为 upsert
  (COALESCE(EXCLUDED, 旧值):平台传非空就刷新、传 null 不覆盖清空)
- login / login_password / /v1/me 响应回带 name/user_name/role

用户名展示(0.26.1):
- 统一兜底链 name → user_name → email → uid8,监控页与 dev 页共用
- 监控页 admin.js:各用户用量 / 存储 / overview 迷你表用户列走 userCellHTML,
  name+user_name 都有时主显 name + 浅灰 user_name;title 悬浮完整身份。
  admin.py 两表 SELECT 补 User.name/user_name
- dev 顶栏 main.js renderWho:默认显 name,hover 显账号/邮箱/ID;
  state.js 加 userUserName/userEmail + setIdentity/userDisplayName/userDisplayTitle helper,
  登录 / embed / /v1/me 校准共用

注:migration 0016 需在目标环境 `main.py db upgrade head` 应用后生效。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:31:32 +08:00
caoqianming 2b2b4531b3 fix(web): 登录失败提示统一为「账号或密码错误」,不再回显原始状态码 + bump 0.25.2
输错密码时前端弹「404」:后端 login_password 实际返 403,前置网关/旧构建
把状态改写成 404 后,doLogin 直接回显 r.status 导致语义错误。

- auth.js doLogin 失败分支:表单已校验非空,非 2xx 绝大多数是凭据不对,
  统一给「账号或密码错误」(pw)/「user_id 或 PLATFORM_KEY 错误」(key);
  仅 5xx 暴露状态码提示服务端问题。
- app.py:1399 detail 同步改中文,保持契约自洽。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:08:00 +08:00
caoqianming b5cfce72b5 feat(wechat): web 端微信 task 只读镜像,锚定微信为单一交互入口 + bump 0.25.1
web 端打开 channel=wechat 的常驻 task 原能正常发消息,但 web→微信单向
不同步(web 发的走通用端点 → _run_agent_bg,不经过 inbound loop 里
send_text 回微信那段,微信侧零感知);微信→web 则同步(同一条 task)。

不做双向打通:回微信需 context_token、只能从入站拿且 24h 过期,双向同步
会被该窗口拖成"有时同步"(不可预测)+ 两入口并发写歧义。改为 web 端只读
镜像,交互权威单一锚定微信;主动推走 wechat_push / 定时简报。

- chat.js: applyChannelComposerLock(selectTask 后调)对 wechat task 置
  chat-input readOnly + 改 placeholder 引导去微信 + 禁润色;sendMessage
  入口加 channel 守卫(Enter 兜底)
- dev.html: .readonly-locked 置灰样式

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 16:30:22 +08:00
caoqianming 529d7f1046 feat(wechat): 入站收图片/文件,CDN 下载+AES 解密落盘 + bump 0.25.0
get_updates 原只抽 text_item,图片/文件 item 被丢成空 text,inbound
又因空文本 continue → 用户发的图/文件静默丢弃、零落库(DB 实证)。

- ilink: InboundAttachment + 解析 image_item/file_item + download_media
  (CDN /c2c/download GET 密文 → AES-128-ECB 解,发送侧加密的逆);key 双
  编码兜底(base64(raw16)/base64(hex32)),图片按 magic bytes 补扩展名
- inbound: handle_message 契约加附件参,文本/附件都空才跳过,下载失败
  只丢该附件不拖垮整条
- app.py: 附件落盘 <wd>/inbound/,图片拼 [用户上传的参考图](走
  look_at_image)、文件拼 [用户上传的文件](走 Read/Shell),复用 web 端
  粘贴图约定,不碰模型链路

crypto roundtrip + 双编码 key decode 已单测;端到端(GET/POST、真实
image_item 结构)待用户重发一张图实测。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 16:15:52 +08:00