Commit Graph

485 Commits

Author SHA1 Message Date
caoqianming ff3bc75fed 合并工作目录新建入口 2026-07-29 14:00:04 +08:00
caoqianming 03ee37e201 改进工作目录可搜索选择 2026-07-29 13:48:16 +08:00
caoqianming d75df5a4b4 优化新对话目录选择 2026-07-29 13:38:20 +08:00
caoqianming cfc1d17af7 优化工具失败观测与重复保护 2026-07-29 09:29:44 +08:00
caoqianming 6f2840a5d0 统一运行生命周期入口 2026-07-28 15:34:42 +08:00
caoqianming 7cfeebfa89 增强消息运行可靠性 2026-07-27 17:50:31 +08:00
caoqianming 70683b7d09 增强知识库文件写入一致性 2026-07-27 16:37:56 +08:00
caoqianming ade3a847f6 Fix web search date handling 2026-07-27 12:50:07 +08:00
caoqianming c3a6f7deee Add workspace-first new conversation flow 2026-07-27 12:28:56 +08:00
caoqianming 85c918dc2f Add safe conversational workspace rename 2026-07-27 11:23:20 +08:00
caoqianming 98713bb7fa feat(seedance): support image-to-video references 2026-07-27 10:50:13 +08:00
caoqianming d4965e196c Add Codex project guidance and memory 2026-07-27 10:38:31 +08:00
caoqianming eab623d5b5 fix(web): kb 条目与单篇页补显原件文件名
已入库条目此前只把 source 用于查看/下载按钮,原件名(含后缀)无处显示。
标题后加灰色小字原件名(悬停提示同步带上),单篇页头部加「原件: xxx」,
新增 .kb-src-name 样式两处共用;不破坏单行精简版式。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 16:17:39 +08:00
caoqianming e03a1ee7eb docs: bump 0.59.4 + CHANGELOG(kb 冷启动/查看原件/条目精简)+ PROGRESS
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:50:05 +08:00
caoqianming 0c74a44224 style(web): kb 已入库条目精简为标题+单行摘要
- 行内去掉关键词、摘要 CSS 单行省略(原 .sk-desc 省略规则只覆盖左栏
  .sk-item,右栏 kb-entry 整段换行铺开导致每条过高)
- 完整摘要+关键词挪进悬停 title,信息不丢

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:45:05 +08:00
caoqianming 0ceb4c5bd9 feat(web): kb 查看原件(复用文件预览弹框)+ 预览层级抬到 modal 之上
- kb.js: 已入库条目行/单篇页/待入库列表三处加「查看原件」,直接
  openFilePreview(.kb 相对路径,与下载同通道 /v1/files/download)
- dev.html: file-preview-modal z 90→114(盖过 kb/skills/memory 等 112 层
  modal,仍低于 app-dialog 130)、fp-open chat-form 95→115、mini 96→116
- main.js: Esc 关栈顺序同步(小预览→文件预览→各 112 层 modal→选入→新任务)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:38:37 +08:00
caoqianming e0aa8130af fix(kb): 零库时注入冷启动契约,修「放进知识库」被误写进记忆
根因:kb_block 零库返回空串,agent 不知道 KB 机制存在,用户点名"知识库"
被就近理解成记忆写进 .memory/(真实用户事故)。与 memory 空契约常驻同一课。

- core/kb.py: 新增 _KB_COLD_START(建库步骤 + INDEX 行格式 + KB/记忆分工,
  ~百 token);kb_block 零库分支改注该契约,有库分支不变
- tests/test_kb_block.py: 锁两分支(冷启动文案/路径展示/全量 INDEX/空库)
- DESIGN.md §3.8: "有库才注入"取舍更新为冷启动注入 + 事故记录

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:31:50 +08:00
caoqianming 0cd5b07124 feat(web): kb 下载入口(原件+解析 Markdown)+ bump 0.59.3
- kb.js:已入库条目行「⬇ 下载原件」、单篇页头「下载原件/下载
  Markdown」、待入库列表逐项下载;复用 media.js downloadFile 走现成
  /v1/files/download(.kb 在 user_root 下,safe_join 天然放行),零后端改动
- dev.html:新按钮三行 CSS(hover 淡入同删除按钮、待入库行间距)
- 弃选:kb 专属下载端点(与 files 下载重复,公测期白养对外契约)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 21:33:23 +08:00
caoqianming 8751f71f5a feat(web): 移动端对话附件入口(拍照/照片库/选文件)+ bump 0.59.2
chat 输入区此前仅粘贴/拖拽两条附件路,移动端双双不可用。加 [附件] 按钮:
触屏弹三项菜单(拍照 capture=environment / 照片库 accept=image,video /
选取文件无 accept),桌面端直开文件选择器;三入口 change 统一走既有
uploadAttachFiles 链路,只读镜像随 applyChannelComposerLock 禁用。
纯前端改动,无后端/schema 变化。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:49:16 +08:00
caoqianming 1332139c9e docs: bump 0.59.1 + PROGRESS 汇总 15 commit 重构 + DESIGN §2 架构树同步
- core/__init__.py 0.59.0 → 0.59.1(patch:全部内部重构/测试/修复,对外行为零变化)
- PROGRESS:07-23 条目(七项重构 + 2 bug + 测试 286→351 + 测试库基建)、
  文件清单刷新(loop/llm_transport/tool_registry/storage 三分/web routers 布局)、
  最后更新行
