Commit Graph

187 Commits

Author SHA1 Message Date
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
caoqianming 4eacfe1bd9 fix(deploy): update.sh 默认源改腾讯避开阿里滞后(卡 litellm>=1.83);build 跳过改 --skip-build;进度可见
阿里 PyPI 镜像同步滞后(只到 litellm 1.82.6),撞 requirements 的 >=1.83.0。
- 默认 APT/PIP/NPM 源阿里 -> 腾讯(已到 1.88);step 2 host pip 显式 --index-url(sudo -u 会洗掉 PIP_INDEX_URL env)
- ZCBOT_SKIP_SANDBOX_BUILD=1 env -> --skip-build CLI flag(开发期不留兼容)
- step 2 pip 去 -q + step 4 docker build --progress=plain,部署逐行流式可见

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 16:00:00 +08:00
caoqianming f66d55cc3b feat(deploy): 加一键部署脚本 deploy/update.sh(pull/pip/migration/build/restart/healthz)
固化日常部署:git pull --ff-only → pip install → db upgrade head →
docker build sandbox → systemctl restart → curl /healthz 验活。

两处钉死:① migration 不能漏,env.py 直读 os.environ 不读 .env,脚本从
.env 抠 ZCBOT_DB_URL 显式喂进去;② build 必先于 restart —— 容器复用 +
tools/ 烤进镜像,restart 时 shutdown_all 清旧容器,下次 ensure() 才用
新镜像重建。sandbox 每次 build 无所谓(重活在 COPY tools/ 之上,cache
让改代码部署秒过)。镜像源默认阿里,可置空回落官方。前置守卫 + healthz
失败 dump journalctl 非零退出。bootstrap 不进脚本,留 RUN.md。

RUN.md §部署 SOP 重写为指向脚本 + 手动 fallback;PROGRESS 加一条。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:52:44 +08:00
caoqianming fb5e68d9e7 fix(sandbox): 装 CJK 字体修 matplotlib / mermaid 出图中文方块
sandbox 镜像基于 python:3.12-slim 一个中文字体都没装,matplotlib /
mermaid(chromium)/ render_icon 出的 PNG 中文全是豆腐块。

- Dockerfile: apt 装 fonts-noto-cjk + fonts-wqy-microhei + fontconfig,fc-cache 刷索引
- style.py: 候选首位加 "Noto Sans CJK SC",matplotlib 优先用 Noto
- PROGRESS / RUN 故障表同步

改了 Dockerfile 必须重 build 镜像 + 清旧容器才生效。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 13:47:04 +08:00
caoqianming 0343dd6dd2 docs(progress): MP host 工具联网实测闭环 —— 换 next-gen key 后 step D 复测通过
初次 403 定位为 .env legacy 旧版 key 在新版 mp-api 失效;换 next-gen materialsproject.org
长 key 后查 Ca3SiO5 返 3 条 mp-xxxx + energy_above_hull,MP host 工具端到端通路确认可用。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 09:38:05 +08:00
caoqianming 68ce996ad2 feat(tools): documents/pymatgen secret-bearing 能力改 host-side tools,key 不进 sandbox
新增 tools/documents.py(document_list_kb/search/download)和 tools/materials_project.py
(mp_search_summary/get_structure/get_entries),key 只在宿主读取,sandbox/run_python 拿不到。
agent_builder 仅在对应 env 存在时注册。删 skills/pymatgen/materials.py::mp_rester() 旧入口,
smoke 改走 host tool。同步 DESIGN §6.7 secret-bearing 规则 + RUN/SKILL_LIST/两个 SKILL.md。

实测:MP step D 真连 api.materialsproject.org 返 403(工具行为正确,干净透传 [Error]),
疑似 .env 里 legacy key 在新版 mp-api 失效,待换 next-gen key 再验。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 09:35:10 +08:00
caoqianming 4bd074079a design: 加 §8.1 seedream i2i + vision 后续步骤 + probe 实测 base64 通
用户场景"seedream 出图→基于该图二次修改"/"上传外部参考图让 agent 据此干活"
两条主模型 DeepSeek V4 纯文本覆盖不了。详评 3 方案选 E+C 组合(seedream
加 reference_images 走 5.0 i2i + 新 tools/look_at_image.py 走豆包 Seed 1.6
vision tool 调度),否决 A(换主模型降 code/tool calling 质量+改 loop/memory
工程面 5×)/ B(后台隐式 vision 路由失 agentic 控制+token 浪费)。

实测 scripts/probe_seedream_i2i.py:豆包 Seedream 5.0 /images/generations
接受 image_urls=[data:image/png;base64,...] 作 i2i 输入,200 返回新图 TOS URL
+ usage.generated_images=1。约束:输出 size≥3686400 像素(~1920²),单张参考
≤10MB,最多 14 张。**内网部署无需对象存储中介**,排除最大工程不确定性。

E+C 实施清单/风险/升级到 A 的信号已落 DESIGN §8.1,本版仅 probe+design,
tool 与 prompt 改造未启动。RUN.md / SKILL_LIST.md 不动(无 CLI/env/skill 变化)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 12:47:04 +08:00
caoqianming e6eebbc9ad feat(web): tool_call 标题行改显中文活动描述 + 修 args 字段 bug
dev.html 实时流读 ev.data.arguments 但后端 emit 的是 args,字段名对不上
导致参数永远为空、连带 artifact 路径提取失效。新增 toolActivityLabel 按
12 个工具的关键参数套中文动词(执行命令/运行 Python/读写编辑文件/查找搜索
/联网搜索/抓取网页/加载技能/生成图像视频),实时流与历史回放两处同步;完整
参数仍在折叠 <pre> 里。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 12:24:54 +08:00
caoqianming aab1da3296 fix(executor_docker): 删 setsid 修 docker exec 延迟 stdout 丢失
实证:`docker exec ... setsid python -c "sleep(2); print"` 等满 2s 输出空,
同条件去 setsid 输出 hello。setsid 调 setsid() syscall 后 docker exec/runc
的 stdio attach 出问题,延迟输出被截。上一条 _run_subprocess 重写修了独立的
poll-loop bug 但不是用户当下症状元凶。setsid 历史是给 §7.5 Step 3b PGID kill
协议铺路,该协议未实现的当下是空头载荷 + 副作用。改 _exec_shell:141 / _exec_python:177
各删 1 个 "setsid"。回归测试加 test_run_subprocess_delayed_output_not_lost
(真子进程 sleep+print)+ test_argv_does_not_contain_setsid(防回潮)。19/19 PASS。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 11:02:17 +08:00
caoqianming 91cc14278c fix(executor_docker): 重写 _run_subprocess 修 docker exec stdout 多 chunk 静默丢失
旧实现在 poll loop 里反复 communicate(timeout=0.5) 违反 subprocess API 假设,
配合 setsid bash -c block-buffered stdout 在多 chunk 输出时 chunk 静默丢失,
LLM 调 run_python / shell 拿到空 [exit 0] 8 字符,误判 paper_server 不可用。
改单次 communicate(timeout=full) + 侧线程 daemon poll cancel + 入口 inline
快路径。回归测试用真子进程 bash sleep 多次 echo 锁死,17/17 PASS。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 10:26:59 +08:00
caoqianming f1a42ef13f skills: 3 SKILL.md 标 sandbox env key 拦截现状(documents/pymatgen ⚠️ + research )
run_python `_SENSITIVE_PATTERNS` 过滤器拦含 API_KEY/TOKEN/SECRET 字面的 host env
(挡 prompt 注入抽 JWT_SECRET 等),误伤 documents/pymatgen 从 env 读 key 的 helper;
docker backend 下 host env 根本不入容器,问题更彻底。LLM 还把 research 跟它们排版
类推也放弃,可 research 不持 secret 任何模式都能用 —— 加 callout 阻止误推 + 反模式
扩到禁所有 HTTP 客户端裸调,免得 urllib 钻空子跳过 helper 教学。
broker 下轮做(host tool 范式不取),DESIGN 当时再补。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 09:23:41 +08:00
caoqianming 09378b56c1 skills/review: 加长文档处理段(骨架扫描 + 用户挑章节深审 + 中间文件落盘) 2026-05-28 16:29:26 +08:00
caoqianming cf23c9d178 models: 加 local.{r1,qwen3} 内网模型档案,涉密任务用
DeepSeek-R1 (满血) + Qwen3-30B-A3B(服务端 alias 名是 Qwen/QwQ-32B,实际后端
Qwen3) 部署在 http://182.54.21.126:9000/v1,OpenAI 兼容,共享 LOCAL_LLM_API_KEY
env。thinking_mode=false(R1/Qwen3 默认就思考,不通过 reasoning_effort 控制)。

local.qwen3 已通连通性,local.r1 服务器侧调试中。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 15:55:37 +08:00
caoqianming 4b7d7e6f77 fix(skill_tool): docker backend 下返回容器路径而非 host 绝对路径
实测部署 dogfood analyze skill 时,LLM load_skill 拿到 host 绝对路径
`/home/lighthouse/zcbot/skills/analyze`,照 SKILL.md 拼 references 路径调 read
→ 容器内 namespace 没这条路径(容器只有 /sandbox/skills:ro 这 mount),抓瞎。

修法:LoadSkillTool 加 container_skills_dir 构造参数;agent_builder 在装它时
判 ZCBOT_SANDBOX_BACKEND==docker → 传 "/sandbox/skills",其它 → 保持原 host
绝对路径(开发期 host backend 不破)。

结构性收益:proposal/ppt/research/coding/pymatgen/stats_ml/plot_pub 全部 skill
references 在 docker backend 下自动 work,不用一个个改 SKILL.md 教用容器路径。

tests/test_load_skill.py 4 case 锁:host 走 host 路径 / docker 走 /sandbox /
末尾斜杠拼接不双斜杠 / 未知 skill 走原路径。docker executor 15/15 回归 PASS。

部署后:git pull + 重启 agent 进程让新代码生效(SkillRegistry 每请求重建但
LoadSkillTool 实例化在 build_agent 里,需要新进程)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 13:37:19 +08:00
caoqianming 203e14d15d skills: 加 analyze(科学问题拆解 / 引导,PICO + Issue Tree + 5Whys/TRIZ/DoE)
服务"用户拿模糊高层科研问题过来、还没决定要不要立项 / 不知道从哪入手"的早期阶段
—— 之前 10 个 skill 没人接这场景(proposal/research/stats_ml 都要先想清楚)。本 skill
不执行任务,只把模糊命题翻译成可操作子问题 + 实施路线图,最后接力给下游 skill。

