Commit Graph

146 Commits

Author SHA1 Message Date
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