zcbot/PROGRESS.md

482 lines
76 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 实施进度
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。
最后更新:2026-06-18(brief 简报重定位为「重要文献速览」+ 精简到三文件 + bump 0.20.0)
---
## 状态
| Phase | 标题 | 状态 | 备注 |
|---|---|---|---|
| 1-3 | 骨架 + Skill + run_python | ✅ | 多 skill(coding/proposal/ppt/research/documents/imagegen/videogen/review/patent);CoreCoder 唯一匹配 edit;敏感 env 过滤 |
| 4 | 演化性能力 | 🟡 | Model Profile + Probing ✅;版本化 prompt 未做 |
| 5 | Eval Suite | ⏸ 不做 | dogfooding 替代,probe 覆盖健康检查 |
| 6 | 长任务工程化 | 🟡 | task + 恢复 ✅;双层记忆 ✅;context 压缩 ✅(加压力门槛) |
| 7 | 打磨 | ❌ | Docker 沙盒 / 更多 skill |
| §7 SaaS | DESIGN §7 路线 | 🟡 | A 事件流化 ✅;B 完工 ✅;D `/v1` JSON API ✅;D' 过渡 auth + dev SPA ✅;单活 run 锁 + cancel ✅;0004 schema 瘦身 ✅;入口归位 ✅;真 OIDC 待;**C Step 1-3 + 3d ✅(Executor + Docker 池 + DockerExecutor + fs 工具进容器)+ Step 5 部署前置对账 ✅ + 容器资源 yaml + 应用层磁盘配额 ✅ + dogfood 网络放开 + 容器内 pip/npm 源持久化 ✅**;**Step 4 完整 egress proxy + Step 3b PGID kill 协议延后到外部用户开放前**(还需 egress proxy + xfs project quota OS 层硬化,§7.5 落地清单 #2 #4)。 |
---
## 已完成关键能力
### 2026-06-18 / brief 简报重定位「重要文献速览」+ 精简三文件(bump 0.20.0)
- 需求漂移收敛:brief 从"热点聚类趋势判断型简报"重定位为**「重要论文列表 + 内容总结」速览型** —— ①只描述不给建议(去掉启示/判断/空白争议);②开头一份重要期刊论文列表(各大相关刊、**Elsevier 数据库优先**),每篇带一段简介/摘要概述;③对这批论文做客观总结即可。
- 数据源:**research + documents 都是取文献主力**(research 逐刊精确取最新 Elsevier 论文 + DOI;documents 取内部材料库全文),web search 取动向**单列**不混进论文总结。
- **精简到三文件**(原 8 文件):`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` 两处小改并已 smoke test 验证:①「重要论文列表」段(标题含"论文列表/文献列表/参考文献")H3 期刊子标题下的 `[n]` 条目仍作锚点(只在 H1/H2 重判段类型);②条目内 DOI 子串(末尾 "DOI: 10.xxx")也做 https://doi.org 超链接。验证:ref 锚点/内部回链/外部 DOI 链/化学式下标全在。
- 文件:`skills/brief/{SKILL.md,references/journals.md,scripts/render_docx.py}`;`core/__init__.py` 0.19.0→0.20.0;`SKILL_LIST.md`(brief 条目重写,总数仍 17)同步。
### 2026-06-18 / 定时任务 v1(scheduled_jobs,DESIGN §8.5)
- 需求:对话方式建"每天 X 点干 Y"的定时任务(跑 skill 出简报 / 发邮件 / 打招呼皆可)。调研 OpenClaw/Autobot/Claude Code/geta 四源收敛,定方案见 DESIGN §8.5。
- **核心解耦**:job 本体 = `cron+tz + 一句 prompt + 会话模式`;"发邮件"不是字段,是 agent 据 prompt 调 `send_email` 的动作 → 加任何能力不改 schema。
- **不引调度框架**:croniter(唯一新依赖)只当 next_run 计算器(正确处理 dom/dow OR 语义 + 时区);"每 30s 醒来扫到点 job"是 plain-asyncio 守护循环,仿 §8.4 `_disk_scanner`,复用 `_run_agent_bg`,不上 APScheduler/Celery。
- **文件**:`core/storage/models.py` 加 `ScheduledJob` + migration `0011_scheduled_jobs`(独立加表,公测兼容)/ `core/scheduler.py`(cron 数学 + claim+advance 防重复触发 + record_result 失败阈值自停 + notify 兜底投递 + CRUD 服务层 `list/create/update/set_enabled/cancel_job`,工具与 REST 共用)/ `tools/schedule.py`(create/list/**update**/cancel 四件套,薄包装服务层,user_id ctor 注入,定时 run 内不挂防自我繁殖)/ `tools/send_email.py`(host-side,SMTP_* 齐才挂)/ `web/app.py` lifespan `_scheduler_loop` + `_execute_scheduled_job`(认领→抢 run 锁→to_thread 跑→超时协作 cancel→notify→记账)+ `/v1/schedules` GET/PATCH/DELETE 三端点。
- **对话端 = 完整 CRUD**(建/改/删/查都说着办);**前端 = 只读展示 + 停用/删除两个便捷按钮**(左栏 rail「定时」按钮 → `crons.js` 只读 master-detail modal,复用 skills modal 范式;建/改无 REST、故意只走对话,§8.5)。两条路径共用 `core.scheduler` 服务层不漂移。
- **会话模式**:isolated(默认,每次新建临时 task `scheduled-<id8>` 目录,省 token)/ persistent(绑定 bound_task_id 续上下文)。env:`SMTP_*` / `ZCBOT_DISABLE_SCHEDULER` / `ZCBOT_SCHEDULER_TICK_SECONDS` / `ZCBOT_SCHEDULER_CONCURRENCY`(见 RUN)。已验:migration 上库 0011、CRUD 服务层端到端、3 REST 路由 + 4 工具注册、crons.js 语法。bump 0.18.0 → 0.19.0。
- **v2 待做**:对话工具教写好 job.prompt 的薄 skill;退避重试(transient/permanent 区分)目前简化为"到下一 cron 点 + 连失败 5 次自停";真机邮件 smoke + 守护循环定时触发的端到端验证(需起 web 进程跑一轮)。
### 2026-06-18 / brief skill:科研方向简报
- 需求:用户要"水泥/建材方向的科研简报"。联网调研简报类做法——Anthropic 官方 digest skill(办公活动聚合)+ Paper Digest(论文影响力周报)+ 文献计量趋势报告(热点聚类/新兴方法/地理格局)。结论:现有 skill 缺"某方向近期文献 → 有判断的趋势简报"这一环(research/documents 只取文献不组织、paper-review 出可投稿综述、analyze 拆问题不查文献)。
- **方案**:新建自包含 `skills/brief/`,定位"文献计量趋势型简报",数据底座**三路并用**:documents(内部胶凝材料库取全文)/ research(补 DOI + year_gte 卡时间窗)/ web(政策·标准·产业动向,单列不混学术引文计数)。六阶段:定题对齐 spec(方向+边界/时间窗/受众/深度/源开关/语言/关注点)→ 三路检索取数(中→英术语转译 + 跨源去重,证据表 evidence.md)→ 趋势分析(3-7 热点簇,BLOCKING-lite 对齐)→ 逐段起草 → 引文核验(复用 paper 三层协议,CITATIONS.md)→ 渲染验收。
- 深度三档 flash/standard/deep 配字数/簇数/引文数预算;骨架:TL;DR→概览→热点聚类→新兴方法→标志性进展→研究空白→产业政策动向(web)→参考文献。渲染早期复用 proposal,后改为自带 render_docx。
- 文件:`SKILL.md` + `templates/{spec,brief_outline}.md` + `references/{search_strategy,citation_verify}.md` + `scripts/quality_check.py`(结构/簇数预算/过度宣称/**无源句式**/引文交叉核对)+ `scripts/render_docx.py`(简报专属:商务红主题 + 引文 [n]/[Wn] 上标并锚到文末 + DOI/URL 可点击超链接 + TL;DR/判断 callout 底纹)。
- **顺带修 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` 是对话原文转录、非排版文档,不动。bump 0.17.0 → 0.18.0。
### 2026-06-17 / 任务软删除(留对话轨迹做语料 + 可恢复)
- 背景:公测后目标转为沉淀用户对话/文件做训练研究语料;原"hard cascade"硬删任务会连带 messages/usage_events 永久丢失,推翻该决策(DESIGN §取舍同步标注)。
- 改动:`tasks` 加 `deleted_at` 列(0010 migration,additive 可空);`DELETE /v1/tasks/{id}` 从 `DELETE` 改为置 `deleted_at=now()`,不再触发 CASCADE、不动工作目录文件(原 rmdir 清理一并去掉);`list_tasks` / `list_folders` 计数加 `WHERE deleted_at IS NULL` 过滤;新增 `POST /v1/tasks/{id}/restore` 恢复;`delete_file` 顶层目录 409 引用检查排除软删 task。
- 文件留存(归档)方案已在 DESIGN 记录(restic 备份地基 + DB 事件日志 + 起步同盘),**实现待办**,优先级靠后。bump 0.16.2 → 0.17.0。
### 2026-06-17 / 用户操作说明书(详 + 精简两版)+ 文献库库容 21W→100W 全量更新
- 新增 `docs/操作说明书.md`(详版)+ `docs/操作说明书-精简版.md`:面向科研用户、不出现产品代号、从登录后正式操作讲起。覆盖三栏布局、**个人文件夹 → 工作目录 → 任务**三层概念(任务≠文件夹、多任务可共享一个工作目录)、新建任务、对话、技能矩阵(含 paper)、文件管理、进阶(方案确认卡/消息目录/记忆)、任务管理、图像视频、账户存储、FAQ;截图留占位标注。突出对外优势(内部文献库、科研计算、可直接产出文件)。
- 文献库库容口径 **21W+ → 100W+** 全量改一遍:`SKILL_LIST.md`、`skills/documents/SKILL.md`(含 description,模型运行时据此向用户描述库容)、`skills/patent/SKILL.md`、`PROGRESS.md`、`scripts/optimize_arch_ppt.py`、两份说明书,共 10 处。bump 0.16.1 → 0.16.2。
### 2026-06-17 / paper skill:学术期刊论文写作
- 需求:现有 skill(proposal 写本子 / review 改稿 / research 查文献 / plot_pub 出图)缺"从零起草期刊投稿稿"这一环。联网调研开源论文 skill(ARS 32.1k★ / paper-writer-skill / claude-scientific-writer 1.9k★)——结论:不直接装(ARS 是 CC-BY-NC 非商用、全偏英文/医学/CS、引文默认 APA、依赖外部 API),但流程值得移植。
- **方案**:新建自包含 `skills/paper/`,流程骨架取 paper-writer 的"先定图表 + stage-gate"与 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) → 验收渲染 + 投稿件。终审复用 review skill。
- 脚本(自带,不跨 skill 引):`render_diagrams.py`(照搬)/ `render_docx.py`(去 fund-type,加 `--lang {zh,en}` 图题切换 + `--toc` 默认关)/ `word_count.py`(类型×语言双口径预算)/ `quality_check.py`(论文版核心=**引文交叉核对** orphan/uncited/编号连续 + 结构/占位符/过度宣称/插图)。
- 验证:微型 fixture 端到端 smoke——word_count 正确标欠预算、quality_check happy path 全 OK 且 orphan/uncited/缺号负例正确触发、render_docx 出 37KB docx。文件:1 SKILL.md + 6 references + 3 templates + 4 scripts。SKILL_LIST 同步(15→16)。bump 0.16.0 → 0.16.1。
### 2026-06-16 / look_at_image 图像理解(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。否决「换主模型走 A 路」——DeepSeek 的 code/tool-calling 仍是核心,vision 当工具更稳。
- 后端:新建 `tools/look_at_image.py`(`/chat/completions` OpenAI 兼容,base64 单图 + question → 文本解读,默认 question 覆盖描述+OCR+图表读数);`config/media/doubao.yaml` 加 `vision:` 段;`core/storage/usage.py` 加 `record_vision_usage`(kind="vision",按 token,单价 snapshot 进 units);`agent_builder.py` 注册(yaml 有 vision 段才挂)+ media prompt 段教「何时调 / 何时别调」。`usage_events.kind` 自由文本,vision **无需 migration**
- 重构:图片路径解析 + base64 抽到 `tools/image_ref.py`,seedream(i2i)与 look_at_image 共用(三形态路径 + user_root 边界 + 扩展名/大小校验)。
- 验证:真机 smoke `scripts/smoke_look_at_image.py` 合成含已知文字图 → OCR 准确读出 + usage_events 落 kind=vision(实测 ¥0.0011)。bump 0.15.0 → 0.16.0。
### 2026-06-16 / seedream i2i 改图(DESIGN §8.1 E 路落地)+ 前端 paste 路径注入
- 需求:覆盖「基于已生成 / 上传的图做修改」(像素级),核心循环=文生图 → 用户"改成 X" → i2i 改那张(不重画)。base64 通路 probe 2026-05-29 已验,本次落 tool。
- 后端 `tools/seedream.py`:加 `reference_images` 数组参数(**v1 单图**,传 >1 直接报错不静默截断)。路径解析走共享 `tools/image_ref.py`(与 look_at_image 同一套)——依次试 `working_dir/rel``user_root/rel` → 绝对,**强制结果落在 user_root 子树内**(防越界读任意文件),吃三种路径形态(`figures/x.png` / saved 形态 `<taskname>/figures/x.png` / 绝对);校验存在 + 图片扩展名(png/jpg/jpeg/webp/gif)+ ≤10MB;读 base64 → data URL → ARK body `image_urls`。**不传 reference_images = 文生图,行为 100% 不变(向后兼容)**。banner 加 `· mode=i2i` + `reference=` 行(前端正则兼容),meta.json 记 `mode` / `reference_images`(派生链可追溯)。
- 前端 `web/static/js/chat.js`:`sendMessage` 发送时 `takePastedRels()` 收集 `chat-hint` 的 paste-chip 路径,作 `[用户上传的参考图] <rel>` 行注入正文 + 清 chip ——**修了既有缺口**(之前粘贴的图路径根本到不了模型)。这样"上传外部图 → 改图 / 看图"才能定位到文件。
- 引导:`skills/imagegen/SKILL.md` 删旧「不接图像输入」结论 + 加「改图(i2i)」专段(最易踩错=该 i2i 却重新 t2i 丢构图);`agent_builder.py` 媒体 block 提 i2i + paste 注入约定;`SKILL_LIST.md` 同步。bump 0.14.0 → 0.15.0(看图 look_at_image 同日落地见上一条)。
### 2026-06-16 / ask_user:回复里渲染可点击「方案确认」选项卡(Claude 式)
- 需求:agent 在分叉点能像 Claude 那样抛出可点选项,用户点一个继续、或不点直接用文字讨论。设计取舍见下。
- **收窄定位**:不是通用提问器,只做「方案/分支确认」——存在 2-4 个互斥方向且选择会实质改变后续动作时才用。防 agent「变爱问」(高轮数烧 token 已知痛点)是成败关键,故系统提示严格约束使用条件。
- **与轮次模型同构、无阻塞**:复用「LLM 出无 tool_call 消息即结束本轮」语义——`ask_user` 是虚拟工具(同 `task_progress` 范式),`core/loop.py` 检测到本步调用它就 emit done 提前结束本轮、不回灌 LLM;点选项 = 把该选项 label 当新用户消息发出(复用 `POST /messages`),零额外 LLM 往返。
- 后端:新增 `tools/ask_user.py`(`AskUserTool`,question + 2-4 个 `{label, description}` 选项,结果仅占位);`core/agent_builder.py` 注册;`core/loop.py` 加提前终止分支;`prompts/system/general_v1.md` 加「方案确认约定」段 + 工具清单一行。
- 前端 `web/static/js/chat.js`:`buildAskUserCard` 渲染选项卡;`handleSseEvent` 的 `tool_call`/`tool_result` 特判 ask_user(选项卡 / 抑制占位结果);`renderMessages` 历史重渲特判(改 index 遍历,向后看有无 user 回复判「已答」,命中项标「✓ 已选」);`sendMessage(overrideText)` 支持点击直发不清输入框;`chat-stream` 点击委托接 `.ask-option`。`dev.html` 加 `.ask-user/.ask-option` 等样式。持久化天然免费(选项在 `tool_calls.arguments` 里,刷新页面按钮还在)。bump 0.13.0 → 0.14.0。
### 2026-06-16 / 消息目录:右侧悬浮圆点轨道导航(ChatGPT 式)+ 双向分页
- 需求:长对话里快速定位历史某轮提问。参考 ChatGPT 扩展(Scrollbar / Outline)的交互——每点=一轮"我"的提问,hover 出标题气泡,点击滚动定位。
- 后端 `web/app.py`:① `list_messages``after_idx` 参数 + 响应加 `has_more_after`,支持**向下**翻页(从目录跳到旧消息后下方还有未加载的新消息);② 新增 `GET /v1/tasks/{id}/outline`,只取全部 role=user 的 `idx + 首行片段`(`payload->>'content'`,不回传整 payload,轻量),`_outline_snippet` 取首个非空行截 48 字。走 `(task_id,idx)` 索引按 task 收窄。
- 前端:`state.js` 加 `outline / msgHasMoreNewer / msgLoadingNewer`;`chat.js` 加 `refreshOutline / renderOutlineRail / jumpToMessage / loadMessagesAround / loadNewerMessages`、消息卡补 `data-idx` 锚点、底部 sentinel(下滑加载更新)、滚动高亮当前轮;`selectTask` 把 outline 并入 meta/messages 并发拉,run 收尾后刷新。跳未加载轮次用 `before_idx=idx+11` 拉居中窗口再 `scrollIntoView`
- `dev.html`:`#pane-mid` 加 `position:relative`,新增 `#msg-outline-rail` 悬浮轨道(容器 `pointer-events:none` 不挡滚动条、仅圆点可点,hover 整列展开标题),手机端隐藏。embed 页无该元素,绑定与渲染均 null-safe。bump 0.12.16 → 0.13.0。
### 2026-06-16 / 切 task 提速:meta+messages 并发拉 + 默认窗口降到 30
- 体感诊断:切 task 慢**不是索引问题**——`messages` 的 `UniqueConstraint(task_id, idx)` 在 PG 自带 `(task_id, idx)` 复合索引,主查询 `WHERE task_id=? ORDER BY idx`(app.py:1442)既走索引过滤又免排序;也不是"全量加载",前端早已尾部窗口分页。真正的低垂果实是 `selectTask` 里 meta 与 messages **串行 await**,以及首屏窗口偏大。
- `web/static/js/chat.js`:`selectTask` 把 `GET /v1/tasks/{id}`(meta)与 `loadMessages`(messages)改 `Promise.all` 并发(两者无依赖、落不同 DOM 区),省一个 RTT;`MSG_PAGE` 60→30,降首屏传输 + markdown/highlight 同步渲染量。bump 0.12.15 → 0.12.16。
### 2026-06-15 / plot_pub 吸收 nature-figure 投稿级复合图设计纪律
- 联网调研 `nature-figure` skill(MIT,github.com/Yuan1z0825/nature-skills):双层 manifest 路由 + Python/R 双后端 + 生物医学 gallery。判断不整包移植 —— 与已有 plot_pub 高度重叠、R/单细胞/在体内容跟建材院领域不沾边、多文件结构破坏 zcbot 单 SKILL.md 约定。
- 只迁移可复用的设计 IP,折进 `skills/plot_pub`:`style.py` 补 `svg.fonttype='none'`(可编辑矢量,原本只设了 PDF Type 42 漏了 SVG)+ `SEMANTIC_COLORS` 语义色表 + `clean_spines()` spine 纪律 + `ablation_alphas()` 同色变 alpha;`SKILL.md` 新增「投稿级多 panel 复合图」段(五点 figure contract / 语义配色 / 信息架构 / 导出纪律),示例全改建材领域。纯 Python、零新依赖、保留中文字体。bump 0.12.14 → 0.12.15。
### 2026-06-15 / 消息分页:尾部窗口 + 向上滚动加载更早(切 task 提速)
- 痛点:切 task 卡顿 —— `/v1/tasks/{id}/messages` 无分页一次拉全量,前端 `renderMessages` 又对每条跑 markdown+highlight+media 全量渲 DOM,消息多时两段成本都线性涨。
- 后端 `web/app.py` `list_messages`:加可选 query `limit`、`before_idx`。不传 → 旧行为(升序全量,仅多返 `has_more:false`,向后兼容);传 `limit` → 取尾部最近 N 条(`idx desc + limit` 再 reverse);传 `before_idx` → 取该 idx 之前更早一批。响应恒含 `has_more`
- 前端 `chat.js`:① `selectTask` 进来立即把 chat-stream 换「加载中…」(治感知,切换瞬时跟手);② `loadMessages` 默认 `limit=60`,结果存 `state.loadedMessages/msgHasMore`;③ 新增 `loadEarlierMessages` + `_msgScrollObserver`(复用 task list 的 sentinel 范式),顶部 sentinel 进视口自动 prepend 更早一批后整窗重渲(renderMessages 仍是对 loadedMessages 的纯函数,时序累积逻辑不动),重渲后锚回滚动位不跳视口。
- `state.js``loadedMessages/msgHasMore/msgLoadingEarlier`;`dev.html` 加 `.msg-top-sentinel` 样式。取舍:只载尾部时进度 dock 仅反映窗口内 task_progress,补满更早后一致。bump 0.12.13 → 0.12.14。
### 2026-06-15 / 图片预览:左键拖动平移 + 光标语义改正
- 光标:100% 时改回普通箭头(原 `zoom-in` 放大镜误导 —— 左键不缩放,缩放是 Ctrl+滚轮);放大后改 `grab`、拖动中 `grabbing`,贴合"可拖"语义。
- 左键拖动平移:放大态下 mousedown 记起点 + body 滚动位,mousemove 改 `bodyEl.scrollLeft/Top` 平移看局部(替代拖滚动条);`img.draggable=false` 关原生 ghost 拖拽。document 上的 move/up 监听存 `z._onMove/_onUp`,`_clearZoom` 时移除避免泄漏。bump 0.12.12 → 0.12.13。
### 2026-06-15 / 文件预览缩放加固 + 双击复位提示
- 图片 load 完即量基准尺寸(`_captureBase`,免首次缩放时还没渲染量到 0px 导致塌成 0);基准未量到时本次缩放跳过不破坏;双击复位时徽标显式提示「已复位 · 100%」(停留 1.4s)。bump 0.12.11 → 0.12.12。
- 排查提示:左栏底部版本号 = `core/__init__.py __version__`,用户报"缩放完全没动静"且本地 8765 无服务 → 多半是**远端实例未 pull/重启**,版本号对不上即旧代码。
### 2026-06-15 / 文件预览缩放改显式 px:修 CSS zoom 放不大
- 接上一条:CSS `zoom` 对带 `max-width/height:100%` 的 flex item 不生效 —— zoom 放大后被百分比 max 约束重新夹回,视觉无变化(用户实测"还是不能放大")。
- 改法(`web/static/js/preview.js` `_applyZoom`):以 scale=1 的贴合显示尺寸(`clientWidth/Height`)为基准缓存到 `z.baseW/baseH`,缩放时 `max-width/height:none` + 显式 `width/height = base × scale` px;复位时清空还原 CSS 自适应。显式 px 真正撑大布局,body 才出滚动条。bump 0.12.10 → 0.12.11。
### 2026-06-15 / 文件预览:修滚动穿透 + 图片 Ctrl+滚轮缩放
- 现象:web 端文件预览弹框内滚滚轮,事件冒泡到背景把对话列表也滚了(scroll chaining);且图片预览无缩放手段。
- 修法(纯前端,`web/static/js/preview.js` + `web/static/dev.html`):
- **滚动不穿透**:主/小预览 `.body``overscroll-behavior: contain`,再挂一次性非 passive `wheel` 监听 ── 容器不可滚(如图片正好铺满)或已到顶/底时 `preventDefault()` 断掉冒泡。
- **图片缩放**:仅图片(文本/md/docx/pdf 各有原生流/阅读器)。Ctrl+滚轮按 ×1.1 步进缩放(夹 0.18×),用 **CSS `zoom`** 而非 transform(zoom 改布局盒尺寸,放大后 body 才出滚动条能看溢出);右下角浮 `xx%` 比例徽标(挂 `.card` 上,滚动不跟走,1s 后淡出);双击复位 100%。`.body.center` 改 `safe center` 防 flex 居中把溢出顶/左裁掉够不到。
- wheel 监听只在 init 挂一次到复用的 body 元素,缩放目标走 `_zoomState` WeakMap,避免每次预览重复 addEventListener 泄漏。
- bump 0.12.9 → 0.12.10。
### 2026-06-15 / sandbox 装 emoji 字体:修 mermaid 图满图豆腐块
- 现象:模型生成的 mermaid 架构图里几乎每个节点标签前缀的 emoji 图标(🌐🔥🛡 等)全渲染成空心方框 □。根因不在 mermaid 语法 / 布局 ── `deploy/sandbox/Dockerfile` 只装了 `fonts-noto-cjk` + `fonts-wqy-microhei`(中文不豆腐),**缺 emoji 字体**,chromium 渲染时找不到 emoji glyph 就用 tofu 占位。
- 修法:Dockerfile 字体安装行加 `fonts-noto-color-emoji`(+~10MB),与 CJK / WQY 同 `fc-cache -f` 刷索引。chromium 支持 COLR/CBDT 彩色 emoji,fontconfig fallback 即正常出图标。纯增量容器改动,不碰对外契约。**需重建 sandbox 镜像 + 重启 per-user 容器生效**。bump 0.12.8 → 0.12.9。
### 2026-06-15 / 左栏任务筛选区默认折叠
- 接 2026-06-13「筛选区可折叠」一条:把默认态从展开改为**折叠**(进页面只见「筛选 ▸」一行,点开才展开)。偏好仍持久化 —— 用户显式展开过(`zcbot.task-filters-collapsed` 存 `"0"`)才默认展开,否则一律折叠。改动:`web/static/js/chat.js`(默认判定 `!== "0"`,onclick 改存 `"1"/"0"`)、`web/static/js/state.js` 注释。bump 0.12.7 → 0.12.8。
### 2026-06-15 / system prompt 按 backend 注入「运行环境」段:纠正平台误报 + 写明禁外网
- 接上两条(--shm-size + mmdc wrapper 修执行层)。再查发现**引导层的根问题在 system prompt**:`general_v1.md` 的「平台」段写死 "Windows + cmd.exe",但线上是 **docker = Ubuntu 容器 + bash** ── 模型被误导在 Linux 里打 cmd 构文(`where mmdc 2>nul`),且没引导"渲图走本地",模型以为 mermaid.ink 等在线服务能用、反复去试(其实**境外被墙**,容器有外网但渲图不该依赖出站)白烧 token。
- 修法(引导层,环境事实归 system 而非 skill):
- `general_v1.md`:删写死的 Windows 平台段,改为中立一句"平台以系统消息「运行环境」段为准"。
- `agent_builder.py`:`_build_system_prompt` 按 backend 注入环境段 ── **docker** = `_CONTAINER_ENV_BLOCK`(Linux/Ubuntu·bash·**渲图走本地 mmdc 别调境外在线服务**·mmdc/chromium/中文字体已装·`mmdc -i x -o y` 直接渲图·/tmp 可写);**host** = `_HOST_ENV_BLOCK`(一行 Windows/cmd 提示,免 general_v1 指向落空)。
- 撤回上一条加到 imagegen skill 的渲图引导(环境事实收归 system,不重复)。
- 原则沉淀:**全局不变的环境事实(在哪/能否联网/装了啥)→ system(高杠杆,一句省一类试错);具体可选方法/流程 → skill**。这是"换"不是"加" ── 删掉的是每轮都发且 docker 下错误的 Windows 段,token 量级相当、信息变对。改动文件:`prompts/system/general_v1.md`、`core/agent_builder.py`、`skills/imagegen/SKILL.md`、`RUN.md`。bump 0.12.6 → 0.12.7。
### 2026-06-14 / mmdc wrapper:容器内裸调 mmdc 自动带 puppeteer config,渲图开箱即用
- 接上条 `--shm-size` 修复。`--shm-size` 只填了"模型自己摸对 config 后那一下能成";模型**初始裸调 `mmdc`** 仍因 chromium 缺 `--no-sandbox`(容器 `--cap-drop=ALL`)直接跪,然后反复试 `mermaid.ink` 等在线服务 ── 但那是**境外、被墙/不稳**(容器虽有外网,渲图也不该依赖出站),实测又一条对话这么烧掉上百 k token。
- 修法(执行层 + 引导层,均不破坏对外契约):
- **执行层 wrapper**:Dockerfile 给 `/usr/local/bin/mmdc` 套 wrapper,没显式 `-p` 时自动注入 `-p /sandbox/puppeteer-config.json`(含 `--no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage`)。裸调 `mmdc -i x.md -o x.png` 一次成;`render_diagrams.py` 等走 `which mmdc` 的脚本透明受益。删掉没人读的 `MERMAID_PUPPETEER_CONFIG` env(mmdc 本就不认它,只认 `-p`)。
- **引导层**:imagegen skill「mermaid vs seedream」段加硬引导 ── 渲图直接 `mmdc -i x -o y`、⛔ 容器禁外网别试 mermaid.ink 等在线 API。
- 取舍:没开 first-class `render_mermaid` tool ── mermaid 是纯本地计算,zcbot 专用 tool 只留给带 key/计费的能力(seedream/seedance);wrapper(执行兜底)+ skill 一句(affordance 引导)已覆盖,不扩工具面。**需 rebuild 镜像**才带 wrapper(旧容器没有)。改动文件:`deploy/sandbox/Dockerfile`、`skills/imagegen/SKILL.md`、`RUN.md`。bump 0.12.5 → 0.12.6。
### 2026-06-14 / sandbox 容器加 `--shm-size`:修 mmdc 渲 mermaid 挂超时
- 实测一个"生图测试"任务(`caoqianming@foxmail.com`)对话:模型裸调 `mmdc` 渲 mermaid,自造的 puppeteer config 漏了 `--disable-dev-shm-usage`,chromium 用 64MB 的 `/dev/shm` 起不来 → 连试 6 次全超时,烧约 120k token 才绕道 mermaid.ink 出了个 SVG。根因:`pool.py` 的 `docker run` 没传 `--shm-size`,容器 `/dev/shm` = docker 默认 64MB(镜像备的 `/sandbox/puppeteer-config.json` 虽有 `--disable-dev-shm-usage`,但模型不一定用那份;且 `mmdc` 不读 `MERMAID_PUPPETEER_CONFIG` env)。
- 修法(只做最小 infra,不动模型侧):`docker run` 加 `--shm-size`(`DEFAULT_SHM_SIZE=512m`,env `ZCBOT_SANDBOX_SHM_SIZE` / yaml `sandbox.shm_size` 可配,优先级同 memory/cpus)。从根上让任何 chromium 路径都不再挂,连模型自造的漏 flag config 也能跑。已 running 旧容器需重启 web + idle 回收后新起才带。
- 实测脚本 `deploy/sandbox/probe_mermaid.sh`(区分 chromium 缺包 vs 纯 shm 超时);诊断脚本 `scripts/diag_dump_task.py`(按 email+任务名 dump 对话)。改动文件:`core/sandbox/pool.py`、`config/agent.yaml`、`RUN.md`。bump 0.12.4 → 0.12.5。
### 2026-06-13 / 模型选择瘦身:对话模型常驻 + 生图/生视频收进 ⚙ 弹层
- `#chat-meta` 右侧原三个带标签下拉(模型/生图/生视频)占满整行。改为**高频的对话模型下拉常驻**(一眼可见当前模型、直接切),**低频的生图/生视频收进一个「⚙ 媒体」弹层**(fixed 定位逃出 pane overflow,点开才渲染 select)。meta 行从"3 下拉"降到"1 下拉 + 1 齿轮"。
- 行为不变:生图/生视频选中值仍只进 `state.imageModel/videoModel`、随下条消息 POST 的 `image_model/video_model` 发(send 逻辑读 state 不读 DOM,迁移安全);`onChangeImageModel/onChangeVideoModel` 复用。imageModels/videoModels 皆空时连 ⚙ 都不画。
- 改动文件:`dev.html`(弹层元素 + CSS)、`chat.js`(renderMediaModelTrigger / openMediaModelPop + 点外/resize/scroll 关闭)。bump 0.12.3 → 0.12.4。
### 2026-06-13 / 左栏筛选区可折叠(默认展开)
- 左栏顶部原 4 行固定头把任务列表压矮。把搜索/状态/目录/排序四个筛选控件归到两行 `.task-filter-row`,标题行加「筛选 ▾」toggle:**默认展开**,点击折叠只藏 UI(已选条件仍生效),偏好存 `localStorage`(`zcbot.task-filters-collapsed`),与 pane 折叠同套范式。折叠后左栏顶部从 4 行降到 2 行(标题 + 新建),列表可视区更高。
- 顺手把状态下拉从标题行并入筛选区(原 `width:auto` → flex),搜索框给 `flex:2` 更宽;目录/排序合一行,去掉独立"排序"文字标签改 `title` 提示。
- 改动文件:`dev.html`(markup + CSS)、`chat.js`(toggle 接线 + 复用 LS 范式)、`state.js`(新增 LS key)。bump 0.12.2 → 0.12.3。
### 2026-06-13 / 前端 UI 优化:中栏操作收菜单 + 阅读限宽 + 色彩收敛
- **中栏顶栏 5 按钮 → 「完成」+「⋯」菜单**:原导出/清空/完成/废弃/删除 平铺,与任务行的 `⋯` 浮层菜单两套范式打架,且破坏性操作(废弃/删除)平铺易误点、移动端挤。改为只留高频「完成」+ 一个 `⋯`,菜单复用 `taskMenuItems`(过滤掉 complete);单一事实源,两处共用。顺带把「清空」在菜单里按 `run_status` 也禁用(taskMeta 带该字段,修了之前菜单清空运行中会 409-after-confirm 的小坑)。
- **消息阅读限宽**:`.msg` 由 `max-width:92%` 收到 `min(92%,48rem)`(assistant ~60-80 字/行),user 气泡 `min(92%,36rem)`;宽屏长文不再满屏铺开难回扫,窄屏 92% 仍生效。
- **色彩负载收敛**:语义色由"每个操作一色"改为"颜色=后果"——正向(完成/下载)绿、破坏性(废弃橙/删除红),中性(导出/清空)不着色;移除紫色"清空"与蓝色"导出"。删掉已不存在的顶栏按钮 hover 规则(保留 file-picker 的 sp-copy/sp-move)。
- 改动文件:`dev.html`(中栏 markup + 三处 CSS)、`chat.js`(菜单接线 + renderChatMeta/deleteTask 收口)。**未动**左栏 4 行筛选头折叠(点 2,行为变化较大,留作下一步)。
- bump 0.12.1 → 0.12.2(patch:UI 重构 + 样式)。
### 2026-06-13 / 前端小修:导出按钮简写 + 任务菜单加清空 + 移动端 task 可滚 + admin 自适应
- **顶栏「导出对话记录」→「导出对话」**:与「清空对话」对齐(`dev.html` 按钮 + `chat.js` 任务菜单 export 项同步)。
- **任务菜单加「清空对话」项**:`chat.js::taskMenuItems` 新增一条,复用已有 `clearMessages`;disabled 条件 `!hasMsg` 与 export 项一致;dropdown 新增 `.dd-item.act-clear` 紫色(与顶栏清空按钮 hover 同色)。
- **修移动端 task 列表无法滚动**:手机断点把 `#pane-left` 设成 `display:block`,但 `#task-scroll``flex:1` 撑高才能滚 —— 父级非 flex 时 flex:1 失效,列表被 `overflow:hidden` 截断不能滚。改 `body.mv-left #pane-left { display:flex }`(`flex-direction:column` 由默认规则给),恢复滚动。
- **admin 移动端自适应增强**:`admin.html` 的 `@media(max-width:640px)` 补 header 紧凑化(缩 padding/字号、gen-at 时间戳截断)+ `.card-head`/`.ctrl` 允许换行(标题长 + 下拉不再撑出横向溢出)。
- bump 0.12.0 → 0.12.1(patch:bugfix + 样式)。
### 2026-06-12 / 双层记忆升级为 agent 自管(写入路径)
- **背景**:`.memory/`(core.md + extended/)存储原语已在,但纯手工维护 —— 系统不往里写,用户也不会主动整理 → 记忆形同虚设。**这轮补「写入」与「召回」两条路,不碰存储/DB,不破坏存量 `.memory/` 数据。**
- **写入 = agent 自管(选型:不引专用工具、不做后台蒸馏)**:`memory_block` 把 `.memory/` 可写绝对路径锚点 + 一段「记忆维护契约」注进 prompt,**契约+锚点常驻(即使记忆为空,解新用户冷启动不知道能记)**。agent 学到跨 task 稳定事实就用已有 `write`/`edit`/`grep` 维护,写前查重、extended 一事一文件 + frontmatter `description`。复用 fs 工具改动最小,人仍可审核手编。
- **召回升级**:extended 索引从「读首行当标题」升成**优先解析 frontmatter `description`**(召回依据更准),无 frontmatter 的存量文件退回首行标题(**公测期平滑兼容**)。
- **docker 路径转译**:发现旧 extended 索引注的是宿主绝对路径,docker 下 agent 看到的是 `/workspace/...` → 指不到。`mem_dir_display` 按 backend 给 host 绝对路径 / `/workspace/.memory`,与 working_dir 同套转译。
- 改动文件:`core/memory.py`(frontmatter 解析 + 契约 + 路径锚点)、`core/agent_builder.py`(算 `mem_dir_display` 传入)、`DESIGN.md` §3.7 同步心智+语义。单测覆盖 frontmatter 解析 / legacy 兜底 / 空记忆常驻契约 / host·docker 路径。明确不做:向量/RAG、全文搜索端点(正交,要做单开)。
- **前端只读记忆面板(GUI 当眼睛、模型当手)**:左栏「记忆」按钮(技能旁)开只读 modal 看全貌。**取舍**:查完业界(Claude 文件式给全套 view+edit;ChatGPT/Gemini 黑箱只给看/删)后定为 **GUI 只读 + "改"全走对话**(agent 自管已建好)—— "看全貌"是读不是 operation,走 LLM 又贵又只拿转述;"改"走对话 = 单一写入口 + 自然语言 + 不会写坏 frontmatter。后端只加 2 个只读端点 `GET /v1/memory`、`GET /v1/memory/extended/{filename}`(路径穿越校验收口在 `core/memory.py::read_extended_file`),**零写/删 API**。前端新增 `web/static/js/memory.js` + modal/CSS,复用 skills-modal 同构。契约里补明「用户说记住/改/忘掉是直接指令」。单测覆盖只读视图 / 单篇读 / 文件名安全 / 越界拦截。bump 0.11.1 → 0.12.0(本批含 agent 自管 + 记忆面板,同一 minor)。
### 2026-06-12 / 进入公测期:对外兼容策略
- 项目进入公测(对外真实用户在用)。`CLAUDE.md`「开发阶段心智」从"开发期可随意 break、不写兼容层"翻新为**对外契约(用户数据 / DB schema / 对外 API / CLI·env·文件布局)必须向后兼容,仅纯内部实现仍以最优为准放手重构**;拿不准 → 当对外契约处理。版本号段同步:公测保持 `0.x`,1.0 留给"对外冻结行为 / 正式 GA"。同条记忆 `feedback_dev_phase_no_compat` 一并翻新。bump 0.11.0 → 0.11.1。
### 2026-06-12(傍晚)修上下文压缩投毒 → run_python 空转报错
- **根因(DB 实测,60 个 task 命中 83 次 `[Error] bad arguments to run_python: code or script_path must be provided`)**:`core/context.py` 把旧 assistant `tool_call.arguments`(>800 字符)压成 `{"_compacted":true,"original_chars":N,"note":...}` marker 发给 LLM。模型在长 doc/ppt 任务里看到几十次"过去的 run_python 长这样",就**照葫芦画瓢把 marker 当真实参数原样吐出来** → executor 拿不到 code/script_path → 报错空转。83 次里 **61 次是模型仿写 marker**(铁证:抓到 `{"_compacted":true,"original_chars":85}`——85<800 压缩器根本不会出手且缺 `note` 字段,压缩器必带 只能是模型伪造),22 次是真· `{}`这正是代码里早已为 `task_progress` 单独豁免注释明写"会毒化模型"的同一个坑,只是 run_python 没豁免
- **修复(方案 A, task_progress 特例升级成通用规则)**:删掉 `_compact_assistant_tool_calls` / `_compact_tool_call_arguments`,`prepare_messages_with_stats` 不再压任何 assistant tool_call 参数(去掉 `old_tool_arg_chars` 形参与 `compacted_tool_call_arguments` 统计)。**只压 tool 结果 + skill( token 的大头)**,参数原样留 = 模型看到的范本永远是真实可执行调用,投毒向量连根拔。代价仅个别一次性大参数(如 12KB pptx 脚本)留在历史 1 条消息,不随轮数翻倍
- 诊断脚本落盘可复用:`scripts/diag_run_python_empty.py`(扫最近 task 的报错形态分桶)、`scripts/diag_run_python_trace.py`(回溯每条报错配对的 assistant 参数)。
- 验证:`tests/test_context_compaction.py` 2 条旧"压参数"断言为"原样保留"+ 去除已删统计键;全量 120 tests OKbump 0.10.0 0.10.1
### 2026-06-12(下午)admin 后台增强:目录 + 筛选排序 + 分页 + 导出 PDF
- **目录(TOC)+ 平滑滚动**:admin.html 左侧加 sticky 目录(运行态/任务/用户与用量/按模型/各用户用量/存储),点击 `scrollIntoView` 平滑滚到对应区(`.anchor { scroll-margin-top }` 避开 sticky 顶栏);IntersectionObserver 高亮当前区;窄屏目录变顶部横向 chip
- **按模型 / 各用户用量:时间筛选 + 排序**:两表从 overview bundle 拆成独立端点 `GET /v1/admin/usage/models?range=&sort=`、`GET /v1/admin/usage/users?range=&sort=&page=&page_size=`。range = all/7d/30d(`_range_cutoff`);sort = cost(按成本)/ tokens(按用量=输入+输出)。**各用户用量含零用量用户**故时间条件放 JOIN ON( WHERE),否则带 cutoff 会把零用量用户挤掉前端每表一组 range/sort 下拉,改筛选即重拉(用户表回第 0 );热力色按当前排序维度上色
- **存储分页**:`GET /v1/admin/storage/users?page=&page_size=`(bytes desc + user_id 兜底),前端独立翻页;overview 不再含 storage/by_model(只留 runtime/tasks/users/usage 总用量+近7d趋势,固定形态供轮询)。三个独立表各自 fetch自管 range/sort/page,overview tick 顺手刷新但不丢状态
- **导出 PDF(客户端打印)**:顶栏导出 PDF」→ 现取 overview + models(all/cost)+ users(all/cost top10)+ storage(top10)+ /healthz 版本,填充隐藏的 `#print-report` `window.print()`;`@media print` 只显报告、`@page` 边距表格描边版式。**零依赖**(不引 jsPDF / 不走服务端 soffice)、中文走浏览器字体版式完全可控;**列表只取前 10**(符合需求)。报告版式:抬头(标题/生成时间/版本)→ 运行态 任务 用户 用量总览 近7天 按模型 Top10 各用户用量 Top10 存储 Top10
- 验证:TestClient 跑通 models(range all=6/7d=4/30d=6、sort cost/tokens)、users(range+sort+分页)、storage(分页 42 );overview 已不含 by_model/storage;admin.js `node --check` 通过bump 0.10.1 0.11.0
### 2026-06-12(上午)
- **admin 管理后台(角色鉴权 + 独立监控页,可扩展为管理动作总入口)**:此前只有共享口令 `ZCBOT_ADMIN_TOKEN`(仅用于发用户),"管理员角色"概念,运维指标只打 stdout(`[stats]`)无界面本次落地按角色的 admin 区: **schema**:`users` `role` (`user`/`admin`,`server_default='user'`,migration 0009 只加列不动现有数据);② **鉴权**:`make_require_admin(cfg)` 先验 JWT( `require_user`)再查 `users.role=='admin'`,否则 403——**role DB 查不进 JWT**,改完下次请求即时生效 token 不重签;③ **端点**:`web/admin.py` `register_admin_routes` `GET /v1/admin/overview`(整组 `Depends(require_admin)`),一次返回 runtime(active_runs/max_workers/sse_subs/rss_peak, app.state, `_stats_logger` 同源)/ tasks( status+run_status 计数)/ users(总数+近7d活跃)/ usage(全局总用量+近7d按天+按模型)/ storage(各用户 bytes/file_count+配额)五段, GROUP BY N+1;另挂 `GET /v1/admin/usage/users?page=&page_size=` 分页返**各用户 token 用量**(全表 LEFT JOIN usage_events 含零用量用户,cost desc,稳定排序兜底 user_id;cost kindtoken/缓存命中仅 chat,与总用量同源)——前端独立翻页不随 overview 轮询丢页码;④ **前端**:独立单页 `web/static/admin.html`+`js/admin.js`(复用 localStorage `zcbot.token` format 工具,不挂主应用模块图),纯数字卡片+表格不画图、**阈值/热力色差**(active_runs 逼近 max_workers 变橙/磁盘按配额占比变色cost 列相对热力底色)、**响应式**(窄屏竖排)、 10s 轮询(切后台暂停);401/403 给明确提示+回控制台链接;⑤ **入口**:`/v1/me` `{user_id, role}`,dev SPA `enterApp` 拉一次,admin 才显顶栏"管理"链接(`/static/admin.html`);⑥ **建用户带 role**:`POST /v1/auth/admin/create_user` + 登录页弹框加角色下拉,`main.py user add --role` / 新增 `main.py user role --email X --role admin` 改角色。**命名取舍**:先按 inspect/dashboard 摇摆,最终定 **admin**——这页会长出建用户/改角色/配置(磁盘配额等)管理动作,admin 既盖""又盖""、且与 `require_admin`/`role='admin'`/`/v1/auth/admin/*` 一脉相承;监控总览只是其第一个 tab,后续在 `web/admin.py` 续挂 `/v1/admin/users`、`/v1/admin/config`。已用 TestClient 验:admin200 admin403 token401;五段聚合对真实数据跑通
### 2026-06-11
- **版本号机制(单一事实源 + 前端展示)**:此前只有 `web/app.py` 写死 `version="0.8"`(仅进 OpenAPI 文档,前端拿不到)。改为 `core/__init__.py` `__version__`(当前 `0.8.0`)作唯一来源 FastAPI `version`、`/healthz` 返回 `{"status":"ok","version":..}`前端左栏底部展示全引它,**改版本只动这一行**。前端 `main.js` boot 时无条件 fetch `/healthz`(auth 豁免,embed/未登录都拿得到)填进 `#app-version`,**钉在右侧文件面板底部存储条(`.storage-foot`)最左带细分隔线垂直居中**(纯展示不可点;随存储条一起显隐)。**不放顶栏**:embed 模式桌面端整层 header CSS 隐藏,顶栏点不到;**也不放左栏**:左栏底部留给后续按钮CLAUDE.md文档维护段已加规矩:每次 commit/push bump `__version__`(patch=修复/重构/调参/skill、minor=成批新功能/对外行为变化、major=1.0 发版)。
- **并发/线程池轻量监控 + 接管默认 executor8.4 落地第 1 )**:已上生产后线程池排队此前无观测手段lifespan 显式建 `ThreadPoolExecutor`(尺寸复刻 Python 默认 `min(32, cpu+4)`,env `ZCBOT_RUN_MAX_WORKERS` 可调大)+ `set_default_executor` 接管——run `asyncio.to_thread` 即用它,这样既能读 `max_workers` 判断排队也成了日后调并发的旋钮(**行为不变**,只从匿名默认池换成显式同尺寸池;run disk scan/pptx/reaper 仍共享此池,同原默认)。 `_stats_logger` 后台 task 60s 采样:`active_runs`(=`len(inflight)`,含排队中)逼近 `max_workers` 即排队 run SSE 会卡着不吐 token;**刷新峰值**时打 `[stats] new peak active_runs=N max_workers=M`(≥max_workers `[WARN 已在排队]`),**有负载**时打 `[stats] active_runs=.. max_workers=.. sse_subs=.. rss_peak=..MB`,**空闲静默不刷屏**。RSS stdlib `resource`(Unix 峰值/high-water;Windows dev 降级跳过),零新依赖; `broker.total_subscribers()` 给全局 SSE 订阅数查看:`journalctl -u zcbot | grep '\[stats\]'`。**不做监控界面**(运维健康是少数标量日志够诊断;业务分析数据已落 DB SQL)——界面阶梯见 DESIGN §8.4
- **dev SPA技能查看 modal(左侧 rail 底部入口)**:因 `.skills` 在文件面板隐藏,加左侧 rail 底部我的资源分组(`#rail-resources`,留位给后续记忆」)+「技能按钮 modal 平台 skill / 我的 skill两组列表,点任一项展开**完整 SKILL.md**(`GET /v1/skills/{name}` + 现有 markdown 渲染),「我的每项带删除(二次确认 `DELETE /v1/skills/{name}`,只删 user + 防穿越);覆盖标 `已覆盖平台同名`,`load_errors` 提示未加载的创建//fork 仍走对话 `web/static/js/skills.js`(零构建 ES module,main.js import + Esc 栈接入);`/v1/skills` 已带 source/overrides/load_errors。**纯查看 + 删除,不在 UI 做创建/编辑**(编辑天然对话式)。
- **用户私有 skill(每用户 `.skills/`,可从零写或 fork 内置再改)**:`SkillRegistry` 从单目录改**多来源**(`SkillSource` 列表:内置 `ROOT/skills` + 用户 `user_root/.skills`),后扫同名覆盖先扫 **user wins**;覆盖关系记进 `user_overrides`,discovery 显式标 `[你的·已覆盖内置]`(不静默)。`Skill` `source` 字段;`from_dir` 区分" SKILL.md(静默跳过)""有但格式错( `SkillLoadError`)",`_scan` 捕获用户来源的错收进 `load_errors`注入 system prompt 提示用户修(一个坏 skill 不再崩整次扫描)。容器路径改写从 LoadSkillTool 下沉到 registry(`container_dir` `source` `/sandbox/skills` `/workspace/.skills`),LoadSkillTool 去掉 `container_skills_dir` 参数。**关键判断**:写 skill host-side typed tool(`save_skill`/`fork_skill`,`tools/skill_authoring.py`)而非 fs/shell —— fs base_dir cwd(host)/ 容器 wd(docker),都够不到 `user_root/.skills`, backend 不可靠;host-side 工具知道 user_root 一个落点两模式通吃( seedream/DocumentDownload 一致范式)。`save_skill` 写时校验 frontmatter(名合法 / YAML 合法 / description / name 一致),`fork_skill` copytree 整目录(带脚本)+ 自动把 frontmatter name 对齐新名(否则 fork ppt 仍叫 ppt 会反覆盖内置)。`.skills` dotfile(文件面板隐藏, `.memory` 一致;`validate_task_name` 已禁 `.` 起头 working_dir,天然不撞)。`/v1/skills` 带上用户 skill + `source`/`overrides_builtin`/`load_errors`。新增 `skill-creator` 引导 skill。+`test_user_skills.py`(20 )+ 改写 `test_load_skill.py`性能:多扫一目录, `.skills` 的用户一次 `exists()` 跳过; skill 仅每 run +1-3ms,不在热路径
### 2026-06-10
- **system prompt 精简(瘦身 ~40 + 媒体段按需注入)**:`general_v1.md` + `_build_system_prompt` 去冗余: 宪法文件命名约定从 ~25 行压到 ~6 (只留格式定义 + 注入值 + 一行 current/重定调,操作细节本就由 proposal/ppt skill 各自讲,引用仍成立);② run_python write script script_path指引去重(原模板 + agent_builder 两处 合并进模板 1 ,顺带把 `scripts/` 子目录约定收进去);③ 媒体工具段(seedream/seedance 红线)从常驻模板抽成 `_MEDIA_TOOLS_BLOCK`, `ArkConfig.load() is not None`( ARK_API_KEY)时由 agent_builder 追加—— key 用户不再背 7 行永远报错工具的说明, ark_cfg 提前 load 一次复用给下方 tool 注册;④ 路径 echo 全形式 8 行压到 4 通用任务每轮 system prompt 净瘦 ~40-50 ,领域 task 加载 skill 后信息不丢。`test_system_prompt_paths` 仍过
- **上下文压缩加压力门槛**:压缩只在总 chars 超阈值(`caps.reliable_context×0.5×2.5 char/token`,flash 33 )时才做,未超则原样发—— DeepSeek 前缀缓存(短任务字节逐轮一致全程命中)+ 不白丢旧细节。`prepare_messages_with_stats(compact_threshold_chars=)`,`compaction_skipped` 进事件;默认 0=向后兼容永远压。实测高轮 task 缓存命中已 92-94%,故只补门槛不改滑动边界。+2 测试
- **单轮停机判据从步数解耦为是否在推进」**:`max_iterations` 轮预算降级为纯安全 backstop(flash 50120 / pro 100150),真正掐空转靠两道进展信号——`_RepeatGuard` 逐指纹无产出重复累计(SOFT2 注提示 / HARD4 拦截)+ run 级全局 `_stall`(整步所有 tool 无净产出 +1任一净产出清零,连续 8 步主动停)。 backstop / 熔断都 emit回复继续可续跑提示,不静默停。(诊断:task `b27466a0` 所谓中途断实为撞旧 50 步上限干净停下。)
- **`systemctl restart` 优雅 drain in-flight run**:restart 不再硬杀 BG run reaper 误标 error纯进程内零 DB 改动:lifespan `draining` + `inflight` 登记,先拒新 run(503+Retry-After) `asyncio.wait(drain_timeout)` 收尾,超时转协作式 cancel部署强耦合:unit `TimeoutStopSec` 提到 90(必须 > drain+grace),前端发送包退避重试。
### 2026-06-09
- **PPTX 前端在线预览(LibreOffice→PDF,DESIGN §8.3 Stage 1)**:文件区点 `.pptx` 改在线预览。关键洞察=前端已有 PDF iframe 路径,所以后端把 pptx 转 PDF 即可前端几乎不动。新 `web/pptx_render.py`(soffice 转 PDF,独立临时 profile 绕单 profile 锁 + 缓存 `.preview/<hash>.pdf` + 超时 kill)+ `GET /v1/files/preview_pdf`(复用鉴权防穿越 + per-path lock + run_in_executor)。转换在 web host 进程不进沙盒;部署装 libreoffice-impress + noto-cjk。
- **药3 复核:`/home/ubuntu/zcbot` 幽灵路径不复现 + 回归测试钉死**:该路径(docker 下 system prompt 焊死宿主路径,容器内找不到致 51 次重试风暴)已于 06-03 修复,复核当前代码 docker 分支只注入容器路径不泄漏宿主路径/uid。加 `test_system_prompt_paths.py`(2 例)防回归。高轮数三味药全部收口。
- **ppt skill 补「信息设计纪律」+ 混合背景 + pptx 预览器**:深读 pptmaster 后定位 ppt 观感差真因是信息设计纪律(~70%)非 SVG 渲染(~30%)——且这些全是 editable python-pptx 能做的。加 `add_takeaway`/`add_kpi(baseline+delta)`/`add_source`/`add_toc` + 组合件 `add_card_grid`/`add_timeline`/`add_cycle` + `render_bg.py`(Chrome 渲 mesh 渐变背景)+ `pptx_preview.py`(渲 PNG 肉眼验观感,当场抓到 set_text 多行只给首段上色的 bug);投影改克制(`add_card` 默认不投影)。**未动**:SVG→原生转换器(论证零增益)。
### 2026-06-08
- **loop 加病理性重复调用守卫(药1)**:`_RepeatGuard` 按 `(工具名, canonical 参数)` 指纹跟踪「无产出重复」——结果每次不同(改脚本重跑)算有产出、清零永不误伤;结果是 `[Error]` 或一字不差才累计;SOFT2 注软提示、HARD4 拦截。顺带堵 `_malformed_tool_calls` 退化成空 `{}` 的风暴。+`test_loop_repeat_guard.py`(7 例)。
- **检索/抓取类 host 工具批量化**:DB 实测高轮数烧 token 三股根因(空 `{}` 风暴 / 报错重试 / 检索不收敛)。把 `web_fetch`/`document_search`/`document_download` 从单数改列表入参、一轮并发处理一批(批内去重 + 单条失败隔离 + 超量截断明示),直接换签名不留单数别名。
- **ppt skill 视觉系统升级为卡片式**:学 ppt-master 后岔路三选,选 B(升级 python-pptx 设计系统,非自建 SVG 转换器——保留单脚本批量架构、原生可编辑)。`pptx_helpers` 加 add_card/gradient/kpi 等质感件 + 派生明暗色阶,layouts 扩到 13 版式,quality_check 按色相归桶对齐三色制。
- **system prompt 加「少来回」全局原则**:互相独立的操作合到一个脚本 / 一轮并发 call(轮数=token 线性乘数),但需看上一步结果的就老实分步。便宜补充(走缓存),不指望动 100+ 轮大头。
- **ppt skill 工作流批量化**:阶段二从逐页(每页一 run_python,~2N 轮)改成写一个 `build_deck.py` 一次建整 deck + 图标全 deck 批量预取,逐页大纲表替代逐页确认。N 页降到 ~3-4 轮。
### 2026-06-06
- **前端模块化 Step 2 收官**:把 main.js 剩余主体按干净度逐个剥成独立 ES module——layout / auth / preview / files / media / newtask / embed / chat(对话视图,合一个 chat.js 而非强拆 tasks+stream,因二者共享 state.liveRuns + run 生命周期)。main.js 2719→75 行入口;靠 ES live binding 解 main↔模块循环依赖;新增 import/export 一致性 + 从 main BFS 可达性校验。逻辑零改动纯剪切+连线。
- **修 deepseek-v4-flash 大参数工具调用 arguments 损坏 → loop 畸形重试**:大参数(7-10K)write/run_python 偶发把碎片错位粘进 `arguments` 致 json 解析失败;真正放大成灾的是 loop 把损坏消息入库 + 每轮重发的投毒级联。`_stream_llm` 改「校验 tool_call arguments 能否 json.loads,不能则丢弃整轮(不入库)重 roll,最多 3 次,最后降级非流式」+ executor 缺参早返友好提示替掉暴露签名的 TypeError。
### 2026-06-05
- **前端模块化 Step 1**:`web/static/dev.html` 4087 行单文件起步拆零构建 ES module(定方案「1 拆文件 → 2 局部引 petite-vue → 3 永不上 Vue+构建链」)。本步抽 5 个无依赖叶子(state/format/dom/api/markdown),主体落 main.js,`app.py` 加 `mimetypes.add_type` 兜底。逻辑零改动。
- **改密码弹框样式修复**:`#chpw-modal` 复用「选入文件」弹框头/体/脚布局,纯 CSS。
- **run_python 过程脚本约定 `<task_dir>/scripts/`**:显式写文件再 `script_path` 跑的过程脚本落 `scripts/`(可见/可重跑),inline `code` 匿名片段维持临时用后即焚。改系统提示 + tool 描述。
- **新增 `standard` skill(国标/行标/团标起草)**:核实市面无可复用 skill,据 GB/T 1.1—2020 自建。覆盖三层级(重点对接 CSTM 团标)× 两体裁;渲染复用 proposal `render_docx`/`render_diagrams`;quality_check 对标准误报无跳过开关 → 改 drafting_rules §8 人工 12 条清单。
### 2026-06-04
- **ppt 版式 helper 收进可 import 模块 + 修中文字体没真生效**:抽出 `pptx_helpers.py`(每页 `import P` 免默写 150 行 + 治长 deck 坐标漂移);字体修复=`set_text` 同时写 latin=Arial + ea/cs=微软雅黑(python-pptx `font.name` 只写 latin 是中文不生效根因)。
- **ppt `quality_check.py` 加内容形状重叠检测**:纯数值两两包围盒,只检有文字/图片的内容形状(装饰元素天然排除),交叠 ≥25% 才报。
- **ppt `quality_check.py` 配色纳入形状填充色 + 改三色制判定**:加 `_shape_fill_hex`,粗阈值「≤5 色」改「非灰阶色 ≤3」(`_is_neutral` 排中性色),否则合规红 deck 狂报假阳。
- **前端顶栏展示用户已用存储**:`GET /v1/user/storage`(复用 `user_disk_usage` 表),右侧文件面板底部钉进度条;不限额只显已用。
- **sandbox 容器 env 收编到一处 + shell 也注入**:`executor_docker` 抽 `_CONTAINER_ENV={PYTHONPATH=/sandbox:/workspace, HOME=/tmp}`,shell/run_python/fs 三路共用(修 shell 里 import skills 报错 + 只读 rootfs 下缓存写不进的噪音)。纯代码改重启生效。
### 2026-06-03
- **修 docker sandbox 下 system prompt 焊死宿主路径**:docker backend 时工具在容器跑但 `_build_system_prompt` 注入的是宿主绝对路径(容器内不存在),LLM 据此 find 全空。docker 下 `task_dir` + 宪法 glob 范例换容器路径 `/workspace/<wd_rel>` + 去掉无意义 cwd 行;host 不变。
- **顺扫清掉 SKILL.md 里残留的宿主路径假设**:patent 跨 skill 调 proposal 脚本改兄弟相对路径;research/patent/proposal/ppt 的硬编码 `D:/projects/zcbot` 与废弃旧布局举例改双形态说明。
- **修 ppt 图标缓存写进只读挂载**:种子图标库降为只读(glob 读),`fetch_icon.py` 新拉图标一律 `-o <task_dir>/assets/icons/`(与「产物只写 task_dir」一致)。
- **默认镜像源改清华(pip+apt)/ 腾讯(npm)**:腾讯 PyPI 吐损坏 litellm wheel(镜像端文件损坏)。
- **回退 `ZCBOT_WORKSPACE_DIR` env 覆盖,workspace 落数据盘改用 bind mount**:env 覆盖与 `paths.py` 锚 ROOT 的相对存储冲突致三家分叉,改 bind mount(`/data/zcbot/workspace`→`ROOT/workspace`)。
### 2026-06-02
- **【已于 06-03 回退】`resolve_workspace` 加 env 覆盖 `ZCBOT_WORKSPACE_DIR`**:prod 想 workspace 落独立数据盘,回退因与相对存储锚点冲突。
- **修 embed 模式「登录页一闪而过」**:`<body>` 首行加同步内联脚本,`?embed=1` 立即加 `embed-mode` class 赶在 `#login` 绘制前隐藏。绘制时机问题非鉴权。
### 2026-06-01
- **`deploy/update.sh` 加自更新重跑守卫**:`git pull` 改脚本自身时 `exec` 用新版本从头重跑(标记防死循环)。
- **`deploy/update.sh` 默认源改腾讯 + build 跳过改 `--skip-build` + 进度可见**:根因=阿里 PyPI 同步滞后缺 `litellm>=1.83`
- **修 MP host 工具的全量下载(IP 被封根因)**:`mp_search_summary` 没传分页致每搜一次整库级下载被 MP 判 abusive 封 IP;改 `num_chunks=1` 服务端限量。(宿主 IP 仍需邮件 support 解封。)
- **加一键部署脚本 `deploy/update.sh`(Ubuntu/systemd)**:`git pull → pip → db upgrade → docker build → restart → curl /healthz`;钉死两点:migration 从 .env 抠 `ZCBOT_DB_URL`、build 必须在 restart 之前。
- **sandbox 镜像加中文字体**:Dockerfile slim 起一个 CJK 字体没装致 matplotlib/mermaid 出中文方块,加 `fonts-noto-cjk fonts-wqy-microhei`
- **documents / Materials Project 带 key 能力改 host-side tools,key 不进 sandbox**:新增 `tools/documents.py` + `tools/materials_project.py`,仅宿主 env 有 key 时注册,写文件绑 task_dir。
- **删 `skills/pymatgen/materials.py::mp_rester()`**:sandbox 内读 key 的旧入口,host tool 化后多余且违背「key 不进 sandbox」。
### 2026-05-29
- **Seedream 5.0 i2i base64 通路 probe + DESIGN §8.1 落册**:实测 `/images/generations` 接受 base64 data URL → 内网部署无需对象存储中介。选 E+C 组合,本版仅 probe + design,tool 改造未启动。
- **web 端 tool_call 标题行改显中文活动描述**:修读错字段(`arguments` vs `args`)+ `toolActivityLabel` 按 12 工具套中文动词。
### 2026-05-28
- **`skills/review/SKILL.md` 加「长文档处理」段**:阶段 1 骨架扫描(停下等用户挑章节)→ 阶段 2 分章深审 + 中间文件落盘。
- **新增 `config/models/local.yaml`(family=local,r1/qwen3)接内网 OpenAI 兼容服务,涉密专用**:关键 `thinking_mode=false`(R1/Qwen3 天生推理,发 reasoning_effort 本地 vLLM 多半 400);不改默认模型。qwen3 跑通,r1 调试中。
- **修 `LoadSkillTool` 在 docker backend 返 host 绝对路径**:加 `container_skills_dir` 参数,docker 时返 `/sandbox/skills/<name>`,references-heavy skill 自动 work。
- **新增 `analyze` skill(科学问题分析/拆解/引导)**:四段式 PICO→Issue Tree→分支(Fishbone/First-principles+TRIZ/DoE)→路线图,定位协调器不执行任务,接力下游 skill。
- **Python 3.10→3.12 升级(host + Dockerfile)**:mp-api 依赖链 `NotRequired`(3.11+)在 3.10 import 不进;顺手修 `executor_docker` PYTHONPATH `/workspace`→`/sandbox:/workspace`。
- **新增 3 个科学计算 skill(pymatgen / stats_ml / plot_pub)**:服务无机非金属材料 R&D。pymatgen 带 `CEMENT_PHASES` 中英文相名映射 50+;stats_ml 纯指南;plot_pub 带 `apply_pub_style()` 出版级中文字体 fallback。挑 4 个 ★★★ fork 单装。
- **DESIGN §7.5 增「image 体积 / 多 user 资源 / 后续加包策略」决策段**:① image 大 ≠ 吃更多 RAM(layer 共享);② 多 user 瓶颈在并发 exec 不在 idle 容器;③ 新增依赖走「base 收敛 + per-user 持久化 venv + 使用频次沉淀」。
### 2026-05-27
- **ppt skill 歧义反问 + general_v1 加「产物形式歧义先问」通用原则**:「汇报方案」被误路由成 PPT,改先反问「PPT 还是文档」并升格到 system prompt 让新 skill 继承。
- **ppt skill description 收紧路由**:改显式白名单(PPT/幻灯片/.pptx/slide/deck)+ 显式反例(报告/文档/纪要不触发)。
- **skill 热更新:`/v1/skills` 每次现扫**:原启动扫一次须重启;改每次现扫(~3ms)。
### 2026-05-25
- **dev SPA 前端依赖 CDN 本地化 + 升级稳定版**:markdown 渲染(marked/dompurify/highlight.js)从 jsDelivr 改本地 `vendor/`,避免内网/跨境 CDN 抖动;`test_static_vendor.py` 回归。
- **dev SPA 一批上传/布局交互打磨**:三类上传入口改 XHR 显进度 + 粘贴 chip 可预览可删;三栏右栏折叠 + 分隔线拖拽调宽(LS 持久化)。
- **接入博查 Web Search + Web Fetch**:`web_search.py`(Bocha,仅 env 有 key 挂)+ `web_fetch.py`(httpx + html2text,SSRF 内网屏蔽)。
### 2026-05-22
- **dev SPA 加 iframe embed 模式(`?embed=1&parent_origin=`)**:父页 postMessage 握手拿 JWT,`event.origin` 双向白名单,`PLATFORM_KEY` 不下发浏览器;`web/EMBED.md` 对接手册。
- **embed 模式接受 `task_id` URL 参数定位 task**:首次签发后 `selectTask`,`once` 标记只生效一次(401 重签不重置用户中途切的 task)。
- **媒体生成每账号每日配额(yaml 可配,默 20 图/5 视频)**:`check_daily_quota` 按服务器本地今日计,超额返中文提示不烧钱;tool 返串不贴 yaml 路径防泄漏 schema。
- **对外路径协议刚性化**:`general_v1.md` 规定助手 echo 产物路径用 user_root 相对全形式 `<wd_name>/<rel>`(简写致 chip 失效),跨产物 skill 统一;UI 一次性兼容历史简写。
- **豆包 Seedance 2.0 Fast 视频生成接入(文生视频)+ videogen skill**:`tools/seedance.py`(ark 建任务→轮询→download mp4,失败/cancel 不计费);build_agent 加 `video_variant` + cancel_check;skill BLOCKING 门槛更严(¥4 vs ¥0.22)。phase 1 仅 t2v,fast 上限 720p。
- **dev SPA 移动端自适应 + 交互打磨**:手机两档断点(平板 rail / 手机单列 `.mobile-tabs`,`100dvh` 解 iOS、输入 ≥16px 防缩放);chat-input 支持 Ctrl+V 粘贴上传。
### 2026-05-21
- **dev SPA UI 打磨**:修 primary 按钮 hover 文字消失;4 个 modal 抽 `.modal` 基类(style 589→522 行);新建任务/filter 工作目录回原生 `<select>` + sentinel + 二级 input(combobox 试过推翻)。
- **sandbox 阻塞地位写进 DESIGN(§7.7/§7.8/§7.9)**:`shell.py::BLOCKED_PATTERNS` 是 trivial-bypass 装饰品(命令注入图灵完备),不继续加规则;正确防线在 OS 层 §7.5。本地 dogfood blast radius 限自身;SaaS 外部开放才 hard prereq。
- **system prompt 注入 task 预选 skill 提示**:`_build_system_prompt` 加 `task_skill` 参数,与 general_v1「对应 skill 先 load」组合 → 主动 load。否决完整 SKILL.md 预注入。
- **imagegen skill 加 ⛔ 调 tool 前必须贴 prompt + BLOCKING 等确认**:把模型脑内装配摊到对话层让用户最后过一眼防白烧 ¥0.22;诊断五维→六维加比例/尺寸。
- **新增 imagegen skill**:单文件五步法(诊断模糊度→给推断+待确认→拍板→装配 prompt→调 seedream);mermaid vs seedream 选型三段式。
- **登录页加「管理员添加用户」入口 + 删 chat meta 条/tok 显示**:`create_user`(CLI/web 共用)+ `POST /v1/auth/admin/create_user` 校验 `ZCBOT_ADMIN_TOKEN`。否决 User 表加 is_admin 列。
- **新增 documents skill(内部材料学科知识库 document_search API)**:四函数 Bearer 认证,search 返整篇 Markdown,反模式约束只 print 前 300 字防爆上下文;库=7 材料学科英文论文 100W+ 文件 + 跨语言语义检索;与 research(OpenAlex)互补。
- **dev SPA SSE 客户端重连**:`fetchSse` 拆 consume + 重连壳(1/2/4s 退避 ×3);后端 `stream_events` 入口检 run_status 非 running 立即吐 done 关流。
- **research skill fetch_pdf 改走静态直链 + list 端点加直链 + pg_trgm GIN 索引**:绕开 paper_pdf_view 路径 bug;`?search` 30s→几十 ms;SKILL 加「XML 优先 PDF」。
- **顶栏 token 累计修(`sync_task_tokens` 改走 messages SUM)**:切 streaming 后内存计数器永不更新,改现算 + backfill。
- **同 wd 并发软警告 banner + `/v1/tasks``run_status` 筛选**:Claude Code 同款「信任 + 软警告」;否决硬挡/short_id 全产物隔离/clone task(DESIGN §7.9)。
- **paper_server → research skill**:范式判断走 skill(非 tool/MCP/裸 httpx),`skills/research/{SKILL.md,paper.py}` 三函数。
### 2026-05-20
- **dev SPA artifact chip 演进**:对话内 tool_call/result 挂产物 chip,门控落到产物工具白名单 `ARTIFACT_PRODUCING_TOOLS={seedream,seedance}`,assistant 正文走 seenRels 去重 + allowInlineMedia 防二次 inline。
- **CLAUDE.md 加「实施前先对方案」段**。
- **loop.py tool message 补 `name` 字段 + backfill 历史**:OpenAI tool spec 本有 name,缺它致历史回放无 banner/chip。
- **dev SPA 输入区删上传按钮 + 加「✨ 润色」按钮**:`POST /v1/tasks/{id}/optimize_prompt` 走 task model 装配 LLM,计费 `kind=prompt_optimize` 不污染顶栏。
- **顶栏加生图模型下拉 + 中间产物图片/视频内联展示**:`GET /v1/image_models` 扫 yaml,`build_agent(image_variant=)`;`renderArtifactBarHtml` 走 blob URL inline,切 task 回收 blob。
- **LLM 调用切 streaming(cancel 秒退)+ 发送/停止合并单按钮**:`chat_stream(stream=True, include_usage=True)` + `stream_chunk_builder` 拼回,chunk 间 poll cancel;前端打字机靠 emit text delta。
- **dev SPA seedream tool 透明性 banner**:tool 返串首行 `[seedream] model=...· cost=¥...`,前端正则 parse 挂折叠徽章。
- **豆包 Seedream 5.0 接入 + 0007 cost_usd → cost_cny 全表统一币种**:`tools/seedream.py` 走 ark_client,产物落 `<wd>/figures/`;0007 全表 ×7.2 折 CNY;仅 ARK_API_KEY 设了才挂。
- **`POST /v1/files/delete` 加 `recursive` + 顶层目录 task 引用闸**:recursive 走 rmtree;顶层目录被 task 引用 → 409。
- **fs tool 输出渲染 user_root-relative 路径**:`Tool` 加 `user_root` + `_display(p)`,消 chip 404 + 防 uuid/部署根泄漏。
- **`POST /v1/tasks/{id}/clear` 清空对话**:同事务 lock + 检 running + DELETE messages + reset 三列累计;usage_events 全不动(账单 source of truth)。
### 2026-05-19
- **0006 模型切换(c 模式 task 级 A 粒度)+ usage_events v2 表**:`tasks.model_profile` 变 source-of-truth,顶栏下拉 PATCH 下条 send 生效;message 历史按 `messages.model_profile` 画切换点;usage_events 重建多态形态(units jsonb,chat 已接入)。
- **dev SPA 登录撤回邮箱+密码,删 invites 表**:「邀请码 env→invites 表」一日游撤回;复用 users.email + bcrypt;dev SPA 双 tab 登录。
- **SENTINEL user 彻底撤(数据 + 代码)**:web 必走 JWT 后 sentinel 无角色;CASCADE 删 + 10 处代码删;`build_agent` 加 `*` 让 user_id 必填。
- **任务/文件行 `⋯` 下拉菜单 + tool_result debounce 刷新右侧**:单例浮层菜单(position:fixed)避 pane overflow 裁剪;tool_result debounce 500ms 刷新文件 panel。
- **proposal skill mermaid 强制 + quality_check 加图相关 4 拦截**:模型曾写满 ASCII 字符画;render_diagrams caption 强制必填+唯一,quality_check 加四条;`/v1/files/download` 加 `Cache-Control: no-cache`
- **dev SPA 文件预览弹框**:点击不再直接下载,90vw 模态按扩展名分派(image/pdf/text/md + docx-preview + SheetJS);vendor 入 git(~1MB)。
### 2026-05-18
- **入口归位:`cli.py`→`main.py`,原 `main.py`→`core/agent_builder.py`,删 CLI REPL,§7 E 撤**:`main.py` 原混三角色按 SoC 拆,CLI 只剩 `db/probe/web/user`;dev SPA 已是 dogfood 主路径,REPL 无双 transport 维护税。
- **0004 schema 大瘦身:删 runs / usage_events 旧版,合 run_status/run_error 入 tasks;路由 run_id→task_id**:单活 run 形态客户端只需 task_id。
- **`POST /v1/files/rename` + 顶层目录 delete 加 task 引用闸**:`/v1/files/*` 升格唯一目录树 mutation 入口,DB-FS 一致性服务端内化;顶层目录走 DB-aware 分支(SELECT FOR UPDATE + 409 + check_no_subtask + UPDATE 先于 FS)。
- **task 级 cancel + AgentLoop 协作式 cancel + dev SPA stop 按钮**:Broker 加 per-task `threading.Event`;Loop 加 cancel_check + `_fill_cancelled_tool_results`;同步 call 本身不可中断(后接 streaming 修)。
- **`POST /v1/tasks/{id}/messages` 单活 run 锁 + 孤儿 reaper**:同事务 SELECT FOR UPDATE + 活跃检查 + 标 running 三步原子;lifespan reaper 清 crash 孤儿。
- **proposal skill 流程图/结构图管线**:`render_diagrams.py` 扫 mermaid → mmdc/mermaid.ink → png;render_docx 识别 `![](...)` + mermaid 围栏;图编号递增。
- **system prompt skill 机制改「可选辅助」**:简单问答/读代码/改 bug 不必硬套 skill;接 GET /v1/skills 下拉。
- **`GET /v1/skills` + dev SPA skill 字段改下拉**;**dev SPA 全套 UI 中文化**(技术字段不动)。
### 2026-05-17
- **0003 schema:name + working_dir + skill 三件套**:任务标识与工作目录解耦;`TRUNCATE tasks CASCADE` + 加 `name NOT NULL`;`GET /v1/folders` 给 datalist。
- **`GET /v1/tasks` 分页 + 多维筛选 + ordering**:`{page,page_size,count,results}` + 6 query;allowlist 防注入;默认 `-created_at`
- **task 硬删 API + dev SPA delete + 文件 per-row 删**:`DELETE /v1/tasks/{id}` 删 DB 行(messages CASCADE)+ **FS task_dir 不动**(同 name 多 task 共享)。
- **files API 全面 user-rooted + dotfile 过滤**;**files 面板 UX 项目名 + 修 root crumb bug**。
- **task_dir 改 eager mkdir**:`build_agent` + `create_task``mkdir(exist_ok=True)`,name=项目声明。
- **task = name-based 项目目录 + memory dotfile**:废 UUID 派生 + `tasks/` 中间层;`task_dir = workspace/users/<uid>/<name>/`,同 name 共享;memory 搬 `.memory/`;`validate_task_name` 拒 `.` 起头。
### 2026-05-15
- **§7 D 阶段 `/v1` JSON API 落地;Phase G Jinja2/HTMX UI 路线撤**:删 templates + jinja2/markdown-it-py/pygments;SSE event 由 HTML 片段切 JSON;dev.html 留作 dogfood 主路径。
- **§7 D' 过渡 auth(PLATFORM_KEY → JWT)+ dev SPA**:pyjwt HS256 + `AuthConfig.from_env()` fail-fast;数据隔离全 `Task.user_id == user_id`;SSE 走 fetch + ReadableStream 手解。
- **task_dir 改相对存储**:DB 存 ROOT 内→相对 posix;`core/paths.py::{ROOT,to_db_path,from_db_path}`;alembic 0002 backfill;CLAUDE.md 加「开发期不写兼容层」。
- **workspace 布局统一 per-user** + **litellm 启动 cost map 网络警告兜底**(`LITELLM_LOCAL_MODEL_COST_MAP=True`,冷启 ~5s→<1s)+ **Phase G Jinja2/HTMX Web UI 全撤**(沉淀的 sink/broker/no-subtask/files 安全归一保留)。
### 2026-05-14
- **§7.1 心智模型修正:Folder-centric Task 一等公民 + Dir 文件副视图**:dir 不是 task 父容器,双视图正交
- **§7 B Steps 1-4 + 6**:SQLAlchemy 2.x ORM(5 )+ alembic + `cli db`;`state.json` 全废入 PG;`check_no_subtask` 查前缀嵌套
### 2026-05-12
- **§7 改写**:platform/core 多租户方案废弃, user-direct(folder-centric task-primary;task/messages PG;no-subtask;hard cascade)。
### 历史(2026-Q1 → 05-11)
- **Phase 1-4**:骨架 / skill / run_python / Model Profile + Probing;ppt v3 商务红 + apply_brand + Iconify;素材摄取改 markitdown CLI
- **05-06 05-11**:Phase 6 部分(task + state.json + tokens 累计);TUI rich + `/resume`;DESIGN §7 初版(05-12 重写);`cli.py export` + `core/export_docx.py`;`atomic_write_text` + `core/memory.py`;loop 事件流化铺 SSE
- **06-04 token 优化启动(DESIGN §8.2)**:chat usage cache hit/miss + LiteLLM cost=0 时按模型档案 CNY/Mtok 兜底;`run_python` `script_path` 模式;`core/context.py` 压缩旧 tool / load_skill / assistant tool_call arguments 不改持久化历史;`llm_start` SSE 输出 `context_*` 压缩统计
- **06-05 记账给缓存命中折价**:发现 task `tokens_in` 88.6% 是缓存命中却按全价记致虚高 2-3x;`ModelCapabilities` `cache_hit_cny_per_mtoken`,成本公式拆三段;前端 hover tooltip 显真实成本 + 命中率(分母改 usage_events 同源, 100%);历史走 backfill 脚本
---
## 关键决策与偏差
| | 决策 | 备注 |
|---|---|---|
| 工具基目录 | cwd()+ working_dir() | system prompt 同时注入两者绝对路径 |
| Workspace 布局 | `workspace/users/<user_id>/{.memory/, <name>/}` | per-user 隔离;memory dotfile 防撞; name task 共享 |
| Eval Suite | 不做 | 个人工具 dogfooding |
| 版本化 prompt | 直接 `general_v1.md` | 真要切再做 |
| run_python 沙盒 | subprocess + env 过滤 | Docker §7 C 阶段 |
| 兼容层 | 开发期不写 | DB schema / 字段 / API 改动直接切, CLAUDE.md |
| `/v1/files/*` DB | files API 作目录树唯一 mutation 入口,DB-FS 一致性服务端内化 | rename / delete 顶层目录 DB-aware |
| 单活 run | task 同时最多 1 个活 run | gate `post_message` 同事务 `SELECT FOR UPDATE` |
| LLM 调用走 streaming | `chat_stream` + `stream_chunk_builder` 拼回;chunk + tool_call poll cancel | cancel 延迟 100ms ;content delta 即时 emit |
| 上下文压缩 | 加压力门槛,超阈值才压 | 护前缀缓存 + 不丢旧细节8.2 / 06-10) |
| 停机判据 | max_iterations 降为 backstop,靠进展信号掐空转 | `_RepeatGuard` + run `_stall`(06-10) |
---
## 文件清单
```
core/capabilities.py 75 ← 模型档案增加 CNY/Mtok 计费兜底字段
core/llm.py 151 ← litellm 离线 cost map env + chat_stream(stream + include_usage)
core/loop.py 300 ← sink.emit + _stream_llm(chunk 间 poll cancel + emit delta)+ _RepeatGuard + usage cache 明细
core/context.py 95 ← LLM 调用前压缩旧 tool / load_skill 消息(带压力门槛),保 tool_call 协议字段
core/sinks.py 101
core/paths.py 50 ← task_dir db form 归一
core/probe.py 243
core/session.py 153 ← ORM
core/task.py 82 ← PG-backed TaskState
core/skills.py 180 ← 多来源 registry(SkillSource)+ source 标记 + 覆盖感知(user wins)+ load_errors + container_dir
core/memory.py 81 ← per-user `.memory/` dotfile
core/export_docx.py 383
core/storage/{__init__,engine,models,usage,utils}.py ← 4 表(0004-0007 演进);record_chat/image_usage
core/ark_client.py 105 ← 火山方舟 HTTP 客户端
core/agent_builder.py 340 ← 装配 lib(有 ARK_API_KEY 才挂 SeedreamTool);build_skill_registry 装两来源
core/executor.py / sandbox/{network,pool}.py / executor_docker.py ← Executor ABC + Docker per-user 容器池
tools/{base,fs,shell,run_python,skill_tool,skill_authoring,seedream,seedance,web_search,web_fetch,documents,materials_project}.py ← skill_authoring=save_skill/fork_skill(host-side 写 user .skills)
main.py ~210 ← 入口:web / db / probe / user / sandbox check
db/migrations/versions/ 0001-0008
web/app.py ~1360 ← /v1 JSON API + user_id 隔离 + run lock + cancel + files + pptx 预览 + skills(列表/正文/删)
web/auth.py ~190 ← 邮箱密码 + platform_key → JWT
web/broker.py / sinks.py / pptx_render.py
web/static/dev.html + js/*.js ← dev SPA 拆 15 个零构建 ES module(main.js 入口;skills.js=技能查看 modal)
web/static/vendor/ ~1 MB ← jszip / docx-preview / xlsx
─────────────────────────────────
Python 合计 ~3400 行(+ dev SPA + vendor 1MB);加 skills 脚本 + 配置,总仓库约 3800 行
```
---
## 下一步候选(性价比排序)
1. **CORS 收紧**(~半小时,已接入真实用户应尽快做)—— `allow_origins` `*` 改读 env 域名 allowlist; OIDC 解耦
- ** OIDC**(~1 ,选做)—— `/v1/auth/login` 内部换 ID token 校验(路由层 Depends 不动);**邮箱密码长期保留并存**。platform_key 信任模型可接受则可延后,真要弃 PLATFORM_KEY 共享密钥时再做(延后无技术债)。
2. **§7 C Executor + sandbox 收尾**(~3-5 , §7.5 落地清单)—— Step 4 完整 egress proxy + Step 3b PGID kill 协议 + xfs project quota OS 层硬化。**Stage C DoD** = 6 条落地清单全完成 + 红队回归通过(metadata IP / PG IP block残留进程清理 user 网络隔离egress allowlist)。**多用户在线跑代码 hard prereq**。
3. **Phase 6 context 第二步 task summary**(~1 )—— 旧消息压成一条 summary(区分硬约束/计划/文件路径/关键事实),不直接塞回旧 tool 原文
> §7 B + D + D' + 单活 run 锁 + cancel + 0004 schema 瘦身 + 入口归位 + Stage C 主体已完工。剩余:真 OIDC → C 收尾 → F(deploy/billing)。§7 E CLI 双模式撤;Phase G Jinja2/HTMX 撤(详见 DESIGN §7.9)。