四段式工作流:
1. PICO/PECO 规范化(P/I/C/O + FINER 五维自检)— BLOCKING
2. Issue Tree 拆解(MECE,默认机理-现象-工艺三层,叶子标 类型/优先级/能力描述)— BLOCKING
3. 按叶子类型分支深化:根因→Fishbone+5Whys / 创新→First-principles+TRIZ / 优化→DoE 选型
4. 实施路线图(每步标干什么/能力/产物/判停)+ TODO + 接力建议

7 文件 657 行:SKILL.md + 5 references(按需读)+ 1 template(analysis.md 骨架)。

关键决策:
- 不硬编码"叶子能力→skill 名"映射(skill discovery 已注入 prompt,改名不破坏)
- description 双重防护:触发条件写死探索阶段 + 输出末尾推 X 能力接力
- 不要 Python helper(全引导式对话,跟 review skill 同范式)
- TRIZ 摘 10 对建材矛盾(强度↑韧性↑ / 早强↑后期↓ / 致密↑透气↑...)够 80% 场景
- DoE 只规划设计类型 + 因素表,具体点位生成由下游 stats_ml 跑 pyDOE2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 13:18:54 +08:00
caoqianming 85415470d2 sandbox+infra: Python 3.10→3.12(host+Dockerfile)+ docker PYTHONPATH 加 /sandbox
为通过 3 个科学 skill 的 smoke 解决两件基础设施问题。

1. emmet-core 0.86.0rc1 用了 typing.NotRequired(3.11+),host .venv 是 3.10
   → mp_rester ImportError。选 3.12(当下 ML 生态默认稳定版,比 3.11 多一年
   优化,比 3.13 wheel 覆盖更全 Windows 不踩坑)。Dockerfile python:3.11
   → 3.12 同步升,部署机 rebuild image 时生效。

2. executor_docker.py:172 PYTHONPATH 由 /workspace 改 /sandbox:/workspace,
   修历史 bug —— skills/ bind mount 到容器 /sandbox/skills:ro,SKILL.md 教
   LLM `from skills.xxx import yyy`,docker backend 之前根本 import 不到
   (research/paper 同款受影响,只是 dogfood 一直跑 host backend 没暴露)。
   test_executor_docker.py:243 regression 测试改为 assertIn 含 /sandbox,
   15/15 PASS。

smoke 验证:pymatgen XRD / sklearn / statsmodels / plot_pub 全通,
mp_rester 联网遇 MP 服务侧 IP/ASN 403(LBNL 对国内 IP 临时封,非代码问题)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 11:35:02 +08:00
caoqianming 52f201404c skills: 加 pymatgen / stats_ml / plot_pub(建材院无机材料场景)
服务中国建材院无机非金属材料 R&D。从 K-Dense AI scientific-agent-skills 仓库挑
3 个 ★★★ skill fork,不走 npx 一键装(138 个 description 入 prompt 噪声 + 误触发)。

- pymatgen: 晶体结构 / XRD / 对称性 / 相图 / Materials Project。helper
  materials.py 内 CEMENT_PHASES 收 66 条中英文相名映射(水泥熟料 / 水化产物 /
  陶瓷耐火 / 玻璃晶相 / 常见矿物)、lookup_phase 大小写不敏感、mp_rester 从
  env MP_API_KEY 拿 key
- stats_ml: 纯指南 skill,场景导航 sklearn / statsmodels / PyMC 三选一,
  5 个工作流(配方-性能回归 / DoE 二阶响应面 / 显著性分析 / 贝叶斯小样本 /
  DBSCAN 异常配方)+ 16 条反模式
- plot_pub: 出版级 matplotlib,helper style.py 内 apply_pub_style() 一键
  设置中文字体跨平台 fallback (SimHei / YaHei / WenQuanYi) + viridis +
  dpi + PDF Type 42

requirements.txt 加 pymatgen / mp-api / scikit-learn / statsmodels
(pymc 注释,装包重按需开)。RUN.md env 段加 MP_API_KEY(可选)。
SCIENTIFIC_SKILLS.md 根目录沉淀整体评估,后续 materials_db 落地参考。
scripts/smoke_scientific_skills.py 三 skill 链路通路验证脚本。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 11:33:59 +08:00
caoqianming 1d6a9898e4 DESIGN §7.5: image 体积 / 多 user 资源 / 后续加包策略
把"image 大 = 资源占用大 = 多 user 容器爆炸"的直觉关联拆开:
① layer 共享 + 空载 sleep,image 大 ≠ 运行时吃更多 RAM;
② 多 user 瓶颈在并发 exec 非 idle 容器,杠杆全在 cgroup
   limit / 并发 semaphore / idle 回收;
③ 新增依赖采用 base 收敛 + per-user 持久化 venv + 使用频次沉淀
   (长尾 domain 包私有装,>30% × ≥3 次再沉淀回 base)。
落地排 Stage C Step 6 Executor。PROGRESS 加一条指针。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 08:13:39 +08:00
caoqianming 0b7c084458 skills+general_v1: ppt 歧义反问 + 路由产物形式歧义先问
skills/ppt/SKILL.md:3 上次收紧白名单+反例后,"MES 汇报方案" 这种请求
还是被路由命中 —— 反例列表只列了 "生成方案/写报告/出文档/做纪要",
"汇报方案" 未列入,而 "汇报" 在 LLM 语义里本就有强烈的 PPT 联想
(工作汇报/季度汇报多以幻灯片形式),足以压过 "必须明确点名 PPT"
的硬约束。

修法:
- ppt SKILL.md description 改三段( 触发白名单 /  不触发 [只留
  "报告/文档/纪要" 明确指向文档的词] / ⚠️ 歧义先反问)。把 "汇报/
  方案/材料" 从反例摘出来,改成 "先反问 'PPT 还是 Word/Markdown 文档'
  再决定 load",把判断权还给用户而不是赌 LLM 路由词典。
- prompts/system/general_v1.md Skill 机制段加 "产物形式歧义时先问"
  通用原则,跨 skill 生效(imagegen/videogen 各自 skill 内本来就有
  '问清楚再画' 逻辑,抽到 system prompt 让新加 skill 也继承)。

否决:
- 继续往反例堆 "汇报方案/汇报材料/汇报内容":堆词典治标不治本,
  下次 "做个 Q4 总结" 又得加。
- 路由层加 required_keywords 结构化字段,在 discovery_block 之前
  grep 兜底:跨多 skill 都得补字段,工程量大,短期 LLM 反问范式
  收益已够。
- ppt skill load 后再反问:路由命中就已是误触发,要在路由阶段拦。

代价:用户心里已经清楚要 PPT 但没说时会多一轮反问;缓解靠反问句式短
+ 暗示默认选项,一个字 "PPT" 就能过,比生成完整 deck 后推翻代价小
一个数量级。

热更新无需重启服务:agent_builder.py:197 每次 build_agent 现读
general_v1.md;SkillRegistry 也每次 build_agent 重建。DESIGN/RUN 不动。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 15:36:25 +08:00
caoqianming 8109f20345 ppt skill: description 收紧路由,避免 "生成方案" 被误命中
skills/ppt/SKILL.md:3 原文含 "方案" / "生成" 字样,Claude 路由时把
"生成一个方案" 也命中到 PPT skill。改成显式白名单
(PPT/幻灯片/演示文稿/.pptx/slide/deck)+ 显式反例
("生成方案/写报告/出文档/做纪要" 不触发)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 15:10:21 +08:00
caoqianming c4229bede4 skills: /v1/skills 每次现扫,加/删 skill 目录无需重启
lifespan 静态快照改 per-request 扫描,SkillRegistry 构造 ~3ms / 次,
e2e ~20ms 在 JWT + DB 噪声里完全可忽略。agent_builder 早已每次新建
registry,本次只补前端下拉这一处。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:47:47 +08:00
caoqianming da4e47139f dev SPA: embed+task_id 模型下拉 + 导出/清空键 disable 修复
- loadModels 收尾若有 task 选中补一次 renderChatMeta,修 embed+task_id 模型下拉
  在 models 接口 resolve 前 selectTask 已跑过的竞态(同步覆盖生图/生视频下拉)
- btn-export/btn-clear-msgs 不再按 n_messages 门禁,只要选中 task 就常亮;清空
  保留 running 期禁用。修历史 bug:清空后两键 disable,新对话进来 taskMeta 不
  重渲一直灰

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:42:19 +08:00
caoqianming eaf7f3ea1e Stage C 收尾包:资源 yaml + 磁盘配额 + 网络放开 + 容器内源持久化
dogfood + 信任同事白名单阶段 Step 4 完整 egress proxy 暂不做(沉淀为升级触发
信号:任一陌生用户注册 / 模型异常 outbound / 信任白名单出现非密切相识者 → 必上)。
本批 3 件:

(A) 容器资源 yaml 化(可调不重 build):
- agent.yaml 加 sandbox 段(memory/cpus/pids_limit)
- SandboxPool ctor 加三字段,优先级 env > yaml > 默(2g/1.0/256)
- setup_pool/init_pool 透传 sandbox_cfg
- sandbox check 输出加 [info] 4 行给运维一眼对账

