caoqianming
1f57bbd201
fix(web): 导出按钮简写 + 任务菜单加清空 + 修移动端 task 滚动 + admin 自适应 + bump 0.12.1
...
- 顶栏「导出对话记录」→「导出对话」,与「清空对话」对齐(dev.html + chat.js 菜单 export 项)
- 任务菜单(每行 ⋯)新增「清空对话」,复用 clearMessages;dropdown 加 .act-clear 紫色
- 修移动端 task 列表无法滚动:手机断点 #pane-left 误用 display:block 致 #task-scroll
flex:1 失效被 overflow:hidden 截断;改 display:flex 恢复滚动
- admin 移动端自适应:header 紧凑化 + .card-head/.ctrl 允许换行,避免窄屏横向溢出
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 12:41:53 +08:00
caoqianming
c870b10368
feat(memory): 双层记忆升级为 agent 自管 + 前端只读记忆面板 + bump 0.12.0
...
写入路径从纯手工改为 agent 自管(prompt 契约,非后台蒸馏):memory_block
注入可写路径锚点 + 「记忆维护契约」,契约/锚点常驻(记忆为空也注,解新用户
冷启动)。extended 索引从首行标题升为优先 frontmatter description(缺则退回
首行,平滑兼容存量)。修旧 bug:extended 路径在 docker 下注的是宿主路径指不到,
改按 backend 给 host 绝对路径 / /workspace/.memory。
前端记忆面板取舍 = GUI 当眼睛、模型当手:左栏「记忆」按钮开只读 modal 看全貌
(GET /v1/memory + GET /v1/memory/extended/{filename},零写/删 API,路径穿越
校验收口在 core/memory.py)。"看全貌"是读不是 operation,走 LLM 又贵又只拿
转述;"改"全走对话(agent 自管),单一写入口 + 自然语言 + 不会写坏 frontmatter。
对照业界:Claude(同文件式)给全套 view+edit,ChatGPT/Gemini 黑箱只给看/删。
单测覆盖:frontmatter 解析 / legacy 兜底 / 空记忆常驻契约 / host·docker 路径 /
只读视图 / 单篇读 / 文件名安全 / 越界拦截。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 12:20:08 +08:00
caoqianming
f12df1bd82
feat(admin): 后台目录导航 + 按模型/各用户用量时间筛选排序 + 存储分页 + 导出 PDF + bump 0.11.0
...
- 左侧目录(sticky,点击平滑滚动 + scrollspy 高亮,窄屏转横向 chip);各区 scroll-margin-top 避开顶栏
- 按模型 / 各用户用量拆为独立端点,带 range(all/7d/30d)+ sort(cost/tokens);
各用户用量含零用量用户(时间条件放 JOIN ON,避免被 cutoff 挤掉)
- 存储分页(/v1/admin/storage/users);各用户用量分页;overview 瘦身为固定指标(runtime/tasks/users/总用量+近7d),独立表自管 range/sort/page
- 导出 PDF:客户端 window.print()(零依赖),填充隐藏报告 DOM + @media print 版式;列表取前 10
- 文档同步 DESIGN §7.3 / PROGRESS / RUN
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 10:44:47 +08:00
caoqianming
ef611b0666
feat(admin): 角色化管理后台 + 分页各用户用量 + bump 0.9.0
...
- users 加 role 列(user/admin,migration 0009);make_require_admin 按 DB role gate(不进 JWT,改完即时生效)
- /v1/admin/overview 监控总览:runtime(并发/线程池/SSE/RSS)+ tasks + users + usage 总用量 + storage
- /v1/admin/usage/users 分页各用户 token 用量(全表 LEFT JOIN 含零用量,cost desc,稳定排序)
- /v1/me 返 role;登录/建用户响应带 role;main.py user role / user add --role;建用户弹框加角色下拉
- 独立页 web/static/admin.html + js/admin.js(阈值/热力色差、响应式、10s 轮询、独立翻页);dev SPA admin 才显"管理"入口
- 文档同步:DESIGN §7.3/§7.4、PROGRESS、RUN
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 10:02:20 +08:00
caoqianming
44be5753f7
fix(version): 版本号挪到右侧存储条最左,垂直居中 + bump 0.8.1
...
- dev.html: #app-version 从左栏 #rail-resources 移入 #storage-foot 最左,
带细分隔线,垂直居中(align-items:center);左栏底部腾给后续按钮
- core.__version__ 0.8.0 → 0.8.1(纯 UI 位置微调,patch)
- PROGRESS 同步新位置描述
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 12:55:35 +08:00
caoqianming
dd797a91e2
feat(version): 版本号单一事实源(core.__version__)+ web 左栏底部展示
...
- core/__init__.py 新增 __version__ = "0.8.0",作唯一来源
- web/app.py: FastAPI version 与 /healthz 返回都引它(不再写死两份)
- dev.html: 左栏「我的资源」技能按钮旁加 #app-version 小灰字(纯展示)
- main.js: boot 时无条件 fetch /healthz 填版本号(auth 豁免,embed/未登录皆可)
- 放左栏底部而非顶栏:embed 模式桌面端 header 被 CSS 隐藏,顶栏点不到
- CLAUDE.md「文档维护」加规矩:每次 commit/push bump __version__(patch/minor/major 分类)
- RUN.md / PROGRESS.md 同步
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 12:00:30 +08:00
caoqianming
15d69b3372
feat(ops): 并发/线程池轻量监控 + 接管默认 executor
...
已上生产后线程池排队此前无观测手段:
- lifespan 显式建 ThreadPoolExecutor(尺寸复刻 Python 默认 min(32,cpu+4),
env ZCBOT_RUN_MAX_WORKERS 可调) + set_default_executor 接管 —— 行为不变
(匿名池换显式同尺寸池),但 max_workers 可读、成调并发的旋钮
- _stats_logger 每 60s 采样:active_runs(含排队)逼近 max_workers 即排队,
刷新峰值/有负载打 [stats],空闲静默不刷屏
- broker.total_subscribers() 全局 SSE 订阅数;RSS 用 stdlib resource
(Unix 峰值;Windows dev 降级),零新依赖
不做监控界面:运维健康是少数标量日志够,业务分析走 SQL。DESIGN 8.4 记
取舍 + 界面阶梯;无感换版(gunicorn/Redis/蓝绿)成本不抵当前收益,搁置。
查看: journalctl -u zcbot | grep [stats]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 11:09:44 +08:00
caoqianming
f614046438
feat(web): 技能弹框平台/我的拆成独立两栏(三栏 master-detail)
...
平台 skill 多、我的是用户核心区,混一栏易被淹。改三栏:平台列表 |
我的列表 | 正文,我的获得与平台对等版面。modal 加宽到 1000px;窄屏
(<=760px)三栏自动上下堆叠。load_errors 归到「我的」栏底。纯前端。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 10:22:06 +08:00
caoqianming
d89ebad272
feat(web): 技能按钮换扁平 SVG 图标 + 弹框改两栏 master-detail
...
- 左侧 rail「技能」按钮 emoji 🧩 换成扁平 inline SVG(2x2 grid,
描边 currentColor 跟随主题);modal 标题同步。
- modal 从"列表→点开换正文+返回"改两栏:左栏平台/我的两组列表(可选中
高亮),右栏展示选中 skill 完整 SKILL.md;删除按钮挪到右栏正文头部
(只对我的),左列表保持干净只显名+描述。窄屏(<=640px)两栏自动改上下堆叠。
- 纯前端,后端/测试无影响。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 10:08:26 +08:00
caoqianming
958678aa12
feat(skills): 用户私有 skill(.skills)+ 创作工具 + skill-creator + Web 查看页
...
每用户可在私有 .skills/ 下造/改 skill,只对自己生效。
- SkillRegistry 改多来源(SkillSource 列表:内置 + 用户 .skills),后扫同名
覆盖先扫 → user wins;user_overrides 记覆盖关系、discovery 显式标注;
Skill 加 source;from_dir 区分"非 skill 目录(静默)"与"格式错(SkillLoadError)",
坏的用户 skill 收进 load_errors 注入 prompt,不崩整次扫描。容器路径改写下沉
到 registry.container_dir(按 source 给 /sandbox/skills 或 /workspace/.skills),
LoadSkillTool 去掉 container_skills_dir 参数。
- 新增 host-side 工具 save_skill / fork_skill(tools/skill_authoring.py):
fs 的 base_dir 锚 cwd/容器 wd 够不到 user_root/.skills,故用 host-side typed
tool(与 seedream/document_* 同范式)。save_skill 写时校验 frontmatter;
fork_skill copytree 整目录(带脚本)+ 自动对齐 frontmatter name。
- 新增 skill-creator 引导 skill(重点教写好 description + fork 语义)。
- Web:左侧 rail 底部「技能」按钮 → modal 分平台/我的两组,点开看完整
SKILL.md,我的可删;后端加 GET /v1/skills/{name}(正文)+ DELETE
/v1/skills/{name}(只删 user 源 + 防穿越);/v1/skills 带 source/overrides/
load_errors;新 web/static/js/skills.js。创建/改/fork 仍走对话。
- .skills 是 dotfile(文件面板隐藏,与 .memory 一致;validate_task_name 已禁
. 起头 working_dir,天然不撞)。
- 测试:test_user_skills.py(20 例)+ 改写 test_load_skill.py;全 121 过。
- 文档:DESIGN §3.5 / PROGRESS / RUN(布局+端点)/ SKILL_LIST 同步。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 09:46:39 +08:00
caoqianming
e45705c672
docs(auth): 邮箱密码定为长期保留,OIDC 降级为选做 + 拆出 CORS 收紧
...
已接入真实用户且邮箱密码长期保留,故:
- DESIGN §7.0/§7.3/§7.7 三处「邮箱密码同步下线」改「与 OIDC 并存长期保留」
- 原「真 OIDC 发布前必做」拆成:CORS 收紧(现做)+ OIDC(选做,信任模型可接受则延后)
- PROGRESS 下一步候选 #1 同步拆分;auth.py docstring 同步
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:47:27 +08:00
caoqianming
815aeb81a9
feat(web): dev SPA 加 9 处克制入场微动效(纯 CSS + 一处极小 JS)
...
保留现有红主色 / VS Code 三栏审美不改风格,只补低风险微动效:
- 消息气泡 .msg 淡入+上滑(批量加载退化为柔和集体淡入)
- 4 个 .modal 卡片 scale 弹入 + 遮罩淡入
- 全局 button:active 下压 1px
- 进度 dock / 上传 toast 顶部滑下淡入
- 下拉操作菜单 #floating-menu 从右上锚点弹出
- 拖拽 overlay #file-droparea 快淡入
- 拖拽文件放下 → 落点 pane-right 一次 drop-pulse 轻回弹(files.js
加 .drop-pulse + animationend once 自摘 + reflow 保证可重放)
- 全部纳入 prefers-reduced-motion 守卫(spinner/blink 等功能动画保留)
刻意未做:进度块「打勾」逐步动画(dock.innerHTML 每 tick 全量重渲染,
keyframe 会逐 tick 重放);复制 ✓ 闪(当前 SPA 无剪贴板复制功能,无触发点)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:15:21 +08:00
caoqianming
4f6e879050
feat(web): systemctl restart 优雅 drain in-flight run,不再误标 error
...
此前 restart 硬杀 BG run 线程,下次启动 reaper 把所有 running/cancelling
标 error: server restarted before run finished —— 用户一多就不能随便重启。
单实例止血,零 DB 改动:
- lifespan 加 draining(Event) + inflight 登记表(顺手修 create_task 不留引用
可能被 GC 的旧坑);finally 先拒新 run → await 收尾 → 超 drain_timeout 转
协作式 cancel(= 用户按停止,标 idle 不报 error、可重发)→ 超 cancel_grace
仍没退的留给 SIGKILL(最坏退化 = 改前)
- POST /messages:draining 期返 503 + Retry-After;起 run 登记 inflight
- main.py uvicorn 加 timeout_graceful_shutdown=5(否则长连 SSE 挡在 drain 前)
- config/agent.yaml 加 shutdown 段(drain 30s / grace 15s,偏短更安全)
- dev SPA chat.js 发送包退避重试(503 背压 + 交接拒连都重试 ~26s)
部署强耦合:unit TimeoutStopSec 10→90(必须 > drain+grace+sandbox 清扫余量),
已写进 RUN.md unit + 故障兜底。B 蓝绿(零 503 窗口)留作触发信号后再做。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 10:54:43 +08:00
caoqianming
c02d20b005
style(web): favicon 换成登录页 logo 样式(红底白 Z),与 brand 统一
...
原内联蓝色机器人 SVG 与登录卡/顶栏 brand(红色圆角方块 + 白 Z,
渐变 #c0392b → #8e2a20)不一致;改为同款,浏览器标签页图标对齐品牌。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 15:32:20 +08:00
caoqianming
b70b993257
feat(preview): pptx 在线预览 —— LibreOffice→PDF + 复用 PDF iframe(DESIGN §8.3 Stage 1)
...
文件区点 .pptx 不再只能下载。后端转 PDF,前端复用现成 PDF iframe。
- web/pptx_render.py: pptx_to_pdf() 调 soffice,独立临时 profile 绕单 profile
锁、60s 超时 kill;缓存 .preview/<stem>.<hash>.pdf(hash=mtime+size,源改即
失效,prune 旧 hash);soffice 缺失抛 SofficeNotFoundError
- web/app.py: GET /v1/files/preview_pdf —— _safe_join 防穿越 + 仅 .ppt(x) +
per-path asyncio.Lock 防并发重转 + run_in_executor 不堵事件循环;缺失 501/失败 500
- preview.js: ppt 组 + main/mini 共用 _showPptAsPdf(spinner loading + 失败回退下载)
- dev.html: .preview-spinner(复用 @keyframes spin)
- 转换跑 web host 进程不进沙盒;部署 host 装 libreoffice-impress + fonts-noto-cjk
(sandbox Dockerfile 不动)
- tests/test_pptx_render.py: 10 例(缓存命中跳 soffice/源变失效+prune/缺失降级/越界拒绝)
- 文档:RUN.md(host 装 + 故障兜底 2 行)、PROGRESS.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:04:02 +08:00
caoqianming
824f746571
fix(progress): 停压 task_progress 参数修进度还原 + 进度区移到对话区顶部
...
问题1(进度不对): 上下文压缩把旧 task_progress tool_call 参数换成
{"_compacted":true,"step_id":"sX"} 这种像合法调用的标记, 既毒化模型让它
照抄出残废 update_step(丢 step.status)入库, 又让前端 applyProgressAction
读不到 args.step → 步骤永停 pending。修复: task_progress 参数一律不压缩。
问题2(没像 codex 在顶部): 删掉每条消息卡内联进度块, 进度统一只在对话区
顶部单一 dock 实时显示(钉顶不滚); 全部完成时折叠成一行摘要。prompt/tool
描述改为跑完标 completed 而非 clear, 留住全绿收尾。
校验: unittest test_context_compaction/test_task_progress_tool 12 过;
node --test frontend_task_progress 2 过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 09:52:44 +08:00
caoqianming
2136fdd306
fix(web): 修登录无反应(newtask.js 漏 import $)+ 补 favicon
...
newtask.js 用了 dom.js 的 $ 简写却漏 import,模块加载即抛
ReferenceError: $ is not defined,中断 newtask 绑定及 auth/chat
链路 → 点登录无反应。补 import 与其余模块对齐。
另在 dev.html head 加内联 SVG favicon,消掉根 /favicon.ico 404。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 09:22:59 +08:00
caoqianming
c898ff863d
Disable static asset caching
2026-06-08 09:16:31 +08:00
caoqianming
4ee09976ee
Show task progress above composer
2026-06-08 09:04:43 +08:00
caoqianming
8616ba2b56
Add task progress tool
2026-06-08 08:44:16 +08:00
caoqianming
15ecf45c93
refactor(dev): 前端模块化 Step 2 收官 — 抽出 chat.js,main 缩成 75 行入口
...
最后也最缠的一块:任务列表(浏览/筛选/滚动)+ selectTask 切换 +
renderChatMeta/模型下拉 + renderMessages + live-run 助手 + sendMessage/cancel +
fetchSse/handleSseEvent + 润色/粘贴文件 + 完成/废弃/删除/导出/清空
(原 main 连续区 64–1132)→ chat.js(1086 行)。
决策:合一个 chat.js 而非强拆 tasks.js+stream.js —— 二者共享
state.liveRuns + chat-stream DOM + run 生命周期,live-run 助手被
selectTask 与 SSE 机器两边调用、骑墙;强拆会制造双向各 ~4-5 个 import
且边界不自然。
- 导出 loadTaskList / loadModels / selectTask;embed/files/newtask 对这
三个的 import 从 ./main.js 改指 ./chat.js;formatUploadProgress 加 export。
- chat 不调 enterApp → 与 main 无环。
- main.js 仅留 enterApp(编排)+ loadStorage + Esc 关栈 + boot = 75 行入口,
import 精简到 11 行(layout/markdown/media 不再被 main 直接引用,但经
chat 仍在依赖图、副作用照常)。
校验升级:node 全检 + import/export 一致性 + 从 main BFS 的模块可达性
(14/14 可达,确保副作用模块不掉出图)。
dev.html 4087 行单文件 → 14 个零构建 ES module + 纯 HTML;main 2719→75。
路径 1(拆文件)完成。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 08:09:53 +08:00
caoqianming
36dbdb2dda
refactor(dev): 前端模块化 Step 2 — 抽出 embed.js(iframe 模式)
...
父页面经 postMessage 推 token 进入应用 + 401 重签(原 main 1147–1209 +
顶层 _embedInitialTaskHandled 一次性标志)→ embed.js(75 行)。
- 导出 embedInit(boot 调)+ embedPostToParent / embedShowWaiting
(auth 的 logout 在 embed 下通知父页面/显示等待态)—— 后两个从 main
迁出后,auth.js 对它们的 import 从 ./main.js 改指 ./embed.js
(auth 仍从 main import enterApp)。
- 反向 import main glue enterApp / loadTaskList / selectTask。
main↔embed、auth↔embed 均运行时调用环,安全。
main.js 删至 1154 行(2719 起,已搬出约 58%)。node 全检过、
import/export 一致性过、静态测试 2 过。剩 main 内:enterApp glue +
tasks(列表/选择/渲染消息)+ stream(发送/SSE)+ boot + Esc 关栈。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:35:01 +08:00
caoqianming
40fefdffef
refactor(dev): 前端模块化 Step 2 — 抽出 newtask.js(新建任务弹框)
...
任务名 / 工作目录(新建 sentinel 或复用已有 + 二级 input 联动)/ 描述 /
skill / 模型 select,提交 POST /v1/tasks(原 main 1146–1320)→
newtask.js(186 行)。
- 顶层自绑 hd-new 打开 / nt-go 提交 / 各 input 联动;唯一对外导出
loadFolderSuggestions(供 main enterApp 初始化顶部 filter-wd、files
复制/移动后刷目录)—— 它从 main 迁来后,files.js 对它的 import 从
./main.js 改指 ./newtask.js。
- 反向 import main glue loadModels(加 export)/ loadTaskList / selectTask
+ logout(auth)。
main.js 删至 1220 行。node 全检过、import/export 一致性校验过、
私有符号清零。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:24:29 +08:00
caoqianming
e5940266ca
refactor(dev): 前端模块化 Step 2 — 抽出 media.js + 收敛 downloadFile 反向依赖
...
对话内工具活动标签 + artifact(产物)抽取/渲染:toolActivityLabel /
extractArtifactRels / extractMediaBanner / renderArtifactBarHtml /
upgradeMediaArtifacts / downloadFile → media.js(237 行,原 main 1134–1359)。
收敛点:downloadFile 移入 media 后,preview.js / files.js 对它的 import
从 ./main.js 改指 ./media.js —— 把这条反向依赖从 main 挪开。media 导入极少
(escapeHtml / _categorize(preview)/ state / logout),与 preview 成
media↔preview 环(均运行时调用,安全)。
两次险漏靠校验抓回:
- 共享 const ARTIFACT_PRODUCING_TOOLS(main renderMessages/SSE 用 4 处,
.has() 访问非函数调用,"被调标识符"法漏掉)
- 内部函数 _flushMediaArtifactCache(selectTask 切任务清缓存用)
两者经残留符号检查发现后补 export。
新增全模块 import/export 一致性校验脚本(每个 import{X} 必在目标 export),
11 模块全过。main.js 删至 1393 行。node --check 11 模块全过、静态测试 2 过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:05:48 +08:00
caoqianming
71bac870ed
refactor(dev): 前端模块化 Step 2 — 抽出 files.js(文件面板 + 选入 + 拖拽上传)
...
右栏文件列表浏览/导航/删除/重命名 + 刷新 + "选入"弹框(跨目录勾选
复制/移动)+ 拖拽 overlay + 上传(XHR 带进度)+ 上传状态条。
代码原分散在 main.js 两段非连续区(1133–1459 文件列表/选入/拖拽 +
1697–1786 上传 helper,中间夹着 media 段)→ 合并进 files.js(433 行)。
- 导出 loadFiles / scheduleFilesRefresh(SSE 文件事件刷新)/
closeSrcPicker(main Esc 关栈)/ uploadFiles(聊天区粘贴/拖拽复用);
其余入口模块顶层自绑。
- 反向 import openFilePreview(preview)、logout(auth)、main glue
downloadFile / selectTask / loadTaskList / loadFolderSuggestions
(后三个加 export,后续随 tasks/newtask 模块化再迁)。
- 依赖分析用"段内被调标识符 − 段内定义 − 叶子/全局"全量提取,补回固定
清单漏掉的 loadFolderSuggestions / loadTaskList。
main.js 删至 1619 行。node --check 双过、main 残留 files 私有符号清零、
files 无未导入 glue、静态测试 2 过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 22:08:55 +08:00
caoqianming
9394e065f1
refactor(dev): 前端模块化 Step 2 — 抽出 preview.js(文件预览 + mini 预览)
...
文件预览主弹框(图/视频/PDF/文本/markdown/docx/xlsx,大文件降级下载,
docx/xlsx 走 loadScript 懒加载 vendor)+ 同时再开的小窗预览
(原 main.js 1687–2048)→ preview.js(379 行)。
- 导出 openFilePreview / openPasteFilePreview / closeFilePreview /
closeMiniPreview / _categorize(媒体段判图/视频用)。
- 反向 import downloadFile(main 媒体段,加 export)、logout(auth)。
- Esc 关弹窗栈处理器留 main(跨模块协调 chpw/选入/文件预览/小预览)。
- 一处去耦:deletePastedFile(留 main)原直接读 preview 私有
_fpCurrentRel/_mpCurrentRel 判断要不要关预览 → 改为 preview 导出封装
closePreviewIfShowing(rel),行为不变但不泄漏内部状态(唯一非纯剪切微调)。
main.js 删至 2034 行。node --check 双过、preview 私有符号在 main 清零、
无未导入 glue 引用、静态测试 2 过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:57:43 +08:00
caoqianming
16dc1719eb
refactor(dev): 前端模块化 Step 2 — 抽出 auth.js(首个 main↔模块 ES 环)
...
登录(邮箱密码 / UUID+PLATFORM_KEY 两 tab)+ 管理员加用户 + 改密码
三节(原 main.js 21–227)→ auth.js(218 行)。
- 各入口在模块顶层自绑 onclick;只导出 logout(供全局 20 处 401 处理)
/ closeChpwModal(供 main 的 Esc 统一关弹窗栈)。
- 反向 import main 的 glue:enterApp / embedPostToParent / embedShowWaiting
(main 给这三个加 export)——首次引入 main↔auth 循环依赖。三者皆 hoisted
函数声明,模块实例化即就绪,且只在运行时(点击/401)调用,绝不在顶层
求值时触发 → ES live binding 下安全。后续 features↔glue 环同理。
- main.js 删至 2397 行。node --check 双过、auth 私有符号在 main 清零、
静态测试 2 过。逻辑零改动。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:39:06 +08:00
caoqianming
cbb16b896f
refactor(dev): 前端模块化 Step 2(起)— 抽出 layout.js
...
三栏布局(pane 折叠 rail + 拖拽 splitter + 手机单列视图)是 main.js 里
唯一对其他功能节零出边的干净段,用它打样增量剥离。
- layout.js(121 行):import $ + 4 个 LS_*_COLLAPSED/WIDTH,只导出
mqPhone / setMobileView(后者供 selectTask 在手机宽下选中任务自动切
对话面板,是唯一跨模块调用)。折叠/splitter/mobile-tab 顶层事件绑定
原样保留(ES module 默认 defer,import 时 DOM 已就绪)。
- main.js:删 114 行 → 2606 行,加 layout import 并清掉随之不再用的
4 个 LS_* import。逻辑零改动,纯剪切 + 连线;node --check 过,
main 残留 layout 私有符号清零。
顺手修 Step 1 遗留测试失败:test_static_vendor 第二用例原只 grep
dev.html 找 formatContextStats / context_original_chars / cache_hit_tokens,
模块化后这些搬进 js/*.js → 改为扫 dev.html + js/*.js 合并源。2 测试全过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:27:51 +08:00
caoqianming
72ae41e122
refactor(dev): 前端模块化 Step 1 — dev.html 拆零构建 ES module(叶子优先)
...
dev.html 原 4087 行单文件原生 JS。本步只做"拆文件"(路径 1),
不引框架、不改逻辑——纯剪切 + import 连线。
抽出 4 个无依赖叶子到 web/static/js/:
- state.js state 单例 + LS_* + EMBED*
- format.js escapeHtml / humanSize / fmtTokens / usage 系列等纯格式化
- dom.js $ + 浮层菜单 showMenu/hideMenu(import escapeHtml)
- api.js api() Bearer 封装(import state)
- markdown.js renderMd / highlightIn(依赖 vendor 全局)
剩余主体(login→boot)落 main.js 并 import 叶子;dev.html 内联大
<script> 换成一行 <script type="module" src="js/main.js">,降到 1121 行。
app.py 加 mimetypes.add_type("text/javascript", ".js") 兜底,防 Windows
把 .js 判成 text/plain 致 module 被浏览器拒执行(本机实测本就 OK,纯防御)。
校验:6 模块 node --check 全过 + 无私有符号泄漏到 main。
后续 Step 2+ 再从 main.js 把 login/tasks/stream/files/preview 逐个剥成独立模块。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:55:20 +08:00
caoqianming
6f9ca26e3f
fix(dev): 改密码弹框复用选入文件的头/体/脚分隔布局
...
#chpw-modal 原先无专属 CSS,.card 只继承公共骨架,缺 padding/width/
表单整形,卡片被撑近全宽且无内边距。改为复用 #src-picker-modal 的
头/体/脚分隔布局(标题/按钮区带分隔线、表单包进 .body),.card 收到
400px + flex column,input focus 高亮。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:01:48 +08:00
caoqianming
f2b3675337
feat(dev): 顶栏自助改密码 + 选入按钮文字改图标(防换行)
...
改密码:web/auth.py::change_password(验旧密码 + bcrypt 重哈希,错误归一到
UserCreateError code);POST /v1/auth/change_password 挂 require_user,
user_id 取自 JWT 不信前端(旧密码错/无密码 403、弱密码 400)。前端顶栏
「退出登录」左侧加「改密码」按钮(并入 embed 隐藏规则)+ 复用 .modal 弹框
(旧/新/确认,前端先验长度与一致性,成功不登出,401 走 logout)。
选入:#btn-src-pick 文字「选入…」→ 单字符 ⊕(同 ⬆ ↻ › 风格,title 保留
语义),修窄面板偶发换行。
文档:PROGRESS / RUN(API 表 + 用户管理 + 两条兜底)/ DESIGN(auth API 清单)同步。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:07:21 +08:00
caoqianming
f81dd2def6
fix(usage): 缓存命中率同源(修 822% 怪值)+ 列表花费改 hover + backfill 加 --assume-cache-hit-rate
...
- 命中率怪值修复:原以 tasks.tokens_prompt 作分母、usage_events.cache_hit 作分子,
跨源(tokens_prompt 会被"清空对话"重置、usage_events 不重置)→ 算出 822%。
_usage_aggregates 改为同时返回 chat tokens_in/out,_task_dict 的 token 总量优先
取 usage_events 聚合(与 cache_hit 同源,命中率恒 ≤100%)
- 前端:任务列表行不再内联 ¥,花费/缓存命中率藏进 hover tooltip(taskUsageTooltip,
多行:输入/输出拆分·命中+命中率·真实花费);顶栏保留内联简版 + 同款 tooltip
- backfill: 加 --assume-cache-hit-rate RATE,对无 cache_hit_tokens 字段的老事件按
估算命中率折价(DeepSeek 当时缓存了只是没记,全价偏高);已记真实值的不受影响
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 08:41:51 +08:00
caoqianming
af2ad3cef1
feat(usage): 记账给前缀缓存命中折价 + 前端体现缓存命中/真实成本
...
排查"rust→PPT"task(flash,34 轮)发现累计 tokens_in 69.9 万里 88.6% 是缓存
命中,但 _fallback_chat_cost_cny 把命中段也按 input 全价算,记账虚高 2-3x。
- capabilities: 加 cache_hit_cny_per_mtoken(deepseek flash 0.1 / pro 0.2;
0=不区分按全价兜底,绝不少记)
- usage: 成本公式拆三段「命中×缓存价 + (input−命中)×input价 + output×output价」;
loop 把 cache_hit_tokens + 缓存单价透传进 record_chat_usage
- web: 不加 DB 列。app.py 加 _usage_aggregates(单查询 GROUP BY usage_events,
复用列表 msg_counts 批量范式,无 N+1)on-the-fly 算每 task 真实成本 + 缓存命中,
_task_dict 带出;dev.html 列表行显 ¥、顶栏 formatTaskUsage 显「tok·缓存命中%·¥」
- scripts: backfill_chat_cost_cache_discount.py 按 units 已存 token 重算历史
cost_cny(只改成本列,默认 dry-run,--apply 落库)
折价只对新 chat 事件即时生效;历史走 backfill 脚本(部署后跑)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 08:26:09 +08:00
caoqianming
1c30a9e54e
Reduce chat context token usage
2026-06-04 16:41:14 +08:00
caoqianming
af97dd7c62
feat(web): 文件面板底部展示用户已用存储 + 配额
...
后端已有 user_disk_usage 表(后台 15min 扫描落库)但无对外查询口,
加 GET /v1/user/storage(require_user)返 {bytes_used, file_count,
limit_bytes, scanned_at};limit_bytes 由 parse_bytes(quotas.
disk_bytes_per_user) 得,≤0/None=不限。disk_quota.get_user_usage
扩为返 (bytes,count,scanned_at) 三元组(复用而非新开函数,顺手改唯一
调用方 check_disk_quota 解包)。
前端 dev.html 右侧文件面板底部钉一条进度条+文字:#pane-right 改 flex
列让 file-list 独占滚动、存储条钉底;loadStorage() 在 enterApp 拉一次;
不限额时只显已用、隐进度条;超额变红;hover 显文件数+统计时间。样式用
class 选择器压低特异性,让折叠/手机隐藏规则能盖住它。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:00:36 +08:00
caoqianming
33bea27d85
fix(web): embed 模式登录页一闪而过 — body 首行同步隐藏 #login
...
#login 默认 display:flex(带 login-in 动画),加 body.embed-mode 隐藏它的
embedInit() 在 body 末尾才执行;单文件 3800+ 行,浏览器常在解析到底部脚本前
就先把登录卡画出来,造成"一闪而过"。改法:在 <body> 第一行加一段同步内联脚本,
?embed=1 时立即 add embed-mode,赶在 #login 解析/绘制之前隐藏它。
只是绘制闸门,底部 embedInit(postMessage 握手 / embed-waiting / token 分支)
完全不动,embed-mode 幂等。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:23:32 +08:00
caoqianming
e6eebbc9ad
feat(web): tool_call 标题行改显中文活动描述 + 修 args 字段 bug
...
dev.html 实时流读 ev.data.arguments 但后端 emit 的是 args,字段名对不上
导致参数永远为空、连带 artifact 路径提取失效。新增 toolActivityLabel 按
12 个工具的关键参数套中文动词(执行命令/运行 Python/读写编辑文件/查找搜索
/联网搜索/抓取网页/加载技能/生成图像视频),实时流与历史回放两处同步;完整
参数仍在折叠 <pre> 里。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 12:24:54 +08:00
caoqianming
c4229bede4
skills: /v1/skills 每次现扫,加/删 skill 目录无需重启
...
lifespan 静态快照改 per-request 扫描,SkillRegistry 构造 ~3ms / 次,
e2e ~20ms 在 JWT + DB 噪声里完全可忽略。agent_builder 早已每次新建
registry,本次只补前端下拉这一处。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:47:47 +08:00
caoqianming
da4e47139f
dev SPA: embed+task_id 模型下拉 + 导出/清空键 disable 修复
...
- loadModels 收尾若有 task 选中补一次 renderChatMeta,修 embed+task_id 模型下拉
在 models 接口 resolve 前 selectTask 已跑过的竞态(同步覆盖生图/生视频下拉)
- btn-export/btn-clear-msgs 不再按 n_messages 门禁,只要选中 task 就常亮;清空
保留 running 期禁用。修历史 bug:清空后两键 disable,新对话进来 taskMeta 不
重渲一直灰
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:42:19 +08:00
caoqianming
eaf7f3ea1e
Stage C 收尾包:资源 yaml + 磁盘配额 + 网络放开 + 容器内源持久化
...
dogfood + 信任同事白名单阶段 Step 4 完整 egress proxy 暂不做(沉淀为升级触发
信号:任一陌生用户注册 / 模型异常 outbound / 信任白名单出现非密切相识者 → 必上)。
本批 3 件:
(A) 容器资源 yaml 化(可调不重 build):
- agent.yaml 加 sandbox 段(memory/cpus/pids_limit)
- SandboxPool ctor 加三字段,优先级 env > yaml > 默(2g/1.0/256)
- setup_pool/init_pool 透传 sandbox_cfg
- sandbox check 输出加 [info] 4 行给运维一眼对账
(B) 应用层磁盘配额(§7.5 #4 软配额):
- migration 0008 user_disk_usage 单行 per user
- core/storage/disk_quota.py:parse_bytes("5gb"/int)+ scan_user_dir
(os.scandir 跳顶层 .zcbot_tmp / .memory)+ upsert ON CONFLICT
+ check_disk_quota + scan_all_users 串行
- lifespan _disk_scanner 后台 task(启动跑一次 + 默 15min 周期)
- DockerExecutor write/edit 起手 gate 超额 [Error] 不调容器
- /v1/files/upload 同款 gate 超额 HTTP 413
- yaml `quotas.disk_bytes_per_user: 5gb` + `disk_scan_interval_seconds: 900`
- race 接受:扫描间隙写入轻微突破(image/video 配额同款 race-tolerant);
外部用户开放前 OS 层 xfs prjquota 兜底
- 11 测试 covered parse_bytes / scan / 跳 dotfile
(C) 网络放开 + 容器内源持久化:
- network.py 去 --internal flag,容器走 docker bridge default 有 NAT outbound
- 已存在 internal network 不自动 rm 仅 warn,RUN.md 给迁移命令(避免破现有容器)
- iptables 红线段不动(169.254/127/10/172.16/192.168/100.64/PG_IP DROP),
挡 cloud metadata + 内网扫描 + loopback,基线不依赖 proxy
- Dockerfile 加 /etc/pip.conf(global index-url + timeout 60) + /etc/npmrc
(global registry),让运行时模型 `pip install foo` / `npm install bar`
也走 mirror(此前 --build-arg 只 build 时生效)
unittest discover 46/46 PASS(原 35 + 新 11)。
DESIGN 不动(延后决策仍在 §7.7 Stage C 阶段语义内,触发信号沉淀进
PROGRESS / RUN);RUN.md 加 env 列表 + 网络迁移 + 配额 + 故障兜底 3 行。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 08:35:53 +08:00
caoqianming
23ff996d38
Stage C Step 3d: fs 工具进容器 + DESIGN §7.5 #6 重写(物理边界替代代码护栏)
...
Ubuntu dogfood 暴露 host 工具漏底:base_dir=Path.cwd() 无 user_root 校验,
模型 glob "*" 列出 host /home/lighthouse/zcbot/.git/.venv/... zcbot 源码自身。
DESIGN §7.5 #6 原写"host 工具走 paths.py::resolve_user_path 校验"是假命题
(代码里没那函数),绝对路径完全不挡。
修法:fs 工具(read/write/edit/glob/grep)也走 docker exec,物理边界替代
代码护栏(Phase B path validator 那条不做 ── 脆弱)。
- core/sandbox/tool_runner.py 新增:容器内 helper,stdin 接 JSON args,
调 tools/fs.py 的 Tool 子类;base_dir=cwd,user_root=/workspace
- DockerExecutor 加 FS_TOOLS 信任域 + _exec_fs_tool:docker exec -i ...
python /sandbox/tool_runner.py <name>,stdin 喂 JSON args(CJK / 引号
透明传不被 shell metachar 切)
- _run_subprocess 加 stdin 参数 + is_fs_tool 分支返 stdout 直透(原 Tool
返回串语义保持),exit≠0 stderr 当 ToolResult content
- SandboxPool 加 repo_root 字段 + <repo>/skills:/sandbox/skills:ro mount
让容器内 read SKILL references 能解析
- Dockerfile COPY tools/ /sandbox/tools/ + tool_runner.py(build-time COPY
而非 mount ── 容器内代码不应跟随 host repo 改动)
- web/app.py 透传 ROOT 给 init_pool
- 留 host 的工具:load_skill(SkillRegistry 内存查找)/ web_search /
web_fetch / seedream / seedance(持 Bocha/ARK key 不入容器)
- DESIGN §7.5 #6 重写:"几乎所有工具进容器,host 只留持 key + 跨 user 的",
原假命题溯源标注 2026-05-26 修正
代价:每 fs tool call +~200ms docker exec overhead,对话级 N≤15 总 1-3s,
LLM 推理 5-30s 下噪声。升级触发(§7.9 升级表)docker exec → unix socket RPC
仍按原信号(overhead/total > 30% 持续 / 长驻服务工作流)。
测试:test_executor_docker 加 4 fs 路径测试(argv 形态 / CJK stdin JSON /
exit≠0 stderr 透传 / timeout);改原 read 直通测试 → load_skill 直通
(read 现在进容器)。unittest discover 35/35 PASS。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 21:56:41 +08:00
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
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
f61503fbdb
ui(dev SPA): 任务/文件行 ⋯ 下拉菜单 + 顶栏长名截断 + 聊天上传按钮 + 工具调用刷新右侧
...
- 单例浮层菜单 (position: fixed) 避开 pane overflow 裁剪
- 任务行 ⋯:完成/废弃/导出 docx/删除 (4 色, 按 status/消息数 disabled)
- 文件行 ⋯:重命名/下载(仅文件)/删除, 替代原内联按钮
- pane-head .label 加 nowrap+flex-shrink:0;files-proj 长项目名 11 字截断+title 全名
- chat-upload 复用同一 upload-input, 上传到右侧当前目录
- tool_result 触发 scheduleFilesRefresh (debounce 500ms)
- 重构 setTaskStatus/deleteTask/exportTask 接 tid 参数, 中间 pane 按钮共用同组函数
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:50:45 +08:00
caoqianming
fafcb14d86
skill(proposal): mermaid 文件名 hash→caption + quality_check 加图相关 4 拦截 + SKILL.md 精简; web cache fix
...
用户报"图没渲染到 docx",诊断后修三件事(同一根因链):
- web/app.py /v1/files/download 加 Cache-Control: no-cache
Starlette FileResponse 只发 ETag/Last-Modified, 浏览器走启发式缓存,
workspace 文件改了 SPA 预览看不到新版
- quality_check 新 check_figures(): 4 条规则
1) figures/ 有 png 但 sections 0 个 ![]() 引用
2) fenced 代码块出现 box-drawing 字符 (┌─┐│└─┘ 等)
3) mermaid 块必须有首行 %% caption: <题>
4) 同 task 内 mermaid caption 不能撞名
- render_diagrams.py: hash → caption 命名
pass-1 验证 caption 完整 + 全 task 唯一, 缺/撞 退 2
pass-2 渲染落 fig_<sanitized>.png, 总是覆盖
- render_docx.py: mermaid 块按 caption 查 fig_<caption>.png
无 caption / 清洗空 / png 缺 → ASCII fallback
- SKILL.md ~193 → ~160 行:
插图段 49→22 行(压 matplotlib 细节 + 删类型选择展开)
反模式合并 ASCII/占位/手写图编号/缺 caption/撞名
删"为什么两段式"长说理段
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:19:09 +08:00
caoqianming
15bbadf6d6
ui(dev SPA): 文件点击弹框预览(image/pdf/text/md/docx/xlsx, 其它 fallback)
...
原行为 click → 走 downloadFile 直接落盘,不能在线看。
现 click → openFilePreview(rel) 打开 #file-preview-modal(90vw × 90vh),
按扩展名分派渲染器:
- image (jpg/png/gif/webp/bmp/svg/ico) → <img> blob URL
- pdf → <iframe> blob URL + application/pdf mime
- text 类 (~30 种 txt/log/json/yaml/code) → <pre> textContent (2MB cap)
- md → 复用 renderMd(marked + DOMPurify + hljs)
- docx → 懒加载 jszip + docx-preview → renderAsync 到 DOM
- xlsx/xls → 懒加载 SheetJS → 多 sheet tab + sheet_to_html
- 其它 (pptx/doc/ppt/...) → fallback "暂不支持在线预览" + 下载按钮
机制:fetch /v1/files/download 取 blob 绕 auth header 限制(后端不动);
懒加载 vendor 脚本(_scriptCache 防重入,失败 fallback);
_trackBlobUrl + _flushBlobUrls 弹框关时统一 revoke 防泄漏;
Esc / 点 backdrop / × 三种关闭路径;
auth 401 → logout;binary 50MB / text 2MB 上限兜底防 OOM。
pptx 整个社区 JS 库都不成熟(动画/复杂版式失真),先 fallback,
真有需求再上服务端 LibreOffice 转 PDF 统一处理。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 08:25:20 +08:00
caoqianming
e3215e023a
vendor(office preview): jszip 3.10.1 + docx-preview 0.3.6 + xlsx 0.18.5
...
dev SPA 文件预览所需的三方 JS 库,入 git 锁版本:
- jszip 3.10.1 (MIT) — docx-preview 依赖
- docx-preview 0.3.6 (Apache-2.0) — docx → DOM 渲染
- xlsx 0.18.5 (Apache-2.0, SheetJS 社区版) — xlsx → HTML table
总共 ~1MB,前端按需懒加载(仅 office 文件首次打开才拉)。
项目无 npm 工具链,直接 vendor 比 fetch 脚本简单。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 08:25:07 +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
49be5e01e4
ui(dev SPA): SSE 回复结束后右侧文件面板自动刷新
...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 16:51:36 +08:00
caoqianming
b057c3bd06
core(/v1/skills + dev SPA): GET /v1/skills + 新建任务弹窗 skill 字段改下拉
...
- web/app.py: lifespan 启动扫一次 SkillRegistry 挂 app.state;新增 GET /v1/skills(JWT 鉴权)
- web/static/dev.html: nt-skill input → select + loadSkillOptions 缓存到 state.skills
- PROGRESS: 记录
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 16:27:10 +08:00