- DESIGN §2:架构树补 llm_transport/tool_registry/storage 三分/web 拆分后布局
  (实施后与代码偏离,按「同步改回」条款修正);CHANGELOG 按「纯内部重构/修复
  不记」规则不动

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:29:19 +08:00
caoqianming bcd2b90942 refactor(storage): 失败埋点拆出 telemetry.py + kind 契约常量化;补 wecom 加解密测试
收官三小项(架构审查残余):

- core/storage/telemetry.py:四个留痕函数(malformed/salvaged/empty/run_error,
  cost 恒 0)从 usage.py 迁出——「usage=计费」心智不再被埋点污染(数据层 Top4);
  kind 字符串升为常量,loop/llm_transport(写)与 toolfail(读 SQL)两侧共用
  同一事实源,此前靠字面量约定、改名会静默漏读。core.storage.__init__ 转发
  出口不变,调用面仅 web/runs.py 一处改 import
- tests/test_wecom_crypto.py:7 用例,企微回调加解密(WXBizMsgCrypt 等价实现)
  测试侧按同方案自造密文,覆盖验签/receiveid/padding 各失败路径——wechat 子系统
  (审查 Top3 零测试)中唯一可纯测的一块补上
- DB 测试探针补 connect_timeout=5:测试库挂掉时快速降级 skip,不再把整个
  discovery 挂死在 TCP 建连上(Docker engine 打嗝时实测挂死过)

带测试库 351 全过 / 无测试库 341 过(DB 组干净 skip)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:21:45 +08:00
caoqianming fd79edb344 test(web): DB 面路由测试(tasks CRUD/files DB-aware/upload)+ 修 restore 500
测试库基建落地(docker postgres:16-alpine @5433 + migration 0001-0022,
RUN.md 补一键命令),tests/test_web_routes_db.py 10 用例补齐 nodb 套件留待面:
- tasks CRUD 全链:建(默认模型/working_dir)/列(分页壳)/详(上下文压力
  字段)/PATCH 校验/软删→列表消失→恢复/同 wd 共享 + folders 计数
- files 顶层目录 DB-aware(§7.4):rename 级联改 tasks.working_dir(tasks_
  updated=1 且 DB 生效)、running→409、move 被引用→409、递归删被引用→409、
  软删 task 后放行
- upload 配额 gate 路径 + 根目录列表(system_wd_names)+ 非法文件名 400
- clear/cancel 状态闸、models 档位列表、schedules 404 路径

修 bug(本套件抓出的存量缺陷,重构前即有):POST /v1/tasks/{id}/restore
用 ORM 脏标记恢复,updated_at 是 server-side onupdate,flush 后列被标记
expired,session 关闭后序列化读它抛 DetachedInstanceError——真软删过的
任务恢复必 500(幂等路径无脏标记故此前未暴露)。修法:flush+refresh 后
在 session 内完成 task_dict 序列化。

带测试库全量 344 全过;未设 ZCBOT_TEST_DB_URL 时干净 skip;测试库零残留。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 13:50:57 +08:00
caoqianming 0d34141527 test(web): 路由级测试首批(无 DB 面)——鉴权门全覆盖 + kb/skills/memory/files FS 路径
拆分 router 后的接线回归(审查 Top1 风险面此前零路由测试):
- 公开端点:/healthz、/v1/changelog(limit clamp)、/ 302
- 鉴权门:11 个 router 的 19 个代表端点无 token 一律 401(哪个模块漏挂
  Depends(require_user) 当场红);坏 token 401、坏 platform_key 403、
  user_id 非法 400(落库之前拦下)
- FS 类业务:kb 建/详/删/非法名、skills 列/详/内置不可删、memory 只读+穿越
  404、files 子目录列/下载/越界 400/非顶层改名/拷贝 409 预检/删文件/非空目录 400
- 隔离:JWT 与 app 同源 env 直签(不假设 key 值,兼容 litellm dotenv 抢先加载);
  随机 user_id 子树 teardown 整树删;TestClient 不进 with(不跑 lifespan 不碰 DB)
- 顶层目录 DB-aware 分支与 upload 配额留给 ZCBOT_TEST_DB_URL 套件

334 测试全过。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 13:34:07 +08:00
caoqianming 21e7dad7f1 refactor(rendering): 块收集器与 inline 切分下沉 common——golden 零 diff 验收
审查 P1:docx_brief 与 docx_manuscript 的 render_md_block 各手写一套块级
扫描,同一 md 走两条路径最易漂移。按「分派语义留 profile、边界收集归 common」
拆分:

- common 新增 gather_fence(闭合判据单一事实源)/ gather_table /
  gather_blockquote / gather_paragraph(软换行并合,is_list 谓词注入)
  ——此前两份逐字拷贝的循环全部换成共享调用
- brief 的 add_inline_rich 手写 INLINE_RE 循环换 common.parse_inline 迭代
  (与 manuscript 同一切分事实源);brief 差异收敛到 plain 段处理
  (引文上标 + 化学式)一处
- 刻意不动:分派顺序与 brief 状态机(in_refs/expect_meta/in_tldr 会改变块
  边界语义,是 profile 的产品行为不是重复)、各 profile 样式

验收:golden 基线(上一提交)逐字节零 diff;319 测试全过。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 13:27:31 +08:00
caoqianming 035aec44ac test(rendering): golden 基线——四 profile 渲染富 fixture,docx XML 逐字节对账
给 md 解析路径的后续重构上硬闸(§8.6 当年"前后字节一致"验收的固化):
- fixture 覆盖信息带/TL;DR 卡/判断 callout/blockquote/表格/代码块(含 mermaid
  回退)/缺图占位/四类列表/参考文献 DOI·URL 条目/相邻引文上标/化学式/软换行
  并合/refs 段外 [n] 行等全部分支
- 对账面 word/document.xml + footer*.xml(无时间戳,确定性已验证:连续两次
  渲染逐字节一致);core.xml 元数据刻意不比
- 5 份 golden(brief 彩/黑白、paper zh、proposal、report+toc);有意变更走
  ZCBOT_REGEN_GOLDENS=1 重生成并在 commit 里说明,重构类改动必须零 diff

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 13:24:26 +08:00
caoqianming 5a5ebfbe24 refactor(tools): 输出处理原语移出 base.py——base 只留 Tool 基类与路径边界
compact_tool_output / format_timeout_result 是「工具输出处理」关注点,与
Tool ABC / FileOutOfBounds 路径安全混装在 base.py(审查小项)。析出
tools/output.py,8 处引用(5 工具 + executor_docker + 2 测试)同步改。
318 测试全过。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 13:15:04 +08:00
caoqianming 8038fb7491 refactor(tools): 媒体工具同构析出 tools/media_common.py + 17 个单测
审查 P1#6:seedance/seedream/gpt_image/look_at_image/read_document 五处
同构(逐字或参数化后逐字)收敛为共享原语:

- quota_gate(每日配额闸,文案逐字保持)/ stamped_path(<ts>-<rand6> 落盘
  命名+建目录)/ write_meta(.meta.json)/ record_usage_safe(记账失败不
  阻塞)/ ark_chat_with_retry(超时透明重试,业务错误不重试)/
  extract_chat_answer(chat 文本+截断标志)/ find_first_url(递归找 URL,
  accept 谓词覆盖 seedance 的 video_url 优先语义)
- 五工具各自的 banner 格式 / body 组装 / seedance 轮询刻意保持不动
- tests/test_media_common.py:17 个纯函数/打桩单测(配额文案、重试次序、
  业务错误不重试、视频 URL 谓词等)

318 测试全过;真实配置冒烟 32 工具挂载不变、schema 完整。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 13:10:32 +08:00
caoqianming 7568ceeb48 test(scheduler): 补 core/scheduler 服务层测试(审查 Top3 缺口)+ DB 测试门控收紧
- tests/test_scheduler.py:纯逻辑(cron 校验/next_run 墙钟时区折算/run message
  包装)+ DB 级(create 校验、claim 推进 next_run 不重复认领、disabled/软删/
  过期不认领、连续失败自动停用、skipped 不动计数)。认领类用例统一用 2000 年
  纪元时间,claim_due_jobs(now=纪元) 只可能命中测试 job,永不触碰真实 job
- **DB 测试门控收紧(test_scheduler + test_usage_report)**:只认显式
  ZCBOT_TEST_DB_URL,绝不回退 .env 的 ZCBOT_DB_URL —— 实锤教训:.env 的 URL
  经隧道指向生产库,测试插入的到点 job 被生产 green 实例调度守护认领并真跑了
  agent(4 次一句话迷你调用,费用几厘,产物已按测试专属 user 全量清理,零残留)
- RUN.md:环境段补 ZCBOT_TEST_DB_URL 说明,故障兜底表加一行