(B) 应用层磁盘配额(§7.5 #4 软配额):
- migration 0008 user_disk_usage 单行 per user
- core/storage/disk_quota.py:parse_bytes("5gb"/int)+ scan_user_dir
  (os.scandir 跳顶层 .zcbot_tmp / .memory)+ upsert ON CONFLICT
  + check_disk_quota + scan_all_users 串行
- lifespan _disk_scanner 后台 task(启动跑一次 + 默 15min 周期)
- DockerExecutor write/edit 起手 gate 超额 [Error] 不调容器
- /v1/files/upload 同款 gate 超额 HTTP 413
- yaml `quotas.disk_bytes_per_user: 5gb` + `disk_scan_interval_seconds: 900`
- race 接受:扫描间隙写入轻微突破(image/video 配额同款 race-tolerant);
  外部用户开放前 OS 层 xfs prjquota 兜底
- 11 测试 covered parse_bytes / scan / 跳 dotfile

(C) 网络放开 + 容器内源持久化:
- network.py 去 --internal flag,容器走 docker bridge default 有 NAT outbound
- 已存在 internal network 不自动 rm 仅 warn,RUN.md 给迁移命令(避免破现有容器)
- iptables 红线段不动(169.254/127/10/172.16/192.168/100.64/PG_IP DROP),
  挡 cloud metadata + 内网扫描 + loopback,基线不依赖 proxy
- Dockerfile 加 /etc/pip.conf(global index-url + timeout 60) + /etc/npmrc
  (global registry),让运行时模型 `pip install foo` / `npm install bar`
  也走 mirror(此前 --build-arg 只 build 时生效)

unittest discover 46/46 PASS(原 35 + 新 11)。
DESIGN 不动(延后决策仍在 §7.7 Stage C 阶段语义内,触发信号沉淀进
PROGRESS / RUN);RUN.md 加 env 列表 + 网络迁移 + 配额 + 故障兜底 3 行。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 08:35:53 +08:00
caoqianming 23ff996d38 Stage C Step 3d: fs 工具进容器 + DESIGN §7.5 #6 重写(物理边界替代代码护栏)
Ubuntu dogfood 暴露 host 工具漏底:base_dir=Path.cwd() 无 user_root 校验,
模型 glob "*" 列出 host /home/lighthouse/zcbot/.git/.venv/... zcbot 源码自身。
DESIGN §7.5 #6 原写"host 工具走 paths.py::resolve_user_path 校验"是假命题
(代码里没那函数),绝对路径完全不挡。

修法:fs 工具(read/write/edit/glob/grep)也走 docker exec,物理边界替代
代码护栏(Phase B path validator 那条不做 ── 脆弱)。

- core/sandbox/tool_runner.py 新增:容器内 helper,stdin 接 JSON args,
  调 tools/fs.py 的 Tool 子类;base_dir=cwd,user_root=/workspace
- DockerExecutor 加 FS_TOOLS 信任域 + _exec_fs_tool:docker exec -i ...
  python /sandbox/tool_runner.py <name>,stdin 喂 JSON args(CJK / 引号
  透明传不被 shell metachar 切)
- _run_subprocess 加 stdin 参数 + is_fs_tool 分支返 stdout 直透(原 Tool
  返回串语义保持),exit≠0 stderr 当 ToolResult content
- SandboxPool 加 repo_root 字段 + <repo>/skills:/sandbox/skills:ro mount
  让容器内 read SKILL references 能解析
- Dockerfile COPY tools/ /sandbox/tools/ + tool_runner.py(build-time COPY
  而非 mount ── 容器内代码不应跟随 host repo 改动)
- web/app.py 透传 ROOT 给 init_pool
- 留 host 的工具:load_skill(SkillRegistry 内存查找)/ web_search /
  web_fetch / seedream / seedance(持 Bocha/ARK key 不入容器)
- DESIGN §7.5 #6 重写:"几乎所有工具进容器,host 只留持 key + 跨 user 的",
  原假命题溯源标注 2026-05-26 修正

代价:每 fs tool call +~200ms docker exec overhead,对话级 N≤15 总 1-3s,
LLM 推理 5-30s 下噪声。升级触发(§7.9 升级表)docker exec → unix socket RPC
仍按原信号(overhead/total > 30% 持续 / 长驻服务工作流)。

测试:test_executor_docker 加 4 fs 路径测试(argv 形态 / CJK stdin JSON /
exit≠0 stderr 透传 / timeout);改原 read 直通测试 → load_skill 直通
(read 现在进容器)。unittest discover 35/35 PASS。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 21:56:41 +08:00
caoqianming d93cc1a949 Stage C Step 3 hotfix: exec_user 跟随 build_arg + 镜像装 mermaid-cli
Ubuntu dogfood 暴露两个真问题:

(1) uid 错配:DockerExecutor 写死 --user 1000:1000,但镜像 build 时
    --build-arg HOST_UID=$(id -u) 跟随 host 实际 uid(腾讯云轻量 lighthouse
    uid=1001),docker exec 进容器 uid=1000 → bind mount owner 1001 错配 →
    写 /workspace/<wd>/ 全 EACCES,文件落 /tmp。
    改 DEFAULT_EXEC_USER = "zcbot"(username,docker 自动查容器 /etc/passwd
    拿 uid),无论 HOST_UID build 成 1000/1001/其他都跟 bind mount owner 对齐。

(2) proposal/patent skill 渲 mermaid 缺 Node:render_diagrams.py 调
    shutil.which("mmdc") 容器没装 → 退 mermaid.ink → sandbox --internal
    默 deny outbound API 也不通 → ASCII fallback 出 docx 没图。
    Dockerfile 加 chromium + nodejs + npm + @mermaid-js/mermaid-cli,
    +~400MB 接受成本(ASCII 不能用)。容器 chromium 缺 setuid sandbox +
    /dev/shm 不够大会跪,镜像落 /sandbox/puppeteer-config.json
    (--no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage) +
    ENV MERMAID_PUPPETEER_CONFIG;render_via_mmdc 改读 env 拼 -p 注入,
    host 上跑 env 没设行为零变化。
    PUPPETEER_SKIP_DOWNLOAD + PUPPETEER_EXECUTABLE_PATH 让 puppeteer 用
    容器内 chromium 不下载自带 Chrome(省 ~300MB)。

NPM_REGISTRY build-arg 同 pip 同款(腾讯云内网 / 阿里 npmmirror)。
RUN.md 加 NPM_REGISTRY 段 + 故障兜底 3 行(EACCES uid 错配 / mmdc 报
launch chromium / npm 慢)。DESIGN 不动(纯 bug fix + skill 依赖)。
unittest discover 31/31 PASS。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 21:47:33 +08:00
caoqianming 1a950dedb5 Stage C Step 5: main.py sandbox check + lifespan fs quota WARN
- main.py sandbox check 子命令:5 项独立探测 + 汇总 exit code
  ① docker daemon 可达 ② zcbot-sandbox:latest 镜像存在
  ③ zcbot-sandbox-net network 存在(warn 不 err) ④ 镜像 zcbot uid 与 host
  uid 对齐 ⑤ workspace/users 所在 fs 类型可 quota
- core/sandbox/check.py:detect_fs_quota(path) -> (level, msg) 抽出来给
  lifespan 与 CLI 共用;识别 xfs+prjquota/ext4+project/zfs/btrfs/tmpfs/其他
- web/app.py lifespan docker backend 启用时调 detect_fs_quota 打 WARN
  到 stdout(不阻塞启动,应用层周期扫描仍生效)
- err vs warn 分界:err = docker backend fail-fast 根因(daemon/镜像/uid),
  warn = 不阻塞启动但外部开放前要清(network 缺/fs 不可 quota)
- run_sandbox_check 用 module-level getattr 而非固化 CHECKS 元组,让
  unittest patch core.sandbox.check.check_xxx 生效
- tests/test_sandbox_check.py 19 测试覆盖各分支 + exit code 汇总;
  unittest discover 31/31 PASS
- RUN.md 加"部署前置对账"小节 + "配额硬化"重写(fs 状态→处理映射表 +
  xfs 升级 4 步) + 故障兜底 3 行;DESIGN 不动

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 16:41:16 +08:00
caoqianming dfac0acfa6 Stage C Step 3: DockerExecutor 集成 AgentLoop + web lifespan reaper
- core/executor_docker.py 新增 DockerExecutor:组合 HostExecutor+SandboxPool,
  shell/run_python 走 docker exec(setsid + --user 1000:1000 + --workdir),
  其他工具直通 host(§7.5 #6 信任域二分)
- run_python tmp .py 落 <user_root>/.zcbot_tmp/<task_id>/(dotfile,/v1/files
  天然过滤),容器内对应 /workspace/.zcbot_tmp/...,跑完 unlink
- ZCBOT_SANDBOX_BACKEND=host|docker env 切 backend,默 host(Windows dogfood
  零变化);docker 路径 pool 未 init → fail-fast 不静默退化
- web/app.py lifespan:docker backend 启动时 init_pool + shutdown_all 清孤儿 +
  60s 后台 reaper(run_in_executor 调 sync reap_idle);关闭时 cancel + 兜底清
- pool.py 顺手清 Step 2 债:asyncio.Lock → threading.Lock,ensure 改同步
  (主使用方是 BG 线程 tool call,ephemeral loop 会让 asyncio.Lock 跨锁失效)
- Cancel limitation 接受:Popen.kill() 仅杀 docker CLI 客户端,容器内进程靠
  idle 5min reaper 兜底;升级到 PGID 协议(§7.5 #3)等用户反馈触发
- tests/test_executor_docker.py 11 测试覆盖关键路径(host 直通/argv 形态/
  tmp 清理/timeout/cancel/未知工具/enable_run_python=False)
- DESIGN.md 不动(纯按 §7.5 #5 #6 既有协议实施)
- RUN.md 加 ZCBOT_SANDBOX_BACKEND env 段 + 切 docker 的前置条件 + 集成验证路径
- unittest discover 12/12 PASS

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 16:13:16 +08:00
caoqianming 160e801ab0 Stage C Step 2: Docker per-user 容器池 + iptables blocklist 基底
- deploy/sandbox/Dockerfile: python:3.11-slim + tini + iptables + non-root uid via HOST_UID build-arg + 全套 requirements
- deploy/sandbox/init.sh: 6 段红线 IPv4 + ::1 + ZCBOT_PG_IPS 环境注入,任一规则失败 fail-fast
- core/sandbox/network.py: ensure_network() 创 --internal zcbot-sandbox-net
- core/sandbox/pool.py: SandboxPool 容器命名 zcbot-sandbox-<user_id>,per-user asyncio.Lock,
  in-memory _last_active dict(Docker 23+ 移除 docker update --label-add),hardening flags
  全套(read-only / tmpfs / cap-drop ALL + NET_ADMIN / no-new-privileges / pids/mem/cpu limit /
  bind mount user_root → /workspace)
- core/sandbox/__init__.py: 公开 SandboxPool / container_name / setup_pool / NETWORK_NAME

Step 2 范围明确不含 AgentLoop 集成 / shell-run_python 切容器 / egress proxy / reaper task ——
pool 孤立 commit,Step 3 接入。本地 Windows 无 docker 走不动,Ubuntu 上 5 条 smoke 命令
(build / iptables 段 / non-root uid / read-only / 销毁)写进 RUN.md "Sandbox(Stage C,Ubuntu)" 段。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:44:34 +08:00
caoqianming 48f99cf66d Stage C Step 1: Executor 接口骨架 + HostExecutor in-process backend
- core/executor.py: Executor ABC + ExecCtx(user_id/task_id/working_dir/cancel_check) + ToolResult
- core/executor_host.py: HostExecutor 包原 tools dict,统一三种错误为 ToolResult
- core/loop.py: AgentLoop 接 executor 而非 tools,_execute_tool_call 收成单条 call_tool 调用
- core/agent_builder.py: tools 装完后 HostExecutor(tools) 包一层,working_dir 透传 AgentLoop

接口形状与 DESIGN §7.5 #5 sketch (`call_tool(name, args, ctx)`) 完全一致,
backend 无关 —— Step 3 docker backend 接入时 AgentLoop 零改动,只换装配层。
行为零变化:smoke 4 分支(unknown/bad args/happy/schemas)全过,unittest 1/1 PASS。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:07:55 +08:00
caoqianming d6af9a59fe 新增 patent skill + REVISIONS.md 修订日志机制
patent skill 写中国发明专利技术交底书,五阶段 workflow (素材摄取 → 挖点 → 检索 → spec → 逐章起草 → 自查渲染),BLOCKING 节奏同 proposal/ppt。复用 markitdown CLI + proposal scripts (render_diagrams/render_docx) + web_search/web_fetch + documents/research skill,零新增脚本;不实现 CNIPA 爬虫(维护成本高)。

REVISIONS.md 作为产物迭代 changelog,覆盖 proposal/patent/ppt 三个产物型 skill — spec = 宪法定调,REVISIONS = 每次卡点累加;单行 bullet 倒序追加,何时记/何时不记按 skill 领域定制(技术路线/区别特征/版式)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 09:34:01 +08:00
caoqianming a6ae6c4647 在 DESIGN §7.5 末尾沉淀 Stage C 沙盒实施硬协议
把 §7.5 的原则具化成 6 条硬协议,Stage C 实施时按表对账而非凭记忆:
① 网络 blocklist 硬编码段(cloud metadata 169.254/16 / loopback / 内网三段 /
   CGNAT,PG IP 单独再 block 一遍 belt-and-suspenders);
② egress proxy 模型(容器 HTTP_PROXY env + iptables DROP except proxy
   端口防 SDK 绕 env;宿主侧 proxy 做域名 allowlist + 字节计量 +
   network_audit 审计日志;allowlist 初始集列出 PyPI/GitHub/npm 等);
③ 进程组清理协议(docker exec 走 setsid + kill -- -PGID,防 nohup & /
   disown 跨 exec 持久化破"同 user 不内隔离"残留风险假设);
④ 磁盘配额硬化时点(外部开放前必须升 xfs/ext4 project quota 或 zfs
   dataset quota,否则扫描间隙打满共享 fs 拖死同节点其他 user);
⑤ Executor 接口走 backend driver + ZCBOT_SANDBOX_RUNTIME config 注入
   (未来切 gVisor/Firecracker/e2b 应用层零改动,避免 Docker API 形状
   泄漏到接口层);
⑥ 工具按信任域二分 dispatch — host in-process: read/write/edit/glob/
   grep/load_skill/web_search/web_fetch(原本就在 host 持凭据 / 走
   paths.py 校验,塞容器无收益付 200ms × N);container exec:
   shell/run_python(执行任意代码必隔离)。

同时写死 gVisor / Firecracker-e2b / 容器内 tool-runner 三档升级触发信号
+ 不升级的反向兜底理由,防凭审美决策。

PROGRESS:状态表 §7 SaaS 行 + 下一步候选 #2 加锚指向新清单;时间估值
2-3 天 → 3-5 天;补 5 项 Stage C 完成 DoD 红队回归用例(metadata IP
timeout / PG IP block / nohup 残留 docker top 不见 / 跨 user 容器互访
阻断 / 非 allowlist 域名 403)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 09:00:16 +08:00
caoqianming fe95df0b9d Add web_search and web_fetch tools via Bocha AI search API
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 11:37:33 +08:00
caoqianming ade7f3d1e1 Vendor markdown frontend assets 2026-05-25 09:31:36 +08:00
caoqianming 7a0d03fb29 Show upload progress 2026-05-25 08:54:06 +08:00
caoqianming 6e33f07bfb Show pasted upload chips 2026-05-25 08:43:07 +08:00
caoqianming f157a4e050 Add resizable web panes 2026-05-25 08:31:31 +08:00
caoqianming f3017480d0 Show full file names on hover 2026-05-25 08:18:20 +08:00
caoqianming 3525399041 Fix task list scroll container 2026-05-25 08:06:33 +08:00
caoqianming ae6b8630e2 ui: 手机端对话面板顶栏 + chat-meta 紧凑化 (5 按钮换行不竖排 + 模型 label 转 emoji)
- #pane-mid .pane-head 加 flex-wrap + 按钮 white-space:nowrap,
  消除 "完\n成" / "废\n弃" 这种内部断行;藏 "对话" label / spacer
- #chat-meta 手机端 gap 8→6px,藏 .tid,.desc 限宽 60vw ellipsis
- 三个模型下拉 label 加 .mdl-text / .mdl-icon 双 span,
  桌面文字 / 手机 emoji (💬/🖼/🎬) CSS 切换,桌面零变化

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 16:00:52 +08:00
caoqianming 72ae0ded0a feat(web): embed 模式接受 ?task_id=<uuid> URL 参数自动定位 task
首次签发 token / 已有缓存 token 两条进入路径都覆盖;
401 重签不重置选择,尊重用户中间 UI 切过的 task。
EMBED.md URL 参数表 + 故障兜底同步更新。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 15:27:19 +08:00
caoqianming 758486e2cd feat(quotas): 媒体生成每账号每日上限 (默 20 图 / 5 视频, yaml 可配)
config/agent.yaml 加 quotas 段;core/storage/usage.py 加 check_daily_quota
(COUNT usage_events WHERE user_id+kind+created_at>=本地今日 00:00);
SeedreamTool / SeedanceTool ctor 收 daily_limit, execute() 起手 if 超额
返 [Error] 不调远端不烧钱。错误串只暴露已用/上限 + 重置时间,不写
yaml 路径 (避免 LLM 转述泄漏内部 schema 给外部用户)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 15:21:39 +08:00
caoqianming 84388b278e ui: "+ 新建任务"按钮改通栏单独一行 (windows 320px pane 不再换行)
挪到 #pane-left 第一行 pane-head 之下的独立行,flex:1 撑满整行(primary 红底
通栏 CTA)。原塞 spacer 之后,中文五字在 320px pane 被挤断行。顺手删 @media
phone 里 #hd-new 紧凑覆盖(通栏不需要缩)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:44:30 +08:00
caoqianming 5e27ea9424 ui: "+ 新建任务"按钮从 header 挪到任务面板 pane-head
语义更贴(任务面板的动作);顶栏减负只剩身份区(brand/who/logout);两种模式
DOM 一致,顺手删了 embedInit 里动态 insertBefore 那段。文案保留完整"+ 新建任务",
样式加 small 跟周边对齐高度,保留 primary 红底显眼。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:40:49 +08:00
caoqianming 76f4c45350 feat(web): dev.html 加 iframe embed 模式 (?embed=1&parent_origin=...) + EMBED.md
embed 模式藏左上 brand / 用户名 / 退出登录,桌面整层 header 移除,移动端保留
header 给 mobile-tabs;"+ 新建任务" JS 移到任务面板 pane-head。postMessage 双向
握手:iframe 发 zcbot-ready / zcbot-401,父端回 zcbot-token{token,user_id,
user_name?};双向 event.origin 校验(parent_origin URL 参数白名单)。401 改写
logout 不 reload,而是通知父端 + 显灰底 spinner 等待层。复用已有 /v1/auth/login
SSO 入口,platform 后端 PLATFORM_KEY 代换 JWT,不下放浏览器。EMBED.md 写给
platform 工程的对接手册(URL / 协议 / Node+Python 后端示例 / 父端前端示例 / CORS
+ CSP frame-ancestors 收紧建议 / 调试 + 故障兜底)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:32:16 +08:00
caoqianming d6cbe8194b ui: chat-input 支持 Ctrl+V 粘贴文件上传 + chat-hint 反馈
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:59:53 +08:00
caoqianming 0c732590a4 ui: 文件预览弹框让出 chat-form 高度,打开期间仍可点击/打字
#file-preview-modal 加 bottom: var(--preview-bottom-inset, 0),openFilePreview 时 JS
量 chat-form 当前高度写到 inline style,关闭时清掉。card max-height 跟着收缩不溢出,
手机段用 100dvh 同理。无活动任务(chat-form 隐藏)走 0,弹框仍全屏。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:41:53 +08:00
caoqianming eec7eb156f feat(paths): 对外路径统一全形式 <wd_name>/<rel> + UI 一次性兼容历史简写
system prompt 加硬约束: 助手 echo 产物文件路径必须用 user_root 相对全形式
<wd_name>/<rel> (<wd_name> = task_dir 末段, 如 生图测试/videos/xxx.mp4),
不简写为 videos/xxx.mp4 这种 task 内裸形式 -- Web UI 按 <wd_name>/ 前缀挂
chip, 简写 → chip 失效用户点不开。媒体 tool (seedream/seedance) 的 saved:
行已是规范形式可直接照抄, ppt/proposal/coding 等 run_python/write 写文件时
自己拼。跨所有产物 skill 统一生效。

imagegen/videogen SKILL 把"把 saved: xxx 告诉用户"重复教学改成"照抄
saved 行, 详见 system「路径」段" (避免协议漂移, 新产物 skill 不用重复教育)。
ppt/proposal 等 SKILL 不动 -- system 协议自动管。

dev.html extractArtifactRels 加一次性兼容兜底: 产物目录裸路径
videos/xxx.<ext> / figures/xxx.<ext> (协议刚性前历史简写) prepend
<wdName>/ 拼成 user_root rel。**白名单显式枚举两项不扩展**, 长期老消息
归档后整段可删。

术语校准: 前缀叫 <wd_name> (working_dir 末段) 而非 <task_name> -- 用户允许
wd_name ≠ task_name, _display 锚 user_root 出来的是 <wd_name>。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:45:54 +08:00
caoqianming 7d3a93fc1f ui: dev.html 手机自适应 (两档 @media + tab 单列切换)
平板 641-1024px 强制 rail (不写 localStorage,回桌面用户偏好仍生效);
手机 ≤640px 单列 + body.mv-{left,mid,right} 切换 + header tab 按钮换行铺底;
selectTask 自动切到对话视图,100dvh 解决 iOS 工具栏挤压,
input/textarea ≥16px 防 focus 缩放,4 modal 改 min(92vw,…) / file-preview 全屏化。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:39:42 +08:00
caoqianming 7ff58c488e feat: 接入豆包 Seedance 2.0 Fast 视频生成 (文生视频) + videogen skill
- tools/seedance.py: 异步 submit /contents/generations/tasks → 5s 轮询 → succeeded
  后 download mp4 + meta.json 落 <wd>/videos/;失败/cancel 不计费;cancel_check 在
  轮询间检查,响应用户停止按钮
- config/media/doubao.yaml: 展开 video.seedance_2_fast (¥37/Mtok 文生 / ¥22/Mtok
  图生,token 公式校验 720p 5s = ¥4.00 完全对上源数据)
- core/storage/usage.py: record_video_usage,kind=video,units jsonb snapshot
  resolution/duration/ratio/fps/tokens/单价
- core/agent_builder.py: build_agent 加 video_variant + cancel_check 形参,
  cancel_check 必须 build 阶段传 (SeedanceTool ctor 持有用于轮询)
- web/app.py: GET /v1/video_models + MessageRequest.video_model + 透传
- web/static/dev.html: 顶栏第三下拉 (image 旁边) + state.videoModels/videoModel
- skills/videogen/SKILL.md: 六维诊断 (运动+镜头 替代 imagegen 的光线);BLOCKING
  门槛比 imagegen 更严 (¥4 vs ¥0.22) + 等 30-90s 出片
- prompts/system/general_v1.md: 加 seedance 触发指引 (平行 seedream)

phase 1 仅 t2v 文生视频,fast 上限 720p。API 端到端 smoke 跑过:路径/auth/错误解析
全通,body schema 待用户在火山方舟控制台开通模型后真出片才能验。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:30:54 +08:00
caoqianming 9a26e85da2 fix(ui): primary button hover 文字消失 (.primary:hover 守住 background)
button:hover:not(:disabled) 与 button.primary:hover 特异性同为 (0,2,1),
平手按源码序后者赢,但后者只声明 filter 没声明 background,导致 background
fallback 到前者的 --hover 浅灰,白字浅灰底视觉消失。

修法显式 background: var(--accent),brightness filter 在红底上正常提亮。
影响 "+ 新建任务" / "发送" 两个 primary 按钮。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:57:45 +08:00
caoqianming 29ff8e6a9e doc: sandbox 阻塞地位写进 DESIGN + §7.9 工具层不再加强黑名单
- §7.7 Stage C 标外部用户开放 hard prereq
- §7.8 加 shell+run_python 无沙箱风险行
- §7.9 加 2026-05-21 取舍:tools/shell.py BLOCKED_PATTERNS 是 trivial-bypass
  装饰品,不在它上面继续加规则;正确防线在 OS 层 (per-task docker exec
  + drop caps + read-only rootfs + bind mount + egress allowlist + cgroup)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:57:33 +08:00
caoqianming 6f9391dee3 ui(css): dev.html 圆角降一档 + 抽 token + modal 基类化 (style -11%)
抽 5 组 CSS token(语义色组 / 圆角分档 / mono / transition / shadow),顶栏按钮 hover + dd-item + badge 全切到 token(同色 selector 合并:export ≈ sp-copy 蓝、abandon ≈ sp-move 橙);4 个 modal 抽 .modal 基类(fixed/inset/bg/.show 五属性合并);.msg .body 与 file-preview .md-render 合并 markdown 渲染规则。圆角主流档 6px → 4px,modal card 8~12px → 6~8px,art-chip 999px 保留(胶囊语言)。功能 0 改动,JS 一行没动。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:50:17 +08:00
caoqianming fa6cb72103 ui: 工作目录回到原生 select + sentinel + 二级 input (modal + 顶部 filter)
combobox 方案推翻 — 即使 show 不过滤,modal wd 因联动有值后用户直觉仍是
"得点开下拉看选项",自实现 panel 不如浏览器原生 select 稳。

- modal nt-wd-sel 第一项 sentinel "+ 新建「<name>」"(updateSentinelLabel
  跟 name 实时刷),sentinel 选中显示二级 nt-wd-new 默认跟随 name,
  选已有目录隐藏;wdManuallyEdited 锚到二级 input
- 顶部 filter-wd 改 select,onchange → loadTaskList(无 debounce)
- loadFolderSuggestions + populateFolderSelects 灌两个 select,保留当前选中
- enterApp fire-and-forget 预拉 folders 让左 pane 一打开就有选项
- hint 在"新名碰到同名"时提示"将复用而非新建"
- combobox 工厂 + .combo CSS + datalist 残留全删

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:20:34 +08:00
caoqianming 32a8c348a8 ui: 工作目录 combobox 从 datalist 改自定义 dropdown (modal + 顶部 filter)
datalist 在 input 非空时下拉被浏览器按前缀过滤丢失,联动 name 后
体验比原 select 还差。改方案:

- 抽 makeFolderCombo({input, panel, onPick}) 工厂:focus/click 显完整列表,
  input 子串过滤,mousedown preventDefault 兜住 blur 提前关 panel,
  键盘 ↑↓ Enter Esc
- modal nt-wd-sel 和顶部 filter-wd 都接工厂,各自传 onPick
  (modal 置 wdManuallyEdited + updateWdHint,filter 走 loadTaskList)
- .combo / .combo-panel 样式提到全局
- 删 <datalist> 元素 + loadFolderSuggestions 灌 datalist 的代码,
  ensureFoldersLoaded 改用 state.folders 判断

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:03:37 +08:00
caoqianming 8d7f60d899 ui: 新建任务弹窗工作目录改 combobox + 跟随任务名联动
- nt-wd-sel 从 <select> 改 <input list=folders-datalist>,删 "+ 新建目录…"
  sentinel 和二级 nt-wd-new 输入框
- 加 wdManuallyEdited flag:name 输入时若未脱钩则同步到 wd;wd 非空输入
  置 true 脱钩;wd 清空重置 flag 但保持空(避免 backspace 想换名字时被
  立刻填回打断)
- loadFolderSuggestions 只灌共享 datalist,缓存到 state.folders 供 hint
  比对"命中已有/新建"
- submit 保留 working_dir || name fallback 兜底空值

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:35:37 +08:00
caoqianming 468cef5fcc feat: system prompt 注入 task 预选 skill 提示 + imagegen 加 BLOCKING 等 prompt 确认硬约束
1) core/agent_builder.py::_build_system_prompt 加 task_skill 参数,非空时在
   「工作目录与 task 上下文」段加一行 "- **task 预选 skill**: `<name>`",空
   字符串走老路径 prompt 字节级一致。LLM 拿到事实 + general_v1.md 已有
   "对应 skill 领域先 load_skill" 规则自然组合 → 主动 load。否决"完整
   SKILL.md 预注入 prompt"方案(会把 tasks.skill 升格成 binding,投入产出
   比不划算)。

2) imagegen SKILL.md 加  调 seedream tool 前必须把最终 prompt 贴给用户看
   + BLOCKING 等明确确认硬约束:① 顶部流程加确认步骤;② 新增「调 tool 前
   的强制门(铁律)」段定义回复分类(可以/OK/画吧/嗯 算确认;改 X → 重贴
   重等;沉默 → 继续等;模棱两可 → 追问到明确);③ 新增「调 tool 前再过
   一道」段给具体贴 prompt 的对话格式;④ 调用范式段加"前置条件已确认才调";
   ⑤ 反模式加两条(没贴就调 / 模棱两可当确认)。本质是把模型脑内装配摊
   到对话层,装配 ≠ 授权调用。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:59:06 +08:00
