Commit Graph

116 Commits

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