# 实施进度 > 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。 最后更新:2026-06-04(sandbox 容器 env:shell 也注入 PYTHONPATH + HOME=/tmp) --- ## 状态 | 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 + 应用层磁盘配额(scan+gate)✅ + dogfood 网络放开 + 容器内 pip/npm 源持久化 ✅**;**Step 4 完整 egress proxy + Step 3b PGID kill 协议延后到外部用户开放前**;**外部用户开放仍需 egress proxy + xfs project quota OS 层硬化(§7.5 落地清单 #2 #4)**。 | --- ## 已完成关键能力 ### 2026-06-04 - **ppt skill 版式 helper 收进可 import 的模块 + 修中文字体没真生效**:逐页生成是「每页一个 run_python」,以前 ~150 行 helper(配色常量/`add_textbox`/`apply_brand` 等)要在每页里默写一遍 —— 烧 token 且长 deck 里坐标会漂(第 7 页 `apply_brand` 跟第 2 页对不上)。抽出 `skills/ppt/scripts/pptx_helpers.py`,每页 `import pptx_helpers as P` 调用;新增 `new_presentation`/`load`(按文件实际尺寸回填画布常量,逐页进程间同步)/`add_slide`/`set_palette`(默认商务红,`spec_path=` 自动取 spec 前 3 个 hex 作主/辅/强调)入口。**字体修复**:python-pptx `font.name` 只写 ``,中文字形走 `` 槽位没设 → 「指定微软雅黑却没真生效」的根因;`set_text` 改为同时写 latin=Arial + ea/cs=微软雅黑,中英混排各命中正确字体。改 `layouts.md`(helper 块换成 import 起手 + API 速查,9 个示例全改 `P.` 调用)、`icons.md` A5 示例、`SKILL.md` 资源/阶段二。冒烟测试过:`ea` 确写入、`set_palette` 覆盖生效、quality_check 正常解析。 - **ppt `quality_check.py` 加内容形状重叠检测**:原有数值检查只覆盖越界 + 按字数估算的文本溢出,盲区是"两个都在画布内的形状互相重叠"(文本框压图标/压另一文本框)。加纯数值两两包围盒重叠检测——**只检"内容形状"**(有非空文字 / 是图片),装饰元素(无文字纯色填充:品牌条/分隔线/圆点/色块标签/装饰星箭头)天然排除,"文字叠在色块上"也不误报(色块无文字)。交叠宽高均 >0.08in 且 交叠面积/较小形状面积 ≥25% 才报,滤掉边缘贴合。测试过:合规 deck(L2 徽章+字 / L5 标签叠 chip / L4 圆点+bullet)零告警、两文本框故意叠触发并报百分比+形状名。零依赖、确定性、host+docker 都跑。 - **ppt `quality_check.py` 配色检查纳入形状填充色 + 改按三色制判定**:原来只数 `run.font.color`(文字色),品牌条/徽章/圆点/标签/底块的**填充色全漏**——而这些恰是最易跑偏处。加 `_shape_fill_hex`(取纯色实心填充,主题色/非实心挡掉)并入 `seen_colors`。同时把粗阈值「≤5 色」改成贴合三色制的「非灰阶色 ≤3」:`_is_neutral`(R/G/B 极差 ≤12 视为灰/黑/白)把中性色排除——否则一旦计入填充,合规商务红 deck(INK/GREY/HAIRLINE/BG/WHITE+3 红)轻松超 6 狂报假阳;spec 比对也只比非灰阶色。测试过:合规红 deck 无配色 warning、塞 4+ 彩色触发、ACCENT 强调线填充被正确捕获。 - **前端顶栏展示用户已用存储**:后端已有 `user_disk_usage` 表(后台 15min 扫描落库),但无对外查询口。加 `GET /v1/user/storage`(`Depends(require_user)`),返 `{bytes_used, file_count, limit_bytes, scanned_at}`,`limit_bytes` 由 `parse_bytes(quotas.disk_bytes_per_user)` 得(≤0/None=不限)。`disk_quota.get_user_usage` 扩为返 `(bytes,count,scanned_at)` 三元组(复用而非新开函数,顺手改唯一调用方 `check_disk_quota` 解包)。前端 `dev.html` 右侧「文件」面板底部钉一条进度条+文字指示器(`#pane-right` 改 flex 列让 `#file-list` 独占滚动、存储条钉底;`loadStorage()` 在 `enterApp` 拉一次;不限额时只显已用、隐进度条;超额变红;hover 显文件数+统计时间)。 - **sandbox 容器 env 收编到一处 + shell 也注入(修两个只读 rootfs 副作用)**:① `PYTHONPATH=/sandbox:/workspace` 原先只 `run_python` 注入,shell 里 `python -c "from skills..."` 撞 ModuleNotFoundError;② `--read-only` rootfs 下 `/home/zcbot` 不可写,matplotlib/fontconfig 往 `~/.config`/`~/.cache` 写缓存刷 "Read-only file system" / "No writable cache" 噪音。改:`executor_docker.py` 抽 `_CONTAINER_ENV = {PYTHONPATH, HOME=/tmp}`,shell/run_python/fs 三路共用(`-e` 确定性覆盖)—— `HOME=/tmp` 一刀让缓存落 tmpfs(matplotlib→/tmp/.config、fontconfig→/tmp/.cache),不用逐个 MPLCONFIGDIR/XDG_CACHE_HOME。纯代码改,重启 web 生效,免重建镜像。 ### 2026-06-03 - **修 docker sandbox 下 system prompt 焊死宿主路径(agent 找不到自己的文件)**:`ZCBOT_SANDBOX_BACKEND=docker` 时 shell/run_python/fs 工具全在容器里跑(`/users/` bind 到 `/workspace`),但 `_build_system_prompt` 注入的 `task_dir` / cwd 是宿主绝对路径(容器内不存在),LLM 据此 `find /home/ubuntu/zcbot ...` 全空、瞎转到 `pwd` 才发现真身在 `/workspace/`。修法同 `LoadSkillTool` 的 `container_skills_dir` 改写:docker 下 `task_dir` + 「宪法」glob 范例换成容器路径 `/workspace/`,并去掉 docker 下无意义的 cwd 行(容器 cwd 恒等 task_dir);host 不变。 - **顺扫清掉 SKILL.md 里残留的宿主路径假设(同 docker 隐患)**:`patent` 跨 skill 调 proposal 脚本原用 `/skills/proposal/...`(还举例硬编码 `D:/projects/zcbot`)→ 改兄弟 skill 相对 `/../proposal/scripts/...`(两后端都对);`research` 的 `fetch_pdf` 范例硬编码 `working_dir=r"D:/..."` → 改 `` 占位;`patent`/`proposal`/`ppt` 的 `` 举例用废弃旧布局 `…/workspace/tasks//` → 改 host/docker 双形态说明。 - **修 ppt 图标缓存写进只读挂载(docker 拉新图标失败)**:`fetch_icon.py -o /assets/icons/` 在 docker 下 skills 是 `:ro` bind,写回必败;host 下还顺带污染仓库工作树。改为(方案 A,与"产物只写 task_dir"的全局规矩一致):种子图标库 `/assets/icons/` 降为**只读**(glob 读),`fetch_icon.py` 新拉的图标一律 `-o /assets/icons/`;读路径改两处都 glob(种子库 + 本 task)。涉 `ppt/SKILL.md`、`references/icons.md`、`assets/icons/INDEX.md`;脚本本身已 `out.parent.mkdir`,无需改。 - **默认镜像源改清华(pip+apt)/ 腾讯(npm)**:腾讯 PyPI 吐损坏 litellm wheel(index 声明 sha256 与文件实际字节不符,非篡改 = 镜像端文件损坏)。`deploy/update.sh` 三默认值改清华(境内稳 + 同步及时;npm 无清华源走腾讯);换默认让下次 build pip 层全量重跑一次。 - **回退 `ZCBOT_WORKSPACE_DIR` env 覆盖,workspace 落数据盘改用 bind mount**:env 覆盖与 `core/paths.py` 锚 ROOT 的相对存储冲突 —— env 指向 ROOT 外致文件面板 / agent resume / 新建 task 三家分叉。`resolve_workspace` 回退成 `arg > cfg > 默`(均 ROOT 内),数据盘改用 bind mount(`/data/zcbot/workspace` → `ROOT/workspace`,DB 不改、dev 不受影响)。 ### 2026-06-02 - **【已于 06-03 回退,见上】`resolve_workspace` 加 env 覆盖 `ZCBOT_WORKSPACE_DIR`**:prod 想把 workspace 落独立数据盘且不碰共用 yaml,改优先级 `arg > env > cfg > 默`。回退因与相对存储锚点冲突(见上)。 - **修 embed 模式"登录页一闪而过"(绘制时机,非鉴权)**:`#login` 在 `embedInit` 标记 `embed-mode` 前已被绘制;在 `` 首行加同步内联脚本,`?embed=1` 时立即加 `embed-mode` class,赶在 `#login` 绘制前隐藏。只挪绘制闸门,底部握手逻辑不动。 ### 2026-06-01 - **`deploy/update.sh` 加自更新重跑守卫**:`git pull` 会改脚本自身,首次拉到"改 update.sh"那轮跑的还是旧脚本行为。pull 后 diff 检出本脚本变更则 `exec` 用新版本从头重跑(标记防死循环),修"改了源仍报旧错"根因。 - **`deploy/update.sh` 默认源改腾讯 + build 跳过改 `--skip-build` + 进度可见**:根因 = 阿里 PyPI 同步滞后缺 `litellm>=1.83`。默认镜像源改腾讯(host venv pip 显式拼 `--index-url`)、跳过 sandbox build 由 env 改 CLI flag、pip 去 `-q` 让进度可见。 - **修 MP host 工具的全量下载(IP 被封根因)**:`mp_search_summary` 没传分页 → mp-api 默认翻完所有页 = 每搜一次整库级下载,MP 判 abusive 封 host IP。改 `num_chunks=1, chunk_size=limit` 服务端限量;`mp_get_entries` 天然全量(相图用途)只在 description 警示。**注:宿主 IP 仍被 MP 临时封,需邮件 support 人工解封后才能联网复测。** - **加一键部署脚本 `deploy/update.sh`(Ubuntu / systemd)**:`git pull → pip install → db upgrade → docker build sandbox → restart → curl /healthz`。两处钉死:migration 从 .env 抠 `ZCBOT_DB_URL` 喂 alembic;**build 必须在 restart 之前**(容器复用,restart 才换新镜像)。前置守卫:非 root / 非 git / 工作区脏 / 缺 .env 中止。 - **sandbox 镜像加中文字体,修 matplotlib / mermaid 出图中文方块**:根因 = `deploy/sandbox/Dockerfile` 从 slim 起一个 CJK 字体都没装。加 `fonts-noto-cjk fonts-wqy-microhei` + `style.py` 候选首位加 Noto。改 Dockerfile 须重 build + 清旧容器才生效。 - **documents / Materials Project 带 key 能力改 host-side tools,key 不进 sandbox**:新增 `tools/documents.py` + `tools/materials_project.py` 各三工具,仅宿主 env 有 key 时注册;写文件绑定 task_dir,模型不能传 working_dir。 - **删 `skills/pymatgen/materials.py::mp_rester()`**:sandbox 内读 key 的旧入口,host tool 化后多余且违背"key 不进 sandbox",直接删;smoke 改走 host tool。换 next-gen MP key 后端到端复测通过。 ### 2026-05-29 - **Seedream 5.0 i2i base64 通路 probe + DESIGN §8.1 落册**:实测豆包 Seedream 5.0 `/images/generations` 接受 `image_urls` base64 data URL → 内网部署无需对象存储中介。选 E+C 组合(`seedream` 加 `reference_images` + 新增 `look_at_image` 豆包 vision tool),本版仅 probe + design,tool 改造未启动。 - **web 端 tool_call 标题行改显中文活动描述**:实时流分支读错字段(`arguments` vs 后端 emit 的 `args`)致 `
` 一直空。修字段 + 新增 `toolActivityLabel(name,args)` 按 12 工具套中文动词(读取文件 / 执行命令 / 运行 Python / 联网搜索…)。纯前端,刷新即生效。

### 2026-05-28

- **`skills/review/SKILL.md` 加"长文档处理"段**:阶段 1 骨架扫描(只出目录 + 全局问题,不出修改稿,停下等用户挑章节)→ 阶段 2 分章深审 + 中间文件落盘。解长稿一轮处理易略读 / 覆盖不全 / 超输出限制。
- **新增 `config/models/local.yaml`(family=local,variant r1 / qwen3)接内网 OpenAI 兼容推理服务,涉密任务专用**:`model_id` 加 `openai/` 前缀走兼容协议,`api_base` 指内网。关键 **`thinking_mode=false`**(R1 / Qwen3 天生推理,发 `reasoning_effort` 本地 vLLM 多半 400)。不改默认模型,涉密显式选;qwen3 跑通,r1 服务端调试中 500。
- **修 `LoadSkillTool` 在 docker backend 返 host 绝对路径致容器内 fs 工具找不到 references**:skills/ bind mount 到容器 `/sandbox/skills`,容器内无 host 路径。加 `container_skills_dir` 参数,docker backend 时返 `/sandbox/skills/`。所有 references-heavy skill 自动 work,不用逐个改 SKILL.md。
- **新增 `analyze` skill(科学问题分析 / 拆解 / 引导)**:服务建材院 R&D 早期模糊问题翻译。四段式:PICO 规范化 → Issue Tree 拆解 → 按叶子类型分支(Fishbone / First-principles+TRIZ / DoE)→ 实施路线图。定位协调器不执行任务,接力下游 skill;不硬编"能力 → skill"映射(靠 runtime skill discovery 自匹配)。
- **Python 3.10→3.12 升级(host + Dockerfile)+ DockerExecutor PYTHONPATH 修**:mp-api 依赖链 `from typing import NotRequired`(3.11+)在 3.10 import 不进;选 3.12(ML 生态默认 + wheel 覆盖全)。顺手修 `executor_docker` PYTHONPATH `/workspace`→`/sandbox:/workspace`(docker backend 下 `import skills.xxx` 找不到的历史 bug)。3 科学 skill smoke A/B/C 通过,D 因 MP 封 IP 返 403。
- **新增 3 个科学计算 skill(pymatgen / stats_ml / plot_pub)**:服务无机非金属材料 R&D。pymatgen 带 `CEMENT_PHASES` 中英文相名映射 50+;stats_ml 纯指南(sklearn / statsmodels / PyMC 场景导航,无 helper);plot_pub 带 `apply_pub_style()` 出版级中文字体跨平台 fallback。挑 4 个 ★★★ fork 单装,不一键装 138 个。
- **DESIGN §7.5 增"image 体积 / 多 user 资源 / 后续加包策略"决策段**:① image 大 ≠ 吃更多 RAM(layer 共享);② 多 user 瓶颈在并发 exec 不在 idle 容器数;③ 新增依赖走"base 收敛 + per-user 持久化 venv + 使用频次沉淀"。

### 2026-05-27

- **ppt skill 歧义反问 + general_v1 加"产物形式歧义先问"通用原则**:"汇报方案"仍被路由命中(LLM 把"汇报"联想成 PPT)。把"汇报 / 方案 / 材料"从反例摘出,改成先反问用户"PPT 还是文档",并把原则升格到 system prompt 让新 skill 继承。
- **ppt skill description 收紧路由**:原文含"方案 / 生成"被误命中,改显式白名单(PPT / 幻灯片 / .pptx / slide / deck)+ 显式反例(报告 / 文档 / 纪要不触发)。
- **skill 热更新:`/v1/skills` 每次现扫**:原 lifespan 启动扫一次的静态快照 → 加新 skill 须重启。改每次现扫(构造 ~3ms,非热路径);`build_agent` 早已每次重建 registry,本次仅补前端下拉这一处。
- **dev SPA "导出对话记录" / "清空对话"按钮改成只要选中 task 就常亮**:原按 `n_messages===0` 禁用,清空后前端计数不刷致两键一直灰。改导出常亮、清空仅在 run 进行时禁;0 条时点也不会出错。
- **修 dev SPA embed + task_id 模式模型下拉不显示**:embed 带 task_id 时 `selectTask` 早于 `loadModels` resolve,`renderModelDropdown` 空 models 直接返空。`loadModels` 尾部加一行:若已有 task 选中则补一次 chat-meta 重渲。
- **Stage C 收尾包:容器资源 yaml 化 + 应用层磁盘配额 + dogfood 网络放开 + 容器内 pip/npm 源持久化**:① `agent.yaml` sandbox 段(memory/cpus/pids,env>yaml>默);② migration 0008 + `core/storage/disk_quota.py` 周期扫描 + write/upload gate(race-tolerant);③ 去 `--internal`(dogfood 能 pip install)+ `/etc/pip.conf` `/etc/npmrc` 让运行时也走 mirror。iptables 红线不动。Step 4 完整 egress proxy 延后到外部开放前(沉淀为升级触发信号)。
- **Stage C Step 3d:fs 工具(read/write/edit/glob/grep)进容器 + DESIGN §7.5 #6 重写**:dogfood 发现 host 工具 base_dir=`Path.cwd()` 漏底(模型 glob 列出 zcbot 源码),且原 DESIGN 写的 `resolve_user_path` 是假命题根本没那函数。选"物理边界替代代码护栏"走 docker exec,`tool_runner.py` 从 stdin 接 JSON args(CJK 路径透明传)。留 host:load_skill / web_* / seedream / seedance(持 key 或跨 user)。
- **Stage C Step 3 hotfix:exec_user 改 username + Dockerfile 加 Node/Chromium/mermaid-cli**:① 写死 `--user 1000:1000` 与 bind mount owner(host uid 1001)错配致 EACCES,改 username `zcbot` 让 docker 查 passwd;② proposal/patent 渲 mermaid 缺 Node 退公网 API 又被 `--internal` 堵,Dockerfile 加 chromium/nodejs/npm + mermaid-cli + puppeteer `--no-sandbox` config。
- **Stage C Step 5:`main.py sandbox check` 部署前置对账 + lifespan fs quota WARN**:5 项探测(docker daemon / 镜像 / network / 镜像内 uid 与 host 对齐 / workspace fs 可 quota)。err = 启动会 fail-fast 的根因(daemon/镜像/uid,exit 1);warn = 外部开放前要清(network 缺 / fs 不可 quota,exit 0)。`detect_fs_quota` 抽出给 lifespan 复用。
- **Stage C Step 3:DockerExecutor 集成 AgentLoop + web lifespan(`ZCBOT_SANDBOX_BACKEND=host|docker` env 切)**:shell / run_python 走 `docker exec`,其他工具直通 host;run_python tmp .py 落 `/.zcbot_tmp/` dotfile(`/v1/files` 天然过滤)。cancel 杀 docker CLI 不杀容器内进程,靠 idle reaper 兜底。pool.py `asyncio.Lock`→`threading.Lock`。Windows dogfood 默 host 零变化。
- **Stage C Step 2:Docker per-user 容器 + iptables blocklist(§7.5 #1 + #3 基底,未接入 AgentLoop)**:`deploy/sandbox/{Dockerfile,init.sh}`(non-root + 任一 iptables 失败 fail-fast + 6 段红线 DROP)+ `core/sandbox/{network,pool}.py`(`--internal` 网络 + per-user 容器 ensure/reap/shutdown + hardening flags `--read-only --cap-drop=ALL --memory=2g` 等)。docker CLI via subprocess 非 SDK;last_active 落内存 dict(Docker 23+ 移除 runtime label 改)。
- **Stage C Step 1:Executor 接口骨架 + HostExecutor in-process backend(§7.5 #5)**:`core/executor.py` `Executor` ABC + `ExecCtx` + `ToolResult`,`AgentLoop` 改接 executor 而非 tools dict。接口刻意 backend 无关(不泄漏 docker 假设),Step 3 切 docker 时 AgentLoop 零改动。
- **REVISIONS.md 修订日志机制(覆盖 proposal/patent/ppt 三个产物型 skill)**:`/REVISIONS.md` 紧凑 changelog —— spec=宪法(定调一次),REVISIONS=实施日志(每次卡点累加)。三 skill 各加"用户确认实质改动后追加一行" + 独立小节;单行 bullet 倒序追加,首次起草 / 错别字微调 / 模型自撤不记。
- **新增 patent skill(中国发明专利技术交底书)**:`skills/patent/` 6 文件,五阶段 workflow(摄取 → 挖点 → 检索 → spec → 逐章起草 → 自查渲染,同 proposal BLOCKING 节奏)。复用 markitdown / proposal 的 render_diagrams+render_docx / web_search+documents+research,不造 CNIPA 爬虫,源 repo 8 Python tools 减到 0。
- **§7.5 沙盒落地清单 6 条写入 DESIGN(Stage C 实施硬协议)**:网络 blocklist 硬编码(含 PG IP 单独再 block)/ egress proxy 模型 / 进程组清理(setsid + PGID kill)/ 磁盘配额硬化时点 / Executor backend driver / 工具按信任域二分 dispatch;并写死 gVisor / Firecracker / 容器内 tool-runner 三档升级触发信号,反向兜底"无信号不升级"。

### 2026-05-25

- **dev SPA 前端依赖 CDN 本地化 + 升级稳定版**:markdown 渲染(`marked@16.2.1` / `dompurify@3.2.6` / `highlight.js@11.11.1`)从 jsDelivr 改本地 `vendor/`,避免内网 / 跨境 CDN 抖动致渲染 / sanitizer 不可用;`tests/test_static_vendor.py` 回归检查 HTML 无 `cdn.jsdelivr.net` + vendor 文件存在。
- **dev SPA 一批上传 / 布局交互打磨(同质合并)**:三类上传入口(粘贴 / 按钮 / 拖拽)改 `XMLHttpRequest` 显进度 + 粘贴上传 chip 可预览(`#mini-preview-modal` 不覆盖主预览)可删除;三栏支持右栏折叠 + 左右分隔线拖拽调宽(localStorage 持久化);右侧文件长名 hover 显全路径;左栏滚动条只覆盖 task 列表(IntersectionObserver root 移到 `#task-scroll`)。
- **接入博查 Web Search + Web Fetch 两个 tool**:`tools/web_search.py`(Bocha POST `/v1/web-search`,Bearer)+ `tools/web_fetch.py`(httpx + html2text,SSRF 内网屏蔽,截断 8000);web_fetch 无条件挂,web_search 仅 env 有 `BOCHA_API_KEY` 时挂。

