Commit Graph

11 Commits

Author SHA1 Message Date
caoqianming 976ef45e87 core(POST /v1/tasks/{id}/messages): 同 task 单活 run 锁 + 启动 reaper
挡住"用户连点 send 两条 → 两个 BG 线程争 messages.idx UniqueConstraint
race"的旧 TODO。POST /messages 把所有权 + 活跃 Run 检查 + 新 Run INSERT
收进一个事务,首步 SELECT Task … FOR UPDATE 锁 task 行,命中 running 已
存在则 409。lifespan 加 stale-run reaper,把进程 crash 留下的孤儿 running
标 error,避免对应 task 被 409 永挂。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:27:48 +08:00
caoqianming 0c577ba0a5 core(GET /v1/tasks): 分页 + 多维筛选 + ordering 排序
- 响应壳固定 {page, page_size, count, results}
- 6 个 query 参数:page(1-based) / page_size(1-100 clamp) /
  status / skill / working_dir(末段名,后端拼前缀比对) /
  q(name + description ILIKE)
- ordering DRF 风格逗号分隔,-field 倒序;allowlist
  created_at/updated_at/name/status;非法字段静默丢弃;**默认 -created_at**
- 单次 COUNT + 单次 SELECT LIMIT/OFFSET,无 N+1
- dev SPA:task pane 三段头(status + 刷新 / q + working_dir / ordering),
  prev/next 翻页 + "from–to / count (第 P/L 页)" + 输入 debounce 300ms +
  默认 -created_at 不发到 URL(参数干净)
- DESIGN §7.2 / RUN 路由表 / PROGRESS 同步

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:26:29 +08:00
caoqianming 4a6aaaf34d core(0003): name + working_dir + skill schema 重构 + per-user .memory
- alembic 0003: TRUNCATE tasks CASCADE + task_dir→working_dir + mode→skill + 加 name TEXT NOT NULL
- name(必填,任务显示名,UI / docx 用)与 working_dir(可选,留空 fallback 用 name 作目录)解耦;
  同 working_dir 多 task 共享物理目录(§7.1)
- skill 字段对齐 skills/ 注册表语义,后续可下拉强校验
- POST /v1/tasks {name(req), working_dir?, description?, skill?};
  PATCH 支持改 name/skill;新增 GET /v1/folders(FS 列表 + n_tasks + last_used)
- DELETE /v1/tasks/{id} 硬删 DB(messages CASCADE)+ FS working_dir 保留;
  dev SPA 加 task delete 按钮 + file per-row 删按钮
- 工作目录改 eager mkdir(取代懒创建):用户给 name 即声明项目,目录立刻存在
- dev SPA modal 拆"任务名" + "工作目录"(<datalist> autocomplete 走 /v1/folders +
  输入实时提示"复用 / 新建 / fallback");renderTaskList 主行 = t.name,副行 = 📁 + skill + desc
- files 面板 UX:pane-head 显示项目名 + crumbs root 用项目名 + 修 root 处多渲 "." crumb 的 bug
- 顺手:memory 搬 workspace/users/<uid>/.memory/(per-user dotfile 隔离);
  CLI --mode → --skill,--name + --working-dir 分开
