caoqianming
972f36db20
fix(loop): tool message append 补 name 字段 + backfill 历史 — 修历史 task 重开后 seedream banner/chip 不展示
...
session.append 的 tool 消息只存 role/tool_call_id/content,没 name;前端历史渲染依赖 payload.name 判断产物工具白名单 + 抽 elapsed banner,刷新后两者全黑(流式正常因为 SSE event 单独带 name)。scripts/backfill_tool_message_name.py 按 task 走 assistant.tool_calls 建 tool_call_id→name map 回填,dry-run 默认,--apply 真写,幂等。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 08:14:23 +08:00
caoqianming
57ac7214e5
refactor(paths): 砍 ROOT 外路径分支 — 写入入口只接 simple name join workspace
...
to_db_path 越界 raise(原 str(pp) 静默存绝对),from_db_path 删 is_absolute 分支只 ROOT/s。ROOT 外分支是防御性死代码:写入入口(POST /v1/tasks → working_dir_from_name)只接 simple name,0002 migration 已清理历史绝对串。DESIGN §7.4 注释同步。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:05:32 +08:00
caoqianming
8c9e0d0d3a
api+ui(media): 顶栏生图模型下拉(消息级,不入 DB) + 中间产物图片/视频内联展示
...
- 新加 GET /v1/image_models(扫 config/media/doubao.yaml image 段)
- POST /v1/tasks/{id}/messages body 加可选 image_model 字段,_run_agent_bg
透传到 build_agent(image_variant=...);agent_builder 据此装配 SeedreamTool
variant,不命中 yaml 静默回 fallback,空 → 沿用第一个
- dev SPA:顶栏「模型」旁加「生图」下拉(默认锁第一个 variant,per-session
state 不持久),sendMessage 携 image_model 一起发
- 中间产物 chip 按文件类型分支:图片/视频走 .art-media 异步 fetch blob →
填 <img>/<video controls>(Bearer header 不允许 <img src=> 直 URL);
图片点击仍弹模态放大,视频用浏览器原生 controls;openFilePreview 加
_showVideo + .mp4/.webm/.mov/.mkv/.m4v 进 _EXT_GROUPS;_mediaArtifactCache
按 rel 复用,切 task 时 revoke
- DESIGN 不动(无架构 / schema 变化);PROGRESS / RUN 同步
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 16:18:36 +08:00
caoqianming
a3acb97079
feat(loop+ui): LLM 调用切 streaming — cancel 秒退 + 前端打字机 + 发送/停止合并单按钮
...
- core/llm.py: 加 chat_stream() generator(stream=True + include_usage),
generator finally 关底层 httpx 连接;_build_kwargs 抽出来 chat/chat_stream 共用
- core/loop.py: 主循环 _stream_llm() 流式迭代,chunk 间 poll cancel 命中 break,
litellm.stream_chunk_builder 拼回 response 给 tool_calls 解析 + usage 记账;
content delta 即时 emit text 事件激活前端打字机渲染
- web/static/dev.html: chat-send + chat-cancel 合并 chat-action 单按钮,
setActionMode(idle/streaming/cancelling) 切态;streaming 期间 Enter 不触发停止
- cancel 延迟从「整轮 generation 时长」(几十秒)降到「单 chunk 间隔」(100ms 级)
- 文档:DESIGN §3.1 + API 表 + risks 表翻转 tradeoff;RUN 接口 + 故障兜底同步;
web/app.py docstring 对齐;PROGRESS 加条目 + 文件清单行数
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:46:54 +08:00
caoqianming
c04b8ba05e
feat(media): 接入豆包 Seedream 5.0 图像生成 tool + 0007 cost_usd→cost_cny 全表统一币种
...
- 新 tools/seedream.py:调 ark /images/generations 同步生成,产物落 figures/<ts>-<rand>.png + 同名 .meta.json
- 新 core/ark_client.py:火山方舟 HTTP 封装(base URL + bearer auth + 异常翻译 + download),共享给后续 seedance
- 新 config/media/doubao.yaml:独立命名空间;价格表注释 last_updated + 调价路径说明
- core/storage/usage.py 加 record_image_usage:单价 snapshot 进 units jsonb,防调价污染历史
- agent_builder.py 注册 SeedreamTool:仅当 ARK_API_KEY 设了才挂(无 key 用户无感)
- 0007 migration:tasks/usage_events 双 rename cost_usd → cost_cny,×7.2 一次性折算;
record_chat_usage 内部把 litellm USD 同样 ×7.2 落 CNY,免分类汇总
- prompts/system/general_v1.md 加「媒体生成工具」段,提示按需调用、不主动装饰
- dev SPA tool_result 折叠态显示 banner(model/size/cost/elapsed 徽章),不展开就透明
- scripts/smoke_seedream.py:端到端走通(待 ARK_API_KEY 配齐真跑会产生 ~¥0.22)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:20:34 +08:00
caoqianming
5ff09b9aca
fs tools 输出 user_root-relative 路径 + dev SPA chip 锚点修正 + assistant 正文也挂 chip
...
- tools/base.Tool: 加 user_root kwarg + _display(p) helper(p 在 user_root 内 → POSIX 相对,外 → 原绝对)
- tools/fs.py: Read/Write/Edit/Glob/Grep 所有结果串里路径都过 _display,不再泄 user_id / 部署根
- core/agent_builder: build_agent 把 user_root 透传给所有 tool(含 ShellTool / RunPythonTool / LoadSkillTool — base 默认 None 不影响)
- tools/skill_tool: __init__ 加 user_root 转传 super
- web/static/dev.html: 新加 _workingDirName helper(从 db 形 working_dir 取末段 + 跳过外部绝对路径);5 个 chip 抽取点统一用它代替原 working_dir 直取 → 根治 chip 点击 404;assistant 正文也接 chip 抽取
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:22:20 +08:00
caoqianming
c4fac2428b
skills+core(命名约定): task 级宪法文件 <date>-<short_id>-<name>.spec.md + spec_lock → spec 简化
...
同 working_dir 多 task 共享中间产物是设计意图(素材跨本子复用),
但 spec 这种 task 1:1 宪法文件必须隔离 — 否则两本子 spec 直接撞。
文件名三段式:
- task_short_id (task_id.hex[:8],永不变) 主锚 → glob *-<short_id>-*.spec.md 字典序最大 = current
- date 让"重定调"写新文件而非 edit 覆盖,旧版自然成历史快照
- task_name 作建时元数据,改 task.name 不 cascade(由 short_id 兜底定位)
约定由 core/agent_builder.py::_build_system_prompt 单点注入
(task_id / today 实际值嵌入,所有 skill SKILL.md 引用同一份)。
proposal / ppt SKILL.md 阶段一加"glob 检测已有 spec → 询问沿用/重定调"分支。
模板 templates/spec_lock.md → spec.md (git mv 保历史),_lock 后缀无信息量去掉。
未动:DB schema / PATCH /v1/tasks/{id} 改 name 入口 / 其他中间产物扁平共享
/ quality_check.py (--spec 接路径)。反方案(cascade rename / spec 入 PG /
物理 task 子目录)及"何时升级到 DB 化"信号见 DESIGN §7.9。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:03:21 +08:00
caoqianming
7925dcef54
files: working_dir 视为可重生 FS 视图(DELETE task 顺手清空孤儿 + delete_file 去 task-ref 闸)
...
DB 是 source of truth,FS working_dir 可独立删 / 用户手删 / 跨机器迁移丢失,
下次 build_agent 自动 mkdir 重建。三处改:
- core/agent_builder.py: working_dir.mkdir(exist_ok=True) 从 if not resume:
里挪出,resume 也兜底建目录
- web/app.py DELETE /v1/tasks/{id}: 删完后若同 user 无其他 task 引用 +
FS 空 + ROOT 内相对路径 → best-effort rmdir 清孤儿;外部 --working-dir
(DB 绝对串)静默跳过
- web/app.py POST /v1/files/delete: 顶层目录去掉"有 task 引用 → 409"闸,
允许独立删空目录,task.working_dir 字段不动
smoke case 4 改 200 + working_dir 不变;新增 case 8(空目录自动清)/
case 9(非空保留),全 9 pass。PROGRESS / RUN 跟着更。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 08:10:36 +08:00
caoqianming
781a216ca6
model: 同 task 内切模型(c 模式 task 级 / A 粒度)+ usage_events v2 表(0006); GET /v1/models; 前端顶栏下拉 + 历史 model 切换点小标
...
- DB(0006): messages 加 model_profile 列(assistant 行有值); 重建 usage_events 表 v2 形态(event_id/user_id/task_id/message_id/kind/model_profile/units jsonb/cost_usd + 三索引), 0004 删的旧 schema 字段不够多态; tasks.tokens_prompt/completion/cost_usd 保留作粗概览
- ModelCapabilities 加 display_name; deepseek_v4.yaml flash/pro 各填名
- GET /v1/models: 扫 config/models/*.yaml 列可选项(profile/display_name/family/thinking_mode/is_default); POST /v1/tasks + PATCH 接受 model_profile(不传 → cfg["default_model"]; 校验走 ModelCapabilities.load 失败 400)
- build_agent: resume 时优先 task.model_profile 而非 cfg default; AgentLoop 加 user_id 透传, 每轮 assistant 入库后调 record_chat_usage(litellm cost map 算钱, 失败吞掉 emit warn 不阻 loop)
- core/storage/usage.py 新文件: record_chat_usage(双写 messages.tokens_in/out + model_profile + insert usage_events 一行)
- session.append() 返回 message_id(供 usage 关联)
- 前端 dev.html: chat-meta 加模型下拉(切了 PATCH + running 中提示"跑完后生效"); 新建对话框 modal 加 nt-model select; renderMessages 按 model_profile 切换点画小标 "── DeepSeek V4 Pro ──"
- CLAUDE.md: 加"开发测试期 / 不删现有数据 / DROP COLUMN 两种情况"规则
- DESIGN §7.4 schema 加 messages.model_profile + usage_events v2 段; PROGRESS 加 0006 条目 + 文件清单
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:43:13 +08:00
caoqianming
2baed6894b
auth(dev SPA): 邀请码撤回 邮箱+密码 (users.email/password_hash bcrypt; 0005 加 UNIQUE; user add CLI; 登录两 tab)
...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 13:58:48 +08:00
caoqianming
53f59eb78a
auth(dev SPA): 邀请码登录(invites 表 0005) + SENTINEL user 彻底撤
...
- 新增 POST /v1/auth/login_invite {token}: dev SPA 给同事试用,token → name → uuid5(NS, name) 推导 user_id;原 /v1/auth/login 保留为 platform 机器对机器入口
- 0005 migration 新表 invites(token PK / name UNIQUE / created_at);先用 ZCBOT_INVITES env 试了一版,讨论后升级到 DB 表 — schema 极薄,不入 user_id (uuid5 推导),不入 revoked_at (DELETE 即撤销);管理直接 SQL,后期可加 main.py invite CLI
- web/auth.py: 删 _parse_invites / AuthConfig.invites / env 读取;新模块函数 resolve_invite(token) 每次 SELECT,无缓存避免 DELETE 后还能登
- SENTINEL_USER_ID 常量 + ensure_local_sentinel 函数 + agent_builder fallback 全删 (CLI 撤后无 caller);storage/utils.py 三函数 user_id 改必填;TaskState 加 user_id 字段;build_agent user_id 改 KEYWORD_ONLY 必填;session.py 删多余 ensure_local_task_row (task 行 web 入口已 INSERT)
- DB 清: SENTINEL 行 + 5 个 dev task + 307 messages + workspace/users/00000000.../ 全删
- dev.html: 登录页 2 格 (uuid+key) → 1 格邀请码,header 显示 name·uuid 前 8 位
- 文档全套同步: RUN/DESIGN/PROGRESS
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 13:14:31 +08:00
caoqianming
9aa2efc335
core(/v1/files): 加 rename + delete 顶层加 task 引用闸
...
- POST /v1/files/rename:任意深度;path 是顶层目录则 DB-aware
(FOR UPDATE 锁 task / 活跃 run 互锁 / check_no_subtask exclude /
UPDATE working_dir 先于 FS rename,FS 失败回滚)
- POST /v1/files/delete:顶层目录 + 有 task 引用 → 409,杜绝悬空
- check_no_subtask 加 exclude_task_ids,rename 平移自己不误判嵌套
- dev SPA:file row 加改名按钮,顶层改名后刷任务列表 + 当前 task header
- smoke 7 case 全绿(scripts/smoke_files_rename.py)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:06:21 +08:00
caoqianming
0d127a7261
core(入口归位): cli.py→main.py, 原 main.py→core/agent_builder.py, 删 REPL
...
按 §5 Less Scaffolding + SoC 把混三角色的 main.py 拆开:入口归位到 main.py,
装配 lib 归位到 core/agent_builder.py。dev SPA 落地后 CLI REPL(chat/tasks/
export)与 web /v1 等价,维护双套 task 切换语义只是"对称美",一并撤(§7 E
CLI 双模式路线同样撤)。
- cli.py → main.py(入口,只剩 web/db/probe 三 click 命令组)
- 原 main.py → core/agent_builder.py(build_agent / system prompt /
validate_task_name 装配 lib;顺手删死代码 _resolve_uuid_or_prefix +
resume "last" 分支)
- 删 chat/tasks/export 三 REPL 命令 + _cleanup_if_empty / _list_task_rows
等 CLI-only helpers ~400 行
- web/app.py 5 处 from main import → from core.agent_builder import
- DESIGN §1/§2/§3.3/§3.6/§7.0/§7.6/§7.7/§7.8/§7.9 + RUN + PROGRESS 全套同步
- Smoke 6 case 全绿(in-process TestClient + 子进程 python main.py db current)
- 净减 486 行
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 14:10:59 +08:00
caoqianming
2e519ab8a6
core(0004): 删 runs / usage_events 表 + cancel/SSE 改 task-level
...
usage_events 全代码库零引用,纯死代码;runs 表实质就是"task 当前 in-flight 状态"
影子表,tokens_p/c 写但从未被读,run_id 唯二实用是 broker 频道键 + cancel 参数 —
单活 run 形态下完全冗余,客户端只需 task_id。按"开发期不写兼容层"心智一把切干净。
- alembic 0004:DROP runs / usage_events,tasks 加 run_status (idle/running/cancelling/error) +
run_error 两列;ok/cancelled 终态都回 idle 不留持久标记,只有 error 持久
- ORM 删 Run / UsageEvent class
- Broker 全 task_id 索引,加 start(tid) 在新 run 前清 _done
- /v1/tasks/{tid}/runs/{rid}/{events,cancel} → /v1/tasks/{tid}/{events,cancel}
- POST /messages 返 {events_url} 去掉 run_id
- dev SPA: state.currentRunId → state.streaming(bool),路径去掉 /runs/{rid}/
- Smoke 18 case 全绿
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 11:05:35 +08:00
caoqianming
bf41631437
core(run cancel): POST /runs/{rid}/cancel + AgentLoop 协作式 cancel + dev SPA stop 按钮
...
落地 DESIGN §7.2 原标"待"的 cancel 路由 — 等待回复 / LLM 操作时也能中断。
- broker 加 request_cancel / is_cancelled / clear_cancel(per-run threading.Event)
- AgentLoop 加 cancel_check 回调,每轮 LLM 前 + tool_calls 之间 poll;命中给
未执行 tool_call 补 [cancelled by user] tool result 保 LiteLLM 协议,emit
cancelled event
- 单活 gate + 启动 reaper 扩到 running | cancelling
- BG 装 cancel_check + 终态判 cancelled/ok + finally clear flag
- dev SPA stop 按钮 + cancelled badge + fetchSse try/finally 失败路径复原 UI
LLM 同步 call 本身不可中断,最坏等当前一轮跑完(通常几十秒)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:42:45 +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
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
caoqianming
aeecc7f0f3
core(§7 B Step 3): TaskState ORM + Web UI 设计 (Phase G)
...
- TaskState dataclass 改 PG-backed:save() → upsert_task (INSERT ON CONFLICT
DO UPDATE,显式刷 updated_at);load(task_id) → SELECT。state.json 全面
废除,task_dir 只承担 skill 产物。
- TaskState 字段去 cwd / 加 task_dir(对齐 §7 SaaS task_dir-as-identity);
cwd 只在 session.meta 内存视图保留(展示用)。
- core/storage/utils.py 新增 upsert_task / update_task;ORM-level UPDATE
自带 onupdate=func.now(),DO UPDATE 需显式 set。
- session.py Session.append 的 ensure 调用补传 mode/description/
reasoning_effort,避免首次 INSERT 后 _list_task_rows 看到空 meta。
- sync_task_tokens 改成 update_task 单字段 UPDATE,避免无谓全字段 UPSERT。
- cli.py _list_task_rows 全字段从 PG 读,status 过滤走 SQL WHERE;
_cleanup_if_empty 去 state.json 特例(任何 FS 文件/子目录都算实质痕迹)。
- core/export_docx.py meta 走 TaskState.load(tid),CWD 字段从 meta 表移除。
- DESIGN.md 追加 Phase G(Web UI 简洁版,FastAPI + Jinja2 + HTMX + SSE),
排在 §7.7 D 后;§7.9 补 server-render 不上 SPA 的取舍 4 条。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 11:25:53 +08:00
caoqianming
4f87bf14ee
core(§7 B Step 2): Session ORM — messages 走 PG, task_id 切 UUID
...
Session 重写
- messages 落 PG `messages` 表(append-only, idx 严格递增, jsonb payload)
- system prompt 不入库(每次 build_agent 重建到 messages[0],memory 演化即时生效)
- Session.load(task_id, system_prompt=...) 从 DB 读历史
- Session.task_exists / n_user_msgs 工具
Storage utils
- ensure_local_task_row: 首条消息前 INSERT ... ON CONFLICT DO NOTHING
打底 tasks 行(Step 3 后由 TaskState.save 接管字段更新)
task_id 切 UUID
- resolve_task_id(workspace, arg, resume): UUID + 前缀匹配,'last' 从 PG
按 updated_at 取最近
- 显示一律截前 8 位;完整 UUID 在 /id /status 保留
- 旧 workspace 老 task(时间戳格式)**不做兼容**
CLI 适配
- _cleanup_if_empty 双检查:DB messages count + FS 产物
- _list_task_rows: PG tasks ORDER BY updated_at + state.json 兜底字段
- _task_has_messages: /export 检查改 DB
- core/export_docx.py: messages 从 PG 读,state.json 留作 meta
Step 5 (migrate-from-fs) 取消。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:55:50 +08:00
caoqianming
425ea59937
core(§7 B Step 1): Storage 基建 — SQLAlchemy ORM + alembic + db CLI
...
- core/storage/{engine,models}.py: 5 表 ORM(users/tasks/messages/runs/
usage_events)+ session_scope 上下文 + 本地 sentinel user 初始化
- alembic 初版 migration 0001_initial_schema: messages.payload GIN
索引 + tasks (user_id, task_dir) 复合索引 + pgcrypto 扩展兜底
- cli.py: db upgrade/downgrade/current 子命令组;ZCBOT_DB_URL 未设
给 ASCII 报错 + exit 2(避开 Windows GBK 控制台编码问题)
- requirements: +sqlalchemy>=2.0 +psycopg[binary]>=3.1 +alembic>=1.13
- DB URL 来自环境变量 ZCBOT_DB_URL,不引导 docker(用户给测试库地址)
已在远端测试 PG 跑通 db upgrade head + db current。Session/TaskState
ORM 接入留 Step 2-3。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:41:44 +08:00
caoqianming
e4a48fbb53
core: Session/TaskState 原子写 + Phase 6 双层记忆
...
- core.session.atomic_write_text (tmp + fsync + os.replace) 接管 Session/
TaskState 落盘, 中途异常不留 0 字节; _cleanup_if_empty 放过 *.tmp 孤儿
- core/memory.py: workspace/memory/{core.md, extended/} 双层记忆.
core.md 注 system prompt, extended/*.md 索引(标题+绝对路径)注 prompt,
内容靠 read 工具按需拉
- _build_system_prompt 从 build_agent 里提出来, new 和 resume 都走同一段,
resume 时覆盖 messages[0] -> memory 演化即时生效
- PROGRESS/DESIGN 同步: §7 platform track 行 + A 阶段完成 + 双层记忆/原子写
+ 文件清单到 2429 行
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:13:56 +08:00
caoqianming
375bb2999c
core: loop 事件流化 (sink 接口, §7 A 阶段)
...
loop 不直接 console.print —— 改成 sink.emit({type, ...}),sink 决定怎么呈现。
新增 ConsoleEventSink 接管 spinner / [in N out N] / assistant 文本 / tool>(args) / 结果预览,
CLI 行为零回归。后续接 SSE 时只换 sink 实现,loop 不动。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:59:32 +08:00
caoqianming
b86c051290
cli+core: 加 task 对话导出为 .docx
...
- core/export_docx.py: 渲染 messages.json 为对话稿,左排小字 + 角色配色
(user/assistant/tool/tool_call/reasoning),meta 信息表置文档开头,tool
结果默认前 1000 + 中间省略 + 后 500
- cli.py: 加 `export <task_id>` 子命令(支持 last / -o / --include-system /
--no-reasoning / --tool-head / --tool-tail)与 REPL `/export [<id>]`
- 默认跳过 system prompt(信息密度低),默认带 reasoning_content(观察价值高)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:42:45 +08:00
caoqianming
72d2b64c40
core/ui: 抽出语义化 console 主题, 调用方去硬编码颜色
...
新增 core/ui.py 集中定义 Rich Theme:
- 语义样式名: user / assistant / tool / ok / warn / err / info / muted / accent
- 在黑底终端上 readable, 弱化用 grey 而非 dim, 强调走 bright_*
- make_console() 统一应用主题, 以后改主题只动这一处
cli.py / main.py / core/loop.py 把内联的 [red] [green] [blue] [yellow]
[cyan] [dim] 等替换为语义样式; 调用 make_console() 取代 Console()。
2026-05-07 16:10:11 +08:00
caoqianming
61ff98bb79
loop: thinking spinner 显示耗时 + 累计 token,每轮回打成本一行
...
spinner 文本由后台 daemon 线程每 100ms 用 status.update() 刷新,显示
'thinking... 1.3s ctx 12,345 tok'。每轮 LLM 返回后追加一行 dim
'[in N out N t Xs]',留痕便于回看本轮成本。全 ASCII。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:05:44 +08:00
caoqianming
80f9934f8f
loop: 用 rich.Markdown 渲染 assistant 输出
...
之前 console.print(f"...{content}") 只解析 [tag] 标签,不识别 **bold**、表格、列表
等 markdown 语法,LLM 回复会以原文显示。改成单独打印前缀后再 console.print(Markdown(content))。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:19:46 +08:00
caoqianming
dbb778fe10
Phase 4 + 6: capability probe + task 概念 / state.json
...
- core/probe.py + cli.py probe: basic_chat / parallel_tools /
thinking_mode / long_context 四项实测对账 yaml;不进启动路径
- core/task.py + main.py: workspace/tasks/<id>/{state.json, messages.json},
TaskState 跟 mode/desc/status/tokens/timestamps;build_agent 返 5 元组
- cli.py tasks 子命令 + REPL /status /done /abandon /desc;chat 加
--mode/--desc 选项;移除 legacy workspace/sessions/ 兼容
Phase 5 evals 评估后决定不做:个人工具用 dogfooding 判断模型升级,
probe 已覆盖健康检查;造作 case 没区分度,维护成本不划算。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:21:17 +08:00
caoqianming
3a66849953
Initial import: zcbot personal task agent
...
DESIGN.md / PROGRESS.md 落地 Phase 1-3:
- core/: LiteLLM 封装 + ReAct loop + 会话持久化 + Anthropic skill registry
- tools/: read/write/edit/glob/grep/shell/run_python/load_skill (Hybrid 范式)
- skills/coding | proposal: WHY+WHAT 风格 SKILL.md
- skills/ppt: 完整渐进披露 (SKILL + 4 references + 3 scripts)
· 借鉴 hugohe3/ppt-master 的两阶段 + spec lock 思路
· MSO_SHAPE 图标体系 + 安全区 + 越界检测
· 默认商务红主题 (#C00000 / #E15554 / #FFC107)
- config/models/: DeepSeek V4 flash/pro 档案
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 11:02:59 +08:00