### 2026-05-22

- **dev SPA 加 iframe embed 模式(`?embed=1&parent_origin=...`)**:父页 postMessage 握手拿 JWT(`zcbot-ready` → 父端用 `PLATFORM_KEY` 换 token → `zcbot-token` 推回),`event.origin` 白名单双向校验,`PLATFORM_KEY` 不下发浏览器;藏 brand / 顶栏 / 退出。401 发 `zcbot-401` 等父端重换 token。`web/EMBED.md` 对接手册。
- **embed 模式接受 `task_id` URL 参数定位 task**:首次签发 token 后 `selectTask`,`once` 标记只生效一次(401 重签不重置用户中途切的 task);task_id 错或不属于当前 user 走原 401/404 分支。
- **媒体生成每账号每日配额(yaml 可配,默 20 图 / 5 视频)**:`quotas` 段 + `check_daily_quota` 按服务器本地今日 00:00 计;tool 超额返中文提示不调远端不烧钱。跨 task 跨 variant 账号级合计,失败不计,软上限不加事务锁。tool 返串只暴露已用 / 上限 + 重置时间,不贴 yaml 路径(防 LLM 复述泄漏内部 schema)。
- **对外路径协议刚性化(system 强约束 + SKILL 简化 + UI 一次性兼容)**:`general_v1.md` 规定助手 echo 产物路径用 user_root 相对全形式 `/`(简写致 Web UI chip 失效),跨所有产物 skill 统一;imagegen/videogen SKILL 改"照抄 saved 行";`dev.html::extractArtifactRels` 一次性兼容历史简写。术语用 ``(working_dir 末段)而非 ``(允许两者不等)。
- **豆包 Seedance 2.0 Fast 视频生成接入(文生视频)+ videogen skill**:`config/media/doubao.yaml` video 段 + `tools/seedance.py`(ark 建任务 → 5s 轮询 → download mp4,失败 / cancel 不计费);`build_agent` 加 `video_variant` + `cancel_check`(build 阶段传,轮询期响应停止);`web/app.py` + `dev.html` 第三下拉。skill 六维诊断把"光线"换成"运动 + 镜头",BLOCKING 门槛更严(¥4 vs ¥0.22)。phase 1 仅 t2v,fast 上限 720p。
- **dev SPA 移动端自适应 + 交互打磨(同质合并)**:手机两档断点(平板 rail / 手机单列 + `.mobile-tabs` 切 pane,`100vh→100dvh` 解 iOS、输入 ≥16px 防 focus 缩放)+ 顶栏 / chat-meta 紧凑化;"+ 新建任务"按钮从 header 挪到任务面板通栏;chat-input 支持 Ctrl+V 粘贴文件上传;文件预览弹框让出 chat-form 高度(打开期输入区仍可点可打字)。