- DESIGN §3.1 / §3.6 / §7.2 / §7.4 + PROGRESS + RUN 全量同步

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:15:37 +08:00
caoqianming 02a69058df core(§7 D + D'): /v1 JSON API + PLATFORM_KEY→JWT auth + dev SPA
整合今日累积的 §7 D 阶段主体工作:

- §7 D /v1 JSON API:删 web/templates/* + web/static/style.css,
  web/app.py 重写为纯 /v1 JSON 路由(tasks CRUD + messages +
  SSE 事件 JSON 化 + files 4 路由 + export),CORS allow_origins
  起步 *,GET / 改 302 → dev SPA(详 DESIGN §7.9)。
- §7 D' 过渡 auth:web/auth.py 新增 — PLATFORM_KEY env(共享密钥)
  + JWT_SECRET env(HS256 签),POST /v1/auth/login 校验 key → 签
  JWT(默 7d TTL),所有 /v1/tasks* 走 Depends(require_user) 验签
  并按 user_id 隔离数据;豁免 /healthz、/docs、/openapi.json、
  /static/*、/v1/auth/login。env 双必填,缺则 fail-fast。
- dev SPA:web/static/dev.html ~600 行 vanilla JS 单文件,login
  overlay(user_id 默 sentinel + platform_key)+ 3 栏布局(task
  list + chat 流 + files 浏览)+ new-task modal + done/abandon/
  export。SSE 走 fetch+ReadableStream(EventSource 不支持 Bearer)。
- task_dir 改相对存储:新增 core/paths.py(to_db_path/from_db_path)
  + alembic 0002 migration 把 ROOT-内绝对路径转 posix 相对,跨 OS
  和混合分隔符历史数据天然兼容。check_no_subtask 改 Python 端归一
  比对,逻辑更清晰。
- litellm 启动 cost map 网络警告兜底:core/llm.py 在 import 前
  setdefault LITELLM_LOCAL_MODEL_COST_MAP=True,墙内冷启动 ~5s →
  <1s。
- docs:DESIGN §7.3 改写(过渡 auth + 真 OIDC 路线)+ §7.7 状态表
  + §7.9 dev SPA 取舍;PROGRESS 加多条今日条目 + 文件清单 + 下一
  步;RUN env 双 auth env + curl 示例 + 路由表 Auth 列 + 5 条故
  障兜底新条目。CLAUDE.md 加"开发期不写兼容层"心智。

Smoke 全绿:env fail-fast / 8 路径无 token 全 401 / login 3 分
支 / 带 token CRUD / 跨 user 4 case 隔离 / token 异常 4 case /
真实 HTTP uvicorn 端到端 login + bearer call + dev.html 服务。

requirements: 加 pyjwt>=2.8.0;删 jinja2 / markdown-it-py /
mdit-py-plugins / pygments(模板路线撤一并清);保留 python-
multipart(files upload 还用)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:14:25 +08:00
caoqianming 1035b12847 core(§7 Phase G G6/new): Web 端新建 task 入口
提前于 G5 落地 — 用户反馈 Web 没"开启新对话"的地方。

- GET /new 渲染 new_task.html 表单(description / mode / task_dir 三字段);
- POST /new:strip 校验 + description 与 task_dir 至少填一个否则 400 +
  check_no_subtask 同 CLI / build_agent 一致拦前缀嵌套 → 409 +
  ensure_local_task_row 写占位行 + 303 See Other 跳转 /tasks/{tid};
- task_dir 空 → 默认派生 workspace/tasks/<uuid>/(同 _default_task_dir),
  显式 → Path.expanduser().resolve() 同 cli.py --task-dir;
- 模板 new_task.html:三字段表单 + error 渲染(400/409 重渲带 form_state
  不丢用户填的值);home.html 加 + new task 主按钮;base.html 默认 nav
  也带 tasks/new 链接;
- CSS:.btn-primary 商务红主按钮 / .new-task-form 表单 + focus / .navlinks
  .active 当前页高亮 / .head-actions flex 容纳 filter + new 按钮;
- 懒创建保留语义:Web /new 入库占位,后续 build_agent 走 resume(已存在
  不冲突);CLI REPL 仍走 build_agent 懒创建路径,两路互不干扰。

Smoke 21 路径全绿:GET 表单 200 + 三字段 / POST happy(description-only
和 custom task_dir)→ 303 + Location 正确 / DB 行字段对 + default-derived
task_dir 含 uuid / 空+空 → 400 重渲表单带 error / no-subtask 父子嵌套 →
409 + 错误文案 / home 页 + new task 按钮 + nav 链接 / /new nav active 标记。

版本 0.4 → 0.5。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:51:35 +08:00
caoqianming 7356d25652 core(§7 Phase G G4): chat 发送 + SSE 流式回复
- web/broker.py RunBroker:in-process pub/sub,subscribe/emit/close/
  unsubscribe;同 run_id 多订阅者 fan-out(刷新 / 多 tab / 桌面+移动
  都能同时看流);_done 集合让晚到订阅者立刻收 done(不挂)。
- web/sinks.py WebEventSink:实现 §7 A sink 协议,把 AgentLoop._emit
  桥到 broker.emit(run_id, ev),AgentLoop 完全不知 web 存在。
- 异步策略 = asyncio.to_thread(不改 core):POST /tasks/{tid}/messages
  async handler → INSERT runs 行 + asyncio.create_task(to_thread(
  _run_agent_bg)),_run_agent_bg 工作线程跑 build_agent + agent.run,
  sink 通过 loop.call_soon_threadsafe 跨线程把 event 桥回 asyncio queue。
- GET /tasks/{tid}/runs/{rid}/events:StreamingResponse async gen,
  响应头 text/event-stream + Cache-Control: no-cache + X-Accel-
  Buffering: no(nginx 反代友好);第一帧 retry/connected 让 ES 立
  即建立,30s 无 event 发 : ping 心跳。SSE multi-line data 每行加
  data: 前缀(SSE spec),客户端 ES 自动还原 \n 拼接的 HTML。
- _render_event_fragment 渲染 text/tool_call/tool_result/error
  HTML 片段;run_start/llm_start/llm_end/done 发空 data(只让客户端
  识别 event type)。
- 新模板:_frag_text/_frag_tool_call/_frag_tool_result/_frag_error +
  _send_response(POST 响应:user msg 卡 + msg-assistant streaming
  容器带 sse-connect/sse-swap/sse-close)。
- chat.html 加 send 表单(Enter 发,Shift+Enter 换行,HTMX hx-post /
  hx-target=#chat-stream / hx-swap=beforeend / 提交后 reset);chat
  section 改 id=chat-stream;非 active task 隐藏表单。
- CSS:.streaming .run-indicator 红点脉冲 / .send-form 输入框 /
  .tool-result-inline 追加式样式 / .msg-error 错误卡。
- runs 表写状态:POST 时 status=running,正常完结 ok + tokens_p/c,
  异常 error + error 文本(DB 写失败不放大噪声,已 emit error 给前端)。
- lifespan bind_loop(asyncio.get_running_loop()) 让 broker 拿到
  loop 引用,emit 跨线程才能 call_soon_threadsafe。
- RUN 故障兜底加 3 条:SSE 经 nginx 卡住、浏览器 send 无反应、并发
  POST messages idx 冲突(已知 TODO)。

Smoke 双层全绿:
- broker 单元 8 case (subscribe/emit/get/fan-out/跨 run 隔离/close/
  late subscribe instant done/unsubscribe/未 bind silent drop)
- 端到端 24 case (POST 200 + sse-connect/run_id 抽取 + content-type/
  x-accel-buffering/cache-control 头对 + event 序列 run_start→done
  + text 片段 <strong> + tool_call <details> + tool_result preview
  + empty body 400 + 各种 404 + late done + runs 行 INSERT)

版本 0.3 → 0.4。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:19:25 +08:00
caoqianming 514d36c481 core(§7 Phase G G3): chat 只读页 + markdown + tool 折叠
- web/app.py 加 _get_md() 单例 MarkdownIt(gfm-like + linkify +
  breaks,html=False 禁内联 HTML 防 XSS),fenced code 走 pygments
  _pygments_highlight 回调(codehilite cssclass)。
- load_chat_messages(tid):PG idx asc 读 messages。
  build_chat_blocks(messages):system / tool 不入 block(tool 内嵌进
  assistant.tool_call.result),user / assistant text 走 md 渲染,
  orphan tool_call → [no result]。_args_preview 60 字截断,
  _pretty_json 解析失败 fallback 原串。
- /tasks/{id} 渲染 chat.html;删 task_placeholder.html。
- chat.html:.msg 卡片(user 浅蓝 / assistant 白底),tool_call 用
  <details> 默认折叠(无 JS,浏览器原生);summary 显示 tool 名 +
  args 前 60 字预览,展开看 args_pretty + result。
- CSS 加 .body 内 markdown 元素样式(table / blockquote / code / pre
  / strikethrough)+ .codehilite 浅色 token 配色(keyword/string/
  comment/function/number/operator,余下黑色)。
- requirements: markdown-it-py[linkify] / mdit-py-plugins / pygments。

Smoke 28 路径全绿(in-process Starlette TestClient):4 display
blocks aggregation + GFM 特性(table/fence/autolink/strikethrough/
bold)+ tool 配对(命中 + orphan [no result])+ HTML 含 <details>/
tool-badge/codehilite/<s> + 空 task 文案 + invalid UUID 404 + util
单测(args_preview/pretty_json/render_md 边界)。版本 0.2 → 0.3。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:38:31 +08:00
caoqianming 80a658eba4 core(§7 Phase G G2): task list 页 + /tasks/{id} 占位
- web/app.py 加 list_tasks(limit, status):PG tasks + messages count,
  updated_at 降序,返回模板友好 dict。Web 与 cli.py 数据形状不一致
  (CLI 用 tuple,Web 用 dict),不预付抽象,等真有 schema 同步成本
  再抽。
- / 路由换成 task 列表,支持 ?status=active|completed|abandoned
  filter(无效值静默降级 all)。/tasks/{task_id} 占位路由:UUID 解析
  失败 → 404,DB 不存在 → 404,有效则渲 task_placeholder.html(G3 来填
  消息流)。
- Linux portability:_norm_path() 显示前 replace('\','/') 把 Win
  存的 backslash 归一,Win Path.resolve()-str → "D:/..." 显示;Linux
  forward-slash 原路通过。Path.as_posix() 在 Linux 读 Win backslash
  串时不归一,所以选 replace 而非 as_posix。
- 模板 home.html 表格(id/updated/status/mode/model/msgs/tokens/desc-dir)
  + status badge 配色(active 绿 / completed 蓝 / abandoned 灰) +
  filter 表单 + 空态文案。task_placeholder.html 渲染 G3 提示。CSS
  tabular-nums 数字对齐 / hover 高亮 / accent-soft note。

Smoke 18 路径全绿(in-process Starlette TestClient):3 task seed
(active/completed/abandoned)+ Win\Linux 双路径形态 → / 渲染对、
status filter 正反向、garbage status 静默 all、UUID 占位、notauuid
404、ghost UUID 404、limit 生效、/healthz 不退化。版本 0.1 → 0.2。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:52:11 +08:00
caoqianming 91202b6172 core(§7 Phase G G1): Web UI 脚手架 + cli.py web 子命令
- web/ 新包:app.py FastAPI 工厂(/ + /healthz + /static),Jinja2
  base.html / home.html,minimal style.css。HTMX + HTMX-SSE 走 CDN
  (无 node 链路,与 §5 Less Scaffolding 一致)。
- cli.py 加 web --host --port --reload 子命令,默认 127.0.0.1:8765,
  本地形态 sentinel user 无 auth(Phase D 才上 OIDC)。
- requirements: fastapi / uvicorn[standard] / jinja2 / python-multipart
  (multipart 为 G5 文件上传留)。
- Starlette 新签名踩坑:TemplateResponse(request, name, context),
  旧式塞 context 里会让 jinja 用 dict 当 cache key 炸 unhashable,记
  RUN.md 故障兜底。
- Linux portability:模板 path 显示约定 .as_posix();SSE 头 G4 上时
  带 X-Accel-Buffering: no(nginx 反代友好)。`cli.py web` 在
  .venv/Scripts/python.exe(Win)/ .venv/bin/python(Linux)走同一路径。

Smoke 四路径(in-process via Starlette TestClient)全绿:/healthz →
"ok" / / → 1063B(title + static + version)/ /static/style.css →
1624B / /nonexistent → 404。`cli.py web --help` 子命令注册 OK。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:37:54 +08:00
caoqianming e8dbfa57a5 core(§7 B Step 6): no-subtask 前缀嵌套校验
- core/storage/utils.py 加 check_no_subtask + NoSubtaskError;PG LIKE
  双向(new LIKE existing/%  OR  existing LIKE new/%),同 task_dir
  允许(同项目多对话),空 / whitespace 跳过。
- 分隔符容差:SQL replace(task_dir, '\', '/') 把存的 Windows 反斜杠
  与新值统一到 '/' 再比;backslash 通过 bind 参数传,绕开 SQL 转义。
- main.py::build_agent 在 resolve_task_id 后、TaskState 构造前调,
  if not resume 单层闸 —— resume 跳过(改名走未来 Folder API cascade).
- cli.py 三处 build_agent 调用现有 try/except 直接接住 NoSubtaskError.
- PROGRESS / RUN 同步:Step 6 完工,故障兜底加一条 NoSubtaskError 处理.

Smoke(9 路径 + e2e 3 分支)全绿。§7 B 完工(Step 5 取消)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 11:59:37 +08:00
caoqianming 2b3692c8bf core(§7 B Step 4): --task-dir 双形态 + RUN.md 运行手册
- CLI `chat --task-dir <path>` 让用户显式指定项目目录(§7.1 task-primary +
  dir 副视图心智模型落地);留空走默认派生 workspace/tasks/<uuid>/。
- main.py::resolve_task_id 加 task_dir_arg;resume 时从 PG tasks.task_dir
  读,空则降级默认派生。新增 is_managed_task_dir(td, ws) 判断 task_dir
  是否在默认模板下。
- cli.py::_cleanup_if_empty 拿 workspace_dir 作保护开关 —— 用户自指定的
  task_dir 绝不 rmtree(可能含用户已有素材);DB 行该删还是删。
- core/export_docx.py::export_chat_to_docx 重构:task_id 升一等参数(从
  task_dir.name 提取改入参传入),task_dir 留空时自动从 PG 读;cli /export
  与 cli.py export 子命令均改走 _resolve_uuid_or_prefix + task_id 直传。
- 新建 RUN.md(运行手册):env / 初始化 / 日常命令 / 故障兜底 / 关键路径。
- CLAUDE.md 加 RUN.md 维护规则(三文档边界:DESIGN=为什么 / PROGRESS=做到哪
  / RUN=怎么跑),对外行为改动同步更 RUN。

Smoke 4 路径:default-derived(managed=True, cleanup rmtree)/ --task-dir
(managed=False, FS preserved)/ resume reads DB task_dir / export 自动 PG
查路径,全绿。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 11:38:05 +08:00