caoqianming
1a950dedb5
Stage C Step 5: main.py sandbox check + lifespan fs quota WARN
...
- main.py sandbox check 子命令:5 项独立探测 + 汇总 exit code
① docker daemon 可达 ② zcbot-sandbox:latest 镜像存在
③ zcbot-sandbox-net network 存在(warn 不 err) ④ 镜像 zcbot uid 与 host
uid 对齐 ⑤ workspace/users 所在 fs 类型可 quota
- core/sandbox/check.py:detect_fs_quota(path) -> (level, msg) 抽出来给
lifespan 与 CLI 共用;识别 xfs+prjquota/ext4+project/zfs/btrfs/tmpfs/其他
- web/app.py lifespan docker backend 启用时调 detect_fs_quota 打 WARN
到 stdout(不阻塞启动,应用层周期扫描仍生效)
- err vs warn 分界:err = docker backend fail-fast 根因(daemon/镜像/uid),
warn = 不阻塞启动但外部开放前要清(network 缺/fs 不可 quota)
- run_sandbox_check 用 module-level getattr 而非固化 CHECKS 元组,让
unittest patch core.sandbox.check.check_xxx 生效
- tests/test_sandbox_check.py 19 测试覆盖各分支 + exit code 汇总;
unittest discover 31/31 PASS
- RUN.md 加"部署前置对账"小节 + "配额硬化"重写(fs 状态→处理映射表 +
xfs 升级 4 步) + 故障兜底 3 行;DESIGN 不动
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 16:41:16 +08:00
caoqianming
dfac0acfa6
Stage C Step 3: DockerExecutor 集成 AgentLoop + web lifespan reaper
...
- core/executor_docker.py 新增 DockerExecutor:组合 HostExecutor+SandboxPool,
shell/run_python 走 docker exec(setsid + --user 1000:1000 + --workdir),
其他工具直通 host(§7.5 #6 信任域二分)
- run_python tmp .py 落 <user_root>/.zcbot_tmp/<task_id>/(dotfile,/v1/files
天然过滤),容器内对应 /workspace/.zcbot_tmp/...,跑完 unlink
- ZCBOT_SANDBOX_BACKEND=host|docker env 切 backend,默 host(Windows dogfood
零变化);docker 路径 pool 未 init → fail-fast 不静默退化
- web/app.py lifespan:docker backend 启动时 init_pool + shutdown_all 清孤儿 +
60s 后台 reaper(run_in_executor 调 sync reap_idle);关闭时 cancel + 兜底清
- pool.py 顺手清 Step 2 债:asyncio.Lock → threading.Lock,ensure 改同步
(主使用方是 BG 线程 tool call,ephemeral loop 会让 asyncio.Lock 跨锁失效)
- Cancel limitation 接受:Popen.kill() 仅杀 docker CLI 客户端,容器内进程靠
idle 5min reaper 兜底;升级到 PGID 协议(§7.5 #3 )等用户反馈触发
- tests/test_executor_docker.py 11 测试覆盖关键路径(host 直通/argv 形态/
tmp 清理/timeout/cancel/未知工具/enable_run_python=False)
- DESIGN.md 不动(纯按 §7.5 #5 #6 既有协议实施)
- RUN.md 加 ZCBOT_SANDBOX_BACKEND env 段 + 切 docker 的前置条件 + 集成验证路径
- unittest discover 12/12 PASS
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 16:13:16 +08:00
caoqianming
ade7f3d1e1
Vendor markdown frontend assets
2026-05-25 09:31:36 +08:00
caoqianming
ea89c5f209
Persist chat loading state across task switches
2026-05-25 09:16:20 +08:00
caoqianming
7a0d03fb29
Show upload progress
2026-05-25 08:54:06 +08:00
caoqianming
6e33f07bfb
Show pasted upload chips
2026-05-25 08:43:07 +08:00
caoqianming
f157a4e050
Add resizable web panes
2026-05-25 08:31:31 +08:00
caoqianming
f3017480d0
Show full file names on hover
2026-05-25 08:18:20 +08:00
caoqianming
3525399041
Fix task list scroll container
2026-05-25 08:06:33 +08:00
caoqianming
ae6b8630e2
ui: 手机端对话面板顶栏 + chat-meta 紧凑化 (5 按钮换行不竖排 + 模型 label 转 emoji)
...
- #pane-mid .pane-head 加 flex-wrap + 按钮 white-space:nowrap,
消除 "完\n成" / "废\n弃" 这种内部断行;藏 "对话" label / spacer
- #chat-meta 手机端 gap 8→6px,藏 .tid,.desc 限宽 60vw ellipsis
- 三个模型下拉 label 加 .mdl-text / .mdl-icon 双 span,
桌面文字 / 手机 emoji (💬 /🖼/🎬 ) CSS 切换,桌面零变化
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 16:00:52 +08:00
caoqianming
72ae0ded0a
feat(web): embed 模式接受 ?task_id=<uuid> URL 参数自动定位 task
...
首次签发 token / 已有缓存 token 两条进入路径都覆盖;
401 重签不重置选择,尊重用户中间 UI 切过的 task。
EMBED.md URL 参数表 + 故障兜底同步更新。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 15:27:19 +08:00
caoqianming
84388b278e
ui: "+ 新建任务"按钮改通栏单独一行 (windows 320px pane 不再换行)
...
挪到 #pane-left 第一行 pane-head 之下的独立行,flex:1 撑满整行(primary 红底
通栏 CTA)。原塞 spacer 之后,中文五字在 320px pane 被挤断行。顺手删 @media
phone 里 #hd-new 紧凑覆盖(通栏不需要缩)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:44:30 +08:00
caoqianming
5e27ea9424
ui: "+ 新建任务"按钮从 header 挪到任务面板 pane-head
...
语义更贴(任务面板的动作);顶栏减负只剩身份区(brand/who/logout);两种模式
DOM 一致,顺手删了 embedInit 里动态 insertBefore 那段。文案保留完整"+ 新建任务",
样式加 small 跟周边对齐高度,保留 primary 红底显眼。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:40:49 +08:00
caoqianming
76f4c45350
feat(web): dev.html 加 iframe embed 模式 (?embed=1&parent_origin=...) + EMBED.md
...
embed 模式藏左上 brand / 用户名 / 退出登录,桌面整层 header 移除,移动端保留
header 给 mobile-tabs;"+ 新建任务" JS 移到任务面板 pane-head。postMessage 双向
握手:iframe 发 zcbot-ready / zcbot-401,父端回 zcbot-token{token,user_id,
user_name?};双向 event.origin 校验(parent_origin URL 参数白名单)。401 改写
logout 不 reload,而是通知父端 + 显灰底 spinner 等待层。复用已有 /v1/auth/login
SSO 入口,platform 后端 PLATFORM_KEY 代换 JWT,不下放浏览器。EMBED.md 写给
platform 工程的对接手册(URL / 协议 / Node+Python 后端示例 / 父端前端示例 / CORS
+ CSP frame-ancestors 收紧建议 / 调试 + 故障兜底)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:32:16 +08:00
caoqianming
d6cbe8194b
ui: chat-input 支持 Ctrl+V 粘贴文件上传 + chat-hint 反馈
...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:59:53 +08:00
caoqianming
0c732590a4
ui: 文件预览弹框让出 chat-form 高度,打开期间仍可点击/打字
...
#file-preview-modal 加 bottom: var(--preview-bottom-inset, 0),openFilePreview 时 JS
量 chat-form 当前高度写到 inline style,关闭时清掉。card max-height 跟着收缩不溢出,
手机段用 100dvh 同理。无活动任务(chat-form 隐藏)走 0,弹框仍全屏。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:41:53 +08:00
caoqianming
eec7eb156f
feat(paths): 对外路径统一全形式 <wd_name>/<rel> + UI 一次性兼容历史简写
...
system prompt 加硬约束: 助手 echo 产物文件路径必须用 user_root 相对全形式
<wd_name>/<rel> (<wd_name> = task_dir 末段, 如 生图测试/videos/xxx.mp4),
不简写为 videos/xxx.mp4 这种 task 内裸形式 -- Web UI 按 <wd_name>/ 前缀挂
chip, 简写 → chip 失效用户点不开。媒体 tool (seedream/seedance) 的 saved:
行已是规范形式可直接照抄, ppt/proposal/coding 等 run_python/write 写文件时
自己拼。跨所有产物 skill 统一生效。
imagegen/videogen SKILL 把"把 saved: xxx 告诉用户"重复教学改成"照抄
saved 行, 详见 system「路径」段" (避免协议漂移, 新产物 skill 不用重复教育)。
ppt/proposal 等 SKILL 不动 -- system 协议自动管。
dev.html extractArtifactRels 加一次性兼容兜底: 产物目录裸路径
videos/xxx.<ext> / figures/xxx.<ext> (协议刚性前历史简写) prepend
<wdName>/ 拼成 user_root rel。**白名单显式枚举两项不扩展**, 长期老消息
归档后整段可删。
术语校准: 前缀叫 <wd_name> (working_dir 末段) 而非 <task_name> -- 用户允许
wd_name ≠ task_name, _display 锚 user_root 出来的是 <wd_name>。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:45:54 +08:00
caoqianming
7d3a93fc1f
ui: dev.html 手机自适应 (两档 @media + tab 单列切换)
...
平板 641-1024px 强制 rail (不写 localStorage,回桌面用户偏好仍生效);
手机 ≤640px 单列 + body.mv-{left,mid,right} 切换 + header tab 按钮换行铺底;
selectTask 自动切到对话视图,100dvh 解决 iOS 工具栏挤压,
input/textarea ≥16px 防 focus 缩放,4 modal 改 min(92vw,…) / file-preview 全屏化。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:39:42 +08:00
caoqianming
7ff58c488e
feat: 接入豆包 Seedance 2.0 Fast 视频生成 (文生视频) + videogen skill
...
- tools/seedance.py: 异步 submit /contents/generations/tasks → 5s 轮询 → succeeded
后 download mp4 + meta.json 落 <wd>/videos/;失败/cancel 不计费;cancel_check 在
轮询间检查,响应用户停止按钮
- config/media/doubao.yaml: 展开 video.seedance_2_fast (¥37/Mtok 文生 / ¥22/Mtok
图生,token 公式校验 720p 5s = ¥4.00 完全对上源数据)
- core/storage/usage.py: record_video_usage,kind=video,units jsonb snapshot
resolution/duration/ratio/fps/tokens/单价
- core/agent_builder.py: build_agent 加 video_variant + cancel_check 形参,
cancel_check 必须 build 阶段传 (SeedanceTool ctor 持有用于轮询)
- web/app.py: GET /v1/video_models + MessageRequest.video_model + 透传
- web/static/dev.html: 顶栏第三下拉 (image 旁边) + state.videoModels/videoModel
- skills/videogen/SKILL.md: 六维诊断 (运动+镜头 替代 imagegen 的光线);BLOCKING
门槛比 imagegen 更严 (¥4 vs ¥0.22) + 等 30-90s 出片
- prompts/system/general_v1.md: 加 seedance 触发指引 (平行 seedream)
phase 1 仅 t2v 文生视频,fast 上限 720p。API 端到端 smoke 跑过:路径/auth/错误解析
全通,body schema 待用户在火山方舟控制台开通模型后真出片才能验。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:30:54 +08:00
caoqianming
9a26e85da2
fix(ui): primary button hover 文字消失 (.primary:hover 守住 background)
...
button:hover:not(:disabled) 与 button.primary:hover 特异性同为 (0,2,1),
平手按源码序后者赢,但后者只声明 filter 没声明 background,导致 background
fallback 到前者的 --hover 浅灰,白字浅灰底视觉消失。
修法显式 background: var(--accent),brightness filter 在红底上正常提亮。
影响 "+ 新建任务" / "发送" 两个 primary 按钮。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:57:45 +08:00
caoqianming
6f9391dee3
ui(css): dev.html 圆角降一档 + 抽 token + modal 基类化 (style -11%)
...
抽 5 组 CSS token(语义色组 / 圆角分档 / mono / transition / shadow),顶栏按钮 hover + dd-item + badge 全切到 token(同色 selector 合并:export ≈ sp-copy 蓝、abandon ≈ sp-move 橙);4 个 modal 抽 .modal 基类(fixed/inset/bg/.show 五属性合并);.msg .body 与 file-preview .md-render 合并 markdown 渲染规则。圆角主流档 6px → 4px,modal card 8~12px → 6~8px,art-chip 999px 保留(胶囊语言)。功能 0 改动,JS 一行没动。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:50:17 +08:00
caoqianming
fa6cb72103
ui: 工作目录回到原生 select + sentinel + 二级 input (modal + 顶部 filter)
...
combobox 方案推翻 — 即使 show 不过滤,modal wd 因联动有值后用户直觉仍是
"得点开下拉看选项",自实现 panel 不如浏览器原生 select 稳。
- modal nt-wd-sel 第一项 sentinel "+ 新建「<name>」"(updateSentinelLabel
跟 name 实时刷),sentinel 选中显示二级 nt-wd-new 默认跟随 name,
选已有目录隐藏;wdManuallyEdited 锚到二级 input
- 顶部 filter-wd 改 select,onchange → loadTaskList(无 debounce)
- loadFolderSuggestions + populateFolderSelects 灌两个 select,保留当前选中
- enterApp fire-and-forget 预拉 folders 让左 pane 一打开就有选项
- hint 在"新名碰到同名"时提示"将复用而非新建"
- combobox 工厂 + .combo CSS + datalist 残留全删
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:20:34 +08:00
caoqianming
32a8c348a8
ui: 工作目录 combobox 从 datalist 改自定义 dropdown (modal + 顶部 filter)
...
datalist 在 input 非空时下拉被浏览器按前缀过滤丢失,联动 name 后
体验比原 select 还差。改方案:
- 抽 makeFolderCombo({input, panel, onPick}) 工厂:focus/click 显完整列表,
input 子串过滤,mousedown preventDefault 兜住 blur 提前关 panel,
键盘 ↑↓ Enter Esc
- modal nt-wd-sel 和顶部 filter-wd 都接工厂,各自传 onPick
(modal 置 wdManuallyEdited + updateWdHint,filter 走 loadTaskList)
- .combo / .combo-panel 样式提到全局
- 删 <datalist> 元素 + loadFolderSuggestions 灌 datalist 的代码,
ensureFoldersLoaded 改用 state.folders 判断
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:03:37 +08:00
caoqianming
8d7f60d899
ui: 新建任务弹窗工作目录改 combobox + 跟随任务名联动
...
- nt-wd-sel 从 <select> 改 <input list=folders-datalist>,删 "+ 新建目录…"
sentinel 和二级 nt-wd-new 输入框
- 加 wdManuallyEdited flag:name 输入时若未脱钩则同步到 wd;wd 非空输入
置 true 脱钩;wd 清空重置 flag 但保持空(避免 backspace 想换名字时被
立刻填回打断)
- loadFolderSuggestions 只灌共享 datalist,缓存到 state.folders 供 hint
比对"命中已有/新建"
- submit 保留 working_dir || name fallback 兜底空值
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:35:37 +08:00
caoqianming
fe21ca1e8c
ui+api: 登录页加管理员发用户入口 + 删 chat meta 重复的 条/tok 显示
...
- web/auth.py 加 `create_user()` helper(CLI / web 共用)+ `AuthConfig.admin_token` 从
`ZCBOT_ADMIN_TOKEN` env 读,未设 → 接口返 503(功能默关)
- web/app.py 新增 POST /v1/auth/admin/create_user,403/400/409 四分支(口令错 /
邮箱不合法或密码 < 6 / 邮箱占用);main.py user_add CLI 改调同 helper 避免漂移
- web/static/dev.html 登录卡片右下加 ghost link "+ 管理员添加用户" + 弹窗
(email/密码/管理员口令),成功后回填邮箱到登录表单不自动登录;
同时删 chat 顶栏 ${n_messages} 条 · ${tokens} tok 一行(与左 task 列表重复)
- RUN.md 加 ZCBOT_ADMIN_TOKEN env 说明 + 故障表两行;PROGRESS.md 加一条 2026-05-21
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:51:02 +08:00
caoqianming
52c25b9404
ui+api: dev SPA SSE 客户端 3 次退避重连 + stream_events 非活跃 task 立即吐 done
...
--reload 重启 / 网络抖时 fetchSse 拆出 consumeSseStream + 包重连壳
(1s/2s/4s,EOF 未见 done/error 触发重连);后端 stream_events 入口检
tasks.run_status,非 running/cancelling 直接关流,避免重连卡在空 broker
无限挂 ping。3 次仍失败 → 卡片末尾红色"请重发"。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:14:51 +08:00
caoqianming
b480147fb2
fix(usage): 顶栏 token 累计修 — sync_task_tokens 改走 messages SUM,删 LLM.TokenCounter
...
5/20 切流式后 LLM.token_counter.add() 只在同步 chat() 里调,流式 chat_stream() 路径从不更新它,
sync_task_tokens 从内存计数器读累计 → tasks.tokens_prompt/completion 一直 0 → 顶栏 0 tok。
DB 验证:5/20 之前 task 数据正常(4568/934),之后 0/0;但 messages.tokens_in/out 一直对
(record_chat_usage 写),source-of-truth 完好,只是冗余汇总列没同步。
改 sync_task_tokens(task_state) 走 SELECT SUM(tokens_in/out) FROM messages WHERE task_id=?,
删 TokenCounter 类 + ConsoleEventSink 的 token_counter 回调 + spinner ctx 尾巴。
一次性 backfill 4 个被影响 task 的累计列。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 13:39:57 +08:00
caoqianming
d2fd89f3a4
api+ui: 同 wd 并发软警告 banner + /v1/tasks 加 run_status 筛选 + task header @wd 显式化
...
评估 γ(同 wd 单活 gate) / short_id 全产物隔离 / clone task 三方案均判定过度工程 — dogfood 同 wd 基本不并发。走 Claude Code 同款"信任 + 软警告 + 承认 limitation"。后端 /v1/tasks 加 run_status query(逗号分隔 allowlist),前端 selectTask + SSE 收尾两点拉同 wd 活跃 task,有命中挂 #wd-concurrent-warn 黄底 banner(⚠ 项目名 + 邻居 task name + run_status + 等 N 个),不挡发送。renderChatMeta 把 📁 wdName 改为仅 wdName !== taskName 时显示。DESIGN §7.8 风险表文件级悲观锁行(本就未实现)换为 known limitation + 软警告;§7.9 新增取舍条说明三方案为何不选。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 13:01:21 +08:00
caoqianming
5f0f296a23
ui(media): chip 三规则定型 — 工具 I/O 走产物白名单 + 助手正文无条件挂 chip 绕开 seenRels
...
修截图反馈"助手回复 echo 的产物路径没挂 chip"。① 工具 I/O(args/result):chip 抽取只对产物工具(seedream/seedance),通用工具 echo 是引用不该挂;② 产物图/视频:inline 大图;③ 助手正文:永远挂 chip 且 allowInlineMedia=false,只小按钮不重复 inline 大图。SSE 处 upgradeMediaArtifacts 同步 gate 到 isProducer 下。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 08:44:36 +08:00
caoqianming
d402c8771c
ui(media): chip 解绑产物白名单 — 通用工具 echo 路径也挂 chip,图片/视频 inline 仍只对产物开
...
renderArtifactBarHtml 加 allowInlineMedia 参,false 时图片/视频也走 .art-chip 按钮(点开仍弹预览 modal);4 处 tool 调用点解绑 ARTIFACT_PRODUCING_TOOLS chip gate,只透传给第二参控制 inline;SSE 两处 upgradeMediaArtifacts 同步 gate 到 if (inlineMedia);assistant 正文默认 true 不变。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 08:31:39 +08:00
caoqianming
1e4548dd0c
ui(media): chip 抽取改"产物工具白名单"门控 — grep/read 类工具结果里 echo 的路径不再误挂 chip / 误 inline 图片
...
用户报"生成的图正常预览,但 grep 工具的结果里 figures/ 下另一张老图也被 inline 出来了"。根因:extractArtifactRels + renderArtifactBarHtml 是通用产物展示(image/video → inline / 其他 → 可点 chip),通用工具结果里 echo 的任何带扩展名路径都会被当产物挂出来,seenRels 只能去重同路径挡不住"figures/ 下别的老图首次出现"。修法:加 ARTIFACT_PRODUCING_TOOLS=new Set(["seedream","seedance"]) 白名单,4 处工具 I/O 调用点(renderMessages tool 历史 + assistant tool_calls args + SSE tool_call + SSE tool_result)用 .has(toolName) 三元短路;assistant 正文不门控沿用 seenRels 兜底。chip 本意是"这次工具调用新产出的东西",grep/read 输出里的路径是引用不是产物。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:06:01 +08:00
caoqianming
3c2e25d912
api+ui(chat): 删输入框冗余上传按钮 + 加润色按钮 — POST /v1/tasks/{id}/optimize_prompt 走 task 当前模型同步润色,usage_events 新 kind=prompt_optimize 单独记账不污染主对话累计;前端 execCommand insertText 接 textarea 原生 undo 栈,Ctrl+Z 一次回到原文
...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 21:20:16 +08:00
caoqianming
febe04a569
ui(media): tool 结果与 assistant 正文同路径 chip/inline 图去重 — Set O(n) + CLAUDE.md 加 "实施前先对方案" 段
...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 16:33:47 +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
e1f09547e0
api+ui(files): POST /v1/files/delete 加 recursive 字段 — 顶层目录被 task 引用闸 + dev SPA 二次确认显示条目数
...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:38:58 +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
d1a2961bf4
api+ui(dev SPA): POST /v1/tasks/{id}/clear 清空对话 + 顶栏「清空对话」按钮
...
- 后端: 同事务 SELECT FOR UPDATE 锁 + active run 检查(running/cancelling → 409) +
DELETE messages + reset tasks.tokens_prompt/completion/cost_usd=0 + run_status='idle'
- usage_events 完全不动 — 用户级账单 source of truth 与对话清空解耦;
message_id FK 是 ondelete=SET NULL,task_id/units/cost_usd 全保留可重建累计
- dev SPA 顶栏在导出后插「清空对话」(紫色 hover,介于完成绿/废弃橙/删除红),
running||n_messages==0 → disabled,confirm 二次确认 + 同步刷新 chat-meta / 消息 / 任务列表
- FS 文件保留(沿用 task delete 的"FS 视图可重生"心智)
- RUN.md API 表 + 故障兜底加 409 case;DESIGN.md 不动(无架构 / schema 字段语义变化)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:19:08 +08:00
caoqianming
ecff1d7858
ui(dev SPA): tool_call/result 卡片下加 artifact chip — 点击复用文件预览 modal,免再去右栏找
...
PROGRESS.md 同步。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:08:15 +08:00
caoqianming
775962d68a
ui(dev SPA): 任务行 meta 数字槽位等宽 + 折叠按钮挪 pane-head + rail 模式 + time-ago 锁宽完成跨行对齐
...
- meta 加 tabular-nums + .num 槽位 (min-width:44px + text-align:right) + fmtTokens (1.2k/123k/1.2M)
- .num.right-group 把 [N条][Ntok][time] 整组用 margin-left:auto 推右
- time-ago 加 min-width:64px 锁宽: 整组右锚点稳定后, 跨行"条/tok"后缀才真正垂直对齐
- 折叠按钮挪到 pane-head 紧贴 ↻ 刷新; 折叠态改 VS Code rail 模式 (40px 列 + 只留 toggle 一直可点)
- 删 header #hd-toggle-left 冗余按钮 + header .icon-btn CSS (rail 模式下不需要)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:30:14 +08:00
caoqianming
5b67d29f59
ui(dev SPA): 左 pane 280→320px + header 折叠 toggle + 任务行精简 meta 防 CJK 断行
...
- grid 左列 280→320px (从 chat 借 40px), 任务名 / 描述 / wd 更舒展
- header 最左 toggle 按钮: body.left-collapsed → 列归零 + #pane-left display:none, chevron 翻向, localStorage 持久化
- 任务行 meta 删 id8 (挪到 row title 仍可查) + 各 span white-space:nowrap + badge/time-ago flex-shrink:0
- wd/desc 副行恢复 inline overflow:hidden ellipsis (单文本带不是 flex 子元素)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:18:42 +08:00
caoqianming
afebf25d79
ui(dev SPA): 任务列表 pager bar → 滚动加载(IntersectionObserver sentinel)
...
- 删 #task-pager / renderPager / resetPageAndReload / btn-prev|next 三件套
- 加 #task-sentinel + IO root=#pane-left + rootMargin 200px 提前触发
- loadTaskList({append}) 双语义: reset 抢占(_taskLoadSeq 丢弃过期响应) / append 互斥
- renderTaskList(append=true) 不 clobber 已渲染行, 事件 handler 只挂新行
- 首 pane-head 加 "共 N 个" 总数小字补偿丢失的 pager-info
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:09:46 +08:00
caoqianming
97d838a9ec
ui(dev SPA): 任务列表行加最近操作时间(updated_at 相对显示) + 新建弹框工作目录改 <select> 下拉
...
- 列表行 meta 加 fmtTimeAgo helper(刚刚/N 分钟前/N 小时前/昨天 HH:MM/MM-DD/YYYY-MM-DD), title 出完整 locale 串
- 新建任务工作目录: input + datalist → <select> 既有目录列表 + "+ 新建目录..." 展开 text input(保留新名建目录能力)
- folders-datalist 保留供左 pane filter-wd 继续 autocomplete
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:57:16 +08:00
caoqianming
515684e60b
ui(dev SPA): 主页轻量美化 — header brand logo + 左 pane 子行轻分隔 + 顶栏语义按钮 hover-only 上色 + 圆角微调
...
- header: 加 24×24 红渐变 "Z" logo + 标题 14→15px + 1px 极淡 box-shadow
- 左 pane: --border-soft #ececec + `#pane-left .pane-head + .pane-head` 把 filter/sort 子行换白底淡分隔,inline border-top 顺手去掉(避免与新 border-bottom 双线)
- 顶栏 4 按钮 + 选入弹框 copy/move: 常态中性 + hover 一次性上语义色(color/border/bg),沿用 button.danger 的 hover-only 范式,button 基础类加 0.15s transition
- 圆角: button/input/.msg/floating-menu 4→6; 三个 modal 卡片 6→8 + 阴影 0 8px 24px → 0 12px 32px
纯 CSS / HTML, 不动 JS / 后端 / DESIGN / RUN; dd-item 菜单语义色保留(菜单内动作类型区分,不在"顶栏中性"范畴)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:36:22 +08:00
caoqianming
337b8896a6
files(dev SPA): UX 翻面 — 主区去 checkbox / 黄 bar,改 [选入到此处] 弹框 + 拖拽上传 + 修全局 input width bug
...
主区从 select-then-pick-dest 改 at-dest-pull-sources:用户切任务时主
区已自动跳 working_dir,destination-first 比 source-first 少一次心智
切换。pane-head 加 [选入…] → 弹框跨目录勾源(Set<rel> 切换路径保留)
→ 底部 [复制到此处] / [移动到此处] 落到主区当前 state.filesPath;弹
框浏览 == 主区路径时同目录 checkbox 灰禁(挡 409)。整个 pane-right
成 drop zone,Files 类型才响应 + dragenter/leave 计数防子元素冒泡闪
烁,落点用 state.filesPath 沿用 /v1/files/upload。
根因 bug:全局 input{ width: 100% } 把新加的行 checkbox 撑成全行宽,
.name(flex:1; flex-basis:0)被挤成 0 宽 — 用户报"看不到文字"的元
凶。修法 selector 排除 [type=checkbox]/[type=radio]/[type=file]。
按 CLAUDE.md 不留兼容:state.selectedFiles / syncBulkBar / dirPicker
/ files-selall / files-bulkbar / row-cb / .file-row.selected 整套删
干净。后端 /v1/files/copy /v1/files/move 一行没动,前端用同样的
{paths, dest_dir}。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:59:21 +08:00
caoqianming
0c5dd3b176
files(dev SPA): /v1/files/copy + /move 跨目录批量搬动 + 多选 + 目录选择弹框
...
后端两路由共用 _validate_transfer 预检 helper(批量原子校验:同名 409 不
覆盖、不自嵌套、不重名、target 已存);move 加闸"顶层目录是某 task
working_dir → 409"维持 working_dir = top-level invariant,copy 无此闸
(新副本无 task 关联)。dev SPA 文件行加 checkbox + 顶栏全选三态 + 黄底
toolbar(复制到/移动到/取消),目录选择弹框复用 /v1/files 浏览。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:29:59 +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
48924d0d56
ui(dev SPA): 对话顶栏按钮改名"导出对话记录" + 语义化上色(完成绿/导出蓝/废弃橙/删除红)
...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:48:46 +08:00