### 2026-05-21

- **dev SPA UI 打磨(同质合并)**:修 primary 按钮 hover 文字消失(`.primary:hover` 补 `background:var(--accent)`,原 fallback 到浅灰 + 白字消失);CSS 精简 + 圆角降档 + 4 个 modal 抽 `.modal` 基类(style 块 589→522 行,功能 0 改);新建任务弹窗 / 顶部 filter 工作目录回原生 `` 首项空值,option 文案 `name — description`。
- **dev SPA 全套 UI 中文化**:静态 + 动态文案全本地化;技术字段(UUID / token / SSE event 名 / API 字段)不动。

### 2026-05-17

- **0003 schema:name + working_dir + skill 三件套**:任务标识与工作目录解耦;`TRUNCATE tasks CASCADE` + 字段改名 + 加 `name TEXT NOT NULL`;`GET /v1/folders` 给 dev SPA modal datalist。
- **`GET /v1/tasks` 分页 + 多维筛选 + ordering**:`{page,page_size,count,results}` + 6 个 query(status/skill/working_dir/q ILIKE/ordering);allowlist 防注入;默认 `-created_at`。
- **task 硬删 API + dev SPA delete 按钮 + 文件 per-row 删**:`DELETE /v1/tasks/{id}` user_id 校验 + DB 行删(messages CASCADE)+ **FS task_dir 不动**(同 name 多 task 共享时 rmtree 易擦素材)。
- **files API 全面 user-rooted(去掉 task_id 前置)**:`_safe_join` 边界改 user_root + dotfile 过滤(`.memory/` 隐藏);dev SPA `loadFiles()` 不再 gate on task_id。
- **files 面板 UX 项目名 + 修 root crumb bug**:`cur_rel == "."` 不追加无意义 "." crumb;crumbs 第一格 label 从 "/" 改项目名。
- **task_dir 改 eager mkdir**:`build_agent` 新建分支 + `create_task` 都 `mkdir(parents=True, exist_ok=True)`;name = 项目声明,目录该 task 创建时存在。
- **task = name-based 项目目录 + memory dotfile**:废 UUID 派生 + `tasks/` 中间层;`task_dir = workspace/users///`,同 name 多 task 共享;memory 搬 `.memory/` dotfile;`validate_task_name` 拒 `.` 起头。

