# 实施进度 > 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。 最后更新:2026-05-21(dev SPA SSE 客户端重连 + 后端 stream_events 非活跃态立即吐 done) --- ## 状态 | Phase | 标题 | 状态 | 备注 | |---|---|---|---| | 1-3 | 骨架 + Skill + run_python | ✅ | 三个 skill;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(Executor)待。 | --- ## 已完成关键能力 ### 2026-05-21 - **dev SPA SSE 客户端重连(覆盖 --reload 抖动)**:`fetchSse` 拆出 `consumeSseStream` + 包重连壳(1s/2s/4s 退避,最多 3 次);reader EOF 未见 done/error 算异常关流触发重连;后端 `stream_events` 入口检 `tasks.run_status`,非 running/cancelling 立即吐 done 关流(否则进程重启后新 broker 内存空,客户端会无限挂 ping)。3 次仍失败 → 卡片末尾红色"连接已断开,请重发"。断开期间 LLM delta 丢失,接受。 - **research skill 三次迭代 fetch_pdf 改走静态直链**:`fetch_pdf` 跟 `fetch_xml` 同范式,从 `paper["pdf_url"]` 流式下载,绕开 paper_pdf_view 路径 bug(disk 路径计算错);smoke 5/5 PASS。 - **research skill 二次迭代 list 端点加 pdf_url / xml_url 直链 + 新增 fetch_xml + pg_trgm GIN 索引**:serializer 后端拼直链(避免 LLM 拿 stale URL),`0006_pg_trgm` 给 title/first_author/institution 加 GIN 把 `?search=xxx` 从 30s timeout 降到几十 ms;SKILL.md 加"XML 优先 PDF"原则(XML 已结构化免 OCR)。 - **顶栏 token 累计修(sync_task_tokens 改走 messages SUM)**:5/20 切 streaming 后 `LLM.TokenCounter` 内存计数器永不更新;删 TokenCounter 整个类,`sync_task_tokens` 改 `SELECT SUM(tokens_in/out) FROM messages WHERE task_id=?` 现算;backfill 4 个 task。 - **同 wd 并发软警告 banner + `/v1/tasks` 加 `run_status` 筛选**:Claude Code 同款"信任 + 软警告"范式;`selectTask` + SSE 收尾两点拉同 wd running task,黄底 banner 提示邻居;task header `📁 wd` 仅在 name≠wdName 时显示。否决了 γ 硬挡 / short_id 全产物隔离 / clone task 三方案(详见 DESIGN §7.9 2026-05-21)。 - **paper_server → research skill**:范式判断走 skill(非 tool / 非 MCP / 非裸 httpx),`skills/research/{SKILL.md, paper.py}`,三函数 `search / get_paper / fetch_pdf`;`run_python` 注入 `PYTHONPATH=base_dir` 让子进程能 `from skills.research.paper import`;paper_server 侧补 retrieve 端点 + `PaperFilterSet` + serializer 加 abstract。 ### 2026-05-20 - **dev SPA chip 二次校准**:工具 I/O 走产物白名单(seedream/seedance);助手正文 echo 路径无条件挂 chip 绕开 seenRels + 强制 `allowInlineMedia=false`(防同图二次 inline)。 - **chip 维度解绑产物工具白名单 + `renderArtifactBarHtml` 加 `allowInlineMedia` 参数**:gate 降级到"图片/视频是否 inline"层,chip 不再受产物白名单限。 - **loop.py tool message 补 `name` 字段 + backfill 历史**:OpenAI tool spec 本来就有 `name`,缺它导致历史回放无 banner / 无 chip;一行 fix + 幂等脚本回填 17 条。 - **chip 抽取改产物工具白名单门控**:`ARTIFACT_PRODUCING_TOOLS = {seedream, seedance}`,grep/read/shell 等通用工具结果里 echo 的路径不再误挂 chip;assistant 正文不门控(seenRels 兜底)。 - **dev SPA 输入区删上传按钮 + 加"✨ 润色"按钮**:`POST /v1/tasks/{id}/optimize_prompt` 同步走 task.model_profile 装配 LLM,meta-prompt 含当前模型 + image variant 元数据;execCommand 插入接入 textarea 原生 undo 栈;计费写 `usage_events.kind="prompt_optimize"`,**不**调 `sync_task_tokens` 不污染顶栏。 - **中间产物 chip / inline 图去重 + CLAUDE.md 加"实施前先对方案"段**:`renderMessages` 顶部建 `seenRels` Set + `pickFresh` 闭包给 5 个渲染点共享;CLAUDE.md 新规:非平凡改动动手前先口头对方案。 - **顶栏加生图模型下拉 + 中间产物图片/视频内联展示**:`GET /v1/image_models` 扫 yaml image 段;`build_agent(image_variant=...)` 装 SeedreamTool;`renderArtifactBarHtml` 按 `_categorize(rel)` 分支,image/video 走 blob URL inline,异步 `upgradeMediaArtifacts` 替换占位;切 task 时 `_flushMediaArtifactCache` 回收 blob。 - **LLM 调用切 streaming(cancel 秒退)+ 发送/停止合并单按钮**:`chat_stream(stream=True, include_usage=True)` + `litellm.stream_chunk_builder` 拼回 response,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` env),`tools/seedream.py` 走 `core/ark_client.py` 同步调 `/images/generations`,产物落 `/figures/-.png` + 同名 .meta.json;`record_image_usage` 把 `price_cny_per_image` snapshot 进 units jsonb(调价防漂移);0007 全表 ×7.2 一次性折 CNY;**仅当 ARK_API_KEY 设了才挂 tool**。 - **`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.py 五 tool 所有结果串走 helper;chip 锚点用 `_workingDirName` 取末段(绝对路径返空);assistant 正文也挂 chip。根因消 chip 404 + 防 uuid/部署根泄漏。 - **`POST /v1/tasks/{id}/clear` 清空对话**:同事务 lock + 检 running 状态 + `DELETE messages` + reset task 三列累计 + run_status='idle';**usage_events 全不动**(账单 source of truth)。 - **dev SPA chip 一期(对话内 tool_call/result 挂 artifact chip)**:`extractArtifactRels` 正则锚定 `/...` + 末段需含 `.`(滤目录);`.art-chip` 点击委托 `openFilePreview`。 - **task 级宪法文件 spec 命名约定 + `spec_lock` → `spec` 简化**:`--.spec.md`,short_id 作主锚 + glob 字典序最大 = current;`_build_system_prompt` 注入 task_id / today;proposal/ppt SKILL.md 加"先 glob 检 spec → 询问沿用/重定调"分支。 - **dev SPA 左 pane 折叠改 VS Code rail 模式 + time-ago 锁宽跨行对齐**:`body.left-collapsed` 用 `grid-template-columns: 40px 1fr 320px`,只显折叠按钮;`time-ago` 加 `flex-shrink:0; min-width:64px` 让 [N 条][N tok][time] 整组位置稳。 - **任务行 meta 数字槽位跨行对齐**:`tabular-nums` + `.num{flex-shrink:0;text-align:right;min-width:44px}` + `fmtTokens(n)` 桶分级(1.2k / 123k);折叠按钮拆双入口(pane / header)。 - **dev SPA 左 pane 调宽 280→320px + 行精简 meta**:删 id8 span 挪到 row title hover;副行恢复 inline ellipsis 三件套;`white-space:nowrap` 防 CJK 断行。 - **任务列表 pager bar → IntersectionObserver 滚动加载**:`loadTaskList({append})` 双语义 + `_taskLoadSeq` token 抢占式;sentinel 三态文案;首 pane-head 补"共 N 个"总数显示。 - **任务行加最近操作时间(`updated_at` + `fmtTimeAgo`)**:相对时间分级(刚刚 / N 分钟前 / N 小时前 / 昨天 HH:MM / MM-DD HH:MM / YYYY-MM-DD)+ title hover 完整时间。 - **新建任务弹框工作目录改 `` 首项空值,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**(~2-3 天)—— `run_python`/`shell` → `Executor.run(...)`,本地保留 subprocess、SaaS 走 docker;`api_key_env` → `KeyProvider` 运行时注入。多用户在线跑代码前置。 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)。