Commit Graph

325 Commits

Author SHA1 Message Date
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 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 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
caoqianming 7b9f0c12ed refactor(wechat): 绑定表合一 channel_bindings(判别列+JSONB),取代 ClawBot/企微两表 + bump 0.24.3
架构复盘:渠道绑定 = "用户在某渠道的一份配置",各渠道字段形态不同 → 判别列 + JSONB 多态
(同本库 usage_events kind+units)最契合,加渠道(飞书/TG…)零 migration。原分表
(0012/0014)对 2 渠道够用但不扛增长、与库内多态范式不一致;单宽表(NULL 列并列)最差。

- models:`ChannelBinding(user_id, channel, status, config JSONB)` PK=(user_id,channel)
  取代 WeChatBotBinding/WeComBinding;clawbot 敏感字段 crypto 加密入 config,wecom 明文 userid。
- migration 0015:建表 + 旧两表数据搬进 config(token 密文串原样搬)+ drop 旧表;
  DDL+DML 同事务失败回滚不丢;含 down 拆回。
- service 存取改读写 config —— **公共 API + BindingSnapshot 形状不变** → inbound/web/tool/
  scheduler 零改动(纯内部数据层重构,对外行为不变)。趁绑定数据极少时合表最省。

import/编译 + _snap 反序列化单测过;DB 往返 + migration 待部署联调。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:55:39 +08:00
caoqianming 2dd1b49725 fix(web): 微信绑定弹框标题样式对齐其他弹框 + bump 0.24.2
#wechat-modal h3 只设了 flex,漏了 margin/padding/font-size/border-bottom,
吃浏览器默认 h3 样式导致标题又大又飘、无分隔线。补齐标题样式 +
h3 svg opacity + .sk-x 关闭按钮样式,与 crons/memory 弹框一致。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:42:57 +08:00
caoqianming 6008e1b8a0 fix(wechat,email): host-side 文件工具翻译容器路径,修复附件发不出 + bump 0.24.1
docker 模式下 fs 工具在容器跑,文件落宿主 users/<uid>/<wd>/,但 send_email /
wechat_push 是宿主进程工具:base_dir=cwd 且不识别容器↔宿主路径映射,agent 给的
相对路径拼到 cwd、容器绝对路径 /workspace/... 宿主上瞎解析,relative_to(user_root)
必越界 → 附件永远发不出(probe 直调 send_file 绕过解析,故"测试可发")。

- tools/base.py: 共享 _resolve_user_file(/workspace 前缀翻回 user_root + 相对拼
  base_dir + 越界校验)+ FileOutOfBounds
- agent_builder: 两个 host 工具 base_dir=working_dir_path(宿主 task 目录)而非 cwd
- send_email / wechat_bot: 改用 helper
- tests: 加 3 例回归(翻译+越界、send_email 容器路径、wechat_push 相对路径)
- scripts/diag_wechat_push.py: 诊断脚本

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:02:48 +08:00
caoqianming 193b545b75 feat(wecom): 企业微信渠道 B 纯推送 + OAuth 扫码绑 userid + bump 0.24.0
企业微信只做推送、不做对话(省回调 + AES + 5s ACK):无条件主动推(不挑活跃度、
无 24h 窗口),补 ClawBot 短板,定时简报必达首选。touser 经 OAuth 网页授权扫码拿成员 userid。

- core/wechat/wecom.py:access_token 2h 缓存(线程安全 + 失效重取)、OAuth getuserinfo、
  message/send text/file、media/upload、state HMAC 签名
- WeComBinding 模型 + migration 0014(0013 被 task_channel 占);service 加 wecom CRUD
  + push_wecom + send_to_user 接 wecom 一路(scheduler deliver_notify 经它自动带上)
- web/app.py 5 端点(/v1/wecom/oauth/url、callback 公开-身份从 state 验、bind GET/DELETE、test)
- 前端 rail「微信」modal 加企业微信段(wechat.js + dev.html)

激活(管理员):建自建应用 → WECOM_CORPID/AGENTID/SECRET + 配「网页授权可信域名」;
db upgrade head(带 0014)。redirect 主机取 ZCBOT_PUBLIC_BASE_URL 或请求 base。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:44:23 +08:00
caoqianming 320f428dd3 feat(email): 配置 foxmail SMTP 发信 + 发件人显示名品牌化 + bump 0.23.2
- .env 填入 smtp.qq.com:25/STARTTLS/授权码,send_email tool 与定时任务
  notify 兜底投递生效(.env 不入库)
- send_email.py 发件人显示名由硬编码 zcbot 改读 SMTP_FROM_NAME,默认
  「总院科研辅助智能体」,对外不暴露内部代号
