Commit Graph

281 Commits

Author SHA1 Message Date
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
caoqianming 815aeb81a9 feat(web): dev SPA 加 9 处克制入场微动效(纯 CSS + 一处极小 JS)
保留现有红主色 / VS Code 三栏审美不改风格,只补低风险微动效:
- 消息气泡 .msg 淡入+上滑(批量加载退化为柔和集体淡入)
- 4 个 .modal 卡片 scale 弹入 + 遮罩淡入
- 全局 button:active 下压 1px
- 进度 dock / 上传 toast 顶部滑下淡入
- 下拉操作菜单 #floating-menu 从右上锚点弹出
- 拖拽 overlay #file-droparea 快淡入
- 拖拽文件放下 → 落点 pane-right 一次 drop-pulse 轻回弹(files.js
  加 .drop-pulse + animationend once 自摘 + reflow 保证可重放)
- 全部纳入 prefers-reduced-motion 守卫(spinner/blink 等功能动画保留)

刻意未做:进度块「打勾」逐步动画(dock.innerHTML 每 tick 全量重渲染,
keyframe 会逐 tick 重放);复制 ✓ 闪(当前 SPA 无剪贴板复制功能,无触发点)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:15:21 +08:00
caoqianming 8b6e66b006 feat(context): 压缩加"上下文压力门槛",短任务不压缩以护缓存+保信息
prepare_messages_with_stats 加 compact_threshold_chars:总 chars 未超阈值
则完全跳过压缩、原样发 —— 短任务 prompt 前缀逐轮字节一致、DeepSeek 前缀
缓存全程命中,且不白丢旧 tool 细节(context 预算还很空时无谓压缩=纯损失)。
超阈值才走原压缩逻辑。

- loop 按 caps.reliable_context × 0.5 × 2.5(char/token 粗折算)算阈值
  (flash ≈ 33 万 chars),_COMPACT_CONTEXT_RATIO/_CHARS_PER_TOKEN 可调
- compaction_skipped 进 stats / llm_start 事件可观测
- 默认 compact_threshold_chars=0 = 永远压缩(向后兼容)

背景:实测 task b27466a0 DeepSeek 缓存命中已 92-94%、滑动边界损失有限
(压缩函数确定性、旧消息压缩态稳定),故只补门槛、暂不改边界为阶梯式。
新增 2 测试(below/above 门槛),全量 105 过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:42:18 +08:00
caoqianming 0df9e5fe3f feat(loop): 停机判据从"步数"解耦为"是否在推进"
max_iterations 降级为纯安全 backstop(flash 50→120 / pro 100→150),
不再当"轮预算"砍正经长任务;真正的空转防护改用进展信号:

- _RepeatGuard.record 多返 productive(净产出=非[Error]且非一字不差重复)
- _execute_tool_call 三个返回点都带 productive
- run loop 全局 _stall:整步全无净产出+1、任一净产出清零,
  连续 _STALL_LIMIT=8 步主动停([stopped: no progress]),
  比撞 backstop 早得多掐死循环,配逐指纹 HARD=4 双保险
- 撞 backstop / 空转停都 emit"回复继续可续跑"提示,不再静默停

诊断依据:task b27466a0"中途断了"实为撞 max_iterations=50 后干净停、
用户离开 25min 回来打"继续"续完(非崩溃);"步骤太长"=DeepSeek API
延迟 126-185s 而非工具(全<13s)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:22:02 +08:00
caoqianming 4f6e879050 feat(web): systemctl restart 优雅 drain in-flight run,不再误标 error
此前 restart 硬杀 BG run 线程,下次启动 reaper 把所有 running/cancelling
标 error: server restarted before run finished —— 用户一多就不能随便重启。

单实例止血,零 DB 改动:
- lifespan 加 draining(Event) + inflight 登记表(顺手修 create_task 不留引用
  可能被 GC 的旧坑);finally 先拒新 run → await 收尾 → 超 drain_timeout 转
  协作式 cancel(= 用户按停止,标 idle 不报 error、可重发)→ 超 cancel_grace
  仍没退的留给 SIGKILL(最坏退化 = 改前)
- POST /messages:draining 期返 503 + Retry-After;起 run 登记 inflight
- main.py uvicorn 加 timeout_graceful_shutdown=5(否则长连 SSE 挡在 drain 前)
- config/agent.yaml 加 shutdown 段(drain 30s / grace 15s,偏短更安全)
- dev SPA chat.js 发送包退避重试(503 背压 + 交接拒连都重试 ~26s)

部署强耦合:unit TimeoutStopSec 10→90(必须 > drain+grace+sandbox 清扫余量),
已写进 RUN.md unit + 故障兜底。B 蓝绿(零 503 窗口)留作触发信号后再做。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 10:54:43 +08:00
caoqianming b70b993257 feat(preview): pptx 在线预览 —— LibreOffice→PDF + 复用 PDF iframe(DESIGN §8.3 Stage 1)
文件区点 .pptx 不再只能下载。后端转 PDF,前端复用现成 PDF iframe。

- web/pptx_render.py: pptx_to_pdf() 调 soffice,独立临时 profile 绕单 profile
  锁、60s 超时 kill;缓存 .preview/<stem>.<hash>.pdf(hash=mtime+size,源改即
  失效,prune 旧 hash);soffice 缺失抛 SofficeNotFoundError
- web/app.py: GET /v1/files/preview_pdf —— _safe_join 防穿越 + 仅 .ppt(x) +
  per-path asyncio.Lock 防并发重转 + run_in_executor 不堵事件循环;缺失 501/失败 500
- preview.js: ppt 组 + main/mini 共用 _showPptAsPdf(spinner loading + 失败回退下载)
- dev.html: .preview-spinner(复用 @keyframes spin)
- 转换跑 web host 进程不进沙盒;部署 host 装 libreoffice-impress + fonts-noto-cjk
  (sandbox Dockerfile 不动)