301 测试全过(未设测试库时 DB 组自动 skip)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 13:00:13 +08:00
caoqianming 8300631a1c refactor(core): export_docx 字体助手复用 rendering.common——消除逐字重复
_set_run_fonts 与 rendering/common.py::set_run_fonts 逐字相同,改为 import
复用(单一事实源)。方向说明:rendering/ 会 bind-mount 进沙箱、必须保持无
core 依赖,故 export_docx(读 PG)留在 core、反向引 rendering —— 修正架构
审查里「迁 rendering/docx_chat.py」的建议,搬过去会破坏该层的沙箱可用性。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 12:51:04 +08:00
caoqianming 9e680314eb chore(scripts): 一次性诊断/backfill 归档 scripts/archive/——一级目录 50→29 个
已结案调查(RepeatGuard/salvage/GLM 禁 thinking/mermaid loopback 均已上线)
的取证脚本与跑完即废的 backfill 共 21 个移入 archive/ 留证;附 README 说明
归档判据。保留:可复用运维诊断工具、DESIGN 引用的协议探针(probe_clawbot*/
probe_ark_doc)、通用取数 diag_dump_task。.gitignore 补 scripts 探针输出。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 12:48:46 +08:00
caoqianming cac0bfcfe4 refactor(core): loop.py 传输健壮性层析出 llm_transport.py + _execute_tool_call 拆分
架构审查 P1#4:loop.py 把「provider wire 层瞬态故障自愈」与「agent 控制流」
塞在同一文件;_execute_tool_call 一个函数线性堆 10 个关注点(148 行)。

- 新增 core/llm_transport.py(438 行):畸形/必填 key 被吞/空响应检测、三类
  故障留痕、usage/delta 提取、robust_stream 重试策略(首败降级非流式 +
  salvage 可救当轮续)。取流两路径与 salvage 以 callable 注入——不 import
  loop,单测在 AgentLoop 实例上打桩 _collect_stream_once/_nonstream_once
  的现有缝隙原样保留
- loop.py 1158→812 行,回归 ReAct 主干:_stream_llm 只留上下文压缩准备
  (context 关注点),wire 健壮性委托 robust_stream;_collect_stream_once/
  _nonstream_once/_try_salvage_response 留在 loop(持 llm/emit 态 + 测试缝)
- _execute_tool_call 148 行拆为编排 + 4 个正交方法:_check_repeat_block
  (两道拦截)/_maybe_skill_model_switch(热切)/_repeat_feedback(登记+
  软提示)/_maybe_pptx_guard(产物机检)
- 4 个测试文件 import 指向同步更新(is_empty_response 等改公有名)

292 测试全过(loop 重试/空响应/repeat-guard/salvage 套件覆盖改动路径)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 12:44:22 +08:00
caoqianming 1197d73432 refactor(core): 工具注册声明式化 core/tool_registry.py——四种 env-gate 风格收敛为一张表
架构审查 P0#3:build_agent 内 ~220 行注册代码,同一个「有 key/开关才注册」
意图混用四种写法(inline getenv / *_configured() / Config.load() 返 None /
caps 开关),范式靠注释口头传承。