caoqianming 02307a08d1 docs(skill): imagegen 补「比例」维度 + 修掉臆造的"只能正方形"
用户反馈生图 skill 缺比例引导。原 size 表写"比例只能正方形"是基于
doubao.yaml + tool 参数描述只列三个正方形例子的间接推断,无验证。

改:
- 诊断五维 → 六维,加「比例/尺寸」(ppt 16:9 / 海报 9:16 / 头像 1:1 /
  公众号 2.35:1 / 书籍 3:4)
- 追问范式加比例项,上下文推断给 ppt/海报/公众号/学术示意四种用途的默认比例
- size 参数表重写成「按用途选比例 → 再选分辨率」二级表
  (1920x1080 / 1080x1920 / 2560x1088 等参考值)
- 删掉臆造的"豆包 5.0 只能正方形"
- 失败解药表加 2 条:比例错改 size 不动 prompt;非方形 API 报错回退默认
- 反模式加 1 条:不问比例就默认走 yaml 1:1 大概率不对

承认 unknown:豆包 5.0 实际支持哪些非方形 size 没验证,首次用错就回退默认 +
让用户协商,不臆造支持列表。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:51:13 +08:00
caoqianming bfcc231f1e fix(skill): imagegen 触发修 — system prompt 强制 load_skill + 扩 description 触发词
实测"绘制一张办公室的艺术图"没 load skill,两根因:① general_v1.md 「媒体生
成工具」段把 seedream 写成一级直觉(列了"画/出/来张"等关键词 + 直接调 tool 的
how-to),压过 skill discovery block 的微弱声音;② imagegen description 关键
词覆盖窄(没有"画/绘制/艺术图/图片"等朴素词)。