- tests/test_pptx_render.py: 10 例(缓存命中跳 soffice/源变失效+prune/缺失降级/越界拒绝)
- 文档:RUN.md(host 装 + 故障兜底 2 行)、PROGRESS.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:04:02 +08:00
caoqianming 087980d027 test(prompt): 锁住 docker 下 system prompt 不漏宿主路径(药3 复核收口)
高轮数烧 token 诊断的最后一味:复核「不停 glob /home/ubuntu/zcbot」幽灵路径。
证据链确认是 2026-06-03「system prompt 焊死宿主路径」那个 bug 的历史残影——
失败 task(ab063233 06-02 / ff1686b7 06-03)首条 assistant tool_calls 即带宿主
路径+真 uid(只可能来自当时运行时拼接的 system prompt;messages 表无 system-role
行),两者均建于修复前后。fs 工具在 docker 容器跑、容器无此宿主路径 → base
path not found → 重试风暴(实测 51 次)。

复核当前代码:docker 模式即便传 tool_base=/home/ubuntu/zcbot + 真 uid,prompt
只含 /workspace/<wd>、不漏宿主路径/uid/tmp(agent_builder.py docker 分支)。
新增 tests/test_system_prompt_paths.py(2 例)钉死防回归。三味药全部收口;
药1 重复守卫此后兜底同类风暴。无功能代码改动。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:30:32 +08:00
caoqianming b2b4a29ad3 feat(ppt): 补信息设计纪律 + 混合背景 + pptx 预览器(深读 pptmaster 后)
深读 ppt-master 的 executor/shared-standards 后定位:它像麦肯锡的真因是
信息设计纪律(~70%)而非 SVG 渲染。这些全是 editable python-pptx 能做的。

- 信息内功:add_takeaway(论断标题下结论框)、add_kpi 加 baseline+delta
  (数据语境化)、add_source、add_toc;SKILL 策略阶段加论断式标题对照表 +
  page_rhythm(breathing 页强制打破卡片网格)+ 内容→版式映射
- 修反了的投影:add_card 默认平卡(shadow=False),投影只给悬浮卡、每页 ≤2-3、
  一容器一手段;quality_check 加绿=语义状态色豁免三色制
- 组合件:add_card_grid(均衡网格,多行图标左置治溢出)/add_timeline/add_cycle
- 混合背景 render_bg.py:无头 Chrome 渲杂志级 mesh 渐变背景 + 原生可编辑白字
- pptx_preview.py:把 .pptx 渲成 PNG 肉眼验观感 —— 当场抓到 set_text 多行
  只给第一段上色的真 bug(封面副标题第二行变暗),已修

验证:重排「大模型与智能体」10 页,逐页渲 PNG 亲眼验收均专业,quality_check 全过。
未做 SVG→原生转换器(论证为可编辑输出零视觉增益)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 09:41:55 +08:00
caoqianming 8150bf0b83 feat(tools+loop): 批量抓取 + 重复调用守卫 —— 治高轮数烧 token
DB 实测高轮数 task 的浪费来自三股根因(空 {} 风暴 / 报错重试 /
检索不收敛)叠加,且 loop 对重复调用零防护。本轮两味药:

药2 检索/抓取类 host 工具批量化(从工具形态减往返):
- web_fetch: url -> urls(1-10 并发,总预算 16000 按条分摊,单条失败不连坐)
- document_search: query -> queries(1-8 并发,批内去重,批量自动缩量防爆 context)
- document_download: file_name+kb_name -> items(1-10 并发,单条失败标 [Error] 不毁整批)
按「开发期不写兼容层」直接换签名、不留单数别名;skills/documents/SKILL.md 同步。

药1 loop 病理性重复守卫(core/loop.py::_RepeatGuard):
- 按 (工具名, 精确参数) 指纹跟踪「无产出重复」
- 只惩罚无产出(结果为 [Error] 或与之前一字不差);结果每次不同=有产出、清零,
  绝不误伤正常迭代(改脚本重跑 / 修 bug 重跑构建)
- SOFT=2 注入软提示;HARD=4 拦截不执行,逼模型换路
- 顺带堵 _malformed_tool_calls 漏空 {} 的洞(空 {} 每次返同句缺参错 -> 走 dup 被拦)

测试:tests/test_loop_repeat_guard.py(7 例)+ test_secret_host_tools.py 改新形态
并加批量/去重/失败隔离 3 例;相邻 24 测试全过。
诊断脚本留 scripts/diag_*.py 供复跑。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 16:47:56 +08:00
caoqianming c0169e7766 feat(ppt): 视觉系统升级为卡片式 —— 治"生成效果不太行"
学 hugohe3/ppt-master 后定位根因:被 python-pptx 原语(平矩形+左色条+
圆点 bullet)摁死视觉天花板。选路径 B(升级 python-pptx 设计系统,保留
单脚本批量架构、原生可编辑)。

- pptx_helpers: 加 add_card(圆角+柔和投影)/add_gradient_rect/add_kpi/
  add_icon_tile/add_pill/add_eyebrow/add_chevron/add_notes;set_palette
  派生明暗色阶 WASH/SOFT/DARK;apply_brand 封面/章节改渐变大色块;
  所有 helper 把 name= 写进形状 .name(原来只喂 assert_inside)
- layouts.md: 9 版式重写成卡片式 + 扩到 13 种(KPI 卡/卡片网格/流程/大数字)
- quality_check: 跟新设计语言对齐 —— 三色制按色相归桶、标签按 .name 豁免
  小字号/bullet、≥40pt 展示字跳过溢出估算、bullet≤5 改按列判
- SKILL.md: opt-in 真实配图(imagegen, ¥0.22/张)+ 每页演讲者备注
- design_principles/SKILL_LIST/PROGRESS 同步