- 新增 core/tool_registry.py:注册表 = (组名, gate, factory) 列表。gate 统一
  零参 bool(§7.5 #7 "secret-bearing 工具仅 env 存在才注册"由此获得代码强制);
  factory 内做 variant 选择(seedance/vision 取 yaml 段),选不出返 [] 自然不挂;
  新增工具 = 表里加一行
- ToolContext 承载 build_agent 已解析的上下文(路径/身份/媒体配置),注册层
  不重复读 env/yaml
- agent_builder.py 892→649 行:注册块换成一次 build_tools 调用,26 个工具类
  import 随之迁走

验证:292 测试全过;真实配置冒烟 32 个工具逐名符合预期,scheduled_run
门控(少 4 个 schedule_*)生效。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 10:36:38 +08:00
caoqianming 7354578aaa refactor(storage): 计费读侧收口 core/storage/usage_report.py + 首批 DB 级测试
风险点(架构审查 P0#2):UsageEvent.units JSONB 的 key 由 usage.py 写入,
读侧 cast(units[...].astext) 却散在 web/admin.py 与 web 各处硬编码——写读
跨文件隐式耦合且零测试,改 key 会静默算错计费统计。

- 新增 core/storage/usage_report.py:units 读侧唯一出口(列表达式单一事实
  源),含 task_usage_aggregates(逐 task 批量)/ usage_overview(全局+7d
  趋势)/ models_usage(按模型)/ user_usage_page(按用户分页)
- web/admin.py 三个内联聚合函数删除,改调 usage_report;web 层不再出现
  任何 JSONB cast(grep 已核)
- web/common.usage_aggregates 移除,tasks/schedules 路由改引 core 版
- tests/test_usage_report.py:6 个 DB 级测试锁口径(cost 全 kind 合计、
  token/cache_hit 仅 chat、task_id 可空的 kb_ingest 不进 task 聚合、cutoff
  过滤、用户分页含档案字段)。无 PG 自动 skip;只插/删测试专属 user 的行。
  发现并记录:ZCBOT_DB_URL 平时靠 import litellm 的隐式 dotenv 进 env,
  测试显式从 .env 抠 key 不背 8s 重依赖。

292 测试全过,测试数据零残留。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 10:33:13 +08:00
caoqianming 546cb34d94 refactor(web): lifespan 后台协程按域析出——app.py 收敛为纯编排(732→200 行)
- web/scheduler_runner.py:定时任务执行引擎(claim→抢锁→run_agent_bg→
  超时协作 cancel→投递记账),start_scheduler 一个入口
- web/wechat_runner.py:ClawBot 入站长轮询 + PG advisory lock 选主
- web/background.py:磁盘扫描/线程池监控/工具失败巡检/sandbox 池初始化+
  reaper/proc 清扫/孤儿 run 收割/优雅 drain,统一 start_*/cancel_and_wait
- app.py lifespan 只剩:executor 接管、state 装配、协程起停编排、收尾顺序

行为零变化(启动钩子与关停顺序逐一保持);286 测试全过,TestClient
lifespan 起停冒烟干净。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 10:24:36 +08:00
caoqianming 25d8a6572f refactor(web): app.py 拆分——73 路由迁 11 个 router 模块,3972 行减至 732
照 admin.py 的 register_*_routes 注册范式把 create_app 巨型闭包拆开,
依赖(require_user/auth_cfg)显式传参,对外 /v1 契约零变化:

- web/routers/:misc/models/authroutes/wechat(含企微)/kb/schedules/
  skills_memory/files/asr/tasks/messages 共 11 个模块
- web/common.py:跨 router 常量与 helper(task_dict/usage_aggregates/
  sse_event/assert_owns_task 等)
- web/schemas.py:13 个 Pydantic 请求模型独立成文件
- web/model_gate.py:模型档位门控 + image/video variant 解析
- web/runs.py:run_agent_bg + run_channel_conversation(BG worker 与
  渠道入站对话核心,从 app.py 模块级函数析出)
- web/userfiles.py:路径安全原语(safe_join 越界校验/transfer 预检)收口
- app.py 只剩 App 工厂 + lifespan 后台协程群

验证:286 测试全过;路由面 81 条逐条一致;TestClient 冒烟
(healthz 200/坏 platform_key 403/未鉴权 401/lifespan 正常启动)。
test_changelog/test_pptx_render 的 import 指向同步更新。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 10:18:57 +08:00
caoqianming 2e063e2ac0 fix(prompt): Word 渲染段命令行按 backend 换路径——host 不再注容器路径
_build_system_prompt 渲染段原硬编码 /sandbox/rendering/render.py,host 模式
(本地 dogfood)下该容器路径不存在,agent 照调必 FileNotFoundError。
按 is_docker 分支:docker 原样不变;host 换 venv 解释器 + 宿主真实路径。
skill 文档里的 /sandbox 写法是 docker 专属管线,不动。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 16:13:56 +08:00
caoqianming 6e455d37be fix(kb): 注入契约改主动查阅——相关即先读无需点名,去掉 document_search 路由
- _KB_CONTRACT 第一条换成主动性规矩:INDEX 条目与问题相关就先 read 正文再答,
  用户无需说"从我的知识库里查";此前契约只写"怎么用"没写"何时主动用",
  flash 容易把 INDEX 当背景略过
- 去掉契约里的 document_search 路由条(契约只管 .kb 自己怎么用,路由靠各自
  工具描述自然分流,少一层耦合)
- DESIGN §3.8 两处描述同步收窄

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 15:43:01 +08:00
caoqianming c6232d8325 feat(kb): 个人知识库——.kb/ 纯文件机制+/v1/kb* API+前端 modal+注入契约(bump 0.59.0)
- core/kb.py: 状态/视图层(INDEX 单行格式 parse/format、已入库判据=INDEX 有条目、
  删单篇连带原件与索引行、kb_block 注入——有库才注)
- core/kb_ingest.py: 入库管线 markitdown→扫描件 OCR 兜底(方舟)→flash 摘要(失败降级
  不阻塞)→追加 INDEX;编排 to_thread+per-(user,库) 锁,幂等可续跑
- 0022 迁移: usage_events.task_id 放宽可 NULL(kb 入库无 task 上下文);
  记账溯源 kind="kb_ingest"/"vision" + units {"kb","source"}
- web/app.py: /v1/kb* 8 端点(列/建/删库、详情带进度、上传即入库、手动 ingest、看/删单篇),
  不设 HTTP 检索端点(agent 走 fs 工具)
- 前端: kb.js 两栏 modal(上传 XHR/进度轮询/删除确认);rail 左下新增知识库入口,
  四按钮改图标+小字两行布局
- agent_builder 在 memory_block 后注 kb_block(docker/host 路径换算同 .memory);
  documents skill「何时不用」补自建资料路由行
- DESIGN §3.8 机制小节 / CHANGELOG 0.59.0 / PROGRESS

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 15:20:43 +08:00
caoqianming 5d2560985f fix(ppt): zongyuan_red 模板配色松绑——品牌红锁骨架+辅助色自由选补锁
模板调色板唯一彩色是红,spec_lock 锁死后 Executor 图表/图标/强调全被迫用红。
拆成品牌恒定色(红+灰阶,版式骨架)与辅助色(锁色时自由挑 1-2 个补入 spec_lock,
候选沉靛蓝/鎏金/青灰绿),红只留骨架+至多一处内容强调。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 10:57:54 +08:00
caoqianming a1db5756aa feat(tools): 扫描件 PDF 直读 read_document——方舟文档理解 base64 内联(bump 0.58.55)
markitdown 只抽文本层,扫描件(老标准/检测报告/红头指南)转出为空=死路。
探针(scripts/probe_ark_doc.py)验证方舟 chat file 内容块直读 PDF 后落地:

- tools/read_document.py:seed_2_lite 同 variant 同 key,记账走
  record_vision_usage;体积/页数双闸(30MB/100 页,pdfminer 软探页数)+
  finish_reason=length 截断提示;多页 OCR save_md 全文落盘只返 1500 字预览
  (防上下文爆),save_md 与读取侧同款三形态路径解析(相对/宿主绝对/容器
  /workspace)+ user_root 边界
- tools/image_ref.py:抽 load_pdf_as_data_url(复用 resolve_in_root;
  data:application/pdf;base64, 前缀为方舟硬校验)
- config/media/doubao.yaml:vision 段加 max_pdf_mb / max_pdf_pages /
  doc_request_timeout_s(probe 实测口径:~1300 token/页、100 页全覆盖、
  17MB 内联可用、单页栅格化 3600 万像素硬限)
- core/agent_builder.py:注册(跟 look_at_image 同 vision 段走)+ 系统提示
  _MEDIA_READDOC_SEG(何时调/何时不调,防对有文本层 PDF 重复花钱)
- 六 skill(paper/patent/standard/proposal/rebuttal/ppt)摄取段加扫描件
  兜底一行(markitdown 转出空/近空 → read_document OCR)
- 冒烟 scripts/smoke_read_document.py 全过(3 页 ¥0.0066,表格→md 表、
  公式→LaTeX,usage_events 记账验证)

选型对比(不接 MinerU 等外部解析 API=新增第三方数据面;不选本地 OCR=
过度投资;不选 file_url/file_id=免认证公网直链/TOS 依赖)见 DESIGN §8.13。
host 侧工具,无需重建沙箱镜像,pull + 重启 web 生效。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 16:57:23 +08:00
caoqianming fc53d21620 fix(web): 长对话点目录圆点首次跳不到位——重建窗口瞬时定位+跳转期抑制 sentinel 补载
根因链:loadMessagesAround 后 renderMessages 尾部无条件滚底,把视口钉到
新窗口末尾;底部 sentinel 落在视口内立刻触发 loadNewerMessages,其整窗
重渲染(innerHTML 清空)删掉平滑滚动的目标元素、再把 scrollTop 恢复到
fetch 前位置——首次点击停在窗口底部附近,第二次点击才能到位。

- renderMessages 加 stickBottom 参数(默认 true 保持首屏/发送行为);
  loadEarlier/loadNewer/loadMessagesAround 三个调窗口路径传 false
- jumpToMessage 重建窗口后用瞬时定位(auto),不给补载/图片异步撑高留
  动画窗口期;已在 DOM 的近距离跳转保留平滑
- _msgScrollObserver 在 _outlineJumpLock 期间不补载;解锁时
  _repokeMsgSentinels 对 sentinel unobserve+observe 补投一次交叉状态,
  避免 sentinel 停留在视口内时解锁后无人触发加载

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 16:42:41 +08:00
caoqianming 2e3a4db4e0 fix(web): 手机端文件预览改悬浮卡片——四边留边距、不压输入区(bump 0.58.54)
移动端预览弹框原为全屏贴边(100vw×100dvh、直角),观感差且糊在消息
输入框上。≤640px 下改为:遮罩层四边 10px 呼吸边距,底部在
--preview-bottom-inset(chat-form 让位)之上再加 10px;卡片尺寸改
100%(相对遮罩内容区,弃 100dvh,旧 WebView 兼容更稳),恢复默认圆角。
输入区仍由 body.fp-open #chat-form z-index 抬升保持可见可用;桌面端不动。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 15:20:08 +08:00
caoqianming c1508d0df0 feat(web,docs): App 套壳 embed app 变体(relogin_url + fragment 注入)+ APP.md 对接文档(bump 0.58.53)
移动 App 方案:原生壳(WebView)+ 原生登录页,登录走 platform 自有接口
(/api/login/token → /api/login/external-login,实测打通,后者服务端换 zcbot JWT)。
顶层 WebView 无父窗口,iframe 的 postMessage 协议失效,泛化企微免登为 app 变体:
?embed=1&relogin_url=<绝对地址>#token=..&user_id=.. —— fragment 注入读完即清,
401/logout 时 location.replace(relogin_url),原生壳拦自定义 scheme 换新 token 重进。

- state.js: EMBED_RELOGIN_URL 解析+消毒(拦 javascript:/data: 等可执行 scheme)
- embed.js: 抽共用 readFragmentToken/gotoInitialTask,加 embedAppInit/embedAppRelogin
- auth.js: logout 分支序 wecom → app → iframe
- APP.md 新增(进入契约 + platform 登录链路 + 原生壳杂活清单 + H5 备选)
- EMBED.md 精简 243→约 120 行,指向 APP.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:59:16 +08:00
caoqianming fb214de960 fix(context): 窗口体量估算实测校准——50%压缩/85%折叠/占用环换 token 实测口径(bump 0.58.52)
diag 实测(scripts/diag_context_pressure.py 留仓):折叠机制有效、零撞硬上限,
但静态 CHARS_PER_TOKEN=2.5 对中文密集窗口低估近一倍——名义 85% 折叠线实际
~155% reliable 才触发(task 9a863424 冲到 40.7 万 tokens);代码密集反向虚高
(fe2d8b73 估 1.2M 实际 616k)。

修法是信号校准、不加新机制:
- context.py: estimate_window_tokens(provider 实报 usage 覆盖窗口主体,仅实测
  点后尾巴按 2.5 估)+ calibrated_chars_per_token(比值夹 [1.0,4.0] 带宽)
- session.py: last_measured_usage() 取窗口内最后一条实报 usage 的 assistant 行
  (best-effort 绝不抛;idx→内存 pos 映射校验 role)
- context_fold.py: maybe_fold 触发判定换 token 实测口径
- loop.py: 压缩门槛与 context_limit_chars(前端环)用校准比值,逐轮以
  sent_chars/prompt_tokens 刷新;校准态挪类属性兜默认,任何异常回退 2.5

已知残余:折叠后 run 在首次 chat 完成前崩掉会多折一次(摘要偏保守,原文全在
DB),不为此加持久化状态。run 中途折叠/超限自愈按 §5 无信号不实施继续搁置。

真实生产 task 只读验证映射与校准(drift 0.95x/0.51x/0.86x),286 测试全绿。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 12:49:02 +08:00
caoqianming 92b8aa4575 docs(progress): 移除 0.58.49 条目里已撤销的 max_tokens 描述
窗口约束 max_tokens 那摊探索后判定不必要、已撤销(glm 空响应是禁 thinking 修的,
max_tokens 防的是没发生过的风险)。PROGRESS 里"max_output 死字段"根因点 + "遗留:
没盲发 max_tokens 需逐档评估"整段随之删除,免误导。纯文档订正,不改代码/不 bump。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:28:22 +08:00
caoqianming 00bbe42823 fix(prompt): 系统提示加护栏——大段中文正文别内联进 .py 源码(bump 0.58.51)
失败面板 run_python/error 语法预检簇(近7天31次/12task)根行为=模型手写
python-docx/pptx 把大段中文正文硬拼进 .py,ASCII 引号/全角标点/缩进崩成 SyntaxError。

定位:现有正规路径充分(出 docx 的 skill 全指向 rendering/render.py md->docx、且写
"别自己手搓"),但 DB 显示 16 涉事 task 中 7 个没 load 任何 skill→够不着 SKILL 指引;
precheck(pysyntax)已 host 侧拦截+回根治 tip+喂 RepeatGuard,浪费已 bound(每任务~2.6次
自愈、无失控循环)。故走最高覆盖最低改动:base 系统提示 general_v1.md(所有任务加载)的
run_python 段加一句原则级护栏——大段中文正文先 write 进 .md/.txt 再 read、有 skill 优先
走其渲染路径(守 recipe-ban,不给可 copy 配方)。纯提示词引导、无法端到端 verify,ROI 本
就低(precheck 已兜),只求少发。遗留:改现有 .docx 无平台路径,需另设计。

顺带补 PROGRESS 缺失的 0.58.50 条目(salvage 提示降噪,并行 session 提交)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 09:05:46 +08:00
caoqianming 62f3ddb658 fix(ui,loop): salvage 成功提示降噪——黄色警告降为灰色 info 行(bump 0.58.50)
用户反馈流内黄字「工具调用参数损坏但已就地抢救」吓人、疑影响后续对话。
核实零影响:salvage 只就地改写 arguments、当轮照常执行,对话历史与正常轮
无异;warn 是纯前端 SSE 展示事件,不入 messages、不回灌 LLM,刷新即消失。

处置取分级降噪而非全静默(上游返脏数据的事实要可见、可与 tool_salvaged
DB 留痕对上):
- core/loop.py:该 emit 加 level:"info",措辞软化为「已自动修复工具调用
  参数…继续执行」;
- web/static/js/chat.js:warn 渲染按 level 分流,info 走灰色 muted 行、
  不带 ⚠;黄色 ⚠ 只留给真打断本轮的路径(丢弃重试/熔断等)。

频发原因研究背书(DB 近7天,bad/chat 轮次):deepseek_v4.pro 14.5%、
flash 8.3%、unifyllm 5.9~7%,glm/sonnet 0 —— DeepSeek wire + unifyllm
网关两链路的流式 delta 乱序病,前缀实为同一调用自身内容的错位碎片
(与 0.58.24 定层一致);社区 opencode/goose/Cline/Roo-Code 同病且多数
closed-not-planned 未修,我方 salvage(85% 救回)+ 非流式重试已是最完整
处置 —— 频繁黄字本质是「救回成功」记录高频刷屏,降灰合理。

顺带:CLAUDE.md 文档节奏调整(bump/PROGRESS/CHANGELOG 改为一次 push
统一更新一次,不逐 commit;DESIGN 仍随触发它的 commit 走)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 08:47:44 +08:00
caoqianming f535dbaa9a fix(llm,loop): glm.pro52 空响应治本——禁 thinking 免推理烧穿输出上限 + loop 区分截断(bump 0.58.49)
失败面板 empty_response 簇 2026-07 主角 glm.pro52。探针 + DB + 代码三重定层根因链:
1. caps.max_output 是全仓死字段(只在 capabilities.py 定义,_build_kwargs 从不作为
   max_tokens 发出)→ 网关放任 glm-5.2 跑到自带 65536 输出硬上限;
2. glm-5.2 thinking 网关侧默认开(线上探针实测 reasoning_content=766>0,尽管 config
   thinking_mode:false —— 那开关是 glm.yaml 未做的 TODO,根本没透传);
3. 重任务(100k 上下文)上思考膨胀烧满 65536 被截断(finish_reason=length)、content 空
   → loop 判空响应整轮丢弃 + 同上下文无效重试(task 35744bea:5 次 empty 全
   tokens_out=65536,事件4=attempt2)。单任务烧 ~327k 输出 token。
与 opus48/deepseek 的网关 wire bug 不同根 —— 这是我方 max_output 死 + 未约束推理模型。

修(禁 thinking + loop 健壮化):
- core/llm.py _build_kwargs 加 family=="glm" 分支,据 thinking_mode 透传
  extra_body={"thinking":{"type":"enabled|disabled"}}(GLM body 级协议,与 OpenAI 的
  reasoning_effort 不同族;litellm zai provider 转发 extra_body)。当前 glm 档均 false=disabled。
  线上探针 A/B 实测:disabled 后 reasoning_content 766->0、正文/工具照常。
- core/loop.py 加 _finish_reason,空响应路径区分 length(截断,我方预算烧穿,重试无效)与
  wire 吐空,warn 措辞据实;record_empty_response units 记 finish_reason(JSON 免 migration)。
- 修既坏的 test_loop_empty_response(mock 缺 executor)+ 补截断措辞用例,61 测试全绿。
- 探针 diag_glm_empty_probe.py 加 monkeypatch 绕沙箱池 + PROBE_THINKING_OFF A/B 开关。

行为变化(知情):glm.pro52 用户现在拿禁思考的 glm-5.2(config 本就 thinking_mode:false)——
不再卡壳/空转,代价是硬任务少了推理链。遗留:max_output 对所有模型仍未生效(本次没盲发
max_tokens,怕截断大 write 的 args),需逐档评估安全值后另做。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 08:16:54 +08:00
caoqianming 9646f571f3 feat(diag): 加 glm.pro52 空响应定层探针(交生产跑)(bump 0.58.48)
失败面板 empty 簇(近7天15)主角从 opus48 换成 glm.pro52(11/15):assistant 轮
既无 tool_calls 又无正文,被 loop 当正常收尾静默 done。要判 glm 是①真吐空、
②工具意图漏成正文(narrated)、还是③瞬态。

scripts/diag_glm_empty_probe.py:build_agent(resume=True) 复原真实 system prompt +
全工具 schema + 完整多轮上下文(空响应发生在深层,单条 user 消息复现不了 —— 与
2a1bc25d narrated 探针的关键差别),打 glm.pro52,stream/non-stream 各一发,
三态判定 EMPTY/NARRATED/STRUCTURED。默认锁 task 35744bea(近7天 empty 5 次
最可复现),argv 可换 task/user/档做对照。

本机 import litellm 卡 ~20min,只能交生产机跑:
  .venv/Scripts/python.exe scripts/diag_glm_empty_probe.py
输出写 scripts/_glm_empty_probe_*.txt。定层后再决处置。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 16:10:51 +08:00
caoqianming 795161adcd fix(ppt,tools): svg 质量门末尾出 [GATE FAIL] 汇总行——失败签名从误标"Font issues"变诚实(bump 0.58.47)
工具失败面板 shell/exit 第1簇「Font issues x20」是误标:svg_quality_checker
按设计 exit-1(质量门),但 core/toolfail 取 [exit] 前尾行当签名,而尾行永远是
通用 tip「4. Font issues:…」→ 所有门失败(typography/alignment/spec_lock drift)
全塌成一条"Font issues",误导排查方向。多文件门也没有可靠尾行(最后文件可能只有 WARN)。

修在工具侧(toolfail 保持通用零改动):
- svg_quality_checker.gate_verdict_line():errors>0 时在 main() 末尾(export 之后)
  多打一行确定性汇总 [GATE FAIL] svg_quality_checker: N error file(s) / M total;
  top issues: … 作为最后一行 stdout,toolfail 现有取尾行逻辑自动拾取诚实签名,
  模型也多一行有用汇总。
- _categorize_issue 加 spec_lock/typography 类,避免真错(typography px)落 "Other"。
- 补 GateVerdictLineTests(3):无 error 不出行 / [GATE FAIL] 打头带 top issues /
  是最后一行 stdout。56 测试全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 15:59:09 +08:00