### 2026-05-15

- **§7 D 阶段 `/v1` JSON API 落地;Phase G Jinja2/HTMX UI 路线撤**:删 templates + CSS + jinja2/markdown-it-py/pygments 依赖;SSE event 由 HTML 片段切 JSON(`event: ` + `data: `);dev SPA `web/static/dev.html` 留作本地 dogfood 主路径。
- **§7 D' 过渡 auth(PLATFORM_KEY → JWT)+ dev SPA**:pyjwt HS256 + `AuthConfig.from_env()` fail-fast;数据隔离全 `Task.user_id == user_id`,跨 user 视 404;SSE 走 fetch + ReadableStream 手解(EventSource 不支持自定义 header)。
- **task_dir 改相对存储**:DB 存 ROOT 内→相对 posix / ROOT 外→保留绝对;`core/paths.py::{ROOT, to_db_path, from_db_path}` 三出口;alembic 0002 一次 UPDATE backfill。CLAUDE.md 加"开发期不写兼容层"心智。
- **workspace 布局统一 per-user**:`workspace/users//{tasks/,memory/}/`;**清旧数据不留兼容**。
- **litellm 启动 cost map 网络警告兜底**:`LITELLM_LOCAL_MODEL_COST_MAP=True` 走本地 cost map,冷启动 ~5s → <1s。
- **Phase G G1-G6 Jinja2/HTMX Web UI** _(全撤,被 D + dev SPA 替换;沉淀的 sink / broker / no-subtask / files 安全归一保留)_