验证:13 版式全覆盖 demo + 6 页样例 deck quality_check 全过;单列 6-bullet
回归仍触发。未动 SVG 路线/live preview/动画(更大工程)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:28:37 +08:00
caoqianming b95c247971 feat(prompt): 工作原则加「少来回」—— 独立步骤合并,广谱减轮
ppt 之外的长尾 task(改代码/跑数据/画图)无专属 skill 兜底,加一条通用原则:
互相独立、不依赖中间结果的操作合到一个脚本/一轮并发 tool call 里做,别一步
一 call(每轮重发整段上下文,轮数=token 体量线性乘数);但下一步要看上一步
结果时(探索检索/按报错改/需用户确认)就分步,别硬批 —— 精准措辞避免过度
批处理踩掉该有的 checkpoint。prompt 走前缀缓存近零成本。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:31:22 +08:00
caoqianming 1f6661b8df refactor(ppt): 工作流批量化减轮 —— 逐页生成改一脚本整建 deck
高成本 task 几乎全是 100+ 轮的逐步 tool 循环,轮数是 token 体量线性乘数。
ppt 作最低风险试点:

- 阶段一 spec 增「逐页大纲」表(页|版式|标题|要点|图标),作替代逐页确认的
  前置 checkpoint —— 改文字大纲比建完 slide 再推翻便宜
- 阶段二改成写一个 build_deck.py 一次建整 deck(同进程 new_presentation→
  按大纲循环 add_slide→一次 save,坐标天然一致;pptx_helpers 模块化已消解
  逐页防漂移理由),图标全 deck 批量预取
- 验收:quality_check 一次 → 改脚本重跑(不 edit 成品)
- 可选「风格探针」(先建封面+1页看观感)兜视觉返工险

N 页从 ~2N 轮降到 ~3-4 轮。改 SKILL.md / layouts.md(§通用起手换单脚本模板)/
SKILL_LIST.md。冒烟过单脚本建 2 页,API 与模板一致。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:22:09 +08:00
caoqianming 824f746571 fix(progress): 停压 task_progress 参数修进度还原 + 进度区移到对话区顶部
问题1(进度不对): 上下文压缩把旧 task_progress tool_call 参数换成
{"_compacted":true,"step_id":"sX"} 这种像合法调用的标记, 既毒化模型让它
照抄出残废 update_step(丢 step.status)入库, 又让前端 applyProgressAction
读不到 args.step → 步骤永停 pending。修复: task_progress 参数一律不压缩。

问题2(没像 codex 在顶部): 删掉每条消息卡内联进度块, 进度统一只在对话区
顶部单一 dock 实时显示(钉顶不滚); 全部完成时折叠成一行摘要。prompt/tool
描述改为跑完标 completed 而非 clear, 留住全绿收尾。

校验: unittest test_context_compaction/test_task_progress_tool 12 过;
node --test frontend_task_progress 2 过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 09:52:44 +08:00
caoqianming 2136fdd306 fix(web): 修登录无反应(newtask.js 漏 import $)+ 补 favicon
newtask.js 用了 dom.js 的 $ 简写却漏 import,模块加载即抛
ReferenceError: $ is not defined,中断 newtask 绑定及 auth/chat
链路 → 点登录无反应。补 import 与其余模块对齐。

另在 dev.html head 加内联 SVG favicon,消掉根 /favicon.ico 404。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 09:22:59 +08:00
caoqianming c898ff863d Disable static asset caching 2026-06-08 09:16:31 +08:00
caoqianming 15ecf45c93 refactor(dev): 前端模块化 Step 2 收官 — 抽出 chat.js,main 缩成 75 行入口
最后也最缠的一块:任务列表(浏览/筛选/滚动)+ selectTask 切换 +
renderChatMeta/模型下拉 + renderMessages + live-run 助手 + sendMessage/cancel +
fetchSse/handleSseEvent + 润色/粘贴文件 + 完成/废弃/删除/导出/清空
(原 main 连续区 64–1132)→ chat.js(1086 行)。

决策:合一个 chat.js 而非强拆 tasks.js+stream.js —— 二者共享
state.liveRuns + chat-stream DOM + run 生命周期,live-run 助手被
selectTask 与 SSE 机器两边调用、骑墙;强拆会制造双向各 ~4-5 个 import
且边界不自然。

- 导出 loadTaskList / loadModels / selectTask;embed/files/newtask 对这
  三个的 import 从 ./main.js 改指 ./chat.js;formatUploadProgress 加 export。
- chat 不调 enterApp → 与 main 无环。
- main.js 仅留 enterApp(编排)+ loadStorage + Esc 关栈 + boot = 75 行入口,
  import 精简到 11 行(layout/markdown/media 不再被 main 直接引用,但经
  chat 仍在依赖图、副作用照常)。

校验升级:node 全检 + import/export 一致性 + 从 main BFS 的模块可达性
(14/14 可达,确保副作用模块不掉出图)。

dev.html 4087 行单文件 → 14 个零构建 ES module + 纯 HTML;main 2719→75。
路径 1(拆文件)完成。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 08:09:53 +08:00
caoqianming 36dbdb2dda refactor(dev): 前端模块化 Step 2 — 抽出 embed.js(iframe 模式)
父页面经 postMessage 推 token 进入应用 + 401 重签(原 main 1147–1209 +
顶层 _embedInitialTaskHandled 一次性标志)→ embed.js(75 行)。

- 导出 embedInit(boot 调)+ embedPostToParent / embedShowWaiting
  (auth 的 logout 在 embed 下通知父页面/显示等待态)—— 后两个从 main
  迁出后,auth.js 对它们的 import 从 ./main.js 改指 ./embed.js
  (auth 仍从 main import enterApp)。
- 反向 import main glue enterApp / loadTaskList / selectTask。
  main↔embed、auth↔embed 均运行时调用环,安全。