修法:system prompt 那段改成"调 seedream 前必须先 load_skill('imagegen')",
细节判断全移到 skill,只留 ¥0.22 计费 + 不装饰生成 + 不连发三条兜底硬约束;
imagegen description 扩 17 个触发词(画 / 绘制 / 出图 / 来张 / 艺术图 / 写实
图 / 场景图 / 插图 / 封皮 / 头图 ...)。两层联动:一级 prompt 指引到 skill,
二级 description 提匹配概率。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:56:29 +08:00
caoqianming f2b1ad085b feat(skill): 新增 imagegen skill 引导用户说清楚生图需求(seedream tool)
五步法:诊断模糊度 → 一次性给推断 + 待确认项 → 用户拍板 → 装配 prompt → 调
seedream tool。五维清单(主体/场景/风格/构图/光线)缺 2 维以上就先问;mermaid
vs seedream 选型给"默认倾向 mermaid + 反向选 seedream 信号 + 模糊时主动问用
户"三段式(没在 system prompt 那条流程图优先 mermaid 上一刀切,留 skill 层细
化判断)。size/watermark/search 默认值取舍 + 失败不复发的解药表 + 8 条反模式。

skills/imagegen/SKILL.md 单文件(参考 coding skill 范式);seedream tool 本身
不动,本 skill 仅是流程引导层。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:49:51 +08:00
caoqianming fe21ca1e8c ui+api: 登录页加管理员发用户入口 + 删 chat meta 重复的 条/tok 显示
- web/auth.py 加 `create_user()` helper(CLI / web 共用)+ `AuthConfig.admin_token` 从
  `ZCBOT_ADMIN_TOKEN` env 读,未设 → 接口返 503(功能默关)