### 2026-05-14

- **§7.1 心智模型修正:Folder-centric → Task 一等公民 + Dir 文件副视图**:dir 不是 task 父容器,双视图正交;task_dir 留空 = 一次性对话 / 指定 = 项目化。
- **§7 B Steps 1-4 + 6**:`core/storage/{engine,models}.py` SQLAlchemy 2.x ORM(5 表)+ alembic + `cli db {upgrade,downgrade,current}`;`state.json` 全废,messages/TaskState 入 PG;`check_no_subtask` 同 user 下查前缀嵌套。

### 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-08**:Phase 6 部分(task + state.json + tokens 累计);TUI rich Markdown + spinner 实时耗时;`/resume [last|]` + 懒创建 + `_cleanup_if_empty`。
- **05-09 → 05-10**:DESIGN §7 初版(05-12 重写);`cli.py export` + `core/export_docx.py`。
- **05-11**:`atomic_write_text` + `core/memory.py`(core.md 入 prompt,extended/* 索引);loop 事件流化 `sink.emit` 铺 SSE 路。

---

## 关键决策与偏差

| 项 | 决策 | 备注 |
|---|---|---|
| 工具基目录 | cwd(读)+ working_dir(写) | system prompt 同时注入两者绝对路径 |
| Workspace 布局 | `workspace/users//{.memory/, /}` | per-user 隔离;memory dotfile 防撞;同 name 多 task 共享 |
| Eval Suite | 不做 | 个人工具 dogfooding |
| 版本化 prompt | 直接 `general_v1.md` | Windows 软链接麻烦,真要切再做 |
| 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` + `litellm.stream_chunk_builder` 拼回;cancel 在 chunk 间 + tool_call 之间 poll | cancel 延迟 100ms 级;content delta 即时 emit 给前端打字机 |
| 发送/停止单按钮 | UI 按 `state.streaming` 切态;streaming 期间 Enter 不触发停止 | 防误触 |

---

## 文件清单

```
core/capabilities.py        71
core/llm.py                151   ← litellm 离线 cost map env + chat_stream(stream=True + include_usage)
core/loop.py               268   ← §7 A sink.emit + _stream_llm(chunk 间 poll cancel + emit delta)
core/sinks.py              101   ← §7 A
core/ui.py                  38
core/paths.py               50   ← task_dir db form 归一
core/probe.py              243
core/session.py            153   ← §7 B Step 2-3: ORM
core/skills.py              81
core/task.py                82   ← §7 B Step 3: PG-backed TaskState
core/memory.py              81   ← per-user `.memory/` dotfile
core/export_docx.py        383
core/storage/__init__.py    29
core/storage/engine.py      80
core/storage/models.py     130   ← 4 表(0004 删 runs;0005 email UNIQUE;0006 usage_events v2 + messages.model_profile;0007 cost_usd→cny)
core/storage/usage.py      125   ← record_chat_usage(USD→CNY ×7.2)+ record_image_usage(单价 snapshot 进 units)
core/storage/utils.py      136
core/ark_client.py         105   ← 火山方舟 HTTP 客户端(seedream / 后续 seedance 共享)
core/agent_builder.py      325   ← 装配 lib(有 ARK_API_KEY 才挂 SeedreamTool)
tools/{base,fs,shell,run_python,skill_tool,seedream}.py  ~640 行
main.py                    ~210  ← 入口:web / db / probe / user
db/migrations/env.py        61
db/migrations/versions/
  0001_initial_schema.py   125
  0002_task_dir_relative.py 61
  0003_task_name_and_working_dir.py 51
  0004_drop_runs_usage_events.py 77
  0005_users_email_unique.py 28
  0006_usage_events_v2_and_message_model.py 60
  0007_cost_usd_to_cny.py    40
web/__init__.py              5
web/app.py                ~1320  ← /v1 JSON API + user_id 隔离 + run lock + cancel + files copy/move
web/auth.py                ~190  ← 邮箱密码 + platform_key → JWT
web/broker.py              121   ← in-process pub/sub + cancel signal(全 task_id 索引)
web/sinks.py                21
web/static/dev.html       ~2480  ← dev SPA(3 栏 + 文件预览 + 双 tab 登录 + 选入弹框 + 发送/停止单按钮 + 流式打字机)
web/static/vendor/        ~1 MB  ← jszip / docx-preview / xlsx
─────────────────────────────────
Python 合计              ~3400 行(+ dev.html 1700 静态 + vendor 1MB)
```

加 `skills/ppt|proposal|coding|research/` 脚本 ~700 行 + SKILL.md / references / config / prompts(含 `config/media/doubao.yaml`)+ alembic.ini,总仓库约 3800 行。

---

## 下一步候选(性价比排序)

1. **真 OIDC 接入 + CORS 收紧**(~1 天)—— `/v1/auth/login` 内部换 OIDC ID token 校验(路由层 Depends 不动);CORS 改 platform 域名 allowlist。**真发布给真实用户前必做**。
2. **§7 C Executor + sandbox**(~3-5 天,按 DESIGN §7.5 落地清单 6 条逐项实施)—— `run_python`/`shell` → `Executor.run(...)`,本地保留 subprocess、SaaS 走 docker;`api_key_env` → `KeyProvider` 运行时注入。多用户在线跑代码前置。**Stage C 完成 DoD** = 6 条落地清单全完成 + 红队回归用例通过:① 容器内 `curl http://169.254.169.254/...` → timeout / connection refused;② 容器内 `psql postgresql://...` → IP block(连接失败);③ 容器内 `nohup sleep 1000 &` exec 退出后 `docker top ` 看不到残留进程;④ 跨 user 容器互访(A 容器 `curl http://:*`)→ 网络隔离阻断;⑤ 出网走 proxy 时未在 allowlist 的域名 → 403。原 ~2-3 天估值未含 egress proxy 部署 / xfs project quota 升级 / 红队用例,补回真实工程量。
3. **Phase 6 context 三层压缩**(~1 天)—— 兜底,V4 长上下文一般用不到。

> §7 B + D + D' + 单活 run 锁 + cancel + 0004 schema 瘦身 + 入口归位 主体已完工。剩余:真 OIDC → C(Executor)→ F(deploy / billing)。§7 E CLI 双模式撤;Phase G Jinja2/HTMX 撤(详见 DESIGN §7.9)。