main.js 删至 1154 行(2719 起,已搬出约 58%)。node 全检过、
import/export 一致性过、静态测试 2 过。剩 main 内:enterApp glue +
tasks(列表/选择/渲染消息)+ stream(发送/SSE)+ boot + Esc 关栈。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:35:01 +08:00
caoqianming 40fefdffef refactor(dev): 前端模块化 Step 2 — 抽出 newtask.js(新建任务弹框)
任务名 / 工作目录(新建 sentinel 或复用已有 + 二级 input 联动)/ 描述 /
skill / 模型 select,提交 POST /v1/tasks(原 main 1146–1320)→
newtask.js(186 行)。

- 顶层自绑 hd-new 打开 / nt-go 提交 / 各 input 联动;唯一对外导出
  loadFolderSuggestions(供 main enterApp 初始化顶部 filter-wd、files
  复制/移动后刷目录)—— 它从 main 迁来后,files.js 对它的 import 从
  ./main.js 改指 ./newtask.js。
- 反向 import main glue loadModels(加 export)/ loadTaskList / selectTask
  + logout(auth)。

main.js 删至 1220 行。node 全检过、import/export 一致性校验过、
私有符号清零。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:24:29 +08:00
caoqianming e5940266ca refactor(dev): 前端模块化 Step 2 — 抽出 media.js + 收敛 downloadFile 反向依赖
对话内工具活动标签 + artifact(产物)抽取/渲染:toolActivityLabel /
extractArtifactRels / extractMediaBanner / renderArtifactBarHtml /
upgradeMediaArtifacts / downloadFile → media.js(237 行,原 main 1134–1359)。

收敛点:downloadFile 移入 media 后,preview.js / files.js 对它的 import
从 ./main.js 改指 ./media.js —— 把这条反向依赖从 main 挪开。media 导入极少
(escapeHtml / _categorize(preview)/ state / logout),与 preview 成
media↔preview 环(均运行时调用,安全)。

两次险漏靠校验抓回:
- 共享 const ARTIFACT_PRODUCING_TOOLS(main renderMessages/SSE 用 4 处,
  .has() 访问非函数调用,"被调标识符"法漏掉)
- 内部函数 _flushMediaArtifactCache(selectTask 切任务清缓存用)
两者经残留符号检查发现后补 export。

新增全模块 import/export 一致性校验脚本(每个 import{X} 必在目标 export),
11 模块全过。main.js 删至 1393 行。node --check 11 模块全过、静态测试 2 过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:05:48 +08:00
caoqianming 71bac870ed refactor(dev): 前端模块化 Step 2 — 抽出 files.js(文件面板 + 选入 + 拖拽上传)
右栏文件列表浏览/导航/删除/重命名 + 刷新 + "选入"弹框(跨目录勾选
复制/移动)+ 拖拽 overlay + 上传(XHR 带进度)+ 上传状态条。

代码原分散在 main.js 两段非连续区(1133–1459 文件列表/选入/拖拽 +
1697–1786 上传 helper,中间夹着 media 段)→ 合并进 files.js(433 行)。

- 导出 loadFiles / scheduleFilesRefresh(SSE 文件事件刷新)/
  closeSrcPicker(main Esc 关栈)/ uploadFiles(聊天区粘贴/拖拽复用);
  其余入口模块顶层自绑。
- 反向 import openFilePreview(preview)、logout(auth)、main glue
  downloadFile / selectTask / loadTaskList / loadFolderSuggestions
  (后三个加 export,后续随 tasks/newtask 模块化再迁)。
- 依赖分析用"段内被调标识符 − 段内定义 − 叶子/全局"全量提取,补回固定
  清单漏掉的 loadFolderSuggestions / loadTaskList。

main.js 删至 1619 行。node --check 双过、main 残留 files 私有符号清零、
files 无未导入 glue、静态测试 2 过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 22:08:55 +08:00
caoqianming 9394e065f1 refactor(dev): 前端模块化 Step 2 — 抽出 preview.js(文件预览 + mini 预览)
文件预览主弹框(图/视频/PDF/文本/markdown/docx/xlsx,大文件降级下载,
docx/xlsx 走 loadScript 懒加载 vendor)+ 同时再开的小窗预览
(原 main.js 1687–2048)→ preview.js(379 行)。

- 导出 openFilePreview / openPasteFilePreview / closeFilePreview /
  closeMiniPreview / _categorize(媒体段判图/视频用)。
- 反向 import downloadFile(main 媒体段,加 export)、logout(auth)。
- Esc 关弹窗栈处理器留 main(跨模块协调 chpw/选入/文件预览/小预览)。
- 一处去耦:deletePastedFile(留 main)原直接读 preview 私有
  _fpCurrentRel/_mpCurrentRel 判断要不要关预览 → 改为 preview 导出封装
  closePreviewIfShowing(rel),行为不变但不泄漏内部状态(唯一非纯剪切微调)。

main.js 删至 2034 行。node --check 双过、preview 私有符号在 main 清零、
无未导入 glue 引用、静态测试 2 过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:57:43 +08:00
caoqianming 16dc1719eb refactor(dev): 前端模块化 Step 2 — 抽出 auth.js(首个 main↔模块 ES 环)
登录(邮箱密码 / UUID+PLATFORM_KEY 两 tab)+ 管理员加用户 + 改密码
三节(原 main.js 21–227)→ auth.js(218 行)。

- 各入口在模块顶层自绑 onclick;只导出 logout(供全局 20 处 401 处理)
  / closeChpwModal(供 main 的 Esc 统一关弹窗栈)。
- 反向 import main 的 glue:enterApp / embedPostToParent / embedShowWaiting
  (main 给这三个加 export)——首次引入 main↔auth 循环依赖。三者皆 hoisted
  函数声明,模块实例化即就绪,且只在运行时(点击/401)调用,绝不在顶层
  求值时触发 → ES live binding 下安全。后续 features↔glue 环同理。
- main.js 删至 2397 行。node --check 双过、auth 私有符号在 main 清零、
  静态测试 2 过。逻辑零改动。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:39:06 +08:00
