308 lines
78 KiB
Markdown
308 lines
78 KiB
Markdown
# 实施进度
|
||
|
||
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。
|
||
|
||
最后更新:2026-06-09(PPTX 前端在线预览:LibreOffice→PDF + 复用 PDF iframe,DESIGN §8.3 Stage 1)
|
||
|
||
---
|
||
|
||
## 状态
|
||
|
||
| 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-10
|
||
|
||
- **单轮停机判据从"步数"解耦为"是否在推进":`max_iterations` 升为纯 backstop + 新增全局「无进展」熔断 + 撞顶明确提示**:DB 诊断 task `b27466a0`(智能体介绍 PPT)所谓"中途断了"——查实=该 run 跑满 `max_iterations`(flash 旧值 50)后 `return "[reached max iterations]"` 干净停下、留一条悬空 tool 结果,用户离开 25min 回来打"继续"才续完(`run_status=idle/run_error=None`,非崩溃);"步骤太长"=少数轮 DeepSeek API 延迟 126-185s,工具本身全 <13s;顺带实测该 task DeepSeek 前缀缓存命中 92-94%,**上下文压缩对缓存几乎无害**(压缩函数确定性→旧消息压缩态稳定,只滑动边界这一处断,每轮 miss 几十~几百 token)。**洞察**:`max_iterations` 把"用户感知的轮(来回对话)"和"一轮内自主工作步数"混在一个旋钮上——自主 tool 链概念上是 1 轮,该松;真正要掐的是"空转"。落地:① yaml `max_iterations` flash 50→120 / pro 100→150,dataclass 默认 50→120,定位为安全兜底非"轮"预算;② `_RepeatGuard.record` 多返一个 `productive`(净产出=非 `[Error]` 且非一字不差重复);③ `_execute_tool_call` 三个返回点都带 `productive`(invalid-JSON/被拦=False);④ run loop 累计 `self._stall`——整步所有 tool 都无净产出则 +1、任一净产出清零,连续 `_STALL_LIMIT=8` 步空转主动停(`[stopped: no progress]`),比撞 120 早得多掐死循环,配 `_RepeatGuard` 逐指纹 HARD=4 双保险;⑤ 撞 backstop / 熔断都 emit 明确"回复『继续』可接着跑"提示,不再静默停。`tests/test_loop_repeat_guard.py` 更新 record 解包调用 + 加 `productive` 信号用例(17 例过,全量 103 过)。
|
||
- **`systemctl restart` 优雅 drain in-flight run(单实例止血,不再误标 error)**:此前 restart 硬杀 BG run 线程,下次启动 reaper 把所有 `running/cancelling` 标 `error: server restarted before run finished` —— 用户一多就不能随便重启。落地纯进程内、**零 DB 改动**:① lifespan 加 `app.state.draining`(asyncio.Event)+ `app.state.inflight`(`{asyncio.Task: task_id}`,顺手修 `create_task` 不留引用可能被 GC 的旧坑);② POST `/messages` 起 run 时登记+done 回调自摘除,draining 置位时返 503+`Retry-After`;③ lifespan `finally` 先置 draining 拒新 run,`asyncio.wait(inflight, drain_timeout)` 等收尾,超时的 `broker.request_cancel` 转协作式 cancel(下个 chunk 间隙退、标 idle 不报 error),再过 `cancel_grace` 仍没退的留给 SIGKILL(最坏退化=改前)。④ `main.py` uvicorn 加 `timeout_graceful_shutdown=5`(否则长连 SSE 无限挡在 drain 前);⑤ `config/agent.yaml` 加 `shutdown` 段(drain_timeout 30s / cancel_grace 15s,超时转 cancel = 用户按停止可重发,故偏短);⑥ dev SPA `chat.js` 发送包退避重试(503 背压 + 交接拒连 TypeError 都重试 ~26s,显"服务更新中",耗尽贴友好提示)。**部署强耦合**:unit `TimeoutStopSec` 从 10 提到 90(必须 > drain+grace+sandbox 清扫余量,否则 SIGKILL 砍掉 drain),已写进 RUN.md unit + 故障兜底。B 蓝绿(零 503 窗口)留作触发信号后再做,前置是 instance-aware reaper(§7.8)。
|
||
|
||
### 2026-06-09
|
||
|
||
- **PPTX 前端在线预览(LibreOffice→PDF,DESIGN §8.3 Stage 1)**:此前文件区点 `.pptx` 只能下载(`preview.js._categorize` 归 fallback)。关键洞察=前端已有 PDF iframe 路径(`_showPdf`),所以只要后端把 pptx 转 PDF 就**前端几乎不动**。落地:① 新 `web/pptx_render.py`——`pptx_to_pdf()` 同步可缓存,调 `soffice --headless --convert-to pdf`、**每次独立 `-env:UserInstallation` 临时 profile** 绕单 profile 锁、超时 60s kill;soffice 路径发现复用 render_bg 思路;缓存落源同目录 `.preview/<stem>.<hash>.pdf`(hash=mtime+size,源改即失效;dotdir 不污染文件列表),`_prune_stale` 清旧 hash。② 新端点 `GET /v1/files/preview_pdf`——复用 `_safe_join` 鉴权防穿越 + 仅 `.ppt(x)` + per-path `asyncio.Lock` 防并发重转 + `run_in_executor` 不堵事件循环;soffice 缺失 501 / 转换失败 500。③ `preview.js` 加 `ppt` 组,main/mini 共用 `_showPptAsPdf`(fetch PDF→iframe,带 spinner loading + 失败回退下载),`dev.html` 加 `.preview-spinner`(复用 `@keyframes spin`)。**转换跑在 web host 进程,不进沙盒**(沙盒不该有 LibreOffice;预览面向 user_root 任意 pptx,与 deck 生成解耦)。部署:host `apt install libreoffice-impress fonts-noto-cjk`(已写进 RUN.md 一次性 + 故障兜底),sandbox Dockerfile 不动。**未做**(Stage 2):常驻 soffice listener 消冷启、deck 生成后 eager 预转、缩略图导航。
|
||
- **药3 复核:`/home/ubuntu/zcbot` 幽灵路径已于 06-03 修复,新任务不复现 + 加回归测试钉死**:接续高轮数烧 token 诊断收尾。证据链——失败 task `ab063233`(06-02 03:54)/`ff1686b7`(06-03 12:02)的首条 assistant 消息(idx1)tool_calls 里就带 `glob(path=/home/ubuntu/zcbot/workspace/users/<真实uid>/数据资源展示)`,**带真 uid 只可能来自当时的 system prompt**(messages 表无 system-role 行,系统提示运行时拼接不入库);两 task 均建于 06-03「system prompt 焊死宿主路径」修复前后。fs 工具在 docker 容器里跑(容器无此宿主路径)→ `[Error] base path not found`(`glob.execute` 对不存在 base 返此错,`_display` 对 user_root 外路径回绝对)→ 重试风暴(实测 51 次)。**复核当前代码**:docker 模式即便传 `tool_base=/home/ubuntu/zcbot` + 真 uid,拼出的 prompt 只含 `/workspace/<wd>`、不含宿主路径/uid/tmp(`agent_builder.py:223-250` docker 分支注入容器路径 + 删 cwd 行);prod 走 docker backend(RUN.md)。新增 `tests/test_system_prompt_paths.py`(2 例:docker 无宿主泄漏 + host 保留本地绝对路径,过)锁住修复防回归。**三味药全部收口**;药1 重复守卫此后还兜底任何同类风暴(同一 51 次会被摁到 ~5 次)。无功能代码改动,仅加测试。
|
||
- **ppt skill 补「信息设计纪律」+ 混合背景 + pptx 预览器(治"效果还是不太行",深读 pptmaster 后的二次修正)**:用户反馈卡片式 v2 仍不够好,拆其真实产物(`大模型与智能体介绍.pptx`)定位毛病=9 页 4 页雷同卡片网格(全卡=AI 味)、发展历程做成网格(该时间轴)、智能体平铺(该闭环)、图标 0.6 寸太小、投影到处加。**深读 pptmaster 的 executor-base/executor-consultant(-top)/shared-standards 后顿悟**:它像麦肯锡的真因是**信息设计纪律(~70%)**而非 SVG 渲染(~30%),而这些**全是 editable python-pptx 能做的**——之前纠结的"可编辑 vs SVG 转换器"搞错了轴(可编辑都落 DrawingML 同一天花板,转换器零视觉增益)。落地三层:① **信息内功**——`add_takeaway`(论断标题下一句话结论框)、`add_kpi` 加 `baseline+delta`(数据语境化:数字带对比基准+升降色 `GOOD/BAD`)、`add_source`(来源)、`add_toc`(贯通整宽目录);SKILL 策略阶段加论断式标题对照表 + page_rhythm(anchor/dense/**breathing 强制打破卡片网格**)+ 内容→版式映射写进逐页大纲。② **修我搞反的投影**——pptmaster"投影是克制":`add_card` 默认 `shadow=False`(平铺对等卡描发丝边不投影)、每页 ≤2-3 投影、一容器一手段不叠;quality_check 加绿=语义状态色豁免三色制。③ **组合件 + 工具**——`add_card_grid`(均衡网格,2 行改图标左置治"图标顶置挤溢出")/`add_timeline`/`add_cycle`;`render_bg.py`(无头 Chrome 渲杂志级 mesh 渐变背景图,**混合方案**:背景图+原生可编辑白字,封面/章节);**`pptx_preview.py`(把 .pptx 渲成 PNG 肉眼验观感)——quality_check 只查结构,预览补"好不好看",当场抓到 `set_text` 多行只给第一段上色的真 bug(封面副标题第二行变暗看不见)并修复**。验证:重排「大模型与智能体」为 10 页(节奏:封面/目录/章节 anchor · 网格/时间轴 dense · 大字 breathing · 章节/闭环/网格 · 致谢),逐页渲 PNG 亲眼验收均专业,quality_check 全过。改 `skills/ppt/{SKILL.md,references/{design_principles,layouts}.md,scripts/{pptx_helpers,quality_check}.py}` + 新增 `scripts/{render_bg,pptx_preview}.py` + `SKILL_LIST.md`。**未动**:SVG→原生转换器(论证为零增益不做)、live preview server、动画;fetch_icon 的 PNG 后端(cairosvg/svglib)本机未装,暂用种子库 PNG。
|
||
|
||
### 2026-06-08
|
||
|
||
- **loop 加病理性重复调用守卫(药1,治「不停调用同一脚本」的根因 ①②)**:接续批量化诊断——DB 实测高轮数 task 的浪费大头是「同名同参 + 无产出」的重复(`document_search` 122 次、空 `shell{}` 51 次、反复 `glob` 同一不存在路径),而 `core/loop.py` 主循环原本对此**零防护**照单全收。新增 `_RepeatGuard`(AgentLoop 实例持有、活在单次 run 内不跨 task):按 `(工具名, 精确参数 canonical-json)` 指纹跟踪「无产出重复」计数。**命门是只惩罚无产出、绝不误伤正常迭代**——同参但**结果每次不同**(改脚本后重跑 run_python、修 bug 后重跑构建)算有产出、计数清零永不拦;同参且**结果是 `[Error]` 或与之前一字不差**才累计。两档:累计 ≥`SOFT`(2)在 tool 结果尾部注入 `[重复调用警告]` 软提示(模型当轮即见);≥`HARD`(4)下一次同参调用 `should_block` 直接拦截不执行、回 `[已拦截重复调用]` 硬停消息逼其换路(一个卡死调用最多放过 ~4 次无产出重复)。**顺带堵 `_malformed_tool_calls` 的洞**:大参数畸形退化成合法空 `{}` 时 executor 每次返同一句「缺少必填参数」→ 走 dup 分支被同一机制拦下,无需单独特判空 `{}`。`_execute_tool_call` 接线:执行前 `should_block` 拦截、执行后用**截断后未加提示的原始结果**算指纹 `record`(保证同输出哈希一致)、`warn` 事件上抛拦截/首次软提示。改 `core/loop.py`;新增 `tests/test_loop_repeat_guard.py`(7 用例:同错拦截/空`{}`堵洞/同结果拦截/变化结果不拦/修好清零/SOFT 阈值/异参分别跟踪,全过)。**注**:阈值常数化(SOFT/HARD)便于后续按实跑调;药3(`/home/ubuntu/zcbot` 幽灵路径是否新任务仍复现)仍未查。
|
||
- **检索/抓取类 host 工具批量化(治高轮数烧 token,先做的「web_fetch 类」一味药)**:DB 实测诊断(`scripts/diag_*.py`)高轮数 task 的 tool_call 序列——`ff1686b7` coding 任务 `document_search` **122 次**(104 个不同 query,关键词反复微调地毯式搜不收敛)、`document_download` 28 次;`ab063233` documents 任务 **64% 的 tool 结果带错误** + `shell{}`/`run_python{}` 空参数风暴。定位「不停调脚本」是三股根因叠加(空 `{}` 风暴 / 报错重试 / 检索不收敛),其中检索/抓取的往返成本可由**工具形态改造**直接压。本轮把三个 host 工具从「一次一个」改成**接受列表、一轮并发处理一批**(按「开发期不写兼容层」直接换签名、不留单数别名):① `web_fetch` `url`→`urls`(1-10,ThreadPoolExecutor 并发 6,全批正文总预算 16000 字符按条数分摊,单条 SSRF/超时/404 不连坐);② `document_search` `query`→`queries`(1-8,**批内去重** + 批量时自动缩 `max_documents`/`content_chars_per_doc` 防爆 context,每 query 独立 try);③ `document_download` `file_name+kb_name`→`items=[{...}]`(1-10 并发,单条失败标 `[Error]` 不毁整批)。三者输出都按条标注 `=== [i/n] ... ===`、超量截断明示不静默。`tools/{web_fetch,documents}.py` 改;`tests/test_secret_host_tools.py` 同步改新形态 + 加批量并发/去重/失败隔离 3 用例(5 过);`skills/documents/SKILL.md` 签名/工作流/反模式更新(加「一个 query 一轮反复搜」「同义近重复 query 堆叠」两条反模式,呼应诊断 ③)。`DOCUMENT_SEARCH_API.md`(上游 HTTP 接口,本就单 query/次)不动。**未动**:药1(loop `(name,args)` 重复探测器 + 堵空 `{}` 洞,最高杠杆但动核心 loop)、药3(`/home/ubuntu/zcbot` 幽灵路径是否新任务仍复现)——见记忆 `project_high_turn_token_burn_root_causes`,留作后续。
|
||
- **ppt skill 视觉系统升级为「卡片式」(治"生成效果不太行")**:学 GitHub `hugohe3/ppt-master`(24.9k★)后定位根因——其好看的核心是「SVG 作画→转原生 PPTX」给足设计自由度,而 zcbot 被 python-pptx **原语**(平矩形+左色条+圆点 bullet)摁死了视觉天花板,出来就是"2010 办公模板"。岔路三选(A 自建 SVG→pptx 转换器=最高天花板但大工程且与"一脚本整建/少来回"冲突;B 升级 python-pptx 设计系统;C 混合),**选 B**(保留单脚本批量架构、原生可编辑、风险低)。落地:① `pptx_helpers.py` 加质感件——`add_card`(圆角矩形 `adjustments[0]` 调圆角 + `a:outerShdw` XML 柔和投影)/`add_gradient_rect`(`fill.gradient()` + 角度)/`add_kpi`(数字卡)/`add_icon_tile`(图标底块)/`add_pill`/`add_eyebrow`/`add_chevron`/`add_notes`(演讲者备注),`set_palette` 从主/辅/强调**派生明暗色阶** `PRIMARY_WASH/SOFT/DARK`+`ACCENT_SOFT`;`apply_brand` 封面/章节改**渐变大色块**;**所有 helper 把 `name=` 写进形状 `.name`**(原来只喂 assert_inside,导致 quality_check 拿不到语义名)。② `layouts.md` 9 版式重写成卡片式 + 扩到 **13 种**(加 L10 KPI 卡 / L11 卡片网格 / L12 流程 / L13 大数字论据)。③ **quality_check 跟新设计语言对齐**(否则每个 deck 淹在假警告里):三色制改**按色相归桶**判(主色深浅/wash tint 不算新色)、小字号/bullet 按 `.name` 豁免标签类、大号展示字(≥40pt)跳过溢出估算、bullet ≤5 改**按列**判(双栏 3+3 不误报、单列 6 仍抓)。④ SKILL.md 工作流加 opt-in 真实配图(走 imagegen,¥0.22/张,大纲标 `[img]`)+ 每页 `add_notes`;`design_principles.md` 加派生色阶/KPI 卡/图表透明底卡片化。验证:13 版式全覆盖 demo deck 建成 + quality_check 全过;单列 6-bullet 回归仍触发。改 `skills/ppt/{SKILL.md,scripts/pptx_helpers.py,scripts/quality_check.py,references/layouts.md,references/design_principles.md}` + `SKILL_LIST.md`。**未动**:SVG 路线(A)、live preview、动画——属更大工程,本轮不上。
|
||
- **system prompt 加「少来回」全局原则(广谱减轮)**:ppt 之外的长尾 task(改代码/跑数据/画图)没专属 skill 兜,加一条通用 `工作原则`:互相独立、不依赖中间结果的操作(建多页产物/批量改文件/生成整份产物)合到一个脚本或一轮并发 tool call 里做,别一步一 call(每轮重发整段上下文,轮数=token 体量线性乘数);但下一步输入要看上一步结果时(探索检索/按报错改/需用户确认)就老实分步,别硬批——精准措辞避免"过度批处理"踩掉该有的 checkpoint。定位是便宜补充(prompt 走缓存近零成本),不指望它动 100+ 轮大头(那靠结构改造)。改 `prompts/system/general_v1.md`。
|
||
- **ppt skill 工作流批量化(减高轮数 task 的来回)**:实测高成本 task 几乎全是 100+ 轮的"逐步 tool 调用循环"(rust→PPT 34 轮、文献采集 245 轮),每轮重发整段上下文,轮数是 token 体量的线性乘数。ppt 是最易压、风险最低的试点:原 §阶段二**逐页**(每页 `读spec→glob图标→一个 run_python 加页→等用户确认→下一页`,N 页 ~2N 轮)。改法:① 阶段一 spec 增「逐页大纲」表(页|版式|标题|要点|图标),作为**替代逐页确认的前置 checkpoint**——改文字大纲比建完 slide 再推翻便宜;② 阶段二改成**写一个 `build_deck.py` 一次建整 deck**(同进程 `new_presentation`→按大纲循环 `add_slide`→一次 `save`,坐标天然一致;`pptx_helpers` 模块化已消解原"逐页防漂移"理由),图标**全 deck 批量预取**(不逐页拉);③ quality_check 一次 → 改脚本重跑(不 edit 成品);④ 可选"风格探针"(先建封面+1 页看观感)兜视觉返工险。N 页从 ~2N 轮降到 ~3-4 轮。改 `skills/ppt/SKILL.md`(阶段一/二/三 + 反模式 + 文件树)、`references/layouts.md`(§通用起手换成"整 deck 单脚本"模板)、`SKILL_LIST.md`(流程描述/典型产物同步)。冒烟过:单脚本 `new_presentation`+循环 `add_slide`+`save` 建 2 页成功,API 调用与模板一致。**注**:数据采集类(host 工具中转免不了)是另一条路(拆采集/处理相位),未动。
|
||
- **修进度还原错乱 + 进度区移到对话区顶部(codex 式)**:根因(查 DB 实锤)= 上下文压缩把旧 `task_progress` tool_call 参数换成 `{"_compacted":true,"step_id":"sX"}` 这种"看着像合法调用"的标记,① 毒化模型让它后续照抄出残废 `update_step`(丢了 `step.status`)并入库,② 残废格式前端 `applyProgressAction` 读不到 `args.step` → s4/s5 永停 pending → 进度显示不对。修复:`context.py` 对 `task_progress` 参数**一律不压缩**(参数本就小,压缩省不了几个 token 却两头坏事);旧的 `_compact_task_progress_arguments` 整个删除。**进度展示重构**:删掉每条消息卡内联进度块(`renderProgressHtml`/`renderProgressInto` 移除),进度统一只在**对话区顶部**单一 `#task-progress-dock`(从 composer 上方移到 `chat-stream` 之上、`flex-shrink:0` 钉顶不滚)实时显示;**完成态折叠**——全部步骤 completed 时 dock 自动收成一行 `✓ 全部完成 · N/N 步`(`<details>` 点开看清单)。prompt + tool 描述改为"跑完把最后一步标 `completed`、不要 `clear`",留住全绿收尾。校验:`python -m unittest tests.test_context_compaction tests.test_task_progress_tool`(12 过,改写 `test_keeps_old_task_progress_arguments_intact` 断言参数原样保留);`node --test tests/frontend_task_progress.test.mjs`(2 过)。
|
||
- **修登录无反应(`$ is not defined`)+ 补 favicon 消 404**:`newtask.js` 用了 DOM 简写 `$`(`dom.js` 导出的 `getElementById`)却漏 import,模块加载到顶层 `$("hd-new").onclick` 即抛 `ReferenceError: $ is not defined`,中断 newtask 全部绑定及其 import 的 auth/chat 链路 → 点登录无反应。补 `import { $ } from "./dom.js"` 与其余模块对齐。另在 `dev.html` `<head>` 加内联 SVG data-URI `<link rel="icon">`(蓝底白机器人),浏览器不再请求根 `/favicon.ico`,消掉 404;选内联 SVG 而非新增 `.ico` 文件 / 服务端路由,零新增文件零 app.py 改动。
|
||
- **新增 Codex 式 `task_progress` 进度工具 + Web 固定进度区**:`TaskProgressTool` 默认注册到 agent,支持 `set_plan/update_step/clear`,返回极短 UI-only 结果;上下文压缩对旧 `task_progress` tool_call/result 做专门折叠,避免进度历史长期占 prompt。前端新增 `progress.js` 做 task 级进度状态合并,修复 `update_step` 只带 `{id,status}` 时因缺标题不显示的问题;当前进度显示从助手消息内提升到 `#task-progress-dock`(对话流下方、输入框上方),历史消息内仍保留进度块作记录。system prompt + coding/ppt/proposal/analyze skill 加轻量使用约定,要求只在多步骤关键阶段少量更新。**部署侧补静态资源 no-cache**:`NoCacheStaticFiles` 替换默认 `StaticFiles`,让浏览器重新校验 `/static/*.js` 等资源,避免前端修复已部署但旧 `chat.js` 仍被缓存导致看不到进度区。校验:`pytest tests/test_context_compaction.py tests/test_task_progress_tool.py tests/test_executor_docker.py tests/test_static_vendor.py -v` 相关集通过;`node --test tests/frontend_task_progress.test.mjs` 2 过;`node --check web/static/js/chat.js web/static/js/progress.js` 过。
|
||
|
||
### 2026-06-06
|
||
|
||
- **前端模块化 Step 2 收官:抽出 `chat.js`(对话视图)+ main.js 缩成 75 行入口**:最后也是最缠的一块——任务列表(浏览/筛选/滚动)+ selectTask 切换 + renderChatMeta/模型下拉 + renderMessages + live-run 助手 + sendMessage/cancel + fetchSse/handleSseEvent + 润色/粘贴文件 + 完成/废弃/删除/导出/清空(原 main.js 连续区 64–1132)→ `chat.js`(1086 行)。**决策:合一个 chat.js 而非强拆 tasks.js+stream.js**——读完依赖图确认二者共享 `state.liveRuns` + `chat-stream` DOM + run 生命周期,且 live-run 助手(renderLiveRunIfVisible/ensureRunningTaskSubscribed 等)被 selectTask 和 SSE 机器两边调用、骑墙;强拆会制造双向各 ~4-5 个 import 且边界不自然(用户已确认选合一)。导出 `loadTaskList`/`loadModels`/`selectTask`,embed/files/newtask 对这三个的 import 从 `./main.js` 改指 `./chat.js`;`formatUploadProgress` 加 export(粘贴上传进度用)。**chat 不调 enterApp → 与 main 无环**。`main.js` 仅留 `enterApp`(编排)+ `loadStorage` + Esc 关栈 + boot = **75 行入口**,import 精简到 11 行(layout/markdown/media 不再被 main 直接引用,但经 chat 仍在依赖图、副作用照常)。**校验升级**:除 node 全检 + import/export 一致性,新增**从 main BFS 的模块可达性检查**(14/14 可达,确保副作用模块不掉出图)。dev.html 4087 行单文件 → 14 个零构建 ES module + 纯 HTML;main 2719→75。**路径 1(拆文件)完成**,后续可按需进路径 2(给 chat/files 等局部引 Alpine/petite-vue 响应式)。
|
||
- **前端模块化 Step 2:抽出 `embed.js`(iframe 模式)**:父页面经 postMessage 推 token 进入应用 + 401 重签(原 main.js 1147–1209 + 顶层 `_embedInitialTaskHandled` 一次性标志)→ `embed.js`(75 行)。导出 `embedInit`(boot 调)+ `embedPostToParent`/`embedShowWaiting`(auth 的 logout 在 embed 下通知父页面/显示等待态)——后两个从 main 迁出后,`auth.js` 对它们的 import 从 `./main.js` 改指 `./embed.js`(auth 仍从 main import enterApp)。反向 import main glue `enterApp`/`loadTaskList`/`selectTask`。main↔embed、auth↔embed 均运行时调用环,安全。main.js 删至 **1154 行**(2719 行起,已搬出约 58%)。node 全检过、import/export 一致性过、静态测试 2 过。剩 main 内:`enterApp` glue + tasks(列表/选择/渲染消息)+ stream(发送/SSE)+ boot + Esc 关栈,待最后一并处理 tasks+stream。
|
||
- **前端模块化 Step 2:抽出 `newtask.js`(新建任务弹框)**:任务名 / 工作目录(新建 sentinel 或复用已有 + 二级 input 联动)/ 描述 / skill / 模型 select,提交 `POST /v1/tasks`(原 main.js 1146–1320)→ `newtask.js`(186 行)。顶层自绑 hd-new 打开 / nt-go 提交 / 各 input 联动;唯一对外导出 `loadFolderSuggestions`(供 main enterApp 初始化顶部 filter-wd、files 复制/移动后刷目录)——它从 main 迁来后,`files.js` 对它的 import 从 `./main.js` 改指 `./newtask.js`。反向 import main glue `loadModels`(加 `export`)/`loadTaskList`/`selectTask` + `logout`(auth)。main.js 删至 1220 行。node 全检过、import/export 一致性校验过、私有符号清零。
|
||
- **前端模块化 Step 2:抽出 `media.js`(工具活动标签 + artifact 抽取/渲染)+ 收敛 downloadFile 反向依赖**:对话内 `toolActivityLabel`(工具调用→中文活动名)、`extractArtifactRels`(从结果文本/working_dir 提产物路径)、`extractMediaBanner`(seedream/seedance 横幅)、`renderArtifactBarHtml`(产物 chip 条 + 图/视频内联占位)、`upgradeMediaArtifacts`(占位异步 fetch blob 填 `<img>`/`<video>` 带缓存)、`downloadFile`(blob 下载)→ `media.js`(237 行,原 main.js 1134–1359)。**收敛点**:downloadFile 移入 media 后,`preview.js`/`files.js` 对它的 import 从 `./main.js` 改指 `./media.js` —— 把这条反向依赖从 main 挪开。media 导入极少(`escapeHtml`/`_categorize`(preview)/`state`/`logout`),与 preview 成 media↔preview 环(均运行时调用,安全)。**两次险漏靠校验抓回**:① 共享 const `ARTIFACT_PRODUCING_TOOLS`(main renderMessages/SSE 用 4 处,`.has()` 访问非函数调用,"被调标识符"法漏掉)② 内部函数 `_flushMediaArtifactCache`(selectTask 切任务清缓存用)—— 残留符号检查发现后补 export。新增**全模块 import/export 一致性校验脚本**(每个 `import{X}` 必在目标 `export`),11 模块全过。main.js 删至 1393 行。`node --check` 11 模块全过、静态测试 2 过。
|
||
- **前端模块化 Step 2:抽出 `files.js`(文件面板 + 选入 + 拖拽上传)**:右栏文件列表浏览/导航/删除/重命名 + 刷新 + "选入"弹框(跨目录勾选复制/移动)+ 拖拽 overlay + 上传(XHR 带进度)+ 上传状态条。代码原分散在 main.js **两段非连续区**(1133–1459 文件列表/选入/拖拽 + 1697–1786 上传 helper,中间夹着 media 段)→ 合并进 `files.js`(433 行)。导出 `loadFiles`/`scheduleFilesRefresh`(SSE 文件事件刷新)/`closeSrcPicker`(main Esc 关栈)/`uploadFiles`(聊天区粘贴/拖拽复用);其余入口模块顶层自绑。反向 import `openFilePreview`(preview)、`logout`(auth)、main glue `downloadFile`/`selectTask`/`loadTaskList`/`loadFolderSuggestions`(后三个加 `export`,后续随 tasks/newtask 模块化再迁)。依赖分析用"段内被调标识符 − 段内定义 − 叶子/全局"全量提取,补回固定清单漏掉的 `loadFolderSuggestions`/`loadTaskList`。main.js 删至 1619 行。`node --check` 双过、main 残留 files 私有符号清零、files 无未导入 glue、静态测试 2 过。
|
||
- **前端模块化 Step 2:抽出 `preview.js`(文件预览 + mini 预览)**:文件预览主弹框(图/视频/PDF/文本/markdown/docx/xlsx,大文件降级下载,docx/xlsx 走 `loadScript` 懒加载 vendor)+ 同时再开的小窗预览(原 main.js 1687–2048)→ `preview.js`(379 行)。导出 `openFilePreview`/`openPasteFilePreview`/`closeFilePreview`/`closeMiniPreview`/`_categorize`(媒体段判图/视频用)。反向 import `downloadFile`(main 媒体段,加 `export`)、`logout`(auth)。**Esc 关弹窗栈处理器留 main**(跨模块协调 chpw/选入/文件预览/小预览,加了节注释)。**一处去耦**:`deletePastedFile`(留 main)原直接读 preview 私有 `_fpCurrentRel`/`_mpCurrentRel` 判断要不要关预览 → 改为 preview 导出封装 `closePreviewIfShowing(rel)`,行为不变但不泄漏内部 current-rel 状态(模块边界更干净;唯一非纯剪切的微调)。main.js 删至 2034 行。`node --check` 双过、preview 私有符号在 main 清零、无未导入 glue 引用、静态测试 2 过。
|
||
- **前端模块化 Step 2:抽出 `auth.js`(首个 main↔模块 ES 环)**:登录(邮箱密码 / UUID+PLATFORM_KEY 两 tab)+ 管理员加用户 + 改密码三节(原 main.js 21–227)→ `auth.js`(218 行)。各入口在模块顶层自绑 onclick,只导出 `logout`(供全局 20 处 401 处理)/`closeChpwModal`(供 main 的 Esc 统一关弹窗栈)。反向 import main 的 glue `enterApp`/`embedPostToParent`/`embedShowWaiting`(main 给这三个加 `export`)——**首次引入 main↔auth 循环依赖**:三者皆 hoisted 函数声明、模块实例化即就绪,且只在运行时(点击/401)调用,绝不在顶层求值时触发 → ES live binding 下安全;这是增量拆单体的标准形态,后续 features↔glue 环同理。main.js 删至 2397 行。`node --check` 双过、auth 私有符号在 main 清零、静态测试仍 2 过。**逻辑零改动**。
|
||
- **前端模块化 Step 2(起):从 main.js 抽出 `layout.js`**:三栏布局(pane 折叠 rail + 拖拽 splitter + 手机单列视图)是 main.js 里唯一对其他功能节零出边的干净段,用它打样增量剥离。`layout.js`(121 行):import `$` + 4 个 `LS_*_COLLAPSED/WIDTH`,只导出 `mqPhone`/`setMobileView`(后者供 selectTask 在手机宽下选中任务自动切对话面板,是唯一跨模块调用);折叠/splitter/mobile-tab 的顶层事件绑定原样保留(ES module 默认 defer,import 时 DOM 已就绪)。main.js 删 114 行 → 2606 行,加 layout import 并清掉随之不再用的 4 个 `LS_*` import。**逻辑零改动,纯剪切+连线**;`node --check` 过、main 残留 layout 私有符号清零。**顺手修 Step 1 遗留测试失败**:`test_static_vendor` 第二用例原只 grep `dev.html` 找 `formatContextStats`/`context_original_chars`/`cache_hit_tokens`,模块化后这些搬进 `js/*.js` → 改为扫 `dev.html + js/*.js` 合并源,2 测试全过。后续按干净度继续剥(下一个 auth = login+加用户+改密码,会引入 main↔auth 的 ES 环,靠 live binding 解)。
|
||
- **修 deepseek-v4-flash 大参数工具调用 arguments 损坏 → loop 畸形重试 + 非流式兜底**:用户报"测试docx"任务里 zcbot 回 `[Error] bad arguments to write: WriteTool.execute() missing 2 required positional arguments`。实证定位(dump 失败 task 全量 messages):**大参数(≈7–10K 字符)的 write/run_python 偶发把别处内容碎片错位粘进 `arguments` 开头**(如 `].cells[1].merge(...{"path":...}`),`json.loads` 直接失败;有时退化成空 `{}` → execute 缺参报 TypeError。**根因双层**:① 上游 deepseek-v4-flash 流式 delta 偶发错位(隔离复现 16/16 全干净,说明概率低);② 真正放大成灾的是 **loop 把损坏的 assistant 消息原样入库 + 每轮重发 → 模型学坏的投毒级联**(失败 task 里大半 write 连锁失败)。读 litellm `stream_chunk_builder` 源码排除"content 混进 args"(content 与 tool_args 两趟独立合并);批量验证非流式 8/8、流式 8/8 在干净上下文均不复现 → 确认是间歇上游抖动 + loop 零容错。**修法**(`core/loop.py`):`_stream_llm` 重构成「拉一轮 → `_malformed_tool_calls` 校验 tool_call arguments 能否 `json.loads` → 不能则**丢弃整轮(不 append/不记账)重 roll**」,最多 3 次;最后一次降级 `_nonstream_once`(provider 服务端拼 tool_calls,绕开流式错位,content 整段补 emit)。断投毒环 + 不依赖猜准上游成因 + 不动正常路径。**backstop**:`executor_host.py` / `sandbox/tool_runner.py` 缺必填参数(空 `{}`)早返 `缺少必填参数 [...];请带齐 [...] 重新调用`,替掉暴露内部签名的 `missing N required positional arguments`。重试消耗 token 不单独记账(罕见路径)。tests 全过(唯一失败 `test_static_vendor::formatContextStats` 是前端 ES module 化遗留,与本改无关)。
|
||
|
||
### 2026-06-05
|
||
|
||
- **前端模块化 Step 1:`dev.html` 单文件拆零构建 ES module(叶子优先)**:`web/static/dev.html` 原 4087 行(纯原生 JS、手写 `state` + 手动 DOM、零内联 `onclick` 全 `addEventListener`、唯一 `window.*` 是比较非赋值)。定方案「1 拆文件 → 2 后续引 Alpine/petite-vue 局部响应式 → 3 永不上 Vue+构建链」,本步只做 1。抽出 4 个无依赖叶子模块到 `web/static/js/`:`state.js`(`state` 单例 + `LS_*` + `EMBED*`)、`format.js`(escapeHtml/humanSize/fmtTokens/fmtCost/usage 系列等纯格式化)、`dom.js`(`$` + 浮层菜单 showMenu/hideMenu,import escapeHtml)、`api.js`(`api()` Bearer 封装,import state)、`markdown.js`(renderMd/highlightIn,依赖 vendor 全局)。剩余主体(login→boot,原 1387–4084)整体落 `main.js` 并 import 上述叶子;`dev.html` 内联大 `<script>` 换成一行 `<script type="module" src="js/main.js">`,降到 1121 行。**逻辑零改动,纯剪切+连线**。`app.py` 加 `mimetypes.add_type("text/javascript", ".js")` 兜底(防 Windows 把 `.js` 判 text/plain 致 module 拒执行;本机实测 `.js`→application/javascript 本就 OK,纯防御)。校验:6 模块 `node --check` 全过 + 无私有符号(`_menuItems`/`_embedQS`)泄漏到 main。后续步骤将从 main.js 把 login/tasks/stream/files/preview 等逐个剥成独立模块(tasks↔stream 循环依赖靠 ES live binding 解)。
|
||
- **改密码弹框样式修复**:`#chpw-modal` 原先没专属 CSS,`.card` 只继承公共骨架(背景+圆角+阴影),缺 padding/width/表单整形 → 卡片被撑到近全宽、无内边距很素。改为复用「选入文件」弹框(`#src-picker-modal`)的头/体/脚分隔布局:标题区底部分隔线、表单内容包进 `.body`(内边距 16/18)、按钮区顶部分隔线右对齐,`.card` 收到 400px + flex column,input focus 红框高亮。纯 CSS/HTML 结构调整,无对外行为变化(不动 DESIGN/RUN)。
|
||
- **run_python 过程脚本约定 `<task_dir>/scripts/`**:确认现状是模型生成的 `.py` 直接落 task_dir 根(系统提示只说"只写到 task_dir",无 scripts/ 分层),过程脚本和交付产物(.docx/.pptx/spec)混在一起。定调:**模型显式写文件再 `script_path` 跑的过程脚本** → `<task_dir>/scripts/`(可见/持久/可重跑,`WriteTool` 自动建父目录);**inline `code` 匿名片段** → 维持临时用后即焚(host 走系统 temp、docker 走 `.zcbot_tmp/<task_id>/` dotfile 隐藏+删,均不动)——不持久化到 scripts/,免把目录污染成匿名垃圾堆。改 `core/agent_builder.py` 系统提示工作目录段加一条 scripts/ 引导(>~15 行/要迭代/出产物用文件,短抛弃代码才内联)+ `tools/run_python.py` 的 tool description / `script_path` 参数说明同步。inline 执行逻辑两后端均未改。tests `test_run_python_script_path` / `test_executor_docker` 全过(2 skip 为 Linux-only)。
|
||
- **新增 `standard` skill(国标/行标/团标起草)**:联网核实市面无可直接复用的"写标准文件本身"的 skill(搜到的 technical-proposal GB/T 8567、official-document GB/T 9704 都是相邻品类——投标书/公文,非标准),据 GB/T 1.1—2020 自建。覆盖三层级(国标 GB·T / 行标 JC·T / 团标 T/,重点对接 **CSTM → T/CSTM**,材料试验团标对口建材院检测方向)× 两体裁骨架(试验方法 GB/T 20001.4 + 产品标准)。文件:`SKILL.md`(阶段化:定层级体裁→八条 spec→逐章段段卡→自检渲染)+ 3 references(`gbt_1_1_structure` 要素骨架/必备可选/规范性资料性/封面前言套话、`standard_levels` 选型+CSTM 体系立项、`drafting_rules` 能愿动词应宜可能/不可考核词过滤/指标量化闭环/术语规则/引用真实性+§8 自检清单)+ 4 templates(spec/test_method/product_standard/编制说明)。**渲染复用 proposal `render_docx.py`+`render_diagrams.py`**(兄弟 skill `../proposal/scripts/`,同 patent 范式);冒烟测过表格/中文渲染正常。**坑**:proposal `quality_check.py` 按申报书固定章节名查"缺章节",对标准全是误报且无跳过开关→阶段三不用机检,改 drafting_rules §8 人工 12 条清单(与 patent self_check 同思路)。产出是结构合规草稿 docx,正式报批再灌官方 TCS/CSTM 模板做版式精修。
|
||
- **dev 页加"改密码"功能 + 文件面板"选入"按钮文字改图标(防换行)**:① 自助改密码——`web/auth.py::change_password(user_id, old, new)`(验旧密码 → 新密码 ≥6 → bcrypt 重哈希写回,错误归一到现成 `UserCreateError` code 体系 `wrong_password/no_password/weak_password/user_not_found`,不为此新开异常类),`POST /v1/auth/change_password` 挂 `Depends(require_user)`(user_id 取自 JWT 不信前端,旧密码错/无密码→403、弱→400、行没了→401)。前端顶栏「退出登录」左侧加「改密码」按钮(`#hd-chpw`,并入 embed 隐藏规则——embed 模式不显示)+ 一个复用 `.modal` 骨架的弹框(旧/新/确认三项,前端先验长度+两次一致再提交,成功 `alert` 提示不登出,401 走 `logout()`)。否决"点用户名展开菜单"(多写菜单逻辑不划算)。② `#btn-src-pick` 的文字 `选入…` 改单字符图标 `⊕`(和旁边 `⬆ ↻ ›` 同款单色字形,`title` 保留"选入"语义)——原中文文字在窄面板偶发换行。
|
||
- **记账给 DeepSeek 前缀缓存命中折价(修虚高 ~2-3x)+ 前端体现缓存命中/真实成本**:排查"rust 优势→PPT"那 task(flash,34 轮)发现 `tokens_in` 累计 69.9 万里 **88.6% 是缓存命中**,但 `usage.py::_fallback_chat_cost_cny` 把命中段也按 `input` 全价(1.0)算 → 记 ¥0.84,真实(命中按 0.1x)只 ~¥0.28,**越大的 task 虚高越多**(文献采集 53% 命中:¥33→~¥16)。修:① `ModelCapabilities` 加 `cache_hit_cny_per_mtoken`(deepseek flash 0.1 / pro 0.2;0=不区分按全价兜底,绝不少记);② 成本公式拆三段「命中×缓存价 + (input−命中)×input价 + output×output价」,`loop.py` 把 `cache_hit_tokens` + 缓存单价透传进 `record_chat_usage`;③ 前端不加 DB 列——`web/app.py` 加 `_usage_aggregates`(单查询 GROUP BY `usage_events`,复用列表 `msg_counts` 同款批量范式,无 N+1)on-the-fly 算每 task 真实成本 + chat token + 缓存命中,`_task_dict` 带出;列表行**不内联花费**、只显 tok 数,花费/缓存命中率藏 hover tooltip(`taskUsageTooltip`,多行:输入/输出拆分 · 命中 + 命中率 · ¥真实花费),顶栏额外内联简版。**折价只对新 chat 事件生效**,历史走 backfill 脚本(`scripts/backfill_chat_cost_cache_discount.py`,默认 dry-run,`--apply` 落库;`--assume-cache-hit-rate RATE` 给无 `cache_hit_tokens` 字段的老事件按估算命中率折价——DeepSeek 当时缓存了只是没记,全价偏高;实测过的事件用真实值不受影响)。**坑修**:命中率分母原误用 `tasks.tokens_prompt`,但该列会被「清空对话」重置而 `usage_events` 不重置 → 跨源相除算出 822% 怪值;改为 `_task_dict` 的 token 总量也优先取 usage_events 聚合(与 cache_hit 同源,命中率恒 ≤100%)。**注**:真正压低 token 体量的杠杆是减少轮数(高成本 task 全是 100+ 轮的逐步 write/run_python 循环),非本次范围。
|
||
|
||
### 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` 只写 `<a:latin>`,中文字形走 `<a:ea>` 槽位没设 → 「指定微软雅黑却没真生效」的根因;`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 工具全在容器里跑(`<workspace>/users/<uid>` bind 到 `/workspace`),但 `_build_system_prompt` 注入的 `task_dir` / cwd 是宿主绝对路径(容器内不存在),LLM 据此 `find /home/ubuntu/zcbot ...` 全空、瞎转到 `pwd` 才发现真身在 `/workspace/<wd>`。修法同 `LoadSkillTool` 的 `container_skills_dir` 改写:docker 下 `task_dir` + 「宪法」glob 范例换成容器路径 `/workspace/<wd_rel>`,并去掉 docker 下无意义的 cwd 行(容器 cwd 恒等 task_dir);host 不变。
|
||
- **顺扫清掉 SKILL.md 里残留的宿主路径假设(同 docker 隐患)**:`patent` 跨 skill 调 proposal 脚本原用 `<repo_root>/skills/proposal/...`(还举例硬编码 `D:/projects/zcbot`)→ 改兄弟 skill 相对 `<skill_dir>/../proposal/scripts/...`(两后端都对);`research` 的 `fetch_pdf` 范例硬编码 `working_dir=r"D:/..."` → 改 `<task_dir>` 占位;`patent`/`proposal`/`ppt` 的 `<task_dir>` 举例用废弃旧布局 `…/workspace/tasks/<task_id>/` → 改 host/docker 双形态说明。
|
||
- **修 ppt 图标缓存写进只读挂载(docker 拉新图标失败)**:`fetch_icon.py -o <skill_dir>/assets/icons/` 在 docker 下 skills 是 `:ro` bind,写回必败;host 下还顺带污染仓库工作树。改为(方案 A,与"产物只写 task_dir"的全局规矩一致):种子图标库 `<skill_dir>/assets/icons/` 降为**只读**(glob 读),`fetch_icon.py` 新拉的图标一律 `-o <task_dir>/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` 前已被绘制;在 `<body>` 首行加同步内联脚本,`?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`)致 `<pre>` 一直空。修字段 + 新增 `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/<name>`。所有 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 落 `<user_root>/.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)**:`<task_dir>/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 相对全形式 `<wd_name>/<rel>`(简写致 Web UI chip 失效),跨所有产物 skill 统一;imagegen/videogen SKILL 改"照抄 saved 行";`dev.html::extractArtifactRels` 一次性兼容历史简写。术语用 `<wd_name>`(working_dir 末段)而非 `<task_name>`(允许两者不等)。
|
||
- **豆包 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 工作目录回原生 `<select>` + sentinel `+ 新建「<name>」` + 二级 input(combobox 方案试过又推翻 —— 原生 select 更稳)。
|
||
- **sandbox 阻塞地位写进 DESIGN(§7.7 hard prereq / §7.8 风险行 / §7.9 取舍)**:`tools/shell.py::BLOCKED_PATTERNS` 是 trivial-bypass 装饰品(命令注入图灵完备,黑名单 fundamentally broken),不继续加规则。正确防线在 OS 层 §7.5(docker exec + drop ALL caps + read-only + egress allowlist)。本地 dogfood blast radius 限自身;SaaS 外部开放才是 hard prereq。
|
||
- **system prompt 注入 task 预选 skill 提示**:`_build_system_prompt` 加 `task_skill` 参数,非空时加一行事实,与 general_v1 已有"对应 skill 先 load"规则组合 → 主动 load。否决"完整 SKILL.md 预注入"(把 skill 从 metadata 升格成 binding,投产比不划算)。
|
||
- **imagegen skill 加 ⛔ 调 tool 前必须贴 prompt + BLOCKING 等确认硬约束**:清楚的描述也可能模型与用户脑里对不上,事后看图才发现白烧 ¥0.22 —— 把"模型脑内装配"摊到对话层让用户最后过一眼(装配 ≠ 授权调用)。诊断五维 → 六维加"比例 / 尺寸";`general_v1` 改"调 seedream 前必须先 `load_skill('imagegen')`",description 扩 17 触发词。
|
||
- **新增 imagegen skill(引导用户说清楚生图需求)**:单文件五步法(诊断模糊度 → 给推断 + 待确认 → 用户拍板 → 装配 prompt → 调 seedream),防一句"画个 XX"直接烧 ¥0.22;mermaid vs seedream 选型三段式。
|
||
- **登录页加"+ 管理员添加用户"入口 + 删 chat meta 条/tok 显示**:`web/auth.py::create_user`(CLI / web 共用)+ `POST /v1/auth/admin/create_user` 校验 `ZCBOT_ADMIN_TOKEN` 共享口令。否决 User 表加 `is_admin` 列 + 管理员 JWT(开发期成本不划算)。
|
||
- **新增 documents skill(内部材料学科知识库 document_search API)**:`skills/documents/{SKILL.md, client.py}` 四函数,Bearer 认证;search 返整篇 Markdown(50K-200K 字符),反模式约束只 print 前 300 字防爆上下文。库实为 7 材料学科英文学术论文 21W+ 文件 + 跨语言语义检索(原写"主语料中文"是错的);与 research(OpenAlex)互补。
|
||
- **dev SPA SSE 客户端重连(覆盖 --reload 抖动)**:`fetchSse` 拆 consume + 重连壳(1/2/4s 退避 ×3);后端 `stream_events` 入口检 run_status,非 running 立即吐 done 关流(防进程重启后无限挂 ping)。断开期 LLM delta 丢失,接受。
|
||
- **research skill fetch_pdf 改走静态直链**:从 `paper["pdf_url"]` 流式下载,绕开 paper_pdf_view 路径 bug(disk 路径计算错);smoke 5/5。
|
||
- **research skill list 端点加 pdf_url / xml_url 直链 + 新增 fetch_xml + pg_trgm GIN 索引**:后端拼直链(避免 stale URL),GIN 把 `?search` 从 30s timeout 降到几十 ms;SKILL.md 加"XML 优先 PDF"(已结构化免 OCR)。
|
||
- **顶栏 token 累计修(`sync_task_tokens` 改走 messages SUM)**:切 streaming 后内存计数器永不更新,删 TokenCounter 类改 `SELECT SUM(...) FROM messages` 现算,backfill 4 task。
|
||
- **同 wd 并发软警告 banner + `/v1/tasks` 加 `run_status` 筛选**:Claude Code 同款"信任 + 软警告",`selectTask` + SSE 收尾拉同 wd running task 黄底提示。否决硬挡 / short_id 全产物隔离 / clone task 三方案(DESIGN §7.9)。
|
||
- **paper_server → research skill**:范式判断走 skill(非 tool / MCP / 裸 httpx),`skills/research/{SKILL.md, paper.py}` 三函数;`run_python` 注入 `PYTHONPATH=base_dir`;paper_server 补 retrieve 端点 + serializer 加 abstract。
|
||
|
||
### 2026-05-20
|
||
|
||
- **dev SPA artifact chip 演进(同质合并)**:对话内 tool_call/result 挂产物 chip,`extractArtifactRels` 正则锚 `<wd>/...` + 末段需含 `.`;门控多次校准后落到产物工具白名单 `ARTIFACT_PRODUCING_TOOLS={seedream,seedance}`(通用工具 echo 路径不误挂),assistant 正文不门控走 `seenRels` 去重 + `allowInlineMedia` 防同图二次 inline;`.art-chip` 点击委托 `openFilePreview`。
|
||
- **CLAUDE.md 加"实施前先对方案"段**:非平凡改动动手前先口头对方案。
|
||
- **loop.py tool message 补 `name` 字段 + backfill 历史**:OpenAI tool spec 本有 `name`,缺它致历史回放无 banner / chip,一行 fix + 幂等回填 17 条。
|
||
- **dev SPA 输入区删上传按钮 + 加"✨ 润色"按钮**:`POST /v1/tasks/{id}/optimize_prompt` 走 task model_profile 装配 LLM(meta-prompt 含当前模型 + image variant),计费 `kind=prompt_optimize`,**不**调 `sync_task_tokens` 不污染顶栏。
|
||
- **顶栏加生图模型下拉 + 中间产物图片/视频内联展示**:`GET /v1/image_models` 扫 yaml image 段,`build_agent(image_variant=...)` 装 SeedreamTool;`renderArtifactBarHtml` 按 `_categorize` image/video 走 blob URL inline,切 task 回收 blob。
|
||
- **LLM 调用切 streaming(cancel 秒退)+ 发送/停止合并单按钮**:`chat_stream(stream=True, include_usage=True)` + `litellm.stream_chunk_builder` 拼回,chunk 间 poll cancel;前端打字机靠 `_emit("text", delta=...)`;`#chat-action` 按 `state.streaming` 切三态。
|
||
- **dev SPA seedream tool 透明性 banner**:tool 返串首行 `[seedream] model=... · size=... · cost=¥... · elapsed=...s`,前端正则 parse 挂折叠态徽章。
|
||
- **豆包 Seedream 5.0 接入 + 0007 cost_usd → cost_cny 全表统一币种**:`config/media/doubao.yaml` 独立命名空间(`ARK_API_KEY`),`tools/seedream.py` 走 `ark_client` 调 `/images/generations`,产物落 `<wd>/figures/`;`record_image_usage` snapshot 单价进 units(调价防漂移);0007 全表 ×7.2 折 CNY;仅 ARK_API_KEY 设了才挂。
|
||
- **`POST /v1/files/delete` 加 `recursive` + 顶层目录 task 引用闸**:`recursive=True` 走 `shutil.rmtree`;顶层目录被 task 引用 → 409"先 DELETE task 再清"。
|
||
- **fs tool 输出渲染 user_root-relative 路径**:`tools/base.py::Tool` 加 `user_root` + `_display(p)` helper,fs 五 tool 走 helper;chip 锚点取末段。消 chip 404 + 防 uuid / 部署根泄漏。
|
||
- **`POST /v1/tasks/{id}/clear` 清空对话**:同事务 lock + 检 running 状态 + DELETE messages + reset task 三列累计 + run_status='idle';**usage_events 全不动**(账单 source of truth)。
|
||
|
||
### 2026-05-19
|
||
|
||
- **0006 模型切换(c 模式 task 级 A 粒度)+ usage_events v2 表**:`tasks.model_profile` 变 source-of-truth,顶栏下拉 PATCH 即换(下条 send 生效);`GET /v1/models` 扫 yaml;message 历史按 `messages.model_profile` 切换点画 `── DeepSeek V4 Pro ──`;usage_events 重建多态形态(units jsonb,chat 已接入,媒体扩展位预留)。
|
||
- **dev SPA 登录撤回 邮箱+密码,删 invites 表**:前两条"邀请码 env → invites 表"一日游撤回;复用 users.email + bcrypt;`/v1/auth/login_password` + `user add` CLI;dev SPA 双 tab 登录(last-used LS 持久化)。
|
||
- **SENTINEL user 彻底撤(数据 + 代码)**:web 必走 JWT 后 sentinel 无角色;DB CASCADE 删 + 10 处代码删;`build_agent` 加 `*` 让 user_id 必填(typechecker 拦多 user 函数)。
|
||
- **任务/文件行 `⋯` 下拉菜单 + tool_result debounce 刷新右侧**:单例浮层菜单(`#floating-menu` position:fixed)避开 pane overflow 裁剪;`tool_result` 事件 debounce 500ms 刷新文件 panel。
|
||
- **proposal skill mermaid 强制 + quality_check 加图相关 4 拦截 + `/v1/files/download` 加 `Cache-Control: no-cache`**:模型曾写满 ASCII 字符画从未用 mermaid;render_diagrams caption 强制必填 + 同 task 唯一;quality_check 加四条(figures 有 png 但 sections 0 引用 / 围栏含 box-drawing / mermaid 缺首行 caption / caption 撞名)。
|
||
- **dev SPA 文件预览弹框**:点击不再直接下载,90vw 模态按扩展名分派(image/pdf/text/md 已有 / docx 用 docx-preview / xlsx 用 SheetJS);vendor 入 git(~1MB)。
|
||
|
||
### 2026-05-18
|
||
|
||
- **入口归位:`cli.py`→`main.py`,原 `main.py`→`core/agent_builder.py`,删 CLI REPL,§7 E 撤**:`main.py` 原混三角色按 SoC 拆;`git mv` + 5 处 import 修;CLI 只剩 `db / probe / web / user`。dev SPA 已是 dogfood 主路径,REPL 无 `--remote` 双 transport 维护税。
|
||
- **0004 schema 大瘦身:删 runs / usage_events 旧版,合 run_status / run_error 入 tasks;路由 run_id → task_id**:单活 run 形态下客户端只需 task_id;broker 全 task_id 索引 + 加 `start(task_id)` 清上轮 done 标记。
|
||
- **`POST /v1/files/rename` + 顶层目录 delete 加 task 引用闸**:`/v1/files/*` 升格为唯一目录树 mutation 入口,DB-FS 一致性服务端内化;顶层目录走 DB-aware 分支(SELECT FOR UPDATE + running/cancelling 409 + check_no_subtask + UPDATE 先于 FS rename)。
|
||
- **task-level cancel + AgentLoop 协作式 cancel + dev SPA stop 按钮**:Broker 加 `request_cancel / is_cancelled / clear_cancel`(per-task `threading.Event` + setdefault);Loop 加 `cancel_check` callable + `_fill_cancelled_tool_results` 补 cancelled tool message;LLM 同步 call 本身不可中断(后接 streaming 修)。
|
||
- **`POST /v1/tasks/{id}/messages` 单活 run 锁 + 孤儿 reaper**:同事务 `SELECT FOR UPDATE` + 活跃状态检查 + 标 running 三步原子;lifespan reaper 清进程 crash 留下的 running/cancelling 孤儿。
|
||
- **proposal skill 流程图/结构图管线**:`render_diagrams.py` 扫 mermaid 块 → mmdc / mermaid.ink → png;render_docx `add_picture` 识别 `` 单行 + mermaid 围栏特判;图编号 `ctx['fig_no']` 递增。
|
||
- **system prompt skill 机制改"可选辅助"**:第 14 行从"永远 load 一下"改"简单问答/读代码/改 bug 不必硬套 skill";接 GET /v1/skills 下拉。
|
||
- **`GET /v1/skills` + dev SPA skill 字段改下拉**:lifespan 启动扫一次挂 `app.state`(FS 静态运行中不变);`<select>` 首项空值,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/<uid>/<name>/`,同 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: <type>` + `data: <JSON>`);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/<user_id>/{tasks/<uuid>,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|<id>]` + 懒创建 + `_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 路。
|
||
- **06-04 token 优化启动**:`DESIGN.md §8.2` 写入上下文治理方案;chat usage 记录 cache hit/miss / reasoning tokens,LiteLLM cost=0 时按模型档案 CNY/Mtok 兜底;`run_python` 新增 `script_path` 模式(长代码先 write .py 再按路径执行,减少 run_python code 入历史);`run_python` / `shell` 长输出只做上下文裁剪,不写 `.tool_logs`;`document_search` 默认召回量保持 6×1200 chars;`core/context.py` 先压缩旧 tool 消息、旧 `load_skill` 结果、旧 assistant tool_call arguments(`write(content=...)` 源码参数),不改持久化历史;`llm_start` SSE 输出 `context_*` 压缩统计,dev SPA 底部 hint 展示上下文压缩与 cache hit/miss。`rust介绍` 实测:task 列表 `70条 / 711k tok` 是历史累计,最近单轮 22k input 且 cache hit 高;新增 arguments 压缩后 sent_chars 估算 `49,166 → 34,415`。
|
||
|
||
---
|
||
|
||
## 关键决策与偏差
|
||
|
||
| 项 | 决策 | 备注 |
|
||
|---|---|---|
|
||
| 工具基目录 | 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` | 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 75 ← 模型档案增加 CNY/Mtok 计费兜底字段
|
||
core/llm.py 151 ← litellm 离线 cost map env + chat_stream(stream=True + include_usage)
|
||
core/loop.py 300 ← §7 A sink.emit + _stream_llm(chunk 间 poll cancel + emit delta)+ usage cache 明细
|
||
core/context.py 95 ← LLM 调用前压缩旧 tool / load_skill 消息,保 tool_call 协议字段,返回压缩统计
|
||
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 150 ← record_chat_usage(USD→CNY ×7.2,LiteLLM cost=0 时 YAML 单价兜底)+ 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 ~680 行(run_python 支持 script_path)
|
||
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://<zcbot_pg_host>...` → IP block(连接失败);③ 容器内 `nohup sleep 1000 &` exec 退出后 `docker top <user_container>` 看不到残留进程;④ 跨 user 容器互访(A 容器 `curl http://<B_container_ip>:*`)→ 网络隔离阻断;⑤ 出网走 proxy 时未在 allowlist 的域名 → 403。原 ~2-3 天估值未含 egress proxy 部署 / xfs project quota 升级 / 红队用例,补回真实工程量。
|
||
3. **Phase 6 context 三层压缩**(~1 天)—— 先做旧 tool 消息压缩(`role/tool_call_id/name` 保持协议完整),再做 task summary;不写 `.tool_logs`,不改 `document_search` 默认召回量。
|
||
|
||
> §7 B + D + D' + 单活 run 锁 + cancel + 0004 schema 瘦身 + 入口归位 主体已完工。剩余:真 OIDC → C(Executor)→ F(deploy / billing)。§7 E CLI 双模式撤;Phase G Jinja2/HTMX 撤(详见 DESIGN §7.9)。
|