- RUN.md 补 SMTP_FROM_NAME 说明;PROGRESS 记一条

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:31:17 +08:00
caoqianming 340786a42f feat(web): 微信任务徽章改品牌绿 + 微信 logo + 整行绿边 + bump 0.23.1
上版徽章复用 .badge.active(蓝灰)与旁边「进行中」状态徽章撞色、不显眼。
新增 .badge.wx(微信绿 #07C160 + 白字 + 内嵌微信 logo SVG)与 .task-row.wx
(绿色左边框 + 极淡绿底 + hover 加深),让置顶的微信任务从普通任务里跳出来。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:23:34 +08:00
caoqianming 85336ccb7e feat(wechat): 微信对话 task 渠道标记 + 列表置顶(channel 字段)+ bump 0.23.0
tasks 加 channel 列(web/wechat,migration 0013,server_default='web' 回填存量,
并把 description='(微信 ClawBot 对话)' 的存量 task backfill 成 wechat)。微信常驻
task 后端强制置顶(列表查询前置 case pin 表达式,跨分页稳定),前端任务名前打绿色
「微信」徽章一眼可辨。channel 仅 INSERT 写定,后续 upsert/save 不传不覆盖。

- core/storage/models.py: Task.channel 列
- db/migrations/.../0013_task_channel.py: 加列 + backfill
- core/storage/utils.py: ensure_local_task_row 加 channel 参数
- web/app.py: 微信建 task 传 channel=wechat;_task_dict 透出;列表 pin 置顶
- web/static/js/chat.js: channel===wechat 打徽章

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:12:16 +08:00
caoqianming 95857ba687 feat(wechat): 绑定 UI 并入主 SPA(左栏 rail「微信」按钮 + 扫码 modal)+ bump 0.22.2
上版绑定页是独立 /static/wechat_bind.html、主界面没入口、用户找不到。集成:
rail 加「微信」按钮(hd-wechat)→ 扫码绑定 modal(wechat-modal),复用 api()
调已有 5 端点(起码/轮询/查/解绑/自检),仿 crons.js 范式;二维码过期自动换码。
独立页 wechat_bind.html 保留作嵌入/兜底入口。

文件:web/static/js/wechat.js(新)、dev.html(rail 按钮 + modal + CSS)、
main.js(import 触发顶层绑定 + Esc 关闭);RUN/PROGRESS 同步去掉"未并入 SPA"。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 09:47:34 +08:00
caoqianming c569438d5f fix(web): 顶栏 token 计量栏回复后不刷新 + bump 0.22.1
提问→助手答完后,对话顶栏「总 token·缓存命中·花费」停在发问前旧值,
要切走再切回才更新。根因:计量栏读 state.taskMeta,而它只在 selectTask
里重拉;SSE 收尾的 fetchSse finally 只刷列表+消息,从未重拉 meta。
修:finally 里当收尾的是当前可见 task 时补一次 GET /v1/tasks/{id} →
重置 state.taskMeta → renderChatMeta(),失败 try/catch 吞掉不打断收尾。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 09:08:19 +08:00
caoqianming 528b974d9f feat(wechat): ClawBot 个人微信接入第一期(后端 + 绑定页)+ 双渠道设计 §8.7 + bump 0.22.0
把 zcbot 送进用户个人微信:对话 + 主动推送(简报/结果)。选官方微信 ClawBot
(iLink Bot API,零封号)先行;企业微信作渠道 B 留接口。协议全程真机实测
(scripts/probe_clawbot*.py,本人微信号在灰度内)。

核心(后端 import/编译自测过):
- core/wechat/{ilink 协议客户端, crypto 凭据加密, service 绑定CRUD+24h窗口推送
  +send_to_user 渠道抽象, inbound 长轮询管理器+回复提取}
- WeChatBotBinding 模型 + migration 0012;tools/wechat_bot.py WechatPushTool
  + agent_builder 注册(有开关才挂)
- scheduler.deliver_notify 加 wechat 通道(未送达退邮件);web/app.py lifespan
  起入站管理器 + _run_wechat_message 回调 + 5 端点;web/static/wechat_bind.html 绑定页

实测要点:每条 sendmessage 必带唯一 client_id(漏则同 token 后续被丢);context_token
24h 可复用→主动推(需用户先开口);文件 getuploadurl→AES-128-ECB(PKCS7)→CDN
(URL 带 filekey)→file_item,docx/pdf 原生直推。

激活:db upgrade head(带 0012)+ env ZCBOT_WECHAT_BOT_ENABLED=1
+ ZCBOT_WECHAT_SECRET_KEY=<串>。待办:部署端到端联调、SPA 集成绑定 UI、企业微信渠道 B。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 08:59:56 +08:00
caoqianming 336db63a01 feat(rendering): 平台渲染层 rendering/ 统一三 skill docx + chromium md→pdf + bump 0.21.0
渲染从「各 skill 自带 render_docx.py」抽成平台能力:新建顶层 rendering/ 包,
bind-mount 进 /sandbox/rendering,各 skill 调 render.py 不再 bundle 渲染脚本
(符合 Skills 自包含/可 fork 标准,跨 skill import 会破坏 fork 故不走 skills/_shared)。

- common.py 叶子原语单一事实源(化学式白名单 CHEM_RE 原先三份逐字重复→收敛一处)
- docx_manuscript.py paper/proposal 配置化双 profile;docx_brief.py brief 富渲染复用 common
- pdf.py md→HTML→沙盒 chromium --print-to-pdf(不用 weasyprint:要 pango/cairo 原生库且不在镜像)
- render.py 统一 CLI --profile {brief,paper,proposal} --format {docx,pdf}

零回归:三 profile 重构前后 docx 解包 diff word/document.xml 字节完全一致。
守护测试 tests/test_rendering.py 5 项全过。chromium 冒烟 deploy/sandbox/probe_chromium_pdf.sh。

删 3 份 render_docx.py + 短命 skills/_shared/render_pdf.py;改 5 个 SKILL.md 调用到
render.py + 补反模式"渲染一律调 render.py、禁止手搓 weasyprint/pip 装包";brief 另删
research 索引滞后描述。requirements 加 markdown,pool.py 加 rendering 挂载。

部署须一次原子激活:/sandbox/rendering 挂载靠 pool.py(restart 重建容器生效)+
markdown 进镜像靠 requirements 触发整体重建——update.sh build→restart 顺序覆盖,
旧 render_docx 路径已删,勿只推代码不重建。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 13:07:19 +08:00
caoqianming d412aa6b24 fix(web): 消息目录点第一个圆点误高亮第二个 + bump 0.20.4
跳转锚点(block:center)与活跃判定锚点(顶线 80px)不一致:第一轮
上方无内容无法居中、被钉到顶端,短轮时下一轮卡片顶也落进 80px 带内
→ 越界高亮第二个圆点。改跳转为 block:start(同锚点)+ .msg
scroll-margin-top:16px,活跃容差 80→24 对齐。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 08:56:02 +08:00
caoqianming 247a887cd6 fix(web): 定时弹窗 z-index 遮挡 + 登录 focus 引用错 id + bump 0.20.3
- #crons-modal 漏了 z-index,退回基础 .modal(无 z-index)被 z-index:5
  的侧栏/面板盖住("弹了但被遮挡");补 z-index:112 与 #skills-modal/
  #memory-modal 对齐。排查用 node+DOM mock 跑通整条前端模块图确认 hd-crons
  绑定确实执行,定位到纯 CSS 层叠问题。
- main.js:106 $("li-token").focus() 引用了不存在的输入框(实际 li-email),
  未登录 boot 末尾会抛 TypeError;改为 li-email。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 10:13:13 +08:00
caoqianming c55d0d11f0 fix(context): 发送期补齐悬空 tool_calls,断中断 run 留下的协议崩 + bump 0.20.2
run 在写入 assistant.tool_calls 之后、tool 结果写库之前被中断(上游流式断连 /
用户取消 / 崩溃),历史里留下一条 tool_calls 后面没有对应 tool 结果的消息;用户
随后继续发言,下一轮原样发给 DeepSeek/OpenAI 即被拒(must be followed by tool
messages),任务卡死在 run_status=error(监控页排查 task 5c5d6d25 实测)。

prepare_messages_with_stats 入口(早返回分支之前)新增 _repair_dangling_tool_calls:
对每条 assistant.tool_calls 扫描紧随其后的 tool 结果,为缺失的 tool_call_id 补占位
tool 消息。纯发送期不改库 → 覆盖所有中断路径 + 存量坏数据自愈,stats 计 repaired_tool_calls。
区别于 06-06/06-12 的 arguments 损坏修复(那治参数投毒,此为结构性悬空)。

新增 4 个单测,context 套件 14 项全过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 15:08:20 +08:00
caoqianming 2b9a7febde feat(skill): brief 重定位为重要文献速览(论文列表+总结,只描述不给建议)+ 精简三文件 + bump 0.20.0
- 重定位:重要论文列表(各大期刊,Elsevier 数据库优先,每篇带简介/摘要概述)+ 内容总结;去掉建议/启示/热点聚类/判断
- 三路取数:research + documents 取文献为主力,web search 取政策·标准·产业动向单列(不混进论文总结)
- 精简 8→3 文件:SKILL.md 自包含(spec 字段/骨架/检索法/核验铁律/渲染说明)+ references/journals.md(各建材子领域主流期刊清单,Elsevier 标注 + 精确 publication_name + 0 命中降级)+ scripts/render_docx.py;删 templates/spec.md、templates/brief_outline.md、references/search_strategy.md、references/citation_verify.md、scripts/quality_check.py
- render_docx.py:论文列表段(标题含"论文列表/文献列表/参考文献")H3 期刊子标题下的 [n] 条目仍作锚点(只在 H1/H2 重判段类型);条目内 DOI 子串(末尾 "DOI: 10.xxx")也做 https://doi.org 超链接;smoke test 验证锚点/回链/外链/化学式下标全在

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 15:24:31 +08:00
caoqianming 108351864e feat(scheduler): 定时任务 v1 — 对话建/管 + 守护循环执行 + 只读前端 (DESIGN §8.5)
到点把一句自然语言 prompt 喂进 agent 主管线,可跑 skill 出简报 / 发邮件 / 打招呼等。
job 本体 = cron+时区 + prompt + 会话模式;"发邮件"不是字段,是 agent 据 prompt 调
send_email 的动作 → 加任何能力不改 schema。

后端:
- scheduled_jobs 表 + migration 0011(独立加表,公测兼容)
- core/scheduler.py:croniter 算 next_run(时区+vixie OR 语义)、claim+advance 防重复触发、
  失败阈值自停、notify 兜底投递、CRUD 服务层(工具与 REST 共用不漂移)
- 守护循环 _scheduler_loop(lifespan,仿 _disk_scanner 的 plain-asyncio,不引 APScheduler/Celery;
  复用 _run_agent_bg,抢 run 锁、超时协作 cancel、并发上限)
- tools/send_email.py(host-side,SMTP_* 齐才挂)
- /v1/schedules GET/PATCH/DELETE 三端点

对话端 = 完整 CRUD:schedule_create/list/update/cancel 四工具(定时 run 内不挂防自我繁殖)。

前端 = 只读 + 停用/删除:左栏 rail「定时」入口 + crons.js 只读 master-detail modal
(复用 skills modal 范式);建/改故意只走对话,规避 cron 构建器 UX。

会话模式:isolated(默认,每次新建临时 task 省 token)/ persistent(绑 bound_task_id 续上下文)。
env:SMTP_* / ZCBOT_DISABLE_SCHEDULER / ZCBOT_SCHEDULER_TICK_SECONDS / ZCBOT_SCHEDULER_CONCURRENCY。

已验:migration 上库、CRUD 端到端、3 REST + 4 工具注册、crons.js 语法。
待验:起 web 进程跑一轮真实触发 + 邮件 smoke。bump 0.18.0 → 0.19.0。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 13:42:31 +08:00
caoqianming 4f61b5fc56 feat(skill): brief 科研方向简报(三路检索 documents/research/web)+ 全局化学式下标修复 + bump 0.18.0
新增 brief skill:给定研究方向 + 时间窗,用三路真实数据(documents 内部库取全文 /
research 取近期 DOI 元数据 / web 取政策·会议·标准动向)产出文献计量趋势型简报。
六阶段:定题对齐 spec → 三路检索取数(中→英术语 + 跨源去重)→ 趋势分析(3-7 热点簇)
→ 逐段起草 → 引文核验(复用 paper 三层协议)→ 渲染验收。深度三档 flash/standard/deep。

自带 render_docx.py(简报专属版式):商务红主题 + 正文 [n]/[Wn] 引文上标并锚到文末
+ DOI/URL 可点击超链接 + TL;DR 卡片 + 标题信息带 + 页脚页码。

顺带修 zcbot 全局「角标」问题:水泥化学式在 docx 里平排数字(CO2/C3S/SO3...)是
paper/proposal 渲染器的老毛病。抽一份化学式下标白名单(长在前 + \b 防误伤
LC3/C595/Ca2+/2026,实测命中精确零误伤)统一补进 paper、proposal、brief 三个
render_docx.py 的 add_inline plain 分支(按"自包含 skill 脚本不跨 skill 引"的既有约定
各自复制同一份)。core/export_docx.py 是对话原文转录、非排版文档,不动。

文件:skills/brief/{SKILL.md, templates/{spec,brief_outline}.md,
references/{search_strategy,citation_verify}.md, scripts/{quality_check,render_docx}.py};
SKILL_LIST.md(16→17)+ PROGRESS.md 同步。bump 0.17.0 → 0.18.0。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 11:28:03 +08:00
caoqianming e87daa7c89 feat(tasks): 任务软删除(留对话轨迹做语料 + 可恢复)+ bump 0.17.0
DELETE /v1/tasks/{id} 从硬删(DELETE + CASCADE + rmdir)改为软删(置 deleted_at),
messages/usage_events 及工作目录文件全部保留,留作训练语料且可恢复;新增
POST /v1/tasks/{id}/restore;list_tasks/list_folders 计数过滤 deleted_at IS NULL;
delete_file 顶层目录 409 引用检查排除软删 task(避免"任务删了文件夹却删不掉")。
0010 migration 加 tasks.deleted_at(additive 可空,存量行自动视为未删)。
推翻 DESIGN 原 hard-cascade 决策;文件归档方案(restic 备份 + DB 事件日志)写入 DESIGN 待办。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:37:47 +08:00
caoqianming 6d6e9f79b5 docs: 用户操作说明书(详+精简)+ 文献库口径 21W→100W + bump 0.16.2
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:34:08 +08:00
caoqianming 660bec0f2f feat(skill): paper 学术论文写作(中英×三类型 + 引文三角核验)+ bump 0.16.1
新增自包含 skills/paper/:把实验数据/前期报告整理成可投稿论文 .docx。
流程骨架取 paper-writer-skill 的"先定图表"纪律 + ARS 的三角引文核验/反谄媚审稿,
底座全换成 zcbot 自有(documents/research 查文献与核验、plot_pub 出图、复用 proposal 渲染心智)。

- 中英双语 × 三类型(original/review/letter)子 md 分流,一篇只挂一套
  cite_gbt7714/cite_elsevier + redlines_zh/redlines_en
- 六阶段:摄取 → 八条对齐 spec → 文献矩阵 → 先定图表 →
  逐章一段一卡(Methods→Results→Intro→Discussion→Abstract→Title) →
  引文三角核验(存在性/三角/支撑度,台账 CITATIONS.md) → 验收渲染 + 投稿件
- 自带脚本:render_docx(--lang 图题切换 + --toc 默认关)/ quality_check
  (引文交叉核对 orphan/uncited/编号连续 + 结构/占位符/过度宣称)/ word_count / render_diagrams
- smoke 验证:happy path 全 OK,orphan/uncited/缺号负例正确触发,render 出 docx
- SKILL_LIST 15→16,PROGRESS 加一条

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 09:55:10 +08:00
caoqianming 0d69ae86e2 feat(media): look_at_image 图像理解(豆包 Seed 2.0 Lite vision)+ bump 0.16.0
DESIGN §8.1 C 路落地 —— 主模型 DeepSeek V4 纯文本无视觉,挂 look_at_image
工具按需读图(OCR / 描述 / 读图表),模型自决何时调。

- 选型:设计时的 Seed 1.6 vision 已过时,改用 Doubao Seed 2.0 Lite
  (doubao-seed-2-0-lite-260428,全模态 SOTA 细粒度感知)。token 计费
  输入 ¥0.6 / 输出 ¥3.6 /Mtok,一次读图 < ¥0.01
- 后端:tools/look_at_image.py(/chat/completions base64 单图+问题→文本解读);
  doubao.yaml 加 vision 段;usage.py 加 record_vision_usage(kind=vision,
  按 token,无需 migration——kind 自由文本);agent_builder 注册 + media prompt 段
- 图片路径解析与 i2i 共用 tools/image_ref.py
- 验证:scripts/smoke_look_at_image.py 真机 OCR 通过(实测 ¥0.0011)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:20:05 +08:00
caoqianming be813629b2 feat(media): seedream i2i 改图 + 前端 paste 路径注入 + bump 0.15.0
- seedream 加 reference_images(v1 单图):传已有图路径做像素级改图,
  不传=文生图行为 100% 不变(向后兼容)。路径解析抽到 tools/image_ref.py
  (三形态路径 + 强制落 user_root 内防越界 + 扩展名/大小校验)
- 前端 chat.js:sendMessage 把粘贴图路径作 [用户上传的参考图] 行注入正文,
  修了粘贴图路径到不了模型的既有缺口("上传外部图→改图"才能定位文件)
- 引导:imagegen SKILL 删旧"不接图像输入"+ 加改图(i2i)专段,纠正
  "该 i2i 却重新文生图丢原构图";agent_builder 媒体 block + SKILL_LIST 同步

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:18:18 +08:00
caoqianming 1cfeb000a6 feat(web): ask_user 工具 — 回复里渲染可点击「方案确认」选项卡 + bump 0.14.0
agent 在真正的分叉点(2-4 个互斥方向且选择会实质改变后续动作)调用 ask_user,
前端渲染可点击选项卡:点一个即作为回复继续,或不点直接用文字讨论。

收窄定位防 agent 变爱问(高轮数烧 token 已知痛点),系统提示严格约束使用条件。
与轮次模型同构、无阻塞:ask_user 是虚拟工具(同 task_progress 范式),loop 检测到
本步调用它就提前结束本轮、不回灌 LLM;点选项=发该选项 label 作新用户消息,零额外
LLM 往返。选项落在 tool_calls.arguments 里,刷新页面按钮还在;已答的卡自动置灰。

- tools/ask_user.py 新增 AskUserTool;core/agent_builder.py 注册
- core/loop.py 加 ask_user 提前终止分支
- prompts/system/general_v1.md 加「方案确认约定」段
- web/static/js/chat.js buildAskUserCard + SSE/历史重渲特判 + sendMessage(overrideText) + 点击委托
- web/static/dev.html 加 .ask-user/.ask-option 样式

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 11:23:59 +08:00
caoqianming 91e200ef4f feat(web): 消息目录-右侧悬浮圆点轨道导航(ChatGPT 式)+ 双向分页 + bump 0.13.0
右缘悬浮圆点轨道:每点=一轮"我"的提问,hover 展开标题,点击滚动定位+高亮,
滚动自动高亮当前轮;覆盖全部轮次(非仅当前窗口)。

后端:新增 GET /v1/tasks/{id}/outline(只取 role=user 的 idx+首行片段,不回传整
payload);list_messages 加 after_idx 参数 + has_more_after 响应,支持向下翻页
(从目录跳旧消息后补回下方未加载的新消息)。纯增量,旧前端不受影响。

前端:消息卡补 data-idx 锚点;jumpToMessage 已加载则 scrollIntoView、未加载用
before_idx 拉居中窗口再定位;refreshOutline 并入 selectTask 并发拉 + run 收尾刷新;
dev.html 加 #msg-outline-rail(容器 pointer-events:none 不挡滚动条、仅圆点可点),
手机端隐藏,embed 页 null-safe。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:52:39 +08:00
caoqianming 82feecef06 perf(web): 切 task 并发拉 meta+messages + 默认窗口 60→30 + bump 0.12.16
selectTask 里 meta 与 messages 原本串行 await,改 Promise.all 并发省一个 RTT;
MSG_PAGE 60→30 降首屏传输与 markdown/highlight 同步渲染量。切 task 慢非索引问题
((task_id, idx) 唯一索引已覆盖主查询),故只优化前端串行与窗口大小。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:21:26 +08:00
caoqianming ec27fcae3e feat(skill): plot_pub 吸收 nature-figure 投稿级复合图设计纪律 + bump 0.12.15
调研 nature-figure skill(MIT)后只迁移可复用设计 IP,不整包移植
(避免与 plot_pub 重叠、R/生物内容不适配、多文件结构破坏单 SKILL.md 约定)。

- style.py: 补 svg.fonttype='none'(原只设 PDF Type 42,漏了 SVG 可编辑)
  + SEMANTIC_COLORS 语义色表 + clean_spines() + ablation_alphas()
- SKILL.md: 新增「投稿级多 panel 复合图」段(五点 figure contract /
  语义配色 / 信息架构 + spine 纪律 / 导出纪律),示例改建材领域
- SKILL_LIST.md / PROGRESS.md 同步;纯 Python 零新依赖,保留中文字体

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:54:32 +08:00
caoqianming 5caa3db62e perf(web): 消息尾部窗口分页 + 向上滚动加载更早 + 切 task loading 占位 + bump 0.12.14
切 task 卡顿:/v1/tasks/{id}/messages 无分页全量拉 + 前端全量渲 DOM,消息多时两段成本线性涨。

- 后端 list_messages 加可选 limit/before_idx:不传=旧行为(升序全量,仅多返 has_more,向后兼容);传 limit 取尾部最近 N 条,before_idx 取更早一批,响应恒含 has_more
- 前端 selectTask 进来立即换「加载中…」占位(治感知);loadMessages 默认 limit=60
- 新增 loadEarlierMessages + _msgScrollObserver(复用 task list sentinel 范式):顶部 sentinel 进视口自动 prepend 更早一批后整窗重渲,锚回滚动位不跳视口
- state 加 loadedMessages/msgHasMore/msgLoadingEarlier;dev.html 加 .msg-top-sentinel 样式

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:30:42 +08:00
caoqianming 888824ba85 feat(web): 图片预览放大后左键拖动平移 + 光标语义改正 + bump 0.12.13
- 光标:100% 改普通箭头(原 zoom-in 放大镜误导,左键不缩放);放大后 grab、
  拖动中 grabbing。
- 左键拖动平移:放大态 mousedown 记起点+滚动位,mousemove 改 body
  scrollLeft/Top 平移;img.draggable=false 关原生拖拽。document move/up
  监听存 z._onMove/_onUp,_clearZoom 时移除避免泄漏。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:00:15 +08:00
caoqianming eb1027b040 fix(web): 图片缩放 load 即量基准尺寸 + 双击复位提示 + bump 0.12.12
- _captureBase:图片加载完即量贴合尺寸做基准,避免首次缩放时还没渲染量到
  0px 把图片塌成 0;量不到则本次跳过不破坏。
- 双击复位徽标显式提示「已复位 · 100%」(停留 1.4s)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:53:00 +08:00
caoqianming 12171a4bdf fix(web): 图片预览缩放改显式 px,修 CSS zoom 被 flex max 夹回放不大 + bump 0.12.11
CSS zoom 对带 max-width/height:100% 的 flex item 无效(放大后被百分比 max
重新约束回去,视觉不变)。改为:以 scale=1 的贴合显示尺寸为基准缓存,缩放时
max:none + 显式 width/height = base × scale 像素,真正撑大布局让 body 出
滚动条;复位时清空还原自适应。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:42:37 +08:00
caoqianming 31f46baaf6 fix(web): 文件预览修滚动穿透 + 图片 Ctrl+滚轮缩放 + bump 0.12.10
- 滚动不穿透:主/小预览 .body 加 overscroll-behavior: contain,再挂一次性
  非 passive wheel 监听,容器不可滚或到边界时 preventDefault 断冒泡,背景
  对话列表不再被带滚。
- 图片缩放(仅图片):Ctrl+滚轮 ×1.1 步进(夹 0.1-8x),用 CSS zoom 而非
  transform(zoom 改布局盒,放大后 body 才出滚动条能看溢出);右下角 xx%
  比例徽标(挂 .card,滚动不跟走,1s 淡出);双击复位 100%;.body.center
  改 safe center 防 flex 居中裁掉溢出顶/左。
- wheel 监听只 init 挂一次到复用 body,缩放目标走 _zoomState WeakMap,免
  每次预览重复 addEventListener 泄漏。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:31:12 +08:00
caoqianming 314a05e111 fix(sandbox): 容器装 fonts-noto-color-emoji 修 mermaid 图满图豆腐块 + bump 0.12.9
模型生成的 mermaid 节点标签常前缀 emoji 图标(🌐🔥🛡 等),容器只装了
CJK 字体缺 emoji 字体,chromium 渲染时每个 emoji 都成空心方框 □。加
fonts-noto-color-emoji(+~10MB)并 fc-cache 刷索引即可正常出图标。
纯增量容器改动,需重建镜像 + 重启 per-user 容器生效。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:21:39 +08:00
caoqianming 977923b6cf feat(web): 左栏任务筛选区默认折叠(偏好仍持久化)+ bump 0.12.8
进页面只见「筛选 ▸」一行,点开才展开;用户显式展开过(localStorage 存 "0")才默认展开,否则一律折叠。已选筛选条件折叠后仍生效。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 08:56:46 +08:00
caoqianming 211b008821 fix(sandbox): 容器渲 mermaid 开箱即用(mmdc wrapper) + system 按 backend 注入运行环境段 + bump 0.12.7
接 --shm-size(0.12.5)。修两层让容器渲 mermaid 不再反复栽:

执行层 ── mmdc wrapper:
- Dockerfile 给 /usr/local/bin/mmdc 套 wrapper,没显式 -p 时自动注入
  -p /sandbox/puppeteer-config.json(含 --no-sandbox/--disable-dev-shm-usage),
  裸调 `mmdc -i x -o y` 一次成;render_diagrams.py 等走 which mmdc 的脚本透明受益。
- 删掉没人读的 MERMAID_PUPPETEER_CONFIG env(mmdc 只认 -p)。

引导层 ── system prompt 按 backend 注入「运行环境」段:
- general_v1.md 删写死的 "Windows+cmd" 平台段(线上是 docker=Ubuntu 容器+bash,
  误报导致模型在 Linux 里打 cmd 构文)。
- agent_builder 注入 _CONTAINER_ENV_BLOCK(docker)/_HOST_ENV_BLOCK(host):写明
  Linux/bash、渲图走本地 mmdc 别调境外在线服务(mermaid.ink 被墙,容器虽有外网但
  渲图不该依赖出站)、mmdc/chromium/中文字体已装。
- 撤回上一轮加到 imagegen 的渲图引导(环境事实收归 system)。

顺带:RUN.md 修正把 sandbox 网络写成 --internal 无 outbound 的过时注释(实际 bridge+NAT
有外网,见 network.py)。

部署:Dockerfile 改动需 rebuild 镜像;prompt 改动重启 web 生效。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 08:30:43 +08:00
caoqianming 32bf6ae917 fix(sandbox): docker run 加 --shm-size 修 mmdc 渲 mermaid 挂超时 + bump 0.12.5
容器 /dev/shm 默 docker 64MB,chromium(mmdc/puppeteer)起不来一直挂到 timeout。
实测一个"生图测试"对话:模型裸调 mmdc,自造 puppeteer config 漏 --disable-dev-shm-usage,
连试 6 次全超时烧约 120k token。从根上给 docker run 加 --shm-size(默 512m,
env ZCBOT_SANDBOX_SHM_SIZE / yaml sandbox.shm_size 可配),任何 chromium 路径都不再挂。

- core/sandbox/pool.py: --shm-size 旋钮(优先级同 memory/cpus)
- config/agent.yaml / RUN.md: 新增 shm_size 配置 + env + 故障兜底一行
- deploy/sandbox/probe_mermaid.sh: 实测脚本(区分 chromium 缺包 vs 纯 shm 超时)
- scripts/diag_dump_task.py: 按 email+任务名 dump 对话的诊断脚本
- 已 running 旧容器需重启 web + idle 回收后新起才生效;镜像无需 rebuild

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 10:40:45 +08:00
caoqianming d30435198c feat(web): 模型选择瘦身 — 对话模型常驻 + 生图/生视频收进 ⚙ 弹层 + bump 0.12.4
- meta 行原三个带标签下拉(模型/生图/生视频)占满整行 → 高频对话模型常驻可见可切,
  低频生图/生视频收进一个「⚙ 媒体」fixed 弹层(点开才渲染 select)
- 行为不变:媒体模型选中值仍只进 state.*,随下条消息 image_model/video_model 发;
  send 读 state 不读 DOM,迁移安全;两个 select 都没配时连 ⚙ 都不画

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 13:07:51 +08:00
caoqianming 18f702886f feat(web): 左栏筛选区可折叠(默认展开,偏好持久化)+ bump 0.12.3
- 搜索/状态/目录/排序四控件归到两行 .task-filter-row,标题行加「筛选 ▾」toggle
- 默认展开,折叠只藏 UI(已选条件仍生效),偏好存 localStorage(同 pane 折叠范式)
- 折叠后左栏顶部 4 行→2 行,任务列表可视区更高
- 状态下拉并入筛选区(flex),搜索框 flex:2 更宽;目录/排序合一行用 title 提示

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 12:58:33 +08:00
caoqianming ae9790601a refactor(web): 中栏操作收进 ⋯ 菜单 + 消息阅读限宽 + 色彩收敛 + bump 0.12.2
- 中栏顶栏 5 个平铺按钮(导出/清空/完成/废弃/删除)→「完成」+「⋯」菜单,
  菜单复用 taskMenuItems(过滤 complete),与任务行同一范式;破坏性操作不再平铺易误点。
  顺带让菜单「清空」按 run_status 也禁用(修运行中 409-after-confirm 小坑)
- 消息限宽:.msg max-width 92% → min(92%,48rem),user 气泡 min(92%,36rem),宽屏可读性↑
- 色彩收敛:颜色=后果(完成/下载绿、废弃橙、删除红),导出/清空中性不着色;移除紫/蓝冗余

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 12:52:56 +08:00
caoqianming 1f57bbd201 fix(web): 导出按钮简写 + 任务菜单加清空 + 修移动端 task 滚动 + admin 自适应 + bump 0.12.1
- 顶栏「导出对话记录」→「导出对话」,与「清空对话」对齐(dev.html + chat.js 菜单 export 项)
- 任务菜单(每行 ⋯)新增「清空对话」,复用 clearMessages;dropdown 加 .act-clear 紫色
- 修移动端 task 列表无法滚动:手机断点 #pane-left 误用 display:block 致 #task-scroll
  flex:1 失效被 overflow:hidden 截断;改 display:flex 恢复滚动
- admin 移动端自适应:header 紧凑化 + .card-head/.ctrl 允许换行,避免窄屏横向溢出

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 12:41:53 +08:00
caoqianming c870b10368 feat(memory): 双层记忆升级为 agent 自管 + 前端只读记忆面板 + bump 0.12.0
写入路径从纯手工改为 agent 自管(prompt 契约,非后台蒸馏):memory_block
注入可写路径锚点 + 「记忆维护契约」,契约/锚点常驻(记忆为空也注,解新用户
冷启动)。extended 索引从首行标题升为优先 frontmatter description(缺则退回
首行,平滑兼容存量)。修旧 bug:extended 路径在 docker 下注的是宿主路径指不到,
改按 backend 给 host 绝对路径 / /workspace/.memory。

前端记忆面板取舍 = GUI 当眼睛、模型当手:左栏「记忆」按钮开只读 modal 看全貌
(GET /v1/memory + GET /v1/memory/extended/{filename},零写/删 API,路径穿越
校验收口在 core/memory.py)。"看全貌"是读不是 operation,走 LLM 又贵又只拿
转述;"改"全走对话(agent 自管),单一写入口 + 自然语言 + 不会写坏 frontmatter。
对照业界:Claude(同文件式)给全套 view+edit,ChatGPT/Gemini 黑箱只给看/删。

单测覆盖:frontmatter 解析 / legacy 兜底 / 空记忆常驻契约 / host·docker 路径 /
只读视图 / 单篇读 / 文件名安全 / 越界拦截。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 12:20:08 +08:00
caoqianming 0259f0ce92 docs(compat): 进入公测期,开发心智翻新为保证对外兼容 + bump 0.11.1
- CLAUDE.md「开发阶段心智」从"开发期可随意 break、不写兼容层"改为:
  对外契约(用户数据/DB schema/对外 API/CLI·env·文件布局)必须向后兼容,
  仅纯内部实现仍以最优为准放手重构;拿不准 → 当对外契约处理
- 版本号段:公测保持 0.x,1.0 留给对外冻结行为 / 正式 GA
- PROGRESS 加一条;bump 0.11.0 → 0.11.1

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 11:23:03 +08:00
caoqianming f12df1bd82 feat(admin): 后台目录导航 + 按模型/各用户用量时间筛选排序 + 存储分页 + 导出 PDF + bump 0.11.0
- 左侧目录(sticky,点击平滑滚动 + scrollspy 高亮,窄屏转横向 chip);各区 scroll-margin-top 避开顶栏
- 按模型 / 各用户用量拆为独立端点,带 range(all/7d/30d)+ sort(cost/tokens);
  各用户用量含零用量用户(时间条件放 JOIN ON,避免被 cutoff 挤掉)
- 存储分页(/v1/admin/storage/users);各用户用量分页;overview 瘦身为固定指标(runtime/tasks/users/总用量+近7d),独立表自管 range/sort/page
- 导出 PDF:客户端 window.print()(零依赖),填充隐藏报告 DOM + @media print 版式;列表取前 10
- 文档同步 DESIGN §7.3 / PROGRESS / RUN

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 10:44:47 +08:00
caoqianming 81da2f6f55 fix(context): 不压 assistant tool_call 参数,断 run_python 投毒空转
旧 assistant tool_call.arguments(>800 字符)被压成 {"_compacted":...} marker 发给
LLM,模型在长 doc/ppt 任务里反复看到后仿写它当真实参数 → run_python 拿不到
code/script_path 报错空转(DB 实测最近 60 个 task 命中 83 次,其中 61 次是模型仿写
marker)。把原本只给 task_progress 的豁免升级成通用规则:删 _compact_assistant_tool_calls
/ _compact_tool_call_arguments,只压 tool 结果 + skill,assistant 参数一律原样保留。

附诊断脚本 scripts/diag_run_python_empty.py / diag_run_python_trace.py;全量 120 tests OK。
bump 0.10.0 -> 0.10.1

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 10:41:54 +08:00
caoqianming ef611b0666 feat(admin): 角色化管理后台 + 分页各用户用量 + bump 0.9.0
- users 加 role 列(user/admin,migration 0009);make_require_admin 按 DB role gate(不进 JWT,改完即时生效)
- /v1/admin/overview 监控总览:runtime(并发/线程池/SSE/RSS)+ tasks + users + usage 总用量 + storage
- /v1/admin/usage/users 分页各用户 token 用量(全表 LEFT JOIN 含零用量,cost desc,稳定排序)
- /v1/me 返 role;登录/建用户响应带 role;main.py user role / user add --role;建用户弹框加角色下拉
- 独立页 web/static/admin.html + js/admin.js(阈值/热力色差、响应式、10s 轮询、独立翻页);dev SPA admin 才显"管理"入口
- 文档同步:DESIGN §7.3/§7.4、PROGRESS、RUN

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 10:02:20 +08:00
caoqianming 44be5753f7 fix(version): 版本号挪到右侧存储条最左,垂直居中 + bump 0.8.1
- dev.html: #app-version 从左栏 #rail-resources 移入 #storage-foot 最左,
  带细分隔线,垂直居中(align-items:center);左栏底部腾给后续按钮
- core.__version__ 0.8.0 → 0.8.1(纯 UI 位置微调,patch)
- PROGRESS 同步新位置描述

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 12:55:35 +08:00
caoqianming dd797a91e2 feat(version): 版本号单一事实源(core.__version__)+ web 左栏底部展示
- core/__init__.py 新增 __version__ = "0.8.0",作唯一来源
- web/app.py: FastAPI version 与 /healthz 返回都引它(不再写死两份)
- dev.html: 左栏「我的资源」技能按钮旁加 #app-version 小灰字(纯展示)
- main.js: boot 时无条件 fetch /healthz 填版本号(auth 豁免,embed/未登录皆可)
- 放左栏底部而非顶栏:embed 模式桌面端 header 被 CSS 隐藏,顶栏点不到
- CLAUDE.md「文档维护」加规矩:每次 commit/push bump __version__(patch/minor/major 分类)
- RUN.md / PROGRESS.md 同步

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 12:00:30 +08:00
caoqianming 15d69b3372 feat(ops): 并发/线程池轻量监控 + 接管默认 executor
已上生产后线程池排队此前无观测手段:
- lifespan 显式建 ThreadPoolExecutor(尺寸复刻 Python 默认 min(32,cpu+4),
  env ZCBOT_RUN_MAX_WORKERS 可调) + set_default_executor 接管 —— 行为不变
  (匿名池换显式同尺寸池),但 max_workers 可读、成调并发的旋钮
- _stats_logger 每 60s 采样:active_runs(含排队)逼近 max_workers 即排队,
  刷新峰值/有负载打 [stats],空闲静默不刷屏
- broker.total_subscribers() 全局 SSE 订阅数;RSS 用 stdlib resource
  (Unix 峰值;Windows dev 降级),零新依赖

不做监控界面:运维健康是少数标量日志够,业务分析走 SQL。DESIGN 8.4 记
取舍 + 界面阶梯;无感换版(gunicorn/Redis/蓝绿)成本不抵当前收益,搁置。

查看: journalctl -u zcbot | grep [stats]

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 11:09:44 +08:00
caoqianming 958678aa12 feat(skills): 用户私有 skill(.skills)+ 创作工具 + skill-creator + Web 查看页
每用户可在私有 .skills/ 下造/改 skill,只对自己生效。

- SkillRegistry 改多来源(SkillSource 列表:内置 + 用户 .skills),后扫同名
  覆盖先扫 → user wins;user_overrides 记覆盖关系、discovery 显式标注;
  Skill 加 source;from_dir 区分"非 skill 目录(静默)"与"格式错(SkillLoadError)",
  坏的用户 skill 收进 load_errors 注入 prompt,不崩整次扫描。容器路径改写下沉
  到 registry.container_dir(按 source 给 /sandbox/skills 或 /workspace/.skills),
  LoadSkillTool 去掉 container_skills_dir 参数。
- 新增 host-side 工具 save_skill / fork_skill(tools/skill_authoring.py):
  fs 的 base_dir 锚 cwd/容器 wd 够不到 user_root/.skills,故用 host-side typed
  tool(与 seedream/document_* 同范式)。save_skill 写时校验 frontmatter;
  fork_skill copytree 整目录(带脚本)+ 自动对齐 frontmatter name。
- 新增 skill-creator 引导 skill(重点教写好 description + fork 语义)。
- Web:左侧 rail 底部「技能」按钮 → modal 分平台/我的两组,点开看完整
  SKILL.md,我的可删;后端加 GET /v1/skills/{name}(正文)+ DELETE
  /v1/skills/{name}(只删 user 源 + 防穿越);/v1/skills 带 source/overrides/
  load_errors;新 web/static/js/skills.js。创建/改/fork 仍走对话。
- .skills 是 dotfile(文件面板隐藏,与 .memory 一致;validate_task_name 已禁
  . 起头 working_dir,天然不撞)。
- 测试:test_user_skills.py(20 例)+ 改写 test_load_skill.py;全 121 过。
- 文档:DESIGN §3.5 / PROGRESS / RUN(布局+端点)/ SKILL_LIST 同步。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 09:46:39 +08:00
caoqianming d9b48bdb96 refactor(prompt): 精简 system prompt,媒体段改按需注入,通用任务每轮瘦 ~40 行
去 system prompt 冗余 + 让无关段不常驻:
- 「宪法」文件命名约定 ~25→~6 行:只留格式定义+注入值+一行 current/重定调;
  操作细节本就由 proposal/ppt skill 各自讲,两 skill 引用不动也不破
- run_python「先 write script 再 script_path」去重:模板+agent_builder 两处合一,
  scripts/ 子目录约定收进模板
- 媒体工具段(seedream/seedance 红线)抽成 _MEDIA_TOOLS_BLOCK,仅 ArkConfig.load()
  非 None(有 ARK_API_KEY)时追加;ark_cfg 提前 load 一次复用给 tool 注册
- 路径 echo 全形式段 8→4 行

实测 media_enabled ON/OFF 差 891 字节(=媒体段),命名约定段拼接正常;
test_system_prompt_paths 仍过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 08:10:27 +08:00
caoqianming e45705c672 docs(auth): 邮箱密码定为长期保留,OIDC 降级为选做 + 拆出 CORS 收紧
已接入真实用户且邮箱密码长期保留,故:
- DESIGN §7.0/§7.3/§7.7 三处「邮箱密码同步下线」改「与 OIDC 并存长期保留」
- 原「真 OIDC 发布前必做」拆成:CORS 收紧(现做)+ OIDC(选做,信任模型可接受则延后)
- PROGRESS 下一步候选 #1 同步拆分;auth.py docstring 同步

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:47:27 +08:00
caoqianming 4b839e6425 docs: 精简 DESIGN/PROGRESS,压缩冗余实施细节回设计/状态边界
PROGRESS 把近期长段落条目压回"1-2 句:做了啥+关键判断"(细节归
git log),字节 81KB→30KB;状态表/文件清单刷到 06-10。
DESIGN §8 已落地的 token 优化/pptx 预览删逐步 checklist 只留取舍,
§7.5 落地清单/§7.9 取舍压掉重复论证保留全部硬规则与红线,71KB→52KB。
架构本体 §1-§6 与 API/schema 未动。

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