- web/app.py 新增 POST /v1/auth/admin/create_user,403/400/409 四分支(口令错 /
  邮箱不合法或密码 < 6 / 邮箱占用);main.py user_add CLI 改调同 helper 避免漂移
- web/static/dev.html 登录卡片右下加 ghost link "+ 管理员添加用户" + 弹窗
  (email/密码/管理员口令),成功后回填邮箱到登录表单不自动登录;
  同时删 chat 顶栏 ${n_messages} 条 · ${tokens} tok 一行(与左 task 列表重复)
- RUN.md 加 ZCBOT_ADMIN_TOKEN env 说明 + 故障表两行;PROGRESS.md 加一条 2026-05-21

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:51:02 +08:00
caoqianming 7bdb6ca5eb feat(skill): documents skill 接内部材料学科知识库(document_search API)
- skills/documents/{SKILL.md, client.py} 4 函数 list_kb / search / download / health
- 走 https://ai.ctc-zc.com:8100/api Bearer 认证;env DOCUMENT_SEARCH_API_KEY + DOCUMENT_SEARCH_URL(可覆盖)
- search 默认返 md_content(整篇 Markdown 50K-200K 字符级),反模式段约束"只 print 前 300 字"防爆上下文
- smoke 实测后校准 SKILL.md:库实质是 7 个材料学科(胶凝/陶瓷/玻璃/晶体/复合/耐火/检验检测,21W+ 文件)预收的英文学术论文 + 跨语言语义检索(原猜"主语料中文"错了)
- 与 research(OpenAlex 全网)互补:documents 已 Markdown 化对 LLM 友好,但仅覆盖材料领域

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:31:21 +08:00
caoqianming 52c25b9404 ui+api: dev SPA SSE 客户端 3 次退避重连 + stream_events 非活跃 task 立即吐 done
--reload 重启 / 网络抖时 fetchSse 拆出 consumeSseStream + 包重连壳
(1s/2s/4s,EOF 未见 done/error 触发重连);后端 stream_events 入口检
tasks.run_status,非 running/cancelling 直接关流,避免重连卡在空 broker
无限挂 ping。3 次仍失败 → 卡片末尾红色"请重发"。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:14:51 +08:00
caoqianming c5dcbb7e24 docs: 精简 PROGRESS.md(每条 1-2 句,~41K → ~7K tokens)
已完成关键能力段每条压到 1-2 句:做了啥 + 关键判断;
删掉大段 Tradeoff / 对比方案 / 反方案(git log + DESIGN §7.9 已存档)。
保留全部时间线条目 + 状态表 + 决策表 + 文件清单 + 下一步候选。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 14:40:38 +08:00
caoqianming f197b06cb4 fix(skill): research fetch_pdf 改走静态直链跟 fetch_xml 对齐(绕开 paper_pdf_view 路径 bug)
二次迭代 redeploy + nginx 修 Host 透传后,fetch_xml 5/5 PASS 但 fetch_pdf 仍 5/5 404 —— 同批 paper 同目录 XML 能下 PDF 不能,说明 paper_pdf_view 的 init_paper_path 路径计算 bug(非数据问题)。fetch_pdf 改成读 paper.pdf_url 静态直链 + _stream_to,跟 fetch_xml 同范式;删常量 _PDF。smoke 跑通 5.4MB / 3843ms + 152ms 复用,5/5 候选 100% 成功。

paper_pdf_view 端点本身的 bug 不修(浏览器用户可能还在用),由 paper_server 后续处理;zcbot 不再走它。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 14:30:25 +08:00
caoqianming a1c0e71703 feat(skill): research list 加 pdf_url / xml_url 直链 + 新增 fetch_xml + smoke 扩 trgm/xml 步
paper.py: _LIST_FIELDS 扩到 16(加 publication_date / has_fulltext_xml / pdf_url / xml_url),新加 fetch_xml(id_or_doi, working_dir) helper 走 paper_server media 静态直链(从 paper.xml_url 读,paper_pdf_view 不支持 XML),抽出 _stream_to 共用;fetch_pdf 行为不变。
SKILL.md: 工作流加 "XML 优先 PDF" 原则(已结构化标签 vs OCR 抽取),四函数清单 + 错误处理表更新 fetch_xml / xml_url 空场景。
smoke: 加 step 0 验 pg_trgm 索引速度(>5s 警告 migration 没生效)+ step 4 fetch_xml 多候选轮询 + 复用,step 1 字段集 expected 同步扩到 16。

paper_server 侧改动(serializers pdf_url/xml_url + migration 0006 pg_trgm)见 paper_server 仓库 6a5a5d7b。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 13:49:34 +08:00
caoqianming b480147fb2 fix(usage): 顶栏 token 累计修 — sync_task_tokens 改走 messages SUM,删 LLM.TokenCounter
5/20 切流式后 LLM.token_counter.add() 只在同步 chat() 里调,流式 chat_stream() 路径从不更新它,
sync_task_tokens 从内存计数器读累计 → tasks.tokens_prompt/completion 一直 0 → 顶栏 0 tok。
DB 验证:5/20 之前 task 数据正常(4568/934),之后 0/0;但 messages.tokens_in/out 一直对
(record_chat_usage 写),source-of-truth 完好,只是冗余汇总列没同步。

改 sync_task_tokens(task_state) 走 SELECT SUM(tokens_in/out) FROM messages WHERE task_id=?,
删 TokenCounter 类 + ConsoleEventSink 的 token_counter 回调 + spinner ctx 尾巴。
一次性 backfill 4 个被影响 task 的累计列。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 13:39:57 +08:00
caoqianming d2fd89f3a4 api+ui: 同 wd 并发软警告 banner + /v1/tasks 加 run_status 筛选 + task header @wd 显式化
评估 γ(同 wd 单活 gate) / short_id 全产物隔离 / clone task 三方案均判定过度工程 — dogfood 同 wd 基本不并发。走 Claude Code 同款"信任 + 软警告 + 承认 limitation"。后端 /v1/tasks 加 run_status query(逗号分隔 allowlist),前端 selectTask + SSE 收尾两点拉同 wd 活跃 task,有命中挂 #wd-concurrent-warn 黄底 banner(⚠ 项目名 + 邻居 task name + run_status + 等 N 个),不挡发送。renderChatMeta 把 📁 wdName 改为仅 wdName !== taskName 时显示。DESIGN §7.8 风险表文件级悲观锁行(本就未实现)换为 known limitation + 软警告;§7.9 新增取舍条说明三方案为何不选。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 13:01:21 +08:00
caoqianming 97bcd5ae1e feat(skill): paper_server → research skill (search / get_paper / fetch_pdf)
skills/research/{SKILL.md, paper.py}: 接内部部署 paper_server 的 3 个 helper,LLM 经 load_skill("research") + run_python 调用。范式选 skill 而非 tool/MCP/裸 httpx/lib —— 频次低且 helper 范式让 API 漂移时改一处。tools/run_python.py 注入 PYTHONPATH=base_dir,让子进程能 `from skills.research.paper import ...` 不必折腾 sys.path。base_url 默 http://paper.xxhhcty.xyz:8080,可 PAPER_SERVER_URL env 覆盖。遗留:paper_server 侧 PaperDetailSerializer 加 abstract 字段由用户重新部署。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 13:00:37 +08:00
caoqianming 5f0f296a23 ui(media): chip 三规则定型 — 工具 I/O 走产物白名单 + 助手正文无条件挂 chip 绕开 seenRels
修截图反馈"助手回复 echo 的产物路径没挂 chip"。① 工具 I/O(args/result):chip 抽取只对产物工具(seedream/seedance),通用工具 echo 是引用不该挂;② 产物图/视频:inline 大图;③ 助手正文:永远挂 chip 且 allowInlineMedia=false,只小按钮不重复 inline 大图。SSE 处 upgradeMediaArtifacts 同步 gate 到 isProducer 下。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 08:44:36 +08:00
caoqianming d402c8771c ui(media): chip 解绑产物白名单 — 通用工具 echo 路径也挂 chip,图片/视频 inline 仍只对产物开
renderArtifactBarHtml 加 allowInlineMedia 参,false 时图片/视频也走 .art-chip 按钮(点开仍弹预览 modal);4 处 tool 调用点解绑 ARTIFACT_PRODUCING_TOOLS chip gate,只透传给第二参控制 inline;SSE 两处 upgradeMediaArtifacts 同步 gate 到 if (inlineMedia);assistant 正文默认 true 不变。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 08:31:39 +08:00
caoqianming 972f36db20 fix(loop): tool message append 补 name 字段 + backfill 历史 — 修历史 task 重开后 seedream banner/chip 不展示
session.append 的 tool 消息只存 role/tool_call_id/content,没 name;前端历史渲染依赖 payload.name 判断产物工具白名单 + 抽 elapsed banner,刷新后两者全黑(流式正常因为 SSE event 单独带 name)。scripts/backfill_tool_message_name.py 按 task 走 assistant.tool_calls 建 tool_call_id→name map 回填,dry-run 默认,--apply 真写,幂等。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 08:14:23 +08:00
caoqianming 1e4548dd0c ui(media): chip 抽取改"产物工具白名单"门控 — grep/read 类工具结果里 echo 的路径不再误挂 chip / 误 inline 图片
用户报"生成的图正常预览,但 grep 工具的结果里 figures/ 下另一张老图也被 inline 出来了"。根因:extractArtifactRels + renderArtifactBarHtml 是通用产物展示(image/video → inline / 其他 → 可点 chip),通用工具结果里 echo 的任何带扩展名路径都会被当产物挂出来,seenRels 只能去重同路径挡不住"figures/ 下别的老图首次出现"。修法:加 ARTIFACT_PRODUCING_TOOLS=new Set(["seedream","seedance"]) 白名单,4 处工具 I/O 调用点(renderMessages tool 历史 + assistant tool_calls args + SSE tool_call + SSE tool_result)用 .has(toolName) 三元短路;assistant 正文不门控沿用 seenRels 兜底。chip 本意是"这次工具调用新产出的东西",grep/read 输出里的路径是引用不是产物。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:06:01 +08:00
caoqianming 57ac7214e5 refactor(paths): 砍 ROOT 外路径分支 — 写入入口只接 simple name join workspace
to_db_path 越界 raise(原 str(pp) 静默存绝对),from_db_path 删 is_absolute 分支只 ROOT/s。ROOT 外分支是防御性死代码:写入入口(POST /v1/tasks → working_dir_from_name)只接 simple name,0002 migration 已清理历史绝对串。DESIGN §7.4 注释同步。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:05:32 +08:00
caoqianming 3c2e25d912 api+ui(chat): 删输入框冗余上传按钮 + 加润色按钮 — POST /v1/tasks/{id}/optimize_prompt 走 task 当前模型同步润色,usage_events 新 kind=prompt_optimize 单独记账不污染主对话累计;前端 execCommand insertText 接 textarea 原生 undo 栈,Ctrl+Z 一次回到原文
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 21:20:16 +08:00
caoqianming febe04a569 ui(media): tool 结果与 assistant 正文同路径 chip/inline 图去重 — Set O(n) + CLAUDE.md 加 "实施前先对方案" 段
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 16:33:47 +08:00
caoqianming 8c9e0d0d3a api+ui(media): 顶栏生图模型下拉(消息级,不入 DB) + 中间产物图片/视频内联展示
- 新加 GET /v1/image_models(扫 config/media/doubao.yaml image 段)
- POST /v1/tasks/{id}/messages body 加可选 image_model 字段,_run_agent_bg
  透传到 build_agent(image_variant=...);agent_builder 据此装配 SeedreamTool
  variant,不命中 yaml 静默回 fallback,空 → 沿用第一个
