zcbot/PROGRESS.md

194 lines
22 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-05-21(登录页加管理员发用户入口 + 删 chat meta 条/tok 显示)
---
## 状态
| 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
- **登录页加"+ 管理员添加用户"入口 + 删 chat meta 条/tok 显示**:`web/auth.py` 加 `create_user()` helper(CLI/web 共用,避免漂移)+ `AuthConfig.admin_token``ZCBOT_ADMIN_TOKEN` env 读(未设 → None);`web/app.py` 加 `POST /v1/auth/admin/create_user` 校验共享口令后落库(503/403/400/409 分支);前端 `dev.html` 登录卡片右下加 ghost link + 弹窗(email/密码/管理员口令),成功后回填邮箱到登录表单提示"已创建请登录",不自动登录;同时删 chat 顶栏 `${n_messages} 条 · ${tokens} tok` 一行(与左 task 列表重复)。否决"User 表加 is_admin 列 + 管理员 JWT"方案 —— 开发期成本不划算,env 共享口令(类 PLATFORM_KEY 范式)够用。
- **新增 documents skill(内部材料学科知识库 document_search API)**:`skills/documents/{SKILL.md, client.py}`,四函数 `list_kb / search / download / health`;走 `https://ai.ctc-zc.com:8100/api` Bearer 认证,env `DOCUMENT_SEARCH_API_KEY` + `DOCUMENT_SEARCH_URL`(可覆盖);search 默认返 `md_content`(整篇 Markdown 50K-200K 字符级),SKILL.md 反模式约束"只 print 前 300 字"防爆上下文;smoke 验证发现库实质是 7 个材料学科预收的英文学术论文(胶凝/陶瓷/玻璃/晶体/复合/耐火/检验检测,21W+ 文件)+ 跨语言语义检索,SKILL.md 据此校准(原写"主语料中文"是错的);与 research(OpenAlex)互补,documents 已 Markdown 化对 LLM 更友好,但仅覆盖材料领域。
- **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`,产物落 `<wd>/figures/<ts>-<rand>.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` 正则锚定 `<wd>/...` + 末段需含 `.`(滤目录);`.art-chip` 点击委托 `openFilePreview`
- **task 级宪法文件 spec 命名约定 + `spec_lock``spec` 简化**:`<YYYY-MM-DD>-<task_short_id>-<task_name>.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 完整时间。
- **新建任务弹框工作目录改 `<select>` 下拉**:含 `+ 新建目录…` sentinel 触发备用 input;`loadFolderSuggestions` 同次灌 select + datalist(后者只服务左 pane filter)。
- **dev SPA 主页轻量美化**:header brand wrapper(24px 红渐变 Z logo)+ pane-head 子层级 + 顶栏按钮"中性 → hover 上语义色" + 圆角 4→6 / modal 6→8 + 阴影加深。
- **`config/models/glm.yaml`:智谱 GLM 5.1 接入(litellm zai provider + bigmodel.cn)**:`zai/glm-5.1` + `api_base=https://open.bigmodel.cn/api/paas/v4` 覆盖国际站默认;env `ZHIPUAI_API_KEY`;**thinking_mode=false**(GLM 协议是 `extra_body.thinking.type=enabled` 与 OpenAI/DeepSeek reasoning_effort 不同,留 TODO)。
- **files SPA UX 翻面(destination-first)+ 拖拽上传 + 修 checkbox 全局 width bug**:模型从 select-then-pick-dest 改 at-dest-pull-sources;`input{width:100%}` 选择器排除 checkbox/radio/file;`#pane-right` 监听拖拽 + 红虚线 overlay 落 `state.filesPath`
- **`POST /v1/files/{copy,move}` 跨目录批量搬动**:`_validate_transfer` 预检 helper 批量原子校验;move 加顶层目录 task 引用闸(维持"working_dir = 顶层目录"invariant),copy 无此闸。
- **working_dir 视为可重生 FS 视图**:DB source of truth,FS 目录可独立删 / 用户手动 rmtree / 跨机器迁移丢失,下次跑自动 mkdir 重建;DELETE task 后空目录 best-effort rmdir 清孤儿;files delete 顶层目录闸去掉。
### 2026-05-19
- **0006 模型切换(c 模式 task 级 A 粒度)+ usage_events v2 表**:`tasks.model_profile` 变 source-of-truth,顶栏下拉 PATCH 即换(A 粒度下条 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 处代码删 import / fallback;`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 路。
---
## 关键决策与偏差
| 项 | 决策 | 备注 |
|---|---|---|
| 工具基目录 | 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 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)。