caoqianming cbb16b896f refactor(dev): 前端模块化 Step 2(起)— 抽出 layout.js
三栏布局(pane 折叠 rail + 拖拽 splitter + 手机单列视图)是 main.js 里
唯一对其他功能节零出边的干净段,用它打样增量剥离。

- layout.js(121 行):import $ + 4 个 LS_*_COLLAPSED/WIDTH,只导出
  mqPhone / setMobileView(后者供 selectTask 在手机宽下选中任务自动切
  对话面板,是唯一跨模块调用)。折叠/splitter/mobile-tab 顶层事件绑定
  原样保留(ES module 默认 defer,import 时 DOM 已就绪)。
- main.js:删 114 行 → 2606 行,加 layout import 并清掉随之不再用的
  4 个 LS_* import。逻辑零改动,纯剪切 + 连线;node --check 过,
  main 残留 layout 私有符号清零。

顺手修 Step 1 遗留测试失败:test_static_vendor 第二用例原只 grep
dev.html 找 formatContextStats / context_original_chars / cache_hit_tokens,
模块化后这些搬进 js/*.js → 改为扫 dev.html + js/*.js 合并源。2 测试全过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:27:51 +08:00
caoqianming 8a7e0cd233 fix(loop): 工具调用 arguments 损坏时丢弃重试 + 非流式兜底,断投毒级联
deepseek-v4-flash 大参数工具调用(大 write/run_python,≈7K+ 字符)偶发把内容
碎片错位粘进 arguments 开头致 JSON 解析失败,或退化成空 {} 缺参。隔离批量验证
(干净上下文)非流式 8/8、流式 8/8 全干净 → 上游间歇抖动,概率低;真正放大成
灾的是 loop 把损坏的 assistant 消息入库 + 每轮重发,诱导模型继续学坏(投毒级联)。

- core/loop.py:_stream_llm 拉一轮后用 _malformed_tool_calls 校验 tool_call
  arguments 能否 json.loads,不能则丢弃整轮(不 append/不记账)重 roll(≤3 次),
  最后一次降级 _nonstream_once(provider 服务端拼,绕开流式错位)。流式收集逻辑
  抽成 _collect_stream_once,正常路径不变。
- core/executor_host.py / core/sandbox/tool_runner.py:缺必填参数早返
  「缺少必填参数 [...];请带齐 [...] 重新调用」,替掉暴露内部签名的
  TypeError missing N required positional arguments(host + docker 两路覆盖)。
- 文档:PROGRESS.md 加 2026-06-06 条;RUN.md 故障兜底加一行。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 20:51:45 +08:00
caoqianming 72ae41e122 refactor(dev): 前端模块化 Step 1 — dev.html 拆零构建 ES module(叶子优先)
dev.html 原 4087 行单文件原生 JS。本步只做"拆文件"(路径 1),
不引框架、不改逻辑——纯剪切 + import 连线。

抽出 4 个无依赖叶子到 web/static/js/:
- state.js   state 单例 + LS_* + EMBED*
- format.js  escapeHtml / humanSize / fmtTokens / usage 系列等纯格式化
- dom.js     $ + 浮层菜单 showMenu/hideMenu(import escapeHtml)
- api.js     api() Bearer 封装(import state)
- markdown.js renderMd / highlightIn(依赖 vendor 全局)

剩余主体(login→boot)落 main.js 并 import 叶子;dev.html 内联大
<script> 换成一行 <script type="module" src="js/main.js">,降到 1121 行。

app.py 加 mimetypes.add_type("text/javascript", ".js") 兜底,防 Windows
把 .js 判成 text/plain 致 module 被浏览器拒执行(本机实测本就 OK,纯防御)。

校验:6 模块 node --check 全过 + 无私有符号泄漏到 main。
后续 Step 2+ 再从 main.js 把 login/tasks/stream/files/preview 逐个剥成独立模块。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:55:20 +08:00
caoqianming 6f9ca26e3f fix(dev): 改密码弹框复用选入文件的头/体/脚分隔布局
#chpw-modal 原先无专属 CSS,.card 只继承公共骨架,缺 padding/width/
表单整形,卡片被撑近全宽且无内边距。改为复用 #src-picker-modal 的
头/体/脚分隔布局(标题/按钮区带分隔线、表单包进 .body),.card 收到
400px + flex column,input focus 高亮。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:01:48 +08:00
caoqianming 69fc2599e3 feat(run_python): 过程脚本约定落 task_dir/scripts/
模型显式写文件再 script_path 跑的过程脚本统一进 <task_dir>/scripts/
(可见/持久/可重跑),交付产物仍落 task_dir 根。inline code 匿名片段
维持临时用后即焚(host 系统 temp、docker .zcbot_tmp dotfile,均不动)。
改 agent_builder 系统提示工作目录段 + run_python tool description/参数说明。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:52:05 +08:00
caoqianming 02cd67f63a feat(skill): 新增 standard skill — 国标/行标/团标起草(GB/T 1.1-2020)
联网核实市面无可直接复用的"写标准文件本身"的 skill(搜到的均为相邻品类:
GB/T 8567 投标技术方案、GB/T 9704 党政公文),据 GB/T 1.1-2020 自建。

- 覆盖三层级:国标 GB·T / 行标 JC·T / 团标 T/(重点对接 CSTM → T/CSTM)
- 两体裁骨架:试验方法标准(GB/T 20001.4)+ 产品标准;配套编制说明(报批必交)
- 3 references:要素骨架(必备/可选·规范性/资料性·封面前言套话)、层级选型
  (CSTM 体系与立项程序)、起草铁律(能愿动词/不可考核词/指标量化闭环/术语/引用真实性)
- 渲染复用 proposal render_docx.py + render_diagrams.py(兄弟 skill,同 patent 范式)
- 不用 proposal quality_check.py(按申报书章节名误报"缺章节"),改 §8 人工自检清单
- 同步 SKILL_LIST.md(13→14)+ PROGRESS.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:46:06 +08:00
caoqianming f2b3675337 feat(dev): 顶栏自助改密码 + 选入按钮文字改图标(防换行)
改密码:web/auth.py::change_password(验旧密码 + bcrypt 重哈希,错误归一到
UserCreateError code);POST /v1/auth/change_password 挂 require_user,
user_id 取自 JWT 不信前端(旧密码错/无密码 403、弱密码 400)。前端顶栏
「退出登录」左侧加「改密码」按钮(并入 embed 隐藏规则)+ 复用 .modal 弹框
(旧/新/确认,前端先验长度与一致性,成功不登出,401 走 logout)。

选入:#btn-src-pick 文字「选入…」→ 单字符 ⊕(同 ⬆ ↻ › 风格,title 保留
语义),修窄面板偶发换行。

文档:PROGRESS / RUN(API 表 + 用户管理 + 两条兜底)/ DESIGN(auth API 清单)同步。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:07:21 +08:00
caoqianming f81dd2def6 fix(usage): 缓存命中率同源(修 822% 怪值)+ 列表花费改 hover + backfill 加 --assume-cache-hit-rate
- 命中率怪值修复:原以 tasks.tokens_prompt 作分母、usage_events.cache_hit 作分子,
  跨源(tokens_prompt 会被"清空对话"重置、usage_events 不重置)→ 算出 822%。
  _usage_aggregates 改为同时返回 chat tokens_in/out,_task_dict 的 token 总量优先
  取 usage_events 聚合(与 cache_hit 同源,命中率恒 ≤100%)
- 前端:任务列表行不再内联 ¥,花费/缓存命中率藏进 hover tooltip(taskUsageTooltip,
  多行:输入/输出拆分·命中+命中率·真实花费);顶栏保留内联简版 + 同款 tooltip
- backfill: 加 --assume-cache-hit-rate RATE,对无 cache_hit_tokens 字段的老事件按
  估算命中率折价(DeepSeek 当时缓存了只是没记,全价偏高);已记真实值的不受影响

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 08:41:51 +08:00
caoqianming af2ad3cef1 feat(usage): 记账给前缀缓存命中折价 + 前端体现缓存命中/真实成本
排查"rust→PPT"task(flash,34 轮)发现累计 tokens_in 69.9 万里 88.6% 是缓存
命中,但 _fallback_chat_cost_cny 把命中段也按 input 全价算,记账虚高 2-3x。

- capabilities: 加 cache_hit_cny_per_mtoken(deepseek flash 0.1 / pro 0.2;
  0=不区分按全价兜底,绝不少记)
- usage: 成本公式拆三段「命中×缓存价 + (input−命中)×input价 + output×output价」;
  loop 把 cache_hit_tokens + 缓存单价透传进 record_chat_usage
- web: 不加 DB 列。app.py 加 _usage_aggregates(单查询 GROUP BY usage_events,
  复用列表 msg_counts 批量范式,无 N+1)on-the-fly 算每 task 真实成本 + 缓存命中,
  _task_dict 带出;dev.html 列表行显 ¥、顶栏 formatTaskUsage 显「tok·缓存命中%·¥」
- scripts: backfill_chat_cost_cache_discount.py 按 units 已存 token 重算历史
  cost_cny(只改成本列,默认 dry-run,--apply 落库)

折价只对新 chat 事件即时生效;历史走 backfill 脚本(部署后跑)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 08:26:09 +08:00
caoqianming 1c30a9e54e Reduce chat context token usage 2026-06-04 16:41:14 +08:00
caoqianming 7f4712dcba feat(ppt): quality_check 加内容形状重叠检测
原有数值检查只覆盖越界 + 按字数估算的文本溢出,盲区是"两个都在画布内的
形状互相重叠"(文本框压图标 / 压另一文本框)。加纯数值两两包围盒重叠检测:

- 只检"内容形状"(有非空文字 / 是图片)。装饰元素(无文字纯色填充:品牌条/
  分隔线/圆点/色块标签/装饰星箭头)天然排除,"文字叠在色块上"也不误报
  (色块无文字 → 非内容)。要抓的是文字压文字 / 文字压图标 / 图标压图标。
- 交叠宽高均 >0.08in 且 交叠面积/较小形状面积 ≥25% 才报,滤掉边缘贴合/发丝线。
- 报告含重叠百分比 + 两形状名 + 文字头,便于定位。

零依赖、确定性、host+docker 都跑(不需 LibreOffice)。测试过:合规 deck
(L2 徽章+字 / L5 标签叠 chip / L4 圆点+bullet)零告警;两文本框故意叠触发。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:47:52 +08:00
caoqianming 07959eb738 refactor(ppt): 版式 helper 抽成可 import 模块 + 修中文字体没真生效 + quality_check 配色查纳入填充色
逐页生成是"每页一个 run_python",以前 ~150 行 helper(配色常量/add_textbox/
apply_brand 等)要在每页里默写一遍 —— 烧 token 且长 deck 里坐标会漂(第 7 页
apply_brand 跟第 2 页对不上)。

- 新增 scripts/pptx_helpers.py:每页 import pptx_helpers as P 调用;含 new_presentation
  / load(按文件实际尺寸回填画布常量,逐页进程间同步) / add_slide / set_palette(默认
  商务红,spec_path= 自动取 spec 前 3 个 hex 作主/辅/强调)入口。
- 字体修复:python-pptx font.name 只写 <a:latin>,中文字形走 <a:ea> 槽位没设 ——
  "指定微软雅黑却没真生效"的根因。set_text 改为同时写 latin=Arial + ea/cs=微软雅黑,
  中英混排各命中正确字体。
- quality_check.py 配色检查纳入形状填充色(品牌条/徽章/圆点/标签/底块以前全漏),
  并把粗阈值"≤5 色"改成贴合三色制的"非灰阶色 ≤3"(灰/黑/白按 R/G/B 极差 ≤12 排除),
  否则计入填充后合规商务红 deck 会狂报假阳;spec 比对也只比非灰阶色。
- 联动:layouts.md(helper 块换成 import 起手 + API 速查,9 个示例全改 P. 调用)、
  icons.md A5 示例、SKILL.md 资源/阶段二、PROGRESS.md。

冒烟测试过:ea 字体确写入、set_palette 覆盖生效、quality_check 正常解析;
合规红 deck 无配色 warning、塞 4+ 彩色触发、ACCENT 强调线填充被正确捕获。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:43:09 +08:00
caoqianming af97dd7c62 feat(web): 文件面板底部展示用户已用存储 + 配额
后端已有 user_disk_usage 表(后台 15min 扫描落库)但无对外查询口,
加 GET /v1/user/storage(require_user)返 {bytes_used, file_count,
limit_bytes, scanned_at};limit_bytes 由 parse_bytes(quotas.
disk_bytes_per_user) 得,≤0/None=不限。disk_quota.get_user_usage
扩为返 (bytes,count,scanned_at) 三元组(复用而非新开函数,顺手改唯一
调用方 check_disk_quota 解包)。

前端 dev.html 右侧文件面板底部钉一条进度条+文字:#pane-right 改 flex
列让 file-list 独占滚动、存储条钉底;loadStorage() 在 enterApp 拉一次;
不限额时只显已用、隐进度条;超额变红;hover 显文件数+统计时间。样式用
class 选择器压低特异性,让折叠/手机隐藏规则能盖住它。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:00:36 +08:00
caoqianming 42755e246e fix(sandbox): shell 也注入 PYTHONPATH + HOME=/tmp(修只读 rootfs 两副作用)
容器 --read-only rootfs 下两个副作用:
- PYTHONPATH 原先只 run_python 注入,shell `python -c "from skills..."` 撞
  ModuleNotFoundError
- /home/zcbot 不可写,matplotlib/fontconfig 往 ~/.config / ~/.cache 写缓存刷
  "Read-only file system" / "No writable cache" 噪音

抽 _CONTAINER_ENV = {PYTHONPATH, HOME=/tmp},shell/run_python/fs 三路共用
(-e 确定性覆盖)。HOME=/tmp 一刀让缓存落 tmpfs(matplotlib→/tmp/.config、
fontconfig→/tmp/.cache),免逐个 MPLCONFIGDIR/XDG_CACHE_HOME。纯代码改,
重启 web 生效,免重建镜像。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 14:12:53 +08:00
caoqianming 7b09c77f76 fix(sandbox): docker 下 system prompt / SKILL 文档统一用容器路径
docker backend 下 shell/run_python/fs 工具全在容器里跑(<workspace>/users/<uid>
bind 到 /workspace),但喂给 LLM 的路径多处仍是宿主绝对路径(容器内不存在),
agent 据此 find 全空、瞎转到 pwd 才发现真身在 /workspace/<wd>。

- core/agent_builder.py::_build_system_prompt:docker 下 task_dir + 「宪法」
  glob 范例换成容器路径 /workspace/<wd>,去掉容器里无意义的 cwd 行(cwd 恒等
  task_dir);host 不变。修法同 LoadSkillTool 的 container_skills_dir 改写。
- skills 文档同类宿主路径残留清扫:patent 跨 skill 调 proposal 脚本由
  <repo_root>/...(硬编码 D:/projects/zcbot)改兄弟相对 <skill_dir>/../proposal/...;
  research fetch_pdf 范例硬编码 D:/... 改 <task_dir> 占位;patent/proposal/ppt
  的 <task_dir> 举例的废弃旧布局 workspace/tasks/<task_id> 改 host/docker 双形态说明。
- ppt 图标缓存写进只读挂载修复(方案 A):种子图标库 <skill_dir>/assets/icons/
  降为只读(glob 读),fetch_icon.py 新拉图标一律 -o <task_dir>/assets/icons/,
  读路径改种子库 + 本 task 两处都 glob。docker 下 skills 是 :ro bind,旧写法必败;
  host 下还顺带污染仓库工作树。脚本已 out.parent.mkdir,无需改。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 16:33:59 +08:00
caoqianming 9a1e88d86f docs: 精简 PROGRESS 每条压回 1-2 句 + DESIGN 状态表去重
PROGRESS.md 按头部自带规约(每条 1-2 句:做了啥+关键判断)把
"已完成关键能力"压缩:砍根因长推理 / 否决方案 (a)(b)(c) / 测试细节 /
部署操作 / 结尾"DESIGN不动·RUN不动"样板尾巴;同质 UI 碎条合并
(05-20~05-25 dev SPA 堆)。字符 131K→35K(-73%),细节仍在 git log/diff。
四段不动:状态表 / 关键决策 / 文件清单 / 下一步候选。

DESIGN.md §7.6/§7.7 phase 表删纯状态列(/待,与 PROGRESS `## 状态`
重复=漂移源),保留设计意图(估时 / 撤销 / 前置依赖),状态指回 PROGRESS。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 13:26:10 +08:00
caoqianming ed136f8ed7 fix(deploy): npm 默认源改回腾讯 — npmmirror 访问不稳
registry.npmmirror.com 实测访问半天连不上。换回腾讯 npm
(https://mirrors.cloud.tencent.com/npm/)—— 它在历来 build 里一直 OK,
之前那次坏 wheel 只是腾讯 PyPI 的问题,npm 不受影响。pip+apt 仍清华不变。
备选记进文档:华为 repo.huaweicloud.com/repository/npm/ / USTC npmreg.mirrors.ustc.edu.cn。

update.sh:NPM_REGISTRY 默认 + 注释。RUN.md:最后更新 / §镜像构建说明 /
手动 build 示例 / 故障表 npm 行。PROGRESS.md:同条更新。DESIGN 不动。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:00:28 +08:00
caoqianming fedf5a2cd5 fix(deploy): 默认镜像源改清华(pip+apt)/ npmmirror(npm)— 腾讯源给出损坏 litellm wheel
腾讯源返回的 litellm-1.87.0 wheel 文件损坏:镜像 index 声明的 sha256(fbbba7e…,
与 PyPI 官方一致)对,但实际吐出的文件字节算出 bbebefff…,pip 报
"THESE PACKAGES DO NOT MATCH THE HASHES"。本仓 requirements.txt 未钉 hash,
是镜像端 index 自声明 hash 与文件不符 = 镜像存的文件损坏/截断(非篡改、非 require-hashes)。

deploy/update.sh 三个默认值:
- PIP_INDEX_URL -> 清华 pypi.tuna.tsinghua.edu.cn(境内稳 + 同步及时;阿里 PyPI 曾滞后到没有 litellm>=1.83)
- APT_MIRROR    -> 清华 mirrors.tuna.tsinghua.edu.cn(Debian / security 路径同官方,sed 替 host 即可)
- NPM_REGISTRY  -> registry.npmmirror.com(清华无 npm registry,npmmirror 是境内事实标准)

换默认会让下次 build 从 pip 层全量重跑一次(~5-10min),之后命中 cache。
Dockerfile ARG 默认(官方源 fallback)不动。

RUN.md:头部「最后更新」、§镜像构建默认源说明、手动 build 示例、故障表
(新增 hash-不匹配诊断行 + 其余镜像行对齐新默认)。PROGRESS.md:2026-06-03 加一条。DESIGN 不动。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:46:54 +08:00
caoqianming 9cd33cf4d6 revert(workspace): 撤 ZCBOT_WORKSPACE_DIR env 覆盖(架构 bug),数据盘改用 bind mount
2026-06-02 给 resolve_workspace 加的 env 覆盖与路径存储层冲突:core/paths.py
把 DB working_dir 锚定 ROOT(代码仓库目录)存相对串,to_db_path 对 ROOT 外路径
直接 relative_to raise。env 一旦指向 ROOT 外的 /data/...,三家分叉:
- 文件面板 /v1/files 走 resolve_workspace(吃到 env)看数据盘
- agent resume 走 from_db_path(锚 ROOT)看 ROOT/workspace
- 新建 task to_db_path 直接 500
现场症状:文件面板"目录尚未创建",但 agent 文件其实写在老 ROOT/workspace。

改法:resolve_workspace 回退成 显式 arg > cfg workspace_dir > 默 workspace
(均 ROOT/<值>)。import os 保留(别处仍用)。要把重写入区落独立数据盘改用
bind mount 把 /data/zcbot/workspace 接到 ROOT/workspace —— .resolve() 不展开
bind,内核路径保持 ROOT 内,relative_to(ROOT) 照常过,DB 不用改,dev 不受影响。

RUN.md:删 .env 的 ZCBOT_WORKSPACE_DIR 段;「workspace 落独立数据盘」段改
bind mount(+ RequiresMountsFor 开机顺序硬化);故障表替换旧 env 行 + 加
"目录尚未创建"诊断行。PROGRESS.md:2026-06-03 加回退记录。DESIGN 不动。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:24:14 +08:00
caoqianming 382a85e88e feat(workspace): resolve_workspace 加 ZCBOT_WORKSPACE_DIR env 覆盖(per-host 落盘)
prod 要把重写入的 workspace/users/ 落到独立数据盘(xfs prjquota),但
config/agent.yaml 的 workspace_dir 是 dev/prod 共用提交的,改绝对路径会带歪 dev。
resolve_workspace 优先级改为 env ZCBOT_WORKSPACE_DIR > yaml workspace_dir > 默 "workspace"
(对齐 sandbox.* 的 yaml+env 模式);env/cfg 值都按 ROOT/<值> 解析,绝对路径直接生效。
dev 不设 env 维持 ROOT/workspace,prod systemd 设数据盘绝对路径,两边不抢同一份 yaml。

PG 暂不迁(元数据库小,留默认 /var/lib/postgresql 少坑)。

RUN.md:env 段加 ZCBOT_WORKSPACE_DIR + 新增「workspace 落独立数据盘」段
(mkfs.xfs + fstab prjquota + rsync 迁移 + systemd env)+ 故障表一行。
PROGRESS.md:2026-06-02 加一条。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:42:00 +08:00
caoqianming 33bea27d85 fix(web): embed 模式登录页一闪而过 — body 首行同步隐藏 #login
#login 默认 display:flex(带 login-in 动画),加 body.embed-mode 隐藏它的
embedInit() 在 body 末尾才执行;单文件 3800+ 行,浏览器常在解析到底部脚本前
就先把登录卡画出来,造成"一闪而过"。改法:在 <body> 第一行加一段同步内联脚本,
?embed=1 时立即 add embed-mode,赶在 #login 解析/绘制之前隐藏它。

只是绘制闸门,底部 embedInit(postMessage 握手 / embed-waiting / token 分支)
完全不动,embed-mode 幂等。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:23:32 +08:00
caoqianming 4c1a26cee8 fix(deploy): update.sh 的 docker build 回到默认 TTY 进度 UI(撤 --progress=plain)
分层折叠刷新的 TTY UI 更直观;step 2 pip 去 -q 已足够让装包进度可见。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 16:07:35 +08:00
caoqianming 74580e951e fix(deploy): update.sh 加自更新重跑守卫,修"改了源仍报旧错"
脚本 git pull 会改自己:变量默认值在 pull 前已求值、bash 又按字节偏移边读边跑,
首次拉到"改 update.sh"的提交那一轮跑的仍是旧脚本(默认源还是阿里 -> litellm 报缺版本)。
pull 后检出本脚本有变更即 exec 新版本从头重跑(ORIG_ARGS 原样回传,ZCBOT_UPDATE_REEXEC 防死循环)。

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