zcbot/RUN.md

148 lines
7.2 KiB
Markdown

# 运行手册
> 怎么把 zcbot 跑起来。env / 常用命令 / 故障兜底。设计看 `DESIGN.md`,进度看 `PROGRESS.md`。
最后更新:2026-05-15(Phase G G4)
---
## 环境
- **Python**:虚拟环境 `.venv/`,所有依赖装在里面。一律用 `.venv/Scripts/python.exe ...`(Windows)/`.venv/bin/python ...`(Unix),不要全局 `python`(litellm/python-pptx 等会 ModuleNotFoundError)。
- **配置文件 `.env`**(项目根,git 忽略,litellm 自动加载):
```
DEEPSEEK_API_KEY=sk-...
ZCBOT_DB_URL=postgresql://user:pass@host:5432/zcbot
```
> litellm 在 import 时副作用加载 .env;CLI 入口直接走 `cli.py`,`.env` 会自动生效。直跑 `python -c "from core.storage import ..."` 不经 litellm 链路时记得自己 `import litellm` 触发,或手动 `export ZCBOT_DB_URL=...`。
- **依赖**:`pip install -r requirements.txt`(已在 `.venv` 里)。
- **PG**:`ZCBOT_DB_URL` 必填。本地 docker compose 起 / 远端 dev / 生产任选。未设置时启动会清晰报错,不引导 docker(§7.4)。
---
## 一次性初始化
```bash
# 1) 装依赖(若 .venv 不在)
python -m venv .venv
.venv/Scripts/python.exe -m pip install -r requirements.txt
# 2) 准备 .env(见上)
# 3) DB schema 上车
.venv/Scripts/python.exe cli.py db upgrade head
.venv/Scripts/python.exe cli.py db current # 应输出 0001 (head)
```
---
## 日常命令
### 聊天 / 任务
```bash
# 新建 task,默认派生 workspace/tasks/<uuid>/
.venv/Scripts/python.exe cli.py chat
# 带模式 + 描述(便于后续 list 识别)
.venv/Scripts/python.exe cli.py chat --mode coding --desc "修 X 的 Y"
# 项目化 task —— 产物落到指定目录(§7.1 task-primary + dir 副视图)
.venv/Scripts/python.exe cli.py chat --task-dir /path/to/proj --mode proposal
# 恢复最近一个 task
.venv/Scripts/python.exe cli.py chat --resume last
# 恢复指定 task(UUID 完整或 ≥8 字符前缀)
.venv/Scripts/python.exe cli.py chat --resume 76c6bd25
# 切模型
.venv/Scripts/python.exe cli.py chat --model deepseek_v4.pro
```
REPL 内命令:`/exit /reset /new /resume [last|<id>] /id /status /done /abandon /desc <文本> /export [<id>]`
### 列表 / 导出
```bash
# 看最近 20 个 task
.venv/Scripts/python.exe cli.py tasks
# 只看 active
.venv/Scripts/python.exe cli.py tasks --status active --limit 50
# 导出某 task 的对话为 .docx(自动从 PG 找 task_dir 作为输出目录)
.venv/Scripts/python.exe cli.py export 76c6bd25
# 导出最近的
.venv/Scripts/python.exe cli.py export last -o /tmp/chat.docx
```
### 能力探测 / DB 管理
```bash
# 实测对账模型 yaml 声称的能力(费 token,有 API 开销)
.venv/Scripts/python.exe cli.py probe --model deepseek_v4.flash
# DB migration
.venv/Scripts/python.exe cli.py db upgrade head
.venv/Scripts/python.exe cli.py db downgrade -1
.venv/Scripts/python.exe cli.py db current
```
### Web UI(§7 Phase G,本地 sentinel user 无 auth)
```bash
# 默认 127.0.0.1:8765 启,浏览器开 http://127.0.0.1:8765
.venv/Scripts/python.exe cli.py web
# 自定义端口 / 监听 0.0.0.0(配合 firewall 慎用,本地形态无 auth)
.venv/Scripts/python.exe cli.py web --port 9000
# dev:文件改动自动重启(uvicorn 工厂模式 reload)
.venv/Scripts/python.exe cli.py web --reload
```
> G1 ✅ 脚手架 + /healthz;G2 ✅ `/` task 列表 + `?status=` filter;G3 ✅ `/tasks/{uuid}` 消息流渲染(markdown-it-py + pygments syntax,tool_call 走 `<details>` 默认折叠);G4 ✅ chat 发送 + SSE 流式回复(POST `/tasks/{tid}/messages` 启 run、GET `/tasks/{tid}/runs/{rid}/events` SSE 流;HTMX `sse-swap` 追加 DOM,无 JS);G5-G6 待。task_dir 显示统一 forward-slash(Win 存 `\` 也归一)。Linux:`.venv/bin/python cli.py web` 一致。SSE 经 nginx 反代记得关 buffering(响应头已带 `X-Accel-Buffering: no` 默认起效)。
---
## 故障兜底
| 现象 | 原因 / 处理 |
|---|---|
| `ZCBOT_DB_URL is not set` | `.env` 没写 / litellm 链路没触发。直跑脚本时 `import litellm`,或 `export ZCBOT_DB_URL=...` |
| `ModuleNotFoundError: litellm` | 用了全局 `python`,改 `.venv/Scripts/python.exe ...` |
| Windows 控制台 emoji 崩 | Python stdout 是 GBK,emoji 不能直 print。用 `[OK]` / `[ng]` 等 ASCII 标签(见 memory) |
| `db upgrade``column already exists` | DB 已被改过,先 `db current` 确认 revision,必要时手 ALTER 或 `db downgrade base` 重来 |
| Resume 找不到 task | `cli.py tasks` 看 task_id 是否在;前缀冲突报 ambiguous 时给完整 UUID |
| `--task-dir` 指定后 `/exit` 没清 task_dir | 设计如此 —— 用户路径绝不 rmtree;DB 行该删还是删。要彻底删手动 `rm -rf <dir>` |
| Export 报 "无可导出内容" | task 没 messages(只 system 不算);先在 REPL 发条消息再 export |
| `NoSubtaskError: task_dir ... 与已有 task ... 前缀嵌套` | §7.4 no-subtask:同 user 不允许 task_dir 嵌套(child 或 parent)。**同项目多对话**请传**完全相同**的 `--task-dir`;否则改路径成 sibling(平级) |
| `cli.py web` 启动后浏览器开不了 | 检查 proxy(`HTTP_PROXY` / `HTTPS_PROXY`):本地形态服务在 127.0.0.1,系统 proxy 拦截会 502。临时 `unset HTTP_PROXY HTTPS_PROXY` 或浏览器配 bypass。`curl` 验通走 `curl --noproxy '*' http://127.0.0.1:8765/healthz`(应返 `ok`) |
| `TypeError: unhashable type: 'dict'` from Jinja templating | Starlette 新版签名:`TemplateResponse(request, name, context)`,旧式 `(name, {"request":..., "...":...})` 在 newer Starlette 会把 context dict 当 cache key 炸 |
| SSE 卡住不流(经 nginx) | 反代要关 buffering — 后端响应头已带 `X-Accel-Buffering: no`,nginx ≥ 1.5.6 默认认。仍卡看 nginx 配 `proxy_buffering off; proxy_read_timeout 3600s;` |
| 浏览器 send 后没反应 | 看 console:HTMX 报 connect failed → 看 `/tasks/{tid}/messages` 响应;200 但流不到 → 看 EventSource 状态(devtools Network → EventStream tab) |
| `UniqueViolation idx already exists` from messages | 同 task 连续两次快速 POST,messages idx 冲突。**已知 TODO**:G6/D 阶段加 task 级 lock_for_update 或 advisory lock |
---
## 关键路径与文件
- **入口**:`cli.py`(REPL + `chat / tasks / probe / db / web` 子命令)→ `main.py::build_agent`(装配)
- **核心**:`core/loop.py`(ReAct)/ `core/session.py`(PG messages)/ `core/task.py`(PG tasks)/ `core/llm.py`(LiteLLM 封装)
- **工具**:`tools/{fs,shell,run_python,skill_tool}.py`
- **存储**:`core/storage/{engine,models,utils}.py`(SQLAlchemy 2.x ORM)+ `db/migrations/`(alembic)
- **Web**:`web/{app.py, templates/, static/}`(§7 Phase G,FastAPI + Jinja2 + HTMX)
- **配置**:`config/agent.yaml`(全局)/ `config/models/*.yaml`(模型档案,§3.2 Model Profile)
- **Skill**:`skills/{coding,ppt,proposal}/SKILL.md`(渐进披露,§3.5)
- **Workspace**:`workspace/memory/{core.md, extended/}`(跨 task 记忆,FS 永久)/ `workspace/tasks/<uuid>/`(默认派生 task_dir,只放 skill 产物)
---
## 维护约定
- **每改一个对外行为(CLI 选项 / REPL 命令 / env 变量 / 文件布局)→ 同步更新本文档**。bug 修不动这个,只动 PROGRESS。
- 故障兜底表新增条目:用过一次的真实坑,写一行(现象 + 处理),不预测。
- 跟 DESIGN/PROGRESS 的边界:DESIGN 写"为什么",PROGRESS 写"做到哪",RUN 写"怎么跑"。