- dev SPA:顶栏「模型」旁加「生图」下拉(默认锁第一个 variant,per-session
  state 不持久),sendMessage 携 image_model 一起发
- 中间产物 chip 按文件类型分支:图片/视频走 .art-media 异步 fetch blob →
  填 <img>/<video controls>(Bearer header 不允许 <img src=> 直 URL);
  图片点击仍弹模态放大,视频用浏览器原生 controls;openFilePreview 加
  _showVideo + .mp4/.webm/.mov/.mkv/.m4v 进 _EXT_GROUPS;_mediaArtifactCache
  按 rel 复用,切 task 时 revoke
- DESIGN 不动(无架构 / schema 变化);PROGRESS / RUN 同步

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 16:18:36 +08:00
caoqianming a3acb97079 feat(loop+ui): LLM 调用切 streaming — cancel 秒退 + 前端打字机 + 发送/停止合并单按钮
- core/llm.py: 加 chat_stream() generator(stream=True + include_usage),
  generator finally 关底层 httpx 连接;_build_kwargs 抽出来 chat/chat_stream 共用
- core/loop.py: 主循环 _stream_llm() 流式迭代,chunk 间 poll cancel 命中 break,
  litellm.stream_chunk_builder 拼回 response 给 tool_calls 解析 + usage 记账;
  content delta 即时 emit text 事件激活前端打字机渲染
- web/static/dev.html: chat-send + chat-cancel 合并 chat-action 单按钮,
  setActionMode(idle/streaming/cancelling) 切态;streaming 期间 Enter 不触发停止
- cancel 延迟从「整轮 generation 时长」(几十秒)降到「单 chunk 间隔」(100ms 级)
- 文档:DESIGN §3.1 + API 表 + risks 表翻转 tradeoff;RUN 接口 + 故障兜底同步;
  web/app.py docstring 对齐;PROGRESS 加条目 + 文件清单行数

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:46:54 +08:00
caoqianming c04b8ba05e feat(media): 接入豆包 Seedream 5.0 图像生成 tool + 0007 cost_usd→cost_cny 全表统一币种
- 新 tools/seedream.py:调 ark /images/generations 同步生成,产物落 figures/<ts>-<rand>.png + 同名 .meta.json
- 新 core/ark_client.py:火山方舟 HTTP 封装(base URL + bearer auth + 异常翻译 + download),共享给后续 seedance
- 新 config/media/doubao.yaml:独立命名空间;价格表注释 last_updated + 调价路径说明
- core/storage/usage.py 加 record_image_usage:单价 snapshot 进 units jsonb,防调价污染历史
- agent_builder.py 注册 SeedreamTool:仅当 ARK_API_KEY 设了才挂(无 key 用户无感)
- 0007 migration:tasks/usage_events 双 rename cost_usd → cost_cny,×7.2 一次性折算;
  record_chat_usage 内部把 litellm USD 同样 ×7.2 落 CNY,免分类汇总
- prompts/system/general_v1.md 加「媒体生成工具」段,提示按需调用、不主动装饰
- dev SPA tool_result 折叠态显示 banner(model/size/cost/elapsed 徽章),不展开就透明
- scripts/smoke_seedream.py:端到端走通(待 ARK_API_KEY 配齐真跑会产生 ~¥0.22)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:20:34 +08:00
caoqianming e1f09547e0 api+ui(files): POST /v1/files/delete 加 recursive 字段 — 顶层目录被 task 引用闸 + dev SPA 二次确认显示条目数
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:38:58 +08:00
caoqianming 5ff09b9aca fs tools 输出 user_root-relative 路径 + dev SPA chip 锚点修正 + assistant 正文也挂 chip
- tools/base.Tool: 加 user_root kwarg + _display(p) helper(p 在 user_root 内 → POSIX 相对,外 → 原绝对)
- tools/fs.py: Read/Write/Edit/Glob/Grep 所有结果串里路径都过 _display,不再泄 user_id / 部署根
- core/agent_builder: build_agent 把 user_root 透传给所有 tool(含 ShellTool / RunPythonTool / LoadSkillTool — base 默认 None 不影响)
- tools/skill_tool: __init__ 加 user_root 转传 super
- web/static/dev.html: 新加 _workingDirName helper(从 db 形 working_dir 取末段 + 跳过外部绝对路径);5 个 chip 抽取点统一用它代替原 working_dir 直取 → 根治 chip 点击 404;assistant 正文也接 chip 抽取

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:22:20 +08:00
caoqianming d1a2961bf4 api+ui(dev SPA): POST /v1/tasks/{id}/clear 清空对话 + 顶栏「清空对话」按钮
- 后端: 同事务 SELECT FOR UPDATE 锁 + active run 检查(running/cancelling → 409) +
  DELETE messages + reset tasks.tokens_prompt/completion/cost_usd=0 + run_status='idle'
- usage_events 完全不动 — 用户级账单 source of truth 与对话清空解耦;
  message_id FK 是 ondelete=SET NULL,task_id/units/cost_usd 全保留可重建累计
- dev SPA 顶栏在导出后插「清空对话」(紫色 hover,介于完成绿/废弃橙/删除红),
  running||n_messages==0 → disabled,confirm 二次确认 + 同步刷新 chat-meta / 消息 / 任务列表
- FS 文件保留(沿用 task delete 的"FS 视图可重生"心智)
- RUN.md API 表 + 故障兜底加 409 case;DESIGN.md 不动(无架构 / schema 字段语义变化)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:19:08 +08:00
caoqianming ecff1d7858 ui(dev SPA): tool_call/result 卡片下加 artifact chip — 点击复用文件预览 modal,免再去右栏找
PROGRESS.md 同步。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:08:15 +08:00
caoqianming c4fac2428b skills+core(命名约定): task 级宪法文件 <date>-<short_id>-<name>.spec.md + spec_lock → spec 简化
同 working_dir 多 task 共享中间产物是设计意图(素材跨本子复用),
但 spec 这种 task 1:1 宪法文件必须隔离 — 否则两本子 spec 直接撞。
文件名三段式:
- task_short_id (task_id.hex[:8],永不变) 主锚 → glob *-<short_id>-*.spec.md 字典序最大 = current
- date 让"重定调"写新文件而非 edit 覆盖,旧版自然成历史快照
- task_name 作建时元数据,改 task.name 不 cascade(由 short_id 兜底定位)

约定由 core/agent_builder.py::_build_system_prompt 单点注入
(task_id / today 实际值嵌入,所有 skill SKILL.md 引用同一份)。
proposal / ppt SKILL.md 阶段一加"glob 检测已有 spec → 询问沿用/重定调"分支。
模板 templates/spec_lock.md → spec.md (git mv 保历史),_lock 后缀无信息量去掉。

未动:DB schema / PATCH /v1/tasks/{id} 改 name 入口 / 其他中间产物扁平共享
/ quality_check.py (--spec 接路径)。反方案(cascade rename / spec 入 PG /
物理 task 子目录)及"何时升级到 DB 化"信号见 DESIGN §7.9。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:03:21 +08:00
caoqianming 775962d68a ui(dev SPA): 任务行 meta 数字槽位等宽 + 折叠按钮挪 pane-head + rail 模式 + time-ago 锁宽完成跨行对齐
- meta 加 tabular-nums + .num 槽位 (min-width:44px + text-align:right) + fmtTokens (1.2k/123k/1.2M)
- .num.right-group 把 [N条][Ntok][time] 整组用 margin-left:auto 推右
- time-ago 加 min-width:64px 锁宽: 整组右锚点稳定后, 跨行"条/tok"后缀才真正垂直对齐
- 折叠按钮挪到 pane-head 紧贴 ↻ 刷新; 折叠态改 VS Code rail 模式 (40px 列 + 只留 toggle 一直可点)
- 删 header #hd-toggle-left 冗余按钮 + header .icon-btn CSS (rail 模式下不需要)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:30:14 +08:00
caoqianming 5b67d29f59 ui(dev SPA): 左 pane 280→320px + header 折叠 toggle + 任务行精简 meta 防 CJK 断行
- grid 左列 280→320px (从 chat 借 40px), 任务名 / 描述 / wd 更舒展
- header 最左 toggle 按钮: body.left-collapsed → 列归零 + #pane-left display:none, chevron 翻向, localStorage 持久化
- 任务行 meta 删 id8 (挪到 row title 仍可查) + 各 span white-space:nowrap + badge/time-ago flex-shrink:0
- wd/desc 副行恢复 inline overflow:hidden ellipsis (单文本带不是 flex 子元素)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:18:42 +08:00
caoqianming afebf25d79 ui(dev SPA): 任务列表 pager bar → 滚动加载(IntersectionObserver sentinel)
- 删 #task-pager / renderPager / resetPageAndReload / btn-prev|next 三件套
- 加 #task-sentinel + IO root=#pane-left + rootMargin 200px 提前触发
- loadTaskList({append}) 双语义: reset 抢占(_taskLoadSeq 丢弃过期响应) / append 互斥
- renderTaskList(append=true) 不 clobber 已渲染行, 事件 handler 只挂新行
- 首 pane-head 加 "共 N 个" 总数小字补偿丢失的 pager-info

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:09:46 +08:00
caoqianming 97d838a9ec ui(dev SPA): 任务列表行加最近操作时间(updated_at 相对显示) + 新建弹框工作目录改 <select> 下拉
- 列表行 meta 加 fmtTimeAgo helper(刚刚/N 分钟前/N 小时前/昨天 HH:MM/MM-DD/YYYY-MM-DD), title 出完整 locale 串
- 新建任务工作目录: input + datalist → <select> 既有目录列表 + "+ 新建目录..." 展开 text input(保留新名建目录能力)
- folders-datalist 保留供左 pane filter-wd 继续 autocomplete

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:57:16 +08:00
caoqianming 7fc9570ffe model: 加 GLM 5.1 档案 (zai provider + 国内站 bigmodel.cn)
- config/models/glm.yaml: family=glm, variant=pro, model_id=zai/glm-5.1,
  api_base 覆盖到 https://open.bigmodel.cn/api/paas/v4,env ZHIPUAI_API_KEY
- thinking_mode 暂不开:GLM 协议是 body {"type":"enabled"} 而非
  reasoning_effort 等级,需 core/llm.py 加 family 分支才能透传,留 TODO
- requirements.txt: litellm 下限 1.50→1.83(zai provider PR #17307
  merge 后才内置,旧版不识别 zai/ 前缀会炸)
- RUN.md: .env 例子加 ZHIPUAI_API_KEY,probe 命令加 glm.pro

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:32:15 +08:00
caoqianming 337b8896a6 files(dev SPA): UX 翻面 — 主区去 checkbox / 黄 bar,改 [选入到此处] 弹框 + 拖拽上传 + 修全局 input width bug
主区从 select-then-pick-dest 改 at-dest-pull-sources:用户切任务时主
区已自动跳 working_dir,destination-first 比 source-first 少一次心智
切换。pane-head 加 [选入…] → 弹框跨目录勾源(Set<rel> 切换路径保留)
→ 底部 [复制到此处] / [移动到此处] 落到主区当前 state.filesPath;弹
框浏览 == 主区路径时同目录 checkbox 灰禁(挡 409)。整个 pane-right
成 drop zone,Files 类型才响应 + dragenter/leave 计数防子元素冒泡闪
烁,落点用 state.filesPath 沿用 /v1/files/upload。

根因 bug:全局 input{ width: 100% } 把新加的行 checkbox 撑成全行宽,
.name(flex:1; flex-basis:0)被挤成 0 宽 — 用户报"看不到文字"的元
凶。修法 selector 排除 [type=checkbox]/[type=radio]/[type=file]。

按 CLAUDE.md 不留兼容:state.selectedFiles / syncBulkBar / dirPicker
/ files-selall / files-bulkbar / row-cb / .file-row.selected 整套删
干净。后端 /v1/files/copy /v1/files/move 一行没动,前端用同样的
{paths, dest_dir}。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:59:21 +08:00
caoqianming 0c5dd3b176 files(dev SPA): /v1/files/copy + /move 跨目录批量搬动 + 多选 + 目录选择弹框
后端两路由共用 _validate_transfer 预检 helper(批量原子校验:同名 409 不
覆盖、不自嵌套、不重名、target 已存);move 加闸"顶层目录是某 task
working_dir → 409"维持 working_dir = top-level invariant,copy 无此闸
(新副本无 task 关联)。dev SPA 文件行加 checkbox + 顶栏全选三态 + 黄底
toolbar(复制到/移动到/取消),目录选择弹框复用 /v1/files 浏览。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:29:59 +08:00
caoqianming 7925dcef54 files: working_dir 视为可重生 FS 视图(DELETE task 顺手清空孤儿 + delete_file 去 task-ref 闸)
DB 是 source of truth,FS working_dir 可独立删 / 用户手删 / 跨机器迁移丢失,
下次 build_agent 自动 mkdir 重建。三处改:

- core/agent_builder.py: working_dir.mkdir(exist_ok=True) 从 if not resume:
  里挪出,resume 也兜底建目录
- web/app.py DELETE /v1/tasks/{id}: 删完后若同 user 无其他 task 引用 +
  FS 空 + ROOT 内相对路径 → best-effort rmdir 清孤儿;外部 --working-dir
  (DB 绝对串)静默跳过
- web/app.py POST /v1/files/delete: 顶层目录去掉"有 task 引用 → 409"闸,
  允许独立删空目录,task.working_dir 字段不动

smoke case 4 改 200 + working_dir 不变;新增 case 8(空目录自动清)/
case 9(非空保留),全 9 pass。PROGRESS / RUN 跟着更。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 08:10:36 +08:00
caoqianming 781a216ca6 model: 同 task 内切模型(c 模式 task 级 / A 粒度)+ usage_events v2 表(0006); GET /v1/models; 前端顶栏下拉 + 历史 model 切换点小标
- DB(0006): messages 加 model_profile 列(assistant 行有值); 重建 usage_events 表 v2 形态(event_id/user_id/task_id/message_id/kind/model_profile/units jsonb/cost_usd + 三索引), 0004 删的旧 schema 字段不够多态; tasks.tokens_prompt/completion/cost_usd 保留作粗概览
- ModelCapabilities 加 display_name; deepseek_v4.yaml flash/pro 各填名
- GET /v1/models: 扫 config/models/*.yaml 列可选项(profile/display_name/family/thinking_mode/is_default); POST /v1/tasks + PATCH 接受 model_profile(不传 → cfg["default_model"]; 校验走 ModelCapabilities.load 失败 400)
- build_agent: resume 时优先 task.model_profile 而非 cfg default; AgentLoop 加 user_id 透传, 每轮 assistant 入库后调 record_chat_usage(litellm cost map 算钱, 失败吞掉 emit warn 不阻 loop)
- core/storage/usage.py 新文件: record_chat_usage(双写 messages.tokens_in/out + model_profile + insert usage_events 一行)
- session.append() 返回 message_id(供 usage 关联)
- 前端 dev.html: chat-meta 加模型下拉(切了 PATCH + running 中提示"跑完后生效"); 新建对话框 modal 加 nt-model select; renderMessages 按 model_profile 切换点画小标 "── DeepSeek V4 Pro ──"
- CLAUDE.md: 加"开发测试期 / 不删现有数据 / DROP COLUMN 两种情况"规则
- DESIGN §7.4 schema 加 messages.model_profile + usage_events v2 段; PROGRESS 加 0006 条目 + 文件清单

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:43:13 +08:00
caoqianming e7f9b005bd doc: 精简 DESIGN/PROGRESS/RUN(总 162KB → 66KB,PROGRESS -80%)
PROGRESS 每条压 2-4 句去 smoke 列表 / 文件清单 / 行数明细;DESIGN 清重复 + 删反方向已废弃的取舍段;RUN 压命令注释 + 修 db current 应输出 0005(原写 0004)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 14:20:48 +08:00
caoqianming 2baed6894b auth(dev SPA): 邀请码撤回 邮箱+密码 (users.email/password_hash bcrypt; 0005 加 UNIQUE; user add CLI; 登录两 tab)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 13:58:48 +08:00
caoqianming 53f59eb78a auth(dev SPA): 邀请码登录(invites 表 0005) + SENTINEL user 彻底撤
- 新增 POST /v1/auth/login_invite {token}: dev SPA 给同事试用,token → name → uuid5(NS, name) 推导 user_id;原 /v1/auth/login 保留为 platform 机器对机器入口
- 0005 migration 新表 invites(token PK / name UNIQUE / created_at);先用 ZCBOT_INVITES env 试了一版,讨论后升级到 DB 表 — schema 极薄,不入 user_id (uuid5 推导),不入 revoked_at (DELETE 即撤销);管理直接 SQL,后期可加 main.py invite CLI
- web/auth.py: 删 _parse_invites / AuthConfig.invites / env 读取;新模块函数 resolve_invite(token) 每次 SELECT,无缓存避免 DELETE 后还能登
- SENTINEL_USER_ID 常量 + ensure_local_sentinel 函数 + agent_builder fallback 全删 (CLI 撤后无 caller);storage/utils.py 三函数 user_id 改必填;TaskState 加 user_id 字段;build_agent user_id 改 KEYWORD_ONLY 必填;session.py 删多余 ensure_local_task_row (task 行 web 入口已 INSERT)
- DB 清: SENTINEL 行 + 5 个 dev task + 307 messages + workspace/users/00000000.../ 全删
- dev.html: 登录页 2 格 (uuid+key) → 1 格邀请码,header 显示 name·uuid 前 8 位
- 文档全套同步: RUN/DESIGN/PROGRESS

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 13:14:31 +08:00