deer-flow/CHANGELOG_zh.md
Hyeonsang Cho 2dc92a2e4a
docs: add a Korean README (README_ko.md) (#6909)
* docs: add a Korean README translation

* docs(changelog): reference #6909 for the Korean README

* docs(readme-ko): restore blank lines before headings and sync with README.md

Six headings sat directly under the preceding paragraph or code fence where
README.md keeps a blank line. Also translate the README.md additions from
#6898 (Codex tool_choice), #6886 (mixed-provider citation cleanup), and #6701
(authenticated HTML preview resources).
2026-10-15 14:45:29 +08:00

724 KiB
Raw Blame History

更新日志

本文件记录 DeerFlow 的所有重要变更。

格式参考 Keep a Changelog, 版本号遵循 语义化版本规范。

English | 中文

未发布

本节累积面向 2.2.0 里程碑(2.2.0)的工作。 该里程碑随本次发布收尾,共合并 621 个 pull request。

新增

  • 渠道: 飞书渠道上基于选项的澄清改为渲染为可交互的按钮卡片,而不是纯文本:每个 选项变成一个按钮(最多 4 个——再多时纯文本路径更易读),点击选项即可完成澄清, 效果与输入该选项完全相同,卡片会原地更新以显示所作决定。渠道管理器把澄清中间件 结构化的 human_input 载荷附加到出站元数据中,各渠道无需 Gateway 协议变更即可渲染 更丰富的交互形式;点击通过现有 WebSocket 长连接到达,回调注入一条携带 human_input_response 的隐藏入站消息,使运行在原对话线程上恢复,与 Web 卡片行为 一致,且不会出现在对话记录中。点击绑定不可猜测的、由中间件签发的请求 id、发起者的 open_id 以及一次性消费——身份不匹配时该入口保留给真正的发起者,重放和过期卡片只 弹出 toast 提示,进程本地注册表在多 worker 部署下按失败关闭处理(已在文档中说明的 限制)。由 channels.feishu.card_interaction_enabled(默认关闭)控制,因为飞书应用 需要在回调配置页签下一次性订阅 card.action.trigger,并发布一个新的应用版本。 (#6670)

  • 工具: 延迟工具发现新增 keywords: 查询模式,跨工具名称和描述做不区分大小写的 字面子串匹配、词序任意,因此 keywords: notebook jupyter 现在能找到 jupyter_notebook,而正则路径此前会把整个查询当作一个模式从而错过它。结果按去重后 的词条覆盖数排序,其次按名称命中排序,并列时按目录顺序稳定排列,上限为 5 个工具; 前缀之后最多处理 256 个字符和 16 个不重复的空白分隔词条,标点保持字面含义。裸正则 查询、+required、精确且不设上限的 select: 以及 schema 晋升均保持不变。(#6659)

  • 鉴权: 设置页新增仅管理员可见的“用户”页面,被禁用的账号现在会在每一个会话相关 的界面上得到明确说明。新增的 core/auth/admin-users.ts 客户端调用 GET/PATCH /api/v1/admin/users(PATCH 只发送 {disabled},userId 做 URI 编码); UserSettingsPage 每行列出邮箱、只读角色和禁用状态,并提供停用/恢复开关,原样展示 Gateway 返回的“最后活跃管理员”409 详情;导航入口对非管理员隐藏,?settings=users 深链接通过一个穷举的分区映射校验。401 account_disabled 响应体在 SSR、共享 fetcher 和 AuthProvider.refreshUser 三条路径上都映射为专门的认证结果,并重定向到说明原因 的登录页,其余 401 仍保持现有的 ?next= 往返;被禁用的账号不保留 next, userSchema 保持不动,因此来自没有该字段的后端的行仍按启用状态渲染。(#6641)

  • 技能: 含畸形 YAML 的自定义技能不再从技能列表中无声消失。管理员会在 “能力中心 → 技能”中看到一个“无法加载”分区,列出自己 YAML 畸形的自定义技能, 附带包内相对文件路径、可用时的实际行/列号,以及一条针对性的冒号加引号提示;诊断数据 来自单独的 GET /api/skills/diagnostics/custom 响应,带有稳定的错误码,并且绝不会 变成可加载的技能、目录条目或斜杠选择器条目——解析仍然严格。上报前会先解析包与文件 路径并做属主检查;其他用户的、legacy/全局、公共、集成以及外部链接的包都被排除,响应 中不含主机路径、源码摘录或原始异常。“重新加载技能”会先调用现有的管理员 reload 端点再重新拉取两个列表,为后续运行保留本地/对等缓存失效;进行中的运行保留各自的 快照,重载失败时警告保持可见。(#6623)

  • 项目: 项目架上的文档可以携带一个可选开启的 LLM 摘要,让智能体的检索决策 不只看到文件名。在设置 projects.summaries_enabled 之前一切保持关闭(新增需要 重启生效的 projects: 键:summary_max_bytes(256)、summary_model_name、 summary_concurrency(2)、summary_queue_size(32)、 summary_timeout_seconds(60);config_version 57 → 58)。启用后,上传或 提升文档会入队一个尽力而为的后台任务:通过 read_text_serving_path 读取有界 文本窗口,调用配置的模型一次,并把净化后的输出(剥离开头的 <think> 推理、 解包、单行、字节上限)存到该行上。摘要以 — <summary> 后缀出现在注入的 <documents> 索引行上、list_project_documents JSON 的 summary 字段中, 以及 GET /api/projects/{id}/documents 里;任何失败路径都让 summary 保持 NULL,渲染与之前完全一致,摘要落地也不会改变文档架的顺序。有界工作池加有界 队列限制在途的提供方调用;迁移 0037_project_document_summaries 新增可空列, 不做回填。摘要文本是经 LLM 处理的不可信文档内容:它走与文档名相同的 user 角色 瞬态通道,并在两个模型可见出口做标签中和。(#6581)

  • 鉴权: 运维可以停用一个账户,所有认证面都会拒绝它,同时该行——凭据、角色、 OAuth 关联——原样保留,重新启用即可完整恢复被停用的内容。新增 users.disabled 列(NOT NULL,服务器默认 0,迁移 0036_user_disabled)在全部四个认证面强制 执行:密码登录在任何凭据比对之前即拒绝;JWT 会话解析返回 401 ACCOUNT_DISABLED(新错误码),既有会话在下一次请求时失效;PAT 解析返回 通用的 401,使该判定结果无法被当作 oracle;OAuth 开通返回 403 并附带联系 管理员的信息,同时保留 IdP 关联。SQLiteUserRepository.set_disabled 是带最后 活跃管理员保护的字段级串行化写入——被停用的管理员无法认证,因此无法自行恢复 管理访问,而重新启用总是允许——凭据写入器从不写 disabled,因此过期的改密 快照无法重新启用被停用的账户。PATCH /api/v1/admin/users/{id} 现在是部分 账户更新(system_role 和/或 disabled):出现的每个字段路由到各自的字段级 写入器,空请求体返回 422,新增的最后活跃管理员 409 加入保持不变的 404/409 映射。(#6566)

  • 渠道: IM 对话可以用 /model <name> 选择模型,以调用方可见的 GET /api/models 列表校验(对用户自有的连接,是否在列表中同时充当鉴权检查)。 该固定项写入线程元数据(channel_model_name),Gateway 重启后依然保留,并从 下一条消息起就地生效——不同于 /agent use,后者必须新开对话,因为线程的 检查点谱系属于其原始智能体。固定项在 Gateway 配置合并之后仍然胜出:由于 merge_run_context_overrides 用 setdefault 复制运行上下文,只在那里携带的 固定项会悄悄输给会话配置的模型,因此 _apply_explicit_model_choice 对全部 三个载体做归一化;与任何显式请求的模型一样,固定项也优先于 Custom Agent 配置的模型。不带参数的 /model 通过一个镜像运行合并顺序的解析器报告生效 模型及其来源(会话层级 default → channel → user,context 载体胜过 configurable,两个会话配置载体都会扫描);/model default 清除固定项; /models 在页脚注明它;Telegram 注册该处理器;"model" 加入斜杠技能保留名 契约(后端 slash.py、contracts/slash_skill_contract.json、前端 slash.ts)。固定项查询会降级而不是让消息失败:状态查询失败时报告 Current model: unknown,复用线程上的瞬时固定项预加载失败不再把一次普通聊天 变成千篇一律的“An internal error occurred”回复。(#6534)

  • MCP: list_background_tasks 接受可选的任务 status 参数,智能体可以找出 失败的任务或等待输入的任务,而不必只在最近或活跃任务组成的截断列表里筛选。 该过滤在 SQL 中应用,然后返回最近 20 条匹配;可与 active_only 组合;省略时 保持既有行为,包括运行时的 user/thread/incarnation 隔离。Gateway 列表端点也 接受经校验的 status 与 active_only 查询参数,在其既有 limit(默认 50, 范围 1–100)之前应用;未知状态返回 422。(#6527)

  • 社区工具: 新增 You.com 作为可选启用的 web_search 提供方 (deerflow.community.youcom)。未配置 API key 时调用免费的无密钥端点 https://api.you.com/v1/agents/search;设置 YDC_API_KEY(或工具的 api_key 配置)则切换到 https://api.you.com/v1/search 的完整 Search API,并携带 X-API-Key 请求头——端点与请求头总是同时选定:无密钥端点会拒绝认证头,而过期的 密钥绝不能发往该端点。两个端点返回相同的响应体,因此一个归一化器即可覆盖两者: web_search 返回 {title, url, snippet} 结果,其中 snippet 拼接 You.com 每页的 snippets 数组(新闻条目回退到 description);合并后的列表以 web 结果优先,并 裁剪回 count 条,因为 You.com 按每个板块应用 count。该提供方作为无需密钥的 选项加入 make setup 的搜索选择器,并进入 make doctor 的免费提供方列表,其 请求携带可识别 DeerFlow 的 User-Agent。不新增依赖;现有用户的默认行为不变。 (#6515)

  • 鉴权: 新增 ActivationDecisions,一种在构造时强制约束的异步决策批量类型, 封堵“未命中即回退同步”这一族故障。此前的异步决策映射只是普通 dict | None, 异步路径上的逐步未命中会从工作线程静默回退到同步 authorize()——对事件循环亲和 的提供方这是错误的 API,且在 fail_closed: false 下提供方错误会把拒绝翻转成 放行。已覆盖的名称返回批量决策;未命中按所配置的提供方错误策略处理(fail-closed 拒绝、fail-open 放行),并发出指名技能的醒目警告,绝不触碰提供方;None 仍是 同步链的信号;空候选集产出空批量而非 None,因此意外的消费者名称仍按策略失败。 get_or_none 让发布方区分“未覆盖”与 False。两个 _activation_allowed 辅助 函数(SkillActivationMiddleware、SkillToolPolicyMiddleware)、它们的收集器和 渲染决策发布方都迁移到该类型;同步链行为不变。(#6508)

  • jina: 为 Jina 网页抓取提供方新增可选启用的进程内请求准入预算。重试、 Retry-After 和响应大小控制此前已经存在,但并发调用者仍能在没有任何共享在途上限 的情况下进入 HTTP 传输。新增 request_admission 设置,限制活跃物理 HTTP 尝试的 最大数量、限定 FIFO 等待队列的长度并限制最长排队等待;该功能在显式配置之前保持 关闭,策略在同一进程内的所有 Jina 调用者、线程和事件循环之间共享,且一旦启用即 被冻结(更改需重启进程)。许可在 HTTP 请求、响应消费与响应流关闭期间持有,在 重试退避与可读性抽取之前释放,并在每次重试时重新获取;队列已满、等待到期、取消 和跨事件循环唤醒都会释放许可,既不泄漏、也不把本地准入失败计入重试,排队时间 消耗原始的重试预算。面向模型的 web_fetch schema 不变。(#6489)

  • serper: 为 Serper 网页与图片搜索新增可选启用的有界瞬时重试。新增 max_retries(默认 0,最大 3)和 retry_budget_seconds 设置;未配置时搜索 仍为单次尝试。重试只覆盖连接错误、502/503/504 响应,以及携带有效 Retry-After 的 429;认证错误、永久性状态码、读写超时和无效 JSON 仍为终态。原始查询、时间 范围、域名过滤、请求体和 API key 在各次尝试间保持不变,公开的工具参数 schema 也 不变。HTTP 日期格式的重试响应头按严格方式解析,包括合法的 asctime 日期。 retry_budget_seconds 约束的是是否再安排一次尝试,而非已在途的请求;启用重试 可能消耗额外的 Serper 配额。(#6456)

  • 记忆: 工具模式的智能体现在可以用 memory_get(fact_id) 按 ID 直接读取已知 事实,经由 MemoryManager.get_memory 从当前运行时的用户与智能体范围返回一条 完整事实。缺失与超出范围的 ID 返回同样的未找到错误,不支持的读取方式和具名智能体 范围会被拒绝;ID 未知时搜索仍是入口。该工具以静态指引注册,并更新了 README 与 模块文档;中间件模式不变,实现从既有的范围化记忆文档中选取事实,而不是新增存储层 的点读。(#6443)

  • examples: 扩展示例中间件现在汇总工具调用的结果与耗时。同步和异步调用都在 现有的 TOOL_VISIBLE 边界处被观测,stats 响应在现有字段之外新增固定的 tool_outcomes 计数器,以及 tool_duration_samples 和 tool_duration_total_ms。下游处理函数仍只调用一次,其结果或异常原样传播: 返回的错误消息计为 returned,逃逸的 graph 中断计为 raised,取消 (asyncio.CancelledError)计为 cancelled。任务统计只在收尾时合并一次, 重复的 stop 不再把任务总计翻倍;结果计数器与耗时样本在同一把锁下一起更新。 (#6430)

  • 子智能体: 持久化子智能体批次新增有界的同线程结果查看。在启用持久化批次 工具时,read_batch_result 读取当前用户在当前线程名下的一个稳定条目位置; 模型参数无法选择其他用户或线程,私有提示词、执行上下文和租约字段都被排除。 结果文档携带已存储的报告、执行/错误状态以及相互独立的验收标准与裁定,按有界 窗口返回(默认 4,000 字符,可请求的最大 8,192,完整转义响应在 10,000 以内), 支持按精确字符偏移续读并带内容修订号;完成、重试或取消状态一旦变化即返回 restart_required,不会混用两次尝试;窗口先拼接、再解析文档。该工具已接入 Gateway 装配和显式 SDK 运行时绑定;批量结果仍走经授权的 JSONL 导出。 (#6416)

  • 子智能体: 持久化的知识研究结果现在会保留其 RAG 证据。成功的条目在租约 围栏的成功结果事务中,把一份有界的 result_artifact.knowledge_sources 快照 与报告一起存储,因此所有者可以把 #knowledge-<id> 引用解析回原始完整摘录、 来源 ID 和数据集/文档/分块定位符,并通过现有的按所有者与线程授权的 results.jsonl 导出分页浏览,即使经历 Gateway 重启或提供方内容变更也不受 影响。只选取受上限约束的报告中由完整引用引用到的已捕获 RAGFlow 来源,包括 由委派 task 转发的来源;快照在报告上限之外另受 subagent_batches.max_result_chars 单独限界,被跳过的摘录通过 omitted_count 上报。迁移 0033_batch_result_artifact 新增该可空列;legacy 结果导出 result_artifact: null,失败、取消和被取代的尝试不发布任何内容, 显式重试会清除先前的快照。精简的条目与进度响应以及批次面板预览不包含该 快照。(#6413)

  • 向导: Opper 加入安装向导的 LLM 提供方列表,与 OpenRouter 并列。Opper (opper.ai)是一个托管在欧盟的 AI 网关,通过一个 OpenAI 兼容 API 提供 50+ 家提供方的 700+ 个模型;该条目使用 langchain_openai:ChatOpenAI,指向 https://api.opper.ai/v3/compat,环境变量为 OPPER_API_KEY。模型使用 Opper 的裸模型 ID:claude-sonnet-4-6(默认)、gpt-5.5、 gemini-3.8-flash 和 gpt-5.4-mini。(#6399)

  • 鉴权: RBAC 提供方中配置的自定义角色现在可以通过管理 API 分配给已认证 用户。User.system_role 此前在 API 层被限定为 Literal["admin", "user"], 因此 YAML 中可声明的角色对真实 Web 用户不可达,尽管数据库列本就存储普通 字符串;现在它接受任意非空字符串,空字符串仍被拒绝,因为它会静默落到 default_role。RbacAuthorizationProvider 新增的 known_roles 属性与 Gateway 的 assignable_role_names() 共同定义可分配集合:内置角色 (admin/user)加上提供方配置的角色名;对没有可枚举角色面的自定义 提供方,则仅内置角色。仅管理员可用的 /api/v1/admin/users 路由列出用户及 其系统角色,并通过 PATCH /{user_id} 分配角色——未知角色名返回 422, 用户不存在返回 404,最后一名管理员不能被降级(409,防止引导期锁死)。 执行逻辑不变:分配只改变主体携带的角色;@require_permission 守卫与提供方 裁决仍是唯一的执行点。(#6382)

  • jina: Jina web fetch 新增可选的响应字节预算。该工具此前先缓冲完整响应, 再抽取文章并截断最终 Markdown,因此输出上限并不约束抽取之前消耗的内容。 新增的可选 web_fetch 配置键 max_response_bytes 流式读取按内容解码后的 字节,恰好接受到上限为止,超限即停止并关闭响应、返回不含正文的 Error—— 绝不部分成功、绝不抽取超限内容、绝不因大小失败而重试。省略或为 null 保持 现状;启用时取值必须为正整数(不含布尔值)。该上限适用于任何响应状态并按 响应重置;文本编码、代理/认证配置、工具 schema、最终输出截断、取消与既有 重试期限均保持不变,文档也说明它并不硬性约束进程内存、HTTPX 解压器分配或 线上字节带宽。(#6373)

  • jina: Jina 网页抓取在现有尝试次数与总时长限制内遵循提供方指定的 Retry-After 恢复。启用重试时,HTTP 503 上有效的 Retry-After 决定等待时间 (缺失或格式错误的提示仍用带抖动的退避);HTTP 429 只在携带有效恢复提示时才 重试,否则原样返回错误。接受的提示涵盖非负整数秒与 HTTP 日期(含过时写法), 过期日期和超长延迟都会被妥善处理,不会退化为提前重试。客户端等待本地节奏与 服务端下限中的较大者;当所需等待无法纳入剩余预算时,直接返回最后一次 HTTP 错误、不再发起请求。默认单次尝试、取消、代理设置、无提示重试和面向模型的 工具 schema 均不变,不新增配置字段或依赖。(#6352)

  • 智能体: 自定义 Agent 定义现在可以打包为可移植包分享。Gateway 导出带版本的 deerflow.custom-agent JSON 包,内含可移植的 Agent 定义与 SOUL;导入在生效 用户配置的文件或 SQL 存储中创建该 Agent,可选本地重命名,校验格式、版本、 名称与模型,拒绝未知字段,名称冲突时返回 409 而不是覆盖。包会保留模型、 工具组、MCP、技能、知识、子智能体、推理与记忆策略设置,刻意排除记忆内容、 对话、凭据、运行时状态以及运维方持有的 GitHub installation 绑定。智能体画廊 新增“导入 Agent”(带重命名对话框)与每张卡片的“导出 Agent”下载,中英文文案 齐备;文档记录了交换契约与安全边界。(#6127)

  • 智能体: history_search 工具现在返回以命中为中心、带源偏移的摘录,而不是 匹配源的前 600 个字符,位于偏移 5000 的关键字不再让调用方去读无关的开头文本。 每个结果携带 excerpt_start 与 excerpt_end,即可读源上从零开始、左闭右开的 Unicode 字符偏移(source_text[start:end] == excerpt),excerpt_start 可直接 传给 history_read。摘录至多 600 个字符,围绕最早可定位的查询词命中构建,在 源边界处平移;casefold 后的位置会映射回原始字符(包括改变长度的 Unicode 折叠),归档源复用其已索引的 token 区间。excerpt_match 标志标记回退情形—— 完整命中无法定位或放不进摘录时返回源开头,但不暗示其中包含命中;重复的归档 版本与活动/归档重叠的对齐方式与 history_read 选定的源版本一致(含其截断 标志)。查询语义、排序、八条结果上限、角色过滤、源 ID、checkpoint 可达性与 用户/对话隔离均不变;任务连续性默认仍关闭。(#6008)

  • search: 新增可选的 Webz News Search 提供方,接入现有 web_search 接口与 设置向导,取代此前 Webz 用户需要的独立 MCP 配置和提供方专用工具名。通过 WEBZ_API_KEY 或工具级密钥配置,使用 HTTPS bearer 认证、30 秒超时、脱敏错误 与常规取消传播,支持结果数量、发布日期、语言、国家、来源域名、情感与分类 过滤(time_range 映射为 UTC 下界日期;显式 published_from 优先)。调用方 显式指定的结果数量优先于配置默认值;规范化后的页面以 returned_results 报告 数量,因为 Webz 自身的 total_results 以请求的 k 为上限,并不代表全库总数。 格式错误的单条目会被跳过并只记录索引警告、保留有效文章;无效信封或全部无效 的页面会被拒绝。web-search 步骤经现有 URL scheme 允许列表,从 Webz 的 { query, returned_results, results } 信封以及旧式数组渲染来源标题与链接,并 跳过此前会在渲染时抛错的、缺少字符串标题和 URL 的条目。设置向导与 doctor 集成、示例配置和 README 指引一并提供;默认提供方不变。(#5880)

调度器

  • 调度器: 按需启用对话工具创建及管理属主绑定的定时任务,支持自动启动上限、 每次执行的目标评估,以及 Agent 请求停止自身调度。目标未达成与自动暂停复用 现有通知 outbox;明确备注和获授权的上次执行引用延续上下文,不增加 goal 状态。 与这些工具是否启用无关:已有的定时、webhook 和 autonomous goal 运行现在可以接受 已声明的低风险、可逆假设,并记录为 relied_on_assumption;交互式 goal 评估仍保持 严格。goal 评估器的调用和 token 现在计入运行用量。(#6229)

  • 调度器: 定时任务的生命周期和上限规则在任务页面、REST 和对话中保持一致。 在任务页面创建的任务也可以设置每次运行的目标、保险上限(max_runs、end_at) 和新增的停止条件,并且会像对话中创建的任务一样检查目标、在条件满足时暂停自己的 定时任务。停止条件保存在新增的可空列 scheduled_tasks.stop_condition 中(迁移 0031_scheduled_streak_boundary,同时新增内部使用的 unmet_streak_after_seq 计数边界),只在运行启动时附加到这次运行的消息里,不会写入保存的任务指令。恢复时 从当前时间计算下次运行(不补跑),时间已过的单次任务不能直接恢复,并可携带可选的 {max_runs, end_at} 请求体;保险上限已用完的任务重新启用时返回 409 limits_exhausted,除非同一请求放宽了已用完的那项上限。暂停已结束的任务返回 409 task_finished。未能检查目标不再计入、也不再清零连续 3 次未达成的自动暂停 计数;修改目标、任务指令或停止条件,以及新增备注,会重新计数。当前 Gateway 进程的 调度器未运行时新建任务返回 409 scheduler_not_running。任务响应新增 automatic_runs_used 和 active_run_status;运行记录新增 run_number、 total_tokens 和 summary;对话关联任务新增 thread_relation;/api/features 新增 scheduled_tasks;手动触发返回 outcome、existing 和 thread_id。(#6378)

    不兼容变更: /api/scheduled-tasks* 的错误改为 {"detail": {"code", "message", "params"}},不再是字符串 detail。把 detail 当作字符串读取的客户端需要改读 detail.message。路由权限产生的 403、FastAPI 对格式错误请求体返回的 422,以及缺少对话存储时共用的 503 保持原格式。错误码列表见 backend/docs/API.md 和 contracts/scheduled_task_errors_contract.json。

  • 调度器: 对话中现在可以 update(修改)和 resume(恢复)任务:任务 ID 和 运行记录不变,校验规则和错误码与 REST 相同,clear_fields 可以取消目标、停止 条件或保险上限;用户“满足条件就停”的规则作为单独的 stop_condition 传入。在某个 任务的运行对话里,你发出的消息也能管理这个任务;定时运行本身仍只能暂停自己的 定时任务。新任务默认使用 Web 应用通过 context.client_timezone 发送的浏览器时区 (明确指定的时区优先;按间隔运行和带偏移的时间不需要时区;其余情况由智能体询问, 不再默认按 UTC 处理)。工具结果改为精简、可 JSON 序列化的任务视图,使用本地时间 (next_run_local、now_local),带有供 Web 卡片使用的 display 字段,list 还会返回最近一次运行的结果;智能体不再向用户复述 ID、cron 表达式和 UTC 时间。 试运行接受更自然的直接请求(如“先跑一次吧”“OK, run it now”),并返回 thread_id 和 existing。定时运行的消息使用稳定的消息 ID,并带有由服务端维护的 deerflow_scheduled_origin 元数据;新建的运行对话标题为“任务名 · 本地时间”;目标 评估结果记录 continuations;主 Agent 提示词要求保存的任务文本和定时运行的回复 使用用户的语言。只有 Gateway 的调度器在运行时才会提供这些工具。(#6378)

  • 调度器: Web 对话中,schedule_task 的结果显示为实时任务卡片(运行时间、 停止条件和保险上限、结果去向,以及“立即试运行”、暂停/恢复和“查看任务”),卡片在 可见时会跟随任务状态更新。定时运行的对话顶部显示一行“定时运行 · 任务 · 第 n 次”, 任务指令默认折叠,不再显示自动发送的原始提示词;调度工具的步骤改为易读的文案; 对话页头的“定时任务”按钮显示本对话的任务数,运行对话中则直接链接到所属任务。 (#6378)

  • 调度器: 定时任务页改为以列表为主:任务列表上方是状态标签和搜索,“新建任务” 对话框可以新建、编辑和复制任务(含每次运行的目标、停止条件和保险上限);详情页 分为运行时间、何时停止、执行内容、对话中保存的备注和运行记录。每条运行记录显示 第几次运行、一行摘要、目标结果、token 用量和“打开对话”链接,原始错误只在“详细 信息”中显示。由智能体暂停、连续 3 次未达成目标而自动暂停、或因保险上限而结束的 任务会说明原因,并提供“查看那次运行”“修改目标”或“延长上限”。?task_id= 可直接 打开某个任务;调度器未开启时页面会说明。(#6378)

  • 调度器: Web 应用在对话请求中附带浏览器时区(context.client_timezone); 定时任务接口的错误按错误码显示为本地化文案,服务端原文只在“详细信息”中显示; 中英文界面统一使用一套定时任务用语(定时任务、运行、智能体,标签中不再出现 lead_agent)。未命名对话以及“定时任务”“智能体”面包屑也会跟随界面语言。(#6378)

  • 调度器: 定时运行的执行位置现在在任务所有者之间公平分配。新增配置 scheduler.max_concurrent_runs_per_user(默认 2,设为 0 即关闭,且不会超过 max_concurrent_runs),限制同一所有者同时处于启动中或运行中的定时运行数量, 手动“立即运行”也计算在内;它与全局上限在同一次原子领取中、持有同一把数据库锁时 检查。无论是否设置这一上限,等待队列都按所有者轮流取出:已达上限的所有者在批量 上限生效前就被排除,因此一个人积压的运行不会再把别人的运行挤出本轮批次。同一 对话内的先进先出顺序,以及同一所有者内部的顺序都保持不变。等待超过 scheduler.queue_timeout_seconds 的运行会被跳过,运行记录显示“已跳过:等待空闲 位置的时间过长”。(#6393)

    行为变更: 默认情况下,同一所有者同时最多运行 2 个定时运行(此前最多为 max_concurrent_runs,默认 3)。设置 scheduler.max_concurrent_runs_per_user: 0 可恢复原来的行为。

  • 调度器: 在对话中创建的调度由智能体暂停(停止条件已满足)、在连续 3 次未达成 目标后被自动暂停,或已结束(max_runs 次运行全部完成、已过 end_at,或单次任务 已运行)时,会为该对话记录一条持久的生命周期事件;它与状态变更在同一个数据库事务中 提交,进程崩溃或租约失效后的恢复也不会重复记录。新增 GET /api/threads/{thread_id}/scheduled-task-events 列出这些事件(含任务标题快照、 停止条件、运行序号和最后一次运行的结果);删除任务后事件仍保留,删除对话时一并删除。 事件名称固定在 contracts/scheduled_goal_notes_contract.json(版本 3)中。迁移 0032_activity_and_task_events 新增这张表,以及后续改动要用的运行来源和已读状态 结构;已有运行的 runs.origin_kind 保持为 NULL。(#6393)

  • 调度器: 定时任务的 IM 通知改为如实、按用户语言、可单独读懂。每次执行最多发 一条消息,由结束处理的观察者在记录执行结果的同一个事务中入队:由智能体暂停 (新增 task_stopped)、自动暂停(合并为一条,不再分别发“目标未达成”和“已暂停”)、 任务已结束(新增 task_finished:max_runs 次运行全部完成或已到 end_at), 否则是这次运行本身的结果;暂停或结束的消息仍会说明最后一次运行的情况。只有支持 主动推送的应用(目前是企业微信)会收到通知:新增的 app/channels/capabilities.py 为每个提供方声明 proactive_notifications(并补上缺失的 qq 条目), GET /api/channels/providers 返回该字段,其它提供方不再写入 outbox。消息包含任务 标题、发生了什么、智能体有回复时附上与任务页一致的一行运行摘要(已脱敏),最后是 “在 DeerFlow 的定时任务页查看详情。”,不含 ID 和链接;语言取任务所有者在网页界面中 选择的语言(新增用户偏好 locale,en-US 或 zh-CN),未设置时用新增的 channel_connections.notification_locale(默认 en-US)。不新增公网访问地址配置: 多数部署运行在 localhost 或内网,链接在收到消息的手机上打不开。(#6393)

    行为变更: 进程崩溃或租约失效后恢复的执行现在会通知一次(以前不通知);每次执行 只发一条消息,不再分别发送“目标未达成”和“已暂停”;智能体暂停和任务结束现在会通知; 通知文案已更改,不再显示任务或运行 ID。已为不支持主动推送的提供方排队的旧消息会 直接落为 failed,不再重试。

  • 前端: 服务端创建的对话现在会标明来源以及是否有新内容。在侧边栏和“对话”页中, 定时运行显示时钟图标(“定时运行”),IM 对话显示对应应用的图标(“来自飞书”),GitHub 对话显示 GitHub 标志,扩展创建的对话显示拼图图标。打开之前,标题后会有一个未读圆点 (当前打开的对话不显示;读屏读作“{标题},未读”)。新建或有变化的服务端对话会在 15 秒内 出现,无需刷新;打开后所有设备上的圆点都会消失,定时运行在当前打开的对话里更新时也会 保持已读。Gateway 不提供对话活动(内存持久化)时不会轮询。(#6393)

  • 前端: 创建定时任务的对话里,任务暂停或结束时会多出一行说明(“发布清单 已由智能体 暂停。停止条件已满足:……”“……已自动暂停:连续 3 次未达成目标。”“……已结束:5 次运行 已全部完成。最后一次运行出错了。”“……已运行。”),位于它之前那轮对话的末尾,带有时间 和“查看那次运行”或“查看任务”。任务状态变化后无需刷新即可出现,删除任务后仍会保留。 “设置 → 渠道”和侧边栏的渠道列表会标明每个应用能否收到定时任务通知(企业微信“会发送 到这里”,其他应用“此应用暂不支持”)。(#6393)

  • 调度器: 网页端会让账号的 locale 偏好始终与界面语言一致(使用会话认证登录后、 以及每次切换语言时写入),定时任务的 IM 通知因此使用用户在 DeerFlow 中看到的语言; 关闭认证时不写入,改用 notification_locale。执行记录里排队中的运行现在显示 “正在等待空闲位置”。服务端创建的对话所需的前端基础能力也在此加入:根据 deerflow_origin 判断来源(IM 提供方名称已本地化,仍识别 channel_source 和旧的定时 运行对话);活动轮询只在服务端发起的对话有变化、或在其他设备上读过对话时才重新拉取 对话列表;防抖的已读标记;以及每个对话的定时任务生命周期事件和它们在对话中的位置。 (#6393)

  • 调度器: 任务页显示对话创建任务的每次执行目标和结束条件。执行记录显示目标 是否达成(含依赖已声明假设的情况);未达成的执行以中性样式显示可读的原因,不再 按执行错误标红显示原始代码;请求停止调度的那次执行会被标出。任务详情里由 Agent 停止、自动暂停和目标未达成写入的信息按界面语言显示;目标未达成和自动暂停的 IM 通知改为用文字说明原因。没有目标的任务显示不变;没有 API 变更。(#6326)

  • 调度器: 定时任务现在可以按标题或 prompt 搜索。此前找一个任 务只能逐个扫标题或打开详情读 prompt。现有过滤器上方新增的搜索框 会匹配两个字段中任一的字面子串(先去首尾空白、大小写不敏感,包括 中文),并与状态/类型过滤器及 thread 范围组合生效;清空搜索会保 留其余过滤器;无匹配时任务操作会隐藏并给出本地化提示。完全运行在 既有已鉴权的列表响应之上——没有 API 变更。(#5355)

智能体与运行时

  • 目标: 对话中达成的目标现在会记录在线程上,历史接口的最新一条也会保留目标状态。 评估器判定目标达成时,清除目标的同一个 checkpoint 会写入新的 goal_outcome 通道: 目标原文、设置和达成的时间、已用和允许的续跑次数、评估理由、relied_on_assumption 以及判定所依据回复的 id。之后任何一次目标写入都会删除它,定时任务的目标不写这条 记录。客户端无法写入:POST /state 或非内部的运行输入携带 goal_outcome 时返回 400。POST /api/threads/{thread_id}/history 的最新一条现在带上激活的 goal 和已 达成的 goal_outcome,停下的目标不会再在运行结束或刷新页面后从 Web UI 消失;其余 条目不变。从较早一轮分支出的对话不会再带回那一轮没有的目标或达成记录。 contracts/thread_goal_contract.json 固定了停止代码、检查失败代码、达成记录的 字段、历史头字段和主机自写的原因文本。(#6556)

  • 网关: 服务端替你创建的对话无需刷新即可被发现。由定时任务、IM 渠道、GitHub 智能体、扩展或 MCP 通知发起的运行,现在带有服务端维护的 metadata.deerflow_origin({kind, provider?, namespace?},由新增的 contracts/thread_origin_contract.json 固定),并冗余写入 runs.origin_kind; 为这类运行新建的对话也带同样的标记。客户端无法设置它:创建、修改对话以及发起运行时 都会去掉客户端传入的副本。新增的 GET /api/thread-activity 基于现有的运行变更时钟 分页读取调用者的运行变化,只返回因服务端发起的运行而变化的对话,并附带每个用户的 read_version;空闲轮询只需一次索引查找。POST /api/threads/{thread_id}/read 按用户保存只增不减的已读位置,对话搜索结果新增 unread。只有调用者自己的服务端 发起的运行才会让对话变为未读;交互式运行、共享对话中其他用户的运行以及升级前的 运行都不会。GET /api/features 返回 thread_activity.available(仅 SQL 持久化 可用)。(#6393)

  • 上传: 为 list_uploaded_files 发现工具新增稳定的光标分页。 当同一批过滤条件匹配到超过 100 条历史上传时, 该工具此前只能返回第一页,智能体没有任何续页途径。现在可选的 cursor 输入与 next_cursor 输出可以翻过默认页大小 20(最大 100),按纳秒级修改时间降序排序,并以确定性的原始文件名作为并列 决胜。令牌绑定到运行时解析出的 user/thread、 规范化后的过滤条件、当前 run 的排除项以及普通文件元数据; 格式非法或已失效的续页会返回受控的重启指示, 告知客户端丢弃先前收集的页。total_count 仍覆盖全部过滤匹配项,且不涉及任何新的依赖、存储层或 HTTP 端点。(#5570)

  • 智能体: 中间件声明的工具现在在每条组装路径(主 Agent、原生子智能体与内嵌 客户端)上都受 Layer-1 工具授权约束。LangChain 会在宿主显式列表过滤之后才把 各中间件的 tools 合入绑定工具集,因此扩展贡献的工具或 plan mode 的 write_todos 这类声明此前完全绕过了 tools 策略。现在每次构建都会从组装后的 中间件栈收集声明,只对普通流程从未裁决过的名字做决定——并以该流程的裁决为种子, 构建级被拒的名字(包括提供方失败时的 fail-closed 拒绝)保持拒绝——再在独立的 状态保留副本上收窄中间件栈,不会改动调用方持有的中间件实例。行为变化: 在 authorization.enabled 且显式 tools 策略不允许 write_todos 时,plan mode 构建不再绑定 write_todos,TodoMiddleware 也随之停止注入 todo 系统提示与 未完成 todo 的完成提醒;内置 RBAC 默认(缺少工具策略 = 不限制)不变,关闭授权 的部署也不受影响。Layer 2 现在还把宿主解析的工具来源写入 AuthzRequest.context["tool_provenance"],并把基础设施豁免(生成的 tool_search 辅助工具)绑定到宿主创建的具体工具对象而非其名字,同名的外来工具 不再能继承该豁免。(#6105)

  • 智能体: 自定义智能体现在可以在智能体设置中持久化默认知识 scope,让专用智能体每次对话都从自己的语料库开始,而不是全部 operator 批准的知识库。当新回合未给出显式选择时——包括没有 composer 选择器的调用方——网关准入会应用保存的默认值; 显式的按消息选择仍然优先,选择全部知识库会清除该绑定, 重新生成/续跑则保留来源回合已接受的 scope。这只是默认值, 不是访问控制策略:operator 允许列表与数据集可用性仍然生效。 (#5579)

  • 配置: 运维者现在无需改源码即可叠加核心系统提示词。 lead_prompt_overlay.prepend/append 环绕装配后的 lead prompt, subagents.agents.<name>.prompt_overlay 扩展内置与配置的子智能体 提示词而不改动共享注册表,DeerMem 的 memory.backend_config.prompt_prepend/prompt_append 则扩展渲染后 的记忆更新系统消息。空叠加保持提示词字节不变,叠加文本按字面 处理,因此在运维者主动启用之前,默认行为与现在完全一致。 (#5794)

  • 代理: 工具产生的文件路径、URL 和任务 id 现在会记录为短的稳定产物句柄 (art_xxxxxxxx),在上下文压缩后仍然有效,模型不再因为早期工具消息被摘要而 丢失或编造确切引用。每个线程在 ThreadState.tool_artifacts 中维护一个有上限 的注册表并投影到模型的隐藏持久上下文块;工具参数里出现的句柄——裸写、反引号 包裹或嵌在 dict/list 参数里——都会在工具执行前解析回真实引用,未知或已过期的 句柄会让这次调用失败而不执行工具。新增 tool_artifacts 配置段(enabled、 max_entries 默认 100、detect_refs_in_text、inject_model_context、 resolve_handles_in_args)分别控制各环节,默认全部开启。句柄是 agent 局部 的:task 参数会把父级句柄解析成具体引用,委派报告也必须返回具体引用。聊天 时间线的工具调用步骤会为每个捕获的产物显示徽标,SSE values 快照新增附加的 tool_artifacts 键。(#4929)

  • 上下文: history_search 新增可选的 role 参数(user、assistant 或 tool)。该搜索跨所有角色最多返回八条结果,当调用方要找用户早先的一条纠正 时,重复出现的 assistant 讨论或工具输出可能先把名额占满。角色过滤在八条上限 之前作用于活跃与已归档消息,使用归档记录中已有的角色字段;省略或传 null 保持 原有的全角色搜索,返回结果中的角色仍是 human/ai/tool,未知角色返回 错误;不涉及存储迁移,history_read 也没有新增参数。(#5927)

  • 子代理: 委派任务现在可以选择 JSON 语法验收标准 acceptance_criteria=["file:../outputs/report.json json-valid"],task 和 batch_task 均可使用。此前委派产生的 report.json 可能非空却不是合法 JSON, 而既有的"存在"与"非空"检查无法区分这种情况。该标准在完整 UTF-8 JSON 且不超过 50,000 字节时成立;完整但非法的 JSON、空文件和 NaN/Infinity 不成立;缺失 文件在确认不存在时判负;超大文件、读取不完整或不可用、解析器资源受限仍为 UNVERIFIED。本地读取是有边界的二进制读取;远端读取保留全新 shell、绝对路径 工具、普通文件与目录包含检查,经 base64 信封最多传输 50,001 字节,没有无界的 read_file 回退——远端工具不支持时保持 UNVERIFIED。顶层标量、重复键和语法 合法的大数字允许;UTF-8 BOM 会被拒绝。只查语法——不做 schema 或业务字段 检查;既有标准与判定语义不变。(#5947)

  • 网关: 多进程启动门控现在除了 GATEWAY_WORKERS > 1,也会在显式 deployment.multi_instance: true(或 DEER_FLOW_MULTI_INSTANCE=1)时触发。 worker 数变量只能看到一个进程树,因此拥有多个单 worker Pod 的 Kubernetes Deployment 此前能通过全部检查,而每个 Pod 的启动孤儿调和会在每次滚动更新时把 其他 Pod 的无租约运行误判为已崩溃。两条路径都要求 Postgres、 run_events.backend: db、run_ownership.heartbeat_enabled: true,现在还要求 Redis stream bridge,并拒绝显式 sandbox.ownership.type: memory、进程本地的 浏览器工具以及未设置 scheduler.multi_instance 的调度器。智能体存储分叉警告、 入站 webhook 去重警告与微信扫码登录守卫都遵循同一声明。行为变化: GATEWAY_WORKERS > 1 且使用 memory stream bridge 时不再启动;请配置 stream_bridge.type: redis 或 DEER_FLOW_STREAM_BRIDGE_REDIS_URL (docker-compose 与 Helm chart 已自动注入)。(#6328)

  • 开发: 新增 scripts/dev_multi_instance.sh(make dev-multi、make dev-multi-check、 make dev-multi-down),在本机把两个 Gateway 作为一个多实例集群运行:回环地址上的 临时 Postgres 与 Redis 容器,Gateway A(8001 端口)与 Gateway B(8011 端口)共享同一 DEER_FLOW_HOME 以及生成的 AUTH_JWT_SECRET、DEER_FLOW_INTERNAL_AUTH_TOKEN 与 DEER_FLOW_CREDENTIALS_KEY,各自使用独立的 DeerMem 检索索引,并可选在 2027 端口启动 轮询 nginx。配置取开发者自己的 config.yaml(或 config.example.yaml),叠加多实例 启动门控要求的设置;门控拒绝的设置会被修改并逐项提示,配置中引用的 checkpointer、Redis 端点、 DeerMem 与 blob 数据目录以及本地 AIO 沙箱容器名前缀都会被重定向到 harness 内部(非 DeerMem 的 记忆后端按原配置使用)。容器带有所属状态目录的标签,删除时连同其匿名卷一并 移除;up 中途失败会拆除已启动的部分。check 自动执行可脚本化的跨实例 检查:就绪探针、共享会话与内部令牌、线程/上传/产物可见性、技能开关、nginx 负载均衡, 以及跨实例的 SSE Last-Event-ID 续接。详见 backend/docs/CONFIGURATION.md 的 “Local two-Gateway harness”一节。 (#6613)

  • 配置: 新增 DEER_FLOW_ENV_FILE,在后端启动时选择一个显式的 UTF-8 dotenv 文件,配置 加载、认证启动和调试入口共用;相对路径以后端进程工作目录解析,已有进程环境变量保持 优先,选择为空、缺失、不可读或编码无效时直接报错而不回退。不设置时保持默认 dotenv 发现; 当 PYTHON_DOTENV_DISABLED 禁用加载时,显式选择会报错。(#6227)

  • 智能体: 运行事件携带每次 LLM 调用的遥测:llm.ai.response 元数据新增可选键,包括 LangChain run id、调用方类别(lead_agent、middleware、subagent、fallback)、 provider、模型、停止原因、provider 上报的 token 数和渲染后的请求大小;llm.error 携带 同样的身份信息加 error_type;摘要中间件会发出 middleware:summarize 事件,标记摘要器 输出与既有 summary_text 完全相同的情形。仅为观测:提示词、请求与 token 统计不变,所有 遥测辅助逻辑失败时只记日志。(#6236)

  • 上下文: 当完全相同的摘要提示词此前已产出与现有摘要逐字节一致的 结果时,压缩会跳过摘要器 LLM 调用。中间件维护一个有界缓存,以渲染后的 提示词加上有序的模型候选列表为键,只缓存首个候选模型返回与当前摘要一致 结果的提示词;因此回退候选产出的未变化结果不会进入缓存,也就不会抑制 对失败的首选模型的重试。复用还要求当天的前一条摘要与缓存输出一致,缓存 出错则回退到正常生成。middleware:summarize 日志事件新增 llm_call_skipped 与 skip_count。(#6318)

记忆

  • 记忆: 跨会话记忆新增 user.cognitiveStyle,用于沉淀稳定的 协作偏好(响应结构、详略程度、纠错方式)。它与 personalContext 及任务技能分开存储、分开注入,因此“先给结论”这类协议不再在 max_injection_tokens 预算下与项目事实争抢名额。旧版 memory.json 文件在加载时会被规范化,<memory> 注入新增一行 Thinking Style:(外加可选的 category: cognitive 事实),该字 段可在设置 → 记忆中编辑,并通过 Gateway 记忆 API 暴露。 (#3182)

  • 记忆: sample-memory 加载器现在可以批量加载已注册用户。此前 它写入旧版共享记忆路径,而已认证用户读取的是按用户隔离的存储,因 此加载即使成功,设置页仍显示没有任何记忆。 scripts/load_memory_sample.py 现在要求 --target PATH 与 --all-users 二者恰好给出其一;--all-users 会从所配置的持久化 数据库枚举已注册用户,并在替换前备份每个用户的既有记忆。非持久化 数据库模式会被拒绝。(#4066)

  • 记忆: 记忆搜索与注入现在可以按词汇相关性为候选排序,而不是 只看置信度。仅按置信度注入可能让与当前任务无关的事实排在有用事实 之前;可选开启的 retrieval_relevance_enabled(默认 false)将 IDF 加权的查询覆盖率与置信度混合打分,在 top_k 之前先做类别过 滤,并可让选择更加多样——不引入 embedding、向量存储,也不改动持 久化格式;分词器无需新增依赖即可处理 CJK 二元组。旧版 get_context 实现继续可用。(#5251)

  • 记忆: 为用户记忆摘要存储新增可选的容错型 MarkdownMemoryStorage(memory.storage_class: markdown)。 损坏或写入不完整的文件不再抛出 MemoryStorageCorruption 并令智能体崩溃:Markdown 摘要通过一个无损的围栏式 memory-json 块来理解,并提供尽力而为的结构化回退,普通 JSON 仍可正常加载。 磁盘上的 JSON 格式与 JSON UI 均未变化, 写入仍是有日志(journaled)保护的 JSON—— 本次只提供读取路径的容错;完整的 Markdown 写入路径留待后续。(#5545)

  • 记忆: 新增确定性的 DeerMem 作用域隔离基准,位于 backend/scripts/benchmark/deermem_scope_isolation。可选的 validate/run/report 工作流会检验生命周期持久性、并发修正, 以及智能体与用户记忆作用域之间的隔离, 且不改变任何运行时记忆行为。(#5564)

  • 内存: 内存管理 API(读取、重载、状态、导入/导出、清空和单条事实增删改) 新增可选的 agent_name 参数,指定后只作用于该 agent 的内存分桶,从而可以独立 地查看和维护各自定义 agent 的记忆。省略该参数则保持既有行为:读取和导入用 默认分桶,清空仍然作用于整个用户。参数按字母/数字/连字符校验(否则 422), 且后端必须声明 supports_agent_scoped_management,否则作用域请求返回 501, 而不是悄悄改错分桶。Gateway 保留调用方的大小写拼写,因为 Mem0 把 agent 名 当作区分大小写的存储身份;DeerMem 仍在内部自行做小写规范化。(#5565)

  • 内存: 记忆提取新增可选的 Jev(TypeSafe)预筛和信号分类,默认均关闭。 memory.prescreen(off/shadow/enforce)在提取 LLM 调用前对每批对话分类: shadow 只记录结论、照常提取;enforce 允许低概率批次跳过调用并推进水位线, 即按"本批无持久内容"消费。memory.signal_classification(off/shadow/ hints)把 reinforcement/correction 标签合并进提取提示文本,且仅在预筛为 enforce 时可以否决跳过。两者都运行在新的共享 TypeSafe 客户端 deerflow.typesafe 上——它从护栏风险门提取而来(问题、阈值与策略不变), 统一错误分类:请求级失败抛异常,问题级失败作为数据返回 (AnswerSet.errors_by_question)。新增可选顶层 typesafe: 配置块(api key/env、base_url、model、timeout、deadline、attempts、backoff)供所有消费者 共享——消费者自身 config 优先,既有护栏部署不受影响。config_version 从 49 升到 50(make config-upgrade 合并新键),backend/scripts/eval_memory_prescreen.py 提供开启 enforce 前必须通过的影子评估(精确的 Clopper–Pearson 错过率上界、 分层检查),证据不足时报 INSUFFICIENT。(#5906)

知识与检索

  • 知识库: 自定义智能体对话可以按消息为 RAGFlow 检索划定范围。 通过可选开启的知识库选择器,可为单轮选择全部运维已批准的数据集、 特定数据集/文件,或本轮不做检索;该选择会以经过校验且不可变的 knowledge_scope 快照存放在 HumanMessage 上,并在 Gateway 准 入、中间件、RAGFlow 检索以及原生与批量子智能体处统一强制执行—— 超出允许清单的选择会失败关闭(fail closed),scope id 也绝不会进 入模型输入或追踪。默认关闭,由 knowledge_base.scope_selection_enabled 控制(配置版本 41)。 (#5238)

  • RAGFlow: RAGFlow 检索结果现在带有可核验的来源引用。 点击行内知识引用、Sources 中的文档标题链接或回答的知识来源列表, 即可打开检索到的摘录、数据集与文档名称,以及 RAGFlow 提供时的页码,且会话重新加载后来源仍然可用。knowledge_search 会把不透明的引用链接与有界的原生 tool-message 工件配对, 后者保存交给模型的原始摘录(标注截断、抹除所配置的 API key); 普通 task 结果只转发子结果中被引用的已捕获来源; 输出预算会在生效限额内保留完整的证据条目, 对放不下的条目附带说明后省略。缺失的记录显示为不可用。 这些是检索时刻的证据快照,而非实时的全文查看器;既有的 knowledge_search() 直接调用方仍获得字符串返回类型。(#5551)

  • 社区工具: 图片搜索工具现在暴露 color 与 license_image 过滤器。二者此前已接入请求路径,却在工具签名中不可达,智能体无法 在为图像生成寻找参考图时把结果收窄到特定调色板或已获授权的图片。 合法取值遵循 ddgs(color:从 Monochrome 到 White; license_image:any、Public、Share、ShareCommercially、 Modify、ModifyCommercially);不设置过滤器时发出的请求与之前 完全一致。(#5723)

  • 搜索: Serper web_search 接受可选的模型参数 time_range (day、week、month、year),映射为 Serper 的 tbs 参数 (qdr:d|w|m|y);省略或传 null 保持原有的不受限请求,非法值在发起 HTTP 请求前即被拒绝,Serper image_search 不受影响。(#6113)

  • 抓取: Jina web_fetch 支持通过其 tools 配置项开启有界重试: max_retries(默认 0,关闭)与 retry_budget_seconds(默认 30)。 仅 HTTP 502/503/504 与连接建立失败会重试;退避等待在覆盖所有尝试的共享 预算内随机化,开启重试最多发出 1 + max_retries 个上游请求并增加开销。 (#6143)

技能

  • 技能: 回答会展示其加载了哪些技能,并提供可检视的快照。成功读取受配置管理的 SKILL.md 与显式斜杠激活现在会记录有界的只读快照,回答工具栏新增 Skills 菜单,仅列出 该回答所属运行加载过的技能;选中一项即打开被捕获的文件(含 frontmatter 的原始 Markdown,包内相对链接保持为不可导航的引用),桌面端为可调整大小的面板,移动端为底部 抽屉,复制会返回原始 Markdown。运行日志在最终答案上持久化首次加载顺序的聚合记录,使 分页历史同样保留证据;没有加载证据时该入口隐藏。(#5758)

模型与集成

  • 社区工具: 新增 Unbrowse web_fetch provider——以 Markdown 形式返回页面 内容:普通 HTTP 足够时直接抓取,需要 JavaScript 的页面则在 Unbrowse 托管的 云端浏览器中渲染(render: auto|never|always)。每次抓取仅一次 JSON-RPC POST,不引入新依赖。(#5981)

  • 模型: 管理员现在可以从“设置 → 模型”管理共享模型, 无需编辑服务器配置。新的仅管理员端点 GET/PUT /api/managed-models 与 POST /api/managed-models/test 维护 $DEER_FLOW_HOME/managed-models/ 下的加密目录——凭据永不返回, 省略的键保留已保存的值,过期的编辑会按 revision 被拒绝。 已启用的托管配置会追加到新的生效配置快照中;YAML 配置保持只读并在命名冲突时胜出。首个版本支持 OpenAI 兼容的 Chat Completions 端点;既有的模型鉴权仍然适用。(#5596)

  • 模型: 为模型档案新增声明式推理能力契约。模型可在旧布尔值之外声明可选的 reasoning: 块(thinking: unsupported|optional|required、on_disable_request、dialect、 history 与 effort: {values, default, aliases, path}),相互矛盾的档案在配置加载时 直接失败。create_chat_model 在所有位置强制同一份规范化策略——主智能体、子智能体、 摘要与标题生成——必需思考的模型不会进入关闭分支,effort 取值经别名映射或回落到声明的 默认值,而不是原样发送。GET /api/models 与 DeerFlowClient 新增 reasoning 对象 (source: legacy|contract),前端按模型声明的取值生成 effort 菜单(例如 GLM-5.3-Flash 的 Low/High/Max),并对必需思考的模型隐藏"关闭";PATCH /api/v1/auth/preferences 接受特定提供商的 effort 词。没有该块的档案保持旧路径不变。 (#5780)

  • 社区工具: InfoQuest 工具不再阻塞 agent 事件循环。InfoQuestClient 的 fetch/web_search_raw_results/image_search_raw_results 此前用阻塞的 requests.post(..., timeout=30),启用该社区工具时每次调用最多让事件循环停顿 30 秒。现在改用 httpx.AsyncClient,web_search/web_fetch/image_search 工具包装改为协程,web_fetch 还通过 asyncio.to_thread 把 CPU 密集的可读性 正文抽取放入线程——与此前迁移的 jina_ai 一致。返回值、错误文案和 30 秒 传输超时均不变。(#5782)

  • 社区工具: Tavily 的 web_search/web_fetch 工具不再阻塞事件循环: _get_tavily_client() 现在返回 AsyncTavilyClient,web_search_tool/ web_fetch_tool 改为协程并 await client.search(...)/client.extract(...), 无论成功失败都会关闭客户端。凭证处理不变——工具配置没有 key 时仍以 api_key=None 构造客户端,由 SDK 继续从环境变量读取 TAVILY_API_KEY。 (#5784)

  • 社区工具: Firecrawl 的 web_search/web_fetch 工具改为协程工具: _get_firecrawl_client() 返回 AsyncFirecrawlApp,工具改为 await search()/scrape(),不再走此前会阻塞事件循环的同步调用。结果归一化、 错误处理、API key 处理和自定义 api_url 行为均不变。(#5786)

  • 社区工具: 图片搜索工具不再在事件循环上执行 DuckDuckGo 搜索。 image_search_tool 现在是协程,通过 asyncio.to_thread 把配置解析和阻塞的 DDGS 调用一并交给工作线程(ddgs SDK 没有异步 API),在读取配置文件和网络 请求期间保持事件循环可响应。搜索参数、结果结构、超时和错误处理均不变;已 发出的请求无法取消,工作线程最多存活到既有的 30 秒超时。(#5788)

渠道

  • channels: IM 通道可以在多个 Gateway 实例上运行。channels.leader_lease 生效时(默认 auto 跟随多进程信号),每个 provider 的平台连接只属于在新表 channel_leases(迁移 0042_channel_leases)中持有该行的那个实例:归属由一条 upsert 依据数据库时钟判定,续约携带防护 epoch,持有者按自己的单调时钟在对端 可以连接之前先停传输,因此数据库卡住也不会出现两个 Telegram 轮询者或两个 Discord 会话。持有者崩溃后,对端在 lease_seconds + grace_seconds 再加一个续约 周期内接管;正常停止的持有者会释放租约,对端在下一次心跳接手;停止超时的传输保留 租约直到过期。GitHub webhook、消息总线和 manager 仍在每个实例上运行。 GET /api/channels/ 会报告每个 provider 的 leader,running 在每个实例上都反映 持有者的传输状态,所以无论负载均衡把请求发到哪个实例,Web UI 的 provider 列表和 连接流程都能正常工作;定时任务的 IM 通知只由该 provider 的持有者认领。启动门禁在 拒绝 enabled: false 时改为推荐 auto/true,并拒绝没有 SQL 数据库的显式 enabled: true。从 Web UI 保存的凭据仍要等持有者下次启动该通道才生效,故障切换会 取消进行中的 IM 回复。 (#6843)

  • channels: 新增 channels.leader_lease 配置段(enabled: auto|true|false、 lease_seconds、grace_seconds;仅启动时读取),为按 provider 的 leader 租约预留, 落地后每个 IM 平台连接只由一个 Gateway 实例持有(传输归属本身另行提交,在此之前 启用通道时仍需保持单实例)。多进程启动门禁现在会在 channels.* 启用了 GitHub 以外的 IM 通道、或 channel_connections 允许从 网页连接时拒绝 enabled: false,错误信息列出相关 provider 和当前真正可用的补救方式;格式 错误的配置段在任何拓扑下都会拒绝启动。auto 跟随多进程信号,单实例行为不变。 ChannelService 不再把该配置段当作 provider,双 Gateway 联调脚本也不再改动它。 config_version 升至 60;Helm chart 与文档保留"启用通道时保持 1 副本"的提示,但不再只靠 这条注释:门禁现在会对显式关闭租约的情况强制拒绝。 (#6746)

  • 渠道: 现在可以在 Web UI 中扫码连接微信。 侧边栏与设置中的连接对话框新增二维码登录,带进度、配对码输入、 过期与重试状态,以及一个保留未过期绑定指令的“再次扫描”选项; 凭据保存在后端,且只有微信渠道会重启。 新的微信连接默认走扫码登录,手动输入 token 仍然可用。(#5582)

  • scheduler: 把定时任务的运行结果推送到任务所有者已绑定的 IM 身份: 完成钩子把结果写入持久化的 notification_deliveries outbox, 由 NotificationDeliveryWorker 负责投递。仅当 channel_connections.enabled 为 true 且渠道服务在运行时生效;手动触发和中断不推送,等待投递期间被解绑的 身份也不再推送。目前只有企业微信实现了主动推送 send_notification, 其它渠道在实现之前会在 outbox 中显式失败。 (issue #4254, #4843, #6135)

  • 频道: 新增 QQ 频道,通过腾讯 QQ 开放平台官方 WebSocket 网关接入, 无需公网回调地址。支持文本单聊(C2C)与群 @ 消息的文本回复,并遵守 QQ 被动回复配额(同一来源单聊 60 分钟 4 条、群聊 5 分钟 5 条),支持 allowed_users、channel_connections 一次性 /connect <code> 绑定及 按发送者隔离的群话题。(#6081)

认证与防护

  • 鉴权: 让 skills 策略键同样约束面向用户的技能列表入口的可 见性。此前策略拒绝 skills 的角色仍能在斜杠命令自动补全、 GET /api/skills、GET /api/skills/custom 与 GET /api/skills/{name} 中看到所有技能,要到激活时才发现被拒 绝。列表可见性现在经由 filter_resources(principal, "skill", ...) 处理,与 list_models 一致:被拒绝的名字从列表中消失,详情端点返回与真实 缺失逐字节相同的 404(不构成存在性 oracle),匿名调用方保持不过 滤,provider 解析错误遵循 authorization.fail_closed。管理端点 仍由 require_admin_user 把关;运行时激活授权仍沿用 #4541 的 层。(#5489)

  • 中间件: 为绑定模型的上下文新增确定性、可选开启的 PII 脱敏 (pii_redaction.enabled,默认关闭)。PiiRedactionMiddleware 会把真实用户消息与远程内容工具结果中的 PII(与工具结果清洗器同一 白名单)改写为 [EMAIL_1] 之类的不可逆占位符,仅用正则检测 器——邮箱;OpenAI/AWS/GitHub/Slack/Google API 密钥;经 Luhn 校 验的信用卡;国际/中国/美国电话号码;中国身份证号、CPF、CUIT/RFC 国民身份证号。不存储任何映射。占位符编号跨回合稳定(每次模型调用 都按值去重的计数器重新脱敏整段对话),外化的工具输出副本同样持有 脱敏后的文本,子智能体自动继承该中间件。原始文本仍留在线程状态 中;默认行为不变。(#5527)

  • 前端: 输入框(composer)发送改为受 runs:create 权限把关。 两条聊天路由都会解析 canCreateRuns,共享 composer 在 submitThreadMessage 顶部检查它——这是提交按钮、回车发送、目标 设置触发的 run 以及程序化提交的唯一汇聚点。被拒绝的角色会得到一 个禁用的发送按钮,通过 aria-label/title 自我解释,提交时弹出 info toast,草稿文本保留以便重试;流式传输期间按钮仍如常作为 runs:cancel 的停止控件。缺失或为 null 的权限列表对混合部署保持 宽容。与 #5294 的停止把关相对应。(#5528)

  • 护栏: 新增可选的 TypeSafe (Jev) 工具调用风险闸门 deerflow.guardrails.typesafe:TypeSafeGuardrailProvider,通过 guardrails.provider.use 选择。每个被探测的调用会向 TypeSafe System One 发送一个携带工具名与完整参数 JSON 的是非题,并在达到 threshold(默认 0.5)时以模型可据此调整的 ToolMessage 错误拒 绝。allowed_tools 仍是在发出任何请求之前于本地强制执行的硬性许 可列表;过大或无法序列化的参数在本地拒绝,零网络请求;provider 错误遵循 guardrails.fail_closed。这是唯一会把工具参数发给第三 方的护栏 provider——启用前请先评估出站流量。config_version 由 46 变为 47。(#5712)

  • 鉴权: 插件动作与管理操作现在经由所配置的鉴权 provider 把关。POST /api/plugins/{ns}/actions/{name} 现在要求对 <ns>/<action> 作出 plugin_action/invoke 决策,检查在解析 之后、读取请求体之前进行,拒绝时返回 403; deerflow-extension-api 新增的 require_plugin_management/arequire_plugin_management 为扩展贡献的管理路由提供 fail-closed 检查,带有独立的 read/write scope;内置 RBAC 新增 plugin_actions 与 plugin_management 键,省略键即表示该资源不受限制;插件 工具描述符现在报告 source: "plugin:<namespace>"。在设置 authorization.enabled: true 之前强制执行保持关闭; extension-api 契约版本 0.2.4、config_version 49。(#5842)

扩展与插件

  • 插件: 全栈插件 API 与一个书签示例包。部署安装的 Python 扩展现在可以自带浏览器页面、会话动作与模型工具: registry.plugin(PluginContribution(...))(extension-api 0.2.2) 在同一个插件命名空间中注册贡献,带鉴权的描述符、ES 模块与声明的动作在 /api/plugins 下提供服务, 前端在运行时加载插件页面、工作区页面与会话动作。 deerflow-extension-bookmarks 示例端到端地演练了 这条路径;默认不启用任何插件。(#5647)

  • 插件: 全栈插件现在支持 manifest 与静态资源目录。 extension-api 0.2.3 新增 BrowserAssets(module, root, manifest="ui_manifest.json"), 作为 BrowserModule 的姊妹 API:带版本的 manifest 指定入口模块,并列出从 namespace/content-revision URL 服务的文件,附带认证、正确的 MIME 类型、nosniff、document sandbox 策略与不可变缓存。打包入口以原生带凭证的模块图加载, 插件因此可以拆分 JavaScript 并用相对导入解析 CSS/图片; 既有内联模块继续可用。(#5685)

  • 扩展: 扩展路由现在可以请求绑定到已认证请求用户的 run 证据读 取器。新增的 resolve_run_evidence_reader(request) 与 require_run_evidence_reader(request) API 返回不可变的按用户隔 离 RunEvidenceReader,受 runs:read 限制,并保留不可见 run 的 行为与作用域边界上的游标校验。既有全局读取器仍对受信任的 Gateway 生命周期服务开放。deerflow-extension-api 升级到 0.2.2。 (#5727)

  • 插件: 新增独立示例插件 deerflow-extension-jev-context (community.jev-context),从长对话中修剪过期的工具结果。它通 过 lead agent 的 before_model/abefore_model 钩子向 Jev 询问 旧的只读工具结果,并缩短保留概率较低的内容,同时保护近期消息、工 具调用记录、host 标记的错误、技能读取与多模态内容。需要部署方显 式启用,且 Jev key 只能来自环境变量;请求失败时保留原始历史。修 剪是有损的并会持久化到图状态——禁用插件不会恢复被删减的文本。 (#5731)

  • 插件: 新增独立示例插件 deerflow-extension-jev-classify (community.jev-classify),贡献 classify_texts 模型工具:针 对调用方提供的 2-32 个类别给 {id, text} 条目打标签,并按输入顺 序返回每个条目的一个标签与状态。提供两种由部署选择的后端:Jev (每个条目一道选择题)或 OpenAI 兼容的 chat endpoint(要求返回经 过校验的 JSON 标签列表)。调用按批进行(默认每请求 10 条),并发 有上限,截止时间 25s;失败按条目显式报告,不做任何重试。 (#5735)

  • 扩展: 打包扩展现在可以在运维者授予的限额内调用宿主模型。 plugins[].host_access.model_invocation 把逻辑角色映射到模型, 共享按安装计的并发、有界准入、超时与输入/输出限制;extension API 0.2.4 新增请求/结果契约与 ExtensionRuntimeDeps.model_invoker。 调用使用宿主的模型工厂与追踪,并在子进程中做内联 Draft 2020-12 schema 校验;未获授权的扩展仍得到 None,授权遵循需要重启的 plugin 生命周期。(#5796)

  • 示例: 新增 deerflow-extension-jev-screening, 一个可选启用的打包扩展,会把抓取内容中被注入的指令以建议 (advisory)形式标记出来。TOOL_VISIBLE 中间件对可见的 web_fetch/web_search/image_search/web_capture 与 MCP 工具结果中的每条文本消息,基于脱敏后的摘录单独发起一次模型 请求进行分类(每次调用至多八条消息),before_model 随后把 最近一步工具的被标记消息替换为固定的建议前缀,绝不改动原始 内容。经 plugins: 加载;在设置 config.enabled 之前保持 惰性,provider 失败与超时会原样放行结果,没有任务存储时的 运行不会筛检任何内容。(#5833)

  • mcp: 管理员缓存重置(POST /api/mcp/cache/reset)现在会在 extensions_config.json 旁写入持久的代次标记,共享该目录的每个 Gateway worker 都会在下一次 MCP 查询前清退各自的工具缓存与会话池——从未改动 配置文件的远端 tools/list 变化也随之生效。响应以 scope: shared_config 标识共享目录重置,无法解析配置路径时返回 scope: process;文件系统 相互独立的副本不会被隐式覆盖。(#6126)

  • 扩展: 能力中心的扩展画廊默认展示仓库内置的扩展目录—— 行为变化: 未注册任何插件的部署现在会看到五个示例包,附带本地化的 名称、描述、搜索和安装说明链接,而不是空白页面。运行时描述符按显式 namespace 合并进同一列表,不产生重复行。仅目录条目只是发现用的元数据: 不声称安装或启用状态,也绝不进入模块加载器,在运行时发现进行中或不可用 时依然可见。(#6188)

  • 扩展: 全栈插件现在可以向输入框的 @ 提及选择器提供候选。浏览器 模块的可选 mentionProviders 会与内置技能、文件、对话一起参与搜索; 选中后插入行内令牌,提交时发送可读的 @label 文本,并附带带命名空间的 additional_kwargs.extension_mentions 元数据(最多 16 个引用)。查询 有防抖与长度限制,绑定查看者与线程,并在查询、查看者、语言或已安装 快照变化时立即失效;某个提供方失败或超时不会影响其他健康候选。这些 元数据属于用户输入而非授权凭据——插件必须自行校验被引用对象与查看者 权限。(#6189)

  • 扩展: 打包扩展现在可以获得宿主绑定的完整 Agent 运行控制 (extension-api 0.2.5)。插件后端动作与模型工具会收到 context.agent_runs,扩展贡献的路由可调用 require_agent_runs(request), 获得 create_thread、start、resume、get、get_state、wait、 cancel 等操作,全部经由 Gateway 常规准入、检查点与取消机制。该句柄 绑定到已认证用户:每次操作都会重新校验权限与属主,仅会话凭据或本机 关闭鉴权模式可获得该能力(PAT 与内部/渠道调用方不会获得),幂等键按 插件命名空间隔离,句柄不可序列化、也无法跨重启存活。(#6190)

  • 扩展: 新增 agent-teams 示例插件,让已有的完整自定义 Agent 以团队协作:每个 成员保留绑定其 Agent 的持久会话,成员通过原生 @ 提及与定向请求相互委派工作, 同级请求异步执行,并在独立的团队页面上共享活动流。团队按用户隔离,通过 config.yaml 的 plugins: 选择性启用;Capability Center 在安装前就会列出该 示例并附本地化的安装说明。(#6243)

持久化

  • 持久化: 新增 checkpoint 保留服务,在 #5255 契约之上实现 #4189 第 3 项的删除侧。默认会修剪末尾的纯 duration 叶子(每个叶 子都会物化其父节点的负载,移除即可回收父节点大小的行,且不触碰仍 存续的链);被取代叶子的兄弟分支修剪仍保持可选,由 prune_leaf_sibling_branches 控制,因为客户端可能把它们当作恢复 点。恢复头、其祖先链以及仍有活跃 pending-writes 行的 checkpoint 均受保护。尚无生产触发路径——调用接线将另行落地。(#5308)

  • 存储: 新增内容寻址的 blob 存储契约(deerflow/storage/), 即 #4189 第 2 项的存储抽象部分。BlobRef(sha256、size、kind、 content type)与分层的 BlobStore 接口一起发布,并附带 local_fs 默认后端:分片布局、原子发布、读取时校验摘要、并发写 入可收敛。纯增量且需主动启用——blob_storage.enabled 默认为 false,尚无任何生产方迁移过来(图片查看与外置工具结果是已点名 的后续项),因此未设置的部署行为与今天完全一致。(#5361)

  • 持久化: 启用 blob 存储后,被外置到虚拟 read_file 路径的超大工具结果 会同时写入共享 blob 存储,处理下一次模型调用的 Gateway 会在模型读取前在 本地恢复经摘要校验的原始字节——此前完整内容只存在于执行工具调用的那台 Gateway 上,负载均衡后的后续读取会遇到失效路径。blob 写入失败或超出 64 MiB 生产者上限的结果会回退为内联内容,而不是留下不可持久化的路径; blob 存储默认仍为关闭。(#6159)

  • 持久化: 启用 blob 存储后,通过 view_image 查看的图片会连同本地路径 一起持久化到共享 blob 存储,这样后续落在另一台 Gateway 上的模型调用可以从 校验过的共享字节重建图片上下文,而不会因写入方本地文件缺失而丢失。存储 写入失败会返回通用工具错误,而不是保存虚假声称跨实例持久的状态;blob 存储默认仍为关闭。(#6160)

前端

  • 前端: 将能力中心统一为带插件配置与自定义智能体选择的可搜索 目录(catalog)。目录条目建立在既有 MCP、Lark CLI 与技能服务之 上,携带本地化元数据、分类、图标与可编辑的配置表单;MCP 安装在保 存前校验必需的 transport 字段,普通用户看到的是安全化的安装投 影,管理员变更保留其角色检查。自定义智能体获得稳定的 MCP 安装 ID 与显式的插件/技能选择——省略或为 null 的选择继承所有已启用的连 接,空列表则一个都不选——冲突的安装 ID 会在写入时被拒绝,含义不 明的 ID 在运行时被排除;该选择会贯穿普通与持久化的批量委派,且不 改动共享的 MCP 缓存,也不授予任何授权。捆绑钉钉/企业微信群通知与 HubSpot CRM 工具;既有 MCP 与 Lark 工作流保持不变,也不会自动启 用任何东西。(#5497)

  • 前端: 输入框支持行内 @ 引用:在光标处键入 @ 或点击 @ 按钮会打开 分组选择器,可搜索已启用的技能、当前项目文档和其他会话,插入的原子引用 可通过常规编辑删除。一条消息最多可选 16 个去重技能,每个选择在激活前都会 经用户注册表与智能体允许列表校验。(#6063)

  • 前端: 输入框的 / 菜单现在只列出内置的 /goal 与 /compact 命令。 行为变更: 网页端斜杠菜单不再选择技能——请改用 @ 内联引用选择技能; 旧的已选技能气泡被移除,旧草稿中的技能选择会恢复为内联引用,手动输入的 旧式斜杠文本仍按普通消息提交。(#6154)

  • 前端: 输入框上方的目标栏现在说明激活的 /goal 在做什么、为什么停下。运行推进 目标时显示“进行中”;自动续跑停下后显示“已停止”“等你回复”“等待中”“未能检查目标” 或“已暂停”,第二行写明原因和下一步,不再一直显示“续跑中 8/8”或看起来像刚设置的 目标;“详情”里有完整目标和评估器原话。目标达成后显示“目标已达成”(以及自动续跑 了几次),直到对话继续往下走,刷新后也一样;第一次运行就达成的目标不会再残留在 目标栏里。设置了目标时,最后一轮的编辑铅笔改为灰显并附提示,而不是直接隐藏;消息 工具栏在键盘聚焦时也会显示。因还有运行未结束而被拒绝的 /goal 会保留草稿并说明 原因。(#6556)

修复

  • llm: 流式响应已返回 200 之后才在流内报告的 provider 错误,现在会像以 HTTP 状态码返回的同一错误一样重试。为了用 SSE keepalive 维持慢请求而提前返回 200 的 OpenAI 兼容网关,会把之后发生的 504 作为 SSE error 事件发出;openai SDK 将其抛出为 不带状态码的 APIError,因此 LLMErrorHandlingMiddleware 此前把它归为普通错误, 运行在第一次尝试后即失败。现在这类不带状态码的错误会依次从数字型 code 或 status 字段、消息中明确写出的 [504]、HTTP 502 或 status 503(messages[502] 这类 数组下标不算)、以及 type: server_error(视为 500)中取得 4xx 或 5xx 状态码;都 取不到时,繁忙判断才会额外匹配 rate-limit、RATE_LIMIT_… 等写法。重试预算、退避 和熔断器按对应的 HTTP 错误处理;带状态码的错误分类不变。 (issue #6896, #6897)

  • 渠道: 从 IM 渠道发起的运行不再随接收消息的 Gateway 实例一起消亡。manager 在整个 运行期间保持 runs.wait / runs.stream 请求打开,而 Gateway 会在创建请求断开时取消 运行,除非该请求声明了 on_disconnect: continue;因此 leader 切换、滚动重启或接收 实例崩溃都会取消所有进行中的 IM 运行。渠道运行现在声明 continue,运行会在执行它的 Gateway 上完成并持久化对话线程状态——即使 IM 回复随旧传输丢失,答案仍可在 Web UI 中查看。同一策略也覆盖 Slack/Discord runs.wait 遇到 SDK 300 秒读超时的情况:聊天里 仍会收到错误回复,但运行会在后台完成而不再被取消。由于运行会继续,manager 现在在运行 请求发出之后保留入站去重键(worker 被取消、读超时、连接断开):provider 在新持有者上的 重投递会被丢弃,而不是把这一轮再执行一遍;Gateway 拒绝的请求或发送前的失败仍会释放该键 以便重试。即发即弃渠道(GitHub)不受影响:runs.create 在运行进入 pending 后即返回。 (#6876)

  • 前端: 用户管理设置页不再在静态网站演示中发出注定失败的查询,非管理员的 ?settings=users 深链接也不再打开仅管理员可见的面板。该页面此前只以 system_role === "admin" 判断,而演示用户名义上是管理员,静态响应层又没有 /api/v1/admin/users 路由,因此演示中出现了加载失败横幅;现在它同时以 isStaticWebsiteOnly() 判断,与模型设置页的先例一致,用户页的 adminOnly 文案也 补上了模型文案已有的“演示中不可用”一句话。深链接此前对照一个无条件包含 users 的分区映射做校验,因此非管理员的深链接会落在一个其导航并不提供的分区上;现在对话框 从实际呈现给当前用户的导航推导激活分区,并回退到第一个可用分区。(#6691)

  • 目标: 外部 state 和运行输入写入现在会在写状态或受理运行之前,以 400 拒绝缺失、 非字符串、空白或超限的目标文本,历史 checkpoint 重放沿用当前目标而不再复活旧目标。 此前一次 state 写入可以存储没有目标的 {status: "active"}——前端会隐藏这个目标, 但后端锁定编辑、其评估器也会失败——而重新生成回答会恢复用户此后已清除或已完成的 目标,连同其已用的续跑预算一并恢复。存在的计数/上限字段必须是非负整数,保留的 reducer Overwrite 标记会被拒绝,重放会丢弃旧回答的 goal_outcome,使其无法为替换后 的回答背书。(#6685)

  • lark: Lark CLI 的技能文件与安装清单现在以原子方式发布,不再使用截断式写入。 _write_skill_file 和 _write_manifest 此前直接写最终路径,写入中途崩溃会留下无法 解析的残缺技能文件——或一份写了一半的清单,而清单的 content_sha256 正是安装校验 所依据的值,因此既无法修复也无法重新校验。两处调用点现在都改走模块内已有的 _publish_bytes_atomically 辅助函数。(#6683)

  • 运行时: 跨 worker 的取消接管现在会给运行标记 stop_reason="orphan_recovered", 使仅从存储读取的 SSE 和 wait 消费者能看到运行结束。此前当取消请求落在不拥有该运行 的 worker 上、而该运行的租约已超过宽限窗口过期时,RunManager.cancel() 会接管运行 并将其持久标记为 error,但让 stop_reason 留空;消费者仅存的存活信号—— _orphan_recovery_observed_after_heartbeat() 中的心跳回退——在合成 END 之前需要该 标记,因此订阅者一直在心跳上循环,等待一个再没有存活 worker 会发布的 END。接管认领 现在传入 stop_reason=ORPHAN_RECOVERY_STOP_REASON,与周期性孤儿恢复保持一致; API、配置和线上格式均无变化。(#6674)

  • 记忆: DeerMem 的抽取防抖现在按对话分别计时,安静的对话不再被繁忙的对话饿死。 此前队列级防抖计时器会被每条被受理的普通更新重置,因此当另一个对话以快于 debounce_seconds 的频率提交更新时,一条更新会无限期等待,对同一个键的持续刷新可能 让抽取被无限推迟。防抖现在按 (thread_id, user_id, agent_name) 独立运行,使用单调 递增的 first_at/due_at 时间,新增的 memory.backend_config.max_wait_seconds (默认 300)把就绪时间限制在 min(last_arrival + debounce, first_arrival + max_wait)。仍然只有一个计时器和一个串行消费者;紧急快照仍然立即就绪,显式的 flush、flush_nowait 和关机时的 flush_sync 也会排空尚未到期的条目。(#6672)

  • lark: 解包出的 lark-cli 运行时二进制和沙箱启动器现在通过临时文件加 os.replace 发布,而不是先截断式打开再 chmod 0o755,写入中途崩溃不会再留下可执行 残片——对解包的运行时来说是一个很大的残片。发布失败时保留原有二进制,也不会留下 临时文件。(#6671)

  • 工具: 默认智能体的全局 SOUL.md 现在通过先写临时文件再 os.replace 的方式 发布,setup_agent 被中断后不再留下被截断的人设文件。此前写入使用截断式的 write_text,会在新内容落地之前先破坏原文件;由于提示词构建器每次运行都会把 SOUL.md 追加到系统提示词中,智能体随后会在人格和指令部分缺失的情况下无声运行。 自定义智能体的持久化早已是原子的——这是该文件最后一处可能被撕裂的写入。(#6668)

  • 智能体: type 不是字符串的内容块现在会被跳过,而不是让整个运行崩溃。 model_response.has_visible_content 和 ParentContextSnapshot.from_state 此前用 集合成员测试探测块类型,因此提供方发出的 type: [] 或 type: {}——合法 JSON——会让 TypeError: unhashable type 从调用中抛出:has_visible_content 经由 llm-error-handling、terminal-response 和 model-length 中间件在每个模型响应上运行, 把本已完成的响应变成一次失败的运行,而该快照是每次后台子智能体派发的种子。两处探测 现在改用相等(元组)成员测试,_MEDIA_BLOCK_TYPES 也改成了元组;格式良好的块的分类 结果与之前完全一致。(#6664)

  • 前端: SSE 重放缺口恢复不再覆盖较新运行的重连指针。恢复期间客户端会清除匹配的 lg:stream:<thread> 键并等待持久化的线程状态;读取未决时另一个运行可能认领该键, 而恢复随后会把旧运行 ID 无条件写回,因此之后查找标签页本地的重连元数据会选中旧运行, 而不是最近认领该指针的运行。现在只有当槽位为空或仍指向正在恢复的运行时才恢复恢复 元数据;初始恢复和加入式恢复仍照常恢复各自的指针,正在恢复的流也保留其保留尾部的 Last-Event-ID。(#6662)

  • 技能: 内置 GitHub 研究技能的无依赖 urllib 回退现在用标准库 urlencode 编码 查询参数。此前它把查询值直接拼接进 URL,因此仓库限定词中包含空格时搜索会以 InvalidURL 失败,带空格的标签过滤器同样失败,保留字符还可能改变查询或变成片段。 离线回归覆盖含空格、Unicode、加号、与号和片段标记的搜索、标签过滤器以及普通分页。 (#6661)

  • acp: invoke_acp_agent 现在能启动 command 为不带扩展名的 Windows 绝对路径 的 ACP 智能体——这是 npm 全局 shim 的标准布局,...\npm\mcode 与 mcode.cmd 并排存在。SDK 用 asyncio.create_subprocess_exec 派生进程,不做 PATHEXT 查找,因此无扩展名的 shell 脚本以 WinError 193 失败(当只存在 .cmd 时则是 FileNotFoundError),而 #6598 的分隔符防护——对相对命令必不可少,因为其 含义取决于派生时的 cwd——也错误地吞掉了绝对路径。_resolve_agent_command 现在把 裸名称和绝对路径都交给 shutil.which(它会在命令自身所在目录探测 PATHEXT),只对 相对路径短路处理;没有匹配时返回配置的原始值,使“未找到”补救提示仍能指出操作者写 的值。查找结果本身已是绝对路径时原样返回,因此符号链接后的 .. 不再被按字面折叠。 (#6656)

  • tavily: Tavily 的 web_search 和 web_fetch 工具现在先校验 HTTP 成功载荷再 做归一化,返回提供方结构化的格式错误而不是原始异常。此前工具直接读取 SDK 解码后的 响应体而不检查其形状,因此一个 200 但并非文档所述结果对象图的响应体—— results: null、字典形状的 results、缺少 title/url/content 的条目、非字符串 的 raw_content——会以 TypeError/KeyError/AttributeError 连同堆栈跟踪逃出 工具边界,没有任何可行动的信号,而 brave、serper、serply、sofya、tencent_wsa、 unbrowse 和 groundroute 早已应用了相应的包容规则。非对象条目按顺序跳过,缺少叶子 字段的对象条目归一化为空文本,results 缺失或为空时保持现有行为,raw_content 在 4096 字符截断之前先转为文本,格式错误只记录畸形容器的名称和类型——绝不记录查询、 URL、凭据或载荷值。(#6645)

  • 运行时: RunManager.shutdown() 现在也会排空终态已暂存但尚未提交的运行,因此 make stop、pod 滚动更新或收尾期间的 SIGINT 不再破坏运行的收尾部分。此前排空只选取 pending/running 记录,但使用事件存储时运行会先在内存中暂存终态 (terminal_commit_pending=True),然后继续收尾——日志刷盘、投递回执、工作区扫描、 时长写入、延迟的终态提交——同时对外已读作 success/error,因此 shutdown 不等待它 就返回,checkpointer 可能在时长写入落地前关闭,这正是排空机制要防止的那类 PoolClosed 竞态。这些运行会被等待而不是被取消(取消会跳过终态收尾);只有真正活跃 的记录才像以前一样被取消,而在排空预算内未能落定的暂存终态运行保留其暂存状态,不会被 改写成 interrupted。(#6620)

  • lark: Lark CLI 的 shim、启动器和运行时标记现在以原子方式发布,不再使用截断式 打开。install_shim() 此前直接写入 bin/lark-cli-shim.py、bin/lark-cli 启动器和 .deerflow-lark-cli-runtime.json;沙箱会把启动器 chmod 0o755 并执行它,写入中途 崩溃会留下可执行残片,JSON 标记同样可能被写坏。现在发布时会先在同一目录写临时文件、 fsync 后用 os.replace 替换到位,与存储后端的既有模式一致;发布失败时原有运行时 保持完好。(#6618)

  • MCP: 以完全相同的配置删除再重新添加一个 MCP stdio 服务器,现在会强制其会话在 每个 Gateway worker 上重新绑定,堵上了 ABA 漏洞:错过两次中间写入的 worker 因为 最终生效配置看起来未变而继续保留旧的持久会话。extensions_config.json 中现在会在 mcpServers 旁边持久化一个严格的 mcpLifecycle v1 块——部署中每个受影响的 stdio 服务器获得一个不透明的 128 位令牌,删除会留下墓碑记录——而缓存刷新、发现认领、 发布检查和已提交配置的交接都以它为准对账,即使连接指纹和最终快照一致也会把绑定纪元 向前推进。同样的“原始配置读改写加生命周期规划”协议现在适用于全部五条 Gateway MCP 写路由和 DeerFlowClient.update_mcp_config(),本地绑定栅栏在配置锁释放之前 安装;个人域保持不变,未发生变化的 legacy 服务器继续使用确定性的内存基线,而不会 收到多余的新令牌;滚动升级期间,在 legacy 写入方退役之前,配置写入必须发往具备生命 周期感知能力的 worker。(#6617)

  • 技能: 字符串字段中包含 U+0085、U+2028 或 U+2029 的技能历史现在能被正确读回。 append_history() 以 ensure_ascii=False 写出 UTF-8 JSONL,这些字符因此会合法地 出现在已保存的内容中,但 read_history() 用 str.splitlines() 切分记录,会把它们 当作记录边界,对合法历史抛出 JSONDecodeError——连带使自定义技能的历史与回滚处理 一起失效。现在本地存储和用户级存储都只按 LF 切分记录;写入方、历史 schema、排序、 加锁、鉴权与回滚逻辑均不变,畸形记录仍会抛错。(#6615)

  • utils: 文本提取现在会跳过 type 不是字符串的响应块,而不是在它上面崩溃。 LangChain 的 AIMessage 可能在块的 type 字段中携带列表或映射,而提取会对该值做 哈希,在后面的有效文本能返回之前就抛出 TypeError——input-polish 路由因此以 HTTP 503 暴露了这个问题。提取现在改为直接比较两个允许的块名而不做哈希,未知块被 跳过,有效的 string、text 和 output-text 块保持原有顺序和换行分隔。(#6614)

  • workspace-changes: 无论进程先导入哪个模块,import deerflow.workspace_changes 都能正常工作。#6282 在 workspace_changes/api.py 中加入的顶层 from deerflow.runtime.user_context import AUTO, _AutoSentinel 使该包首次被导入时 会执行 deerflow.runtime 的 __init__,其 .runs worker 又会为 capture_workspace_snapshot 反向导入 workspace_changes——此时包的 __init__.py 还在执行第一条语句、名字尚未绑定,因此导入以循环导入 ImportError 失败(先导入 deerflow.runtime 会碰巧掩盖该问题)。get_workspace_changes_response 的再导出 现在通过模块级 __getattr__ 惰性解析,与 deerflow/subagents/__init__.py 使用的 模式相同;公开调用点不受影响。(#6608)

  • MCP: MCP 的 ResourceLink 结果不再以 URL 来源的 file 块形式送达模型,已被 毒害的对话线程也会在下一轮自愈。转换层此前把 ResourceLink 条目转换为携带 url 键的 file 块,但 Chat Completions 只接受 base64 或 file-id 形式的 file 块,于是在 紧接着的下一轮,langchain-core 的 OpenAI 转换器会对任何带 url 键的 file 块抛出 ValueError: OpenAI Chat Completions does not support file URLs.,之后每一轮都以 回退错误作答——没有任何办法挽救该线程。转换现在把 http(s) 的 image/* 链接保留 为图像块,把其余所有链接变成 [Resource: name (mime) available at url] 文本占位符, 结构化链接保留在产物的 resource_links 键下;data:/blob: URI 退化为不含位置的 占位符,并从 resource_links 中移除。占位符中的位置要经过新模块 deerflow.tools.resource_placeholder 中的 model_visible_location 门槛,因此原始 file:// URI、裸主机路径或 Windows 盘符路径不再泄漏进模型可见文本。注册在主导链、 子智能体链和 SDK 工厂链上的读取时 ModelContentCompatibilityMiddleware 只在请求 视图中把 URL 来源的 file 块(任意角色,url 键存在即使为空)和不可抓取 scheme 的图像块改写成同样的规范占位符——checkpoint 保留原始块,因此被卡死的线程无需迁移 即可自愈,持久化的畸形 URL 会被降级而不是抛错。产物注册表消费 resource_links, 并跨来源对句柄去重。(#6606)

  • 流式: Redis 流订阅者对比流中最新保留条目更新的重连 Last-Event-ID 现在会显式 上报重放缺口,而不是在它上面等待。此前重放缺口处理只检测早于保留历史的游标,因此 一个语法合法但指向未来的游标会让 XREAD 一直等待其后的事件——即使运行的结束标记 早已发布,仍会无限心跳下去。在既有的原子保留历史快照期间,比最新保留事件 ID 更新的 游标现在会发出携带保留边界的现有 StreamGap 事件并停止,活跃流和已结束流均适用。 (#6605)

  • groundroute: GroundRoute 的 web_search 与 web_fetch 在归一化之前先 校验 HTTP 成功响应的载荷,返回提供方结构化的格式错误,而不是抛出原始异常。 此前两个工具在请求的 try/except 之外读取载荷,任何不符合文档所述 results[] 对象结构的响应体都会以 AttributeError/KeyError 冲出工具 边界——一次带堆栈跟踪却毫无可用信号的失败工具调用,而其它内置提供方此时返回 的是“unexpected response format”错误。现在顶层不是对象、results 不是列表、 或非空列表中不含任何对象都返回格式错误;合法列表中的非对象条目按顺序跳过; 缺失、null 与空的 results 仍返回 No results found。格式错误日志只携带 畸形容器的类型——绝不包含查询、URL、凭据或载荷值;web_fetch 会把非字符串的 content 转为文本,缺失的标题渲染为空,而不是抛出 TypeError 或渲染出 # None。(#6603)

  • acp: 以裸启动器名(command: npx、mcode 等)配置的 ACP 智能体现在能 在 Windows 上启动。此前配置的字符串被直接交给 ACP SDK,由它用 asyncio.create_subprocess_exec 启动——该 API 不做 PATHEXT 查找——因此 npm 的 .cmd 垫片永远找不到,对一个已安装且位于 PATH 的启动器,工具报 “Command 'npx' was not found on PATH”,除非运维硬编码绝对的 .cmd 路径, 否则该集成在 Windows 上无法使用。现在 invoke_acp_agent 在把命令交给传输层 之前,会先用 shutil.which 解析裸的 acp_agents.<name>.command(经 asyncio.to_thread 在事件循环外运行),与 MCP Python SDK 对 stdio 服务器 命令做的归一化一致;含路径分隔符的值原样通过;未命中的名称保留配置值,安装 指引仍能触发;_format_invocation_error 的修复建议文本仍指名配置的命令。 (#6598)

  • checkpoints: Redis 检查点历史缓存中的损坏条目现在退化为缓存未命中,而不 是中断历史解析。此前 RedisCheckpointHistoryCache.aget_many() 对所有非空 缓存值的切分与反序列化毫无防护,一条畸形条目会从这个纯性能缓存中直接抛出, 而不回退到权威 saver。现在每条缓存独立解码,畸形序列化或意外的条目形状按 未命中计;混合批次中的有效命中保持不变,命中/未命中统计准确。(#6592)

  • 沙箱: 远程沙箱提供方对 read_file 行号范围的切分现在与 LocalSandbox 一致。E2B、BoxLite、Tenki 与 OpenSandbox 提供方此前用 str.splitlines() 切分 范围读取,该函数还会在裸 \r、\f、\v、\x85、
/
 及其它控制分隔符处 断行,因此同一个行号 N 在远程与本地沙箱指向不同的行:更新以裸 \r 结尾的 进度日志读出来是截断的,而标记里写的是 start_line=30;在这些提供方上 read_file(start_line=30) 返回的却是第 15 行的下半段。新增 sandbox/read_file_contract.py 中的 split_file_lines(content) 只按 "\n" 切分,并像 LocalSandbox 一样去掉行尾的 \r,因此始终与 count_file_lines 一致;四个提供方都用它切分,钳制与拼接逻辑不变。(#6585)

  • 智能体: 产物文本检测不再截断查询串或片段中含类文件扩展名的远程文件 URL。 此前检测在扩展名处把 URL 切断,产物句柄因此给后续工具提供了另一个 URL,破坏 下载或带签名的查询参数。现在先完整捕获 HTTP(S) URL 词元,再检查解析出的路径 是否以受支持的文件后缀结尾,查询串与片段保持完整;仅出现在主机名、查询或 片段中,或作为更长路径里类文件前缀出现的扩展名会被拒绝。常见 CJK 收尾标点 只在自由文本扫描中剥除;后缀匹配不区分大小写,同时保留原始 URL。(#6574)

  • e2b: E2B grep 结果保留文件名中的冒号,且 E2B 的 grep 与 glob 都只按 LF 切分返回的记录。此前 grep 解析器在头两个冒号处切分 filename:line:text, 形如 2026-10-09T13:45:00.txt 的时间戳文件名会返回成不存在的路径 2026-10-09T13、行号 45,而不是真实文件的第 2 行;其它含冒号的文件名则悄悄 丢失匹配。结果循环还使用 splitlines(),它把垂直制表符、换页符、回车与 Unicode 分隔符都当作记录边界,造成路径错误、glob 结果缺失或 grep 内容被 截断。现在 grep 命令用 --null 界定文件名,解析器只在该定界符之后切分行号 与内容;匹配文本中的冒号、glob 过滤与既有结果上限的语义保持不变。(#6573)

  • 运行时: 当自定义 RunStore 覆写仅通过含糊的 **kwargs 接受请求标识时, keyed resume 准入现在失败关闭(fail closed)。#6499 的兼容门把 VAR_KEYWORD 参数当作存储支持私有恢复标识的证明,但这只能证明调用不会抛 TypeError: 兼容包装器可以悄悄丢弃该关键字,带着幂等 key 准入这次运行却不设置 idempotency_request,重试时无法证明命令匹配,于是返回 409 而不是复用原 运行。仅接受 kwargs 的覆写现在必须在真正定义 create_thread_operation_atomic() 的类上显式声明 supports_idempotency_request_kwargs = True(从实现持有者解析,而不是盲目 继承)才能加入;显式的 idempotency_request 参数仍是能力声明;含糊的存储在 任何持久准入之前返回既有的 503 能力错误。(#6571)

  • 运行时: RunJournal.flush() 现在在清空之前先向事件循环让出一次,一次 运行的最后一次跨线程中间件事件不再在 Python 3.13+ 上丢失。CPython gh-105836 (已回移到 3.13)让 _chain_future 在目标循环就是正在运行的循环时同步复制 executor future 的结果,因此当 worker 在其 future 被链接之前就完成时, await asyncio.to_thread(...) 可以不经让出直接返回——worker 用 call_soon_threadsafe 排队的回调此刻仍待处理,而缓冲为空的 flush() 从不 让出让它们落地,排队的事件就在 _detach_runtime_dependencies() 期间丢失。 多出的这次让出在 3.12 上是无操作;已交给属主循环的事件在所有受支持的 Python 版本上都会提交。(#6565)

  • 渠道: IM 会话在多个 Gateway 副本之间现在会保留同一条 DeerFlow 线程。ChannelManager 此前把聊天到线程的绑定 (channel_name:chat_id[:topic_id] → thread_id)保存在进程私有的 JSON 文件 channels/store.json 中,启动时读取一次、 每次变更整体重写,因此在多个 Gateway 实例下,一个实例上创建的绑定对其他实例不可见——同一聊天的下一条消息若落到 另一个实例,就会新开第二条线程——并发写入还会互相覆盖文件。现在只要 database.backend 是 sqlite 或 postgres, 绑定就保存在共享的 channel_thread_bindings 表中(迁移 0038_channel_thread_bindings);memory 后端继续使用 JSON 文件。升级后的首次启动会把已有的 store.json 一次性导入空表(INSERT … ON CONFLICT DO NOTHING,因此两个副本同时 导入也不会产生重复绑定),并将文件重命名为 store.json.migrated;表非空时保持该文件不动;键组成部分超出表列宽度 的条目会被跳过并计数,不会让整个导入失败。存储 API 为异步,数据库永远不会阻塞 Gateway 事件循环;飞书的同步 lark 回调以短暂的有限等待把查询桥接到该循环,数据库未及时应答的查询会在 Gateway 循环上重试(至多 30 秒,之后丢弃消息 并记录警告),而不是因缺少映射被路由到新线程;这类延迟消息会占用有限入站队列(channels.inbound_queue_maxsize) 的一个槽位,因此数据库中断不会让积压增长,无法入队的消息会被丢弃并记录警告。(#6558)

  • 子智能体: 持久子智能体批次的停机清理现在能在调用方被取消之后继续存活。 #6517 加入的批次停止在取消 poller 与条目任务后直接 await 它们,而 Gateway 有界的 asyncio.wait_for() 会在停机期限到达时取消 stop() 调用方——该取消 经 gather() 传播进服务自有的任务,而它们当时正在清理自身,导致后续 stop() 无法加入最初的清理、子智能体被拆除后条目簿记可能仍处于被持有状态、 重启也没有可靠的生命周期边界。现在每次停止世代拥有一个对所有调用方屏蔽的 清理任务;并发与重试的 stop() 调用方加入同一次清理,而不是各自发起竞争性 取消;共享清理完成前 start() 一直被栅栏挡住;清理失败对后续生命周期调用 仍然可见。Gateway 的 5 秒停机钩子仍按时返回,服务自有的清理在底下继续。 (#6552)

  • brave: Brave 网页搜索现在能兜住畸形的响应载荷,而不是让工具调用失败。 此前校验只覆盖顶层响应,并假定 web 是对象、results 是对象列表,因此嵌套 的错误类型会抛出 AttributeError,或被误报为“No results found”;本可使用的 结果列表中的一条非对象记录会让整个调用失败并丢弃有效结果。现在嵌套容器—— 包括错误但为假值的类型——返回既有的结构化格式错误;非对象记录被跳过,有效 对象、顺序与字段默认值保持不变;不含可用对象的非空列表返回格式错误;缺失、 null 或空的容器仍返回 No results found。(#6550)

  • 沙箱: 本地沙箱现在把 Git Bash/MSYS 的命令输出按 UTF-8 解码,Windows 上的 CJK 及其它非 ASCII 工具输出不再乱码。Git Bash 总是向管道写入 UTF-8,但解码器 使用主机代码页(zh-CN 主机上为代码页 936),又因以 errors="replace" 解码而 从不报错——execute_command("echo 你好世界") 悄悄把乱码交给模型。 PowerShell 分支此前已固定 UTF-8(#5440);cmd.exe 与其它 shell 保持按 locale 解码,POSIX 行为不变。(#6548)

  • 沙箱: E2B 提供方在重连确认虚拟机已消失时释放该沙箱的本地容量槽位,而不是 泄漏到重启为止。此前对一个已被回收的虚拟机,Sandbox.connect() 抛出可信的 “sandbox not found”错误时,_reconnect_live_client() 释放了部署级(Redis) 槽位却仍重新抛出异常,_evict_oldest_warm() 便把这个已确认消失的结果归为 未知状态失败:保留驱逐墓碑、从不调用 _end_transition_locked(),每次重试都 泄漏一个本地 transitioning 槽位——一旦 replicas 个槽位全部泄漏,所有工具 调用都以 SANDBOX_CAPACITY_EXCEEDED 失败,而 active=warm=reserved=0 又让 该错误从自身文本无从诊断。现在 _reconnect_live_client() 对可信的已消失 错误返回 None,warm 驱逐、warm 回收、远程发现与对账由此进入既有的 client is None 清理路径(丢弃墓碑、释放 transitioning 槽位、重试获取); 未知状态的错误仍保留槽位,以维持防超卖保护。SandboxCapacityExceededError 还在其属性、详情与等待超时消息中带上 transitioning 计数,满容量报告而毫无 活动时可以自解释。(#6547)

  • 事件: JSONL 事件存储在运行被删除时保留线程的序号下限,重启后的序号恢复 不会复用已删除的序号。此前恢复只看到幸存的文件,读取 after_seq=2 的客户端 随后会错过新追加、而序号被复用为 1 或 2 的消息。现在在删除非空运行之前,先 在既有的循环外变更栅栏下以原子替换方式把线程下限写入 runs/.seq-watermark; 重新打开时取该下限与幸存运行记录的最大值(legacy 目录仍然可用);整线程删除 会重置下限;水位发布失败让运行保持原样;损坏的水位按失败关闭处理。DB 后端 本来就能在运行删除后保住下限。(#6546)

  • 模型: Claude 适配器不再在单次请求准入内部重试。启用 Claude 请求准入后, 工厂已禁用 SDK 重试,但适配器自己的重试循环仍会在一次准入内发送额外请求—— 通过公开的同步与异步模型调用,用一个带 Retry-After: 0 的合成 429/500 响应 即可复现。现在只要使用 DeerFlow RequestAdmission,包装器的重试循环就被禁用, 重试交由每次尝试都会被准入的中间件处理,配置与调用方的重试设置都无法重新 启用它;未启用准入的模型与无关的自定义限流器保持既有重试策略。(#6545)

  • 部署: Helm chart 不再把沙箱 provisioner 钉在单个 Pod 上。provisioner.replicas(默认 1,不变)设置副本数, provisioner Deployment 采用与 gateway 相同的先扩后缩滚动策略(maxSurge: 1、maxUnavailable: 0),并在 provisioner.replicas > 1 时渲染 PodDisruptionBudget(provisioner.podDisruptionBudget,minAvailable: 1),因此 多副本 gateway 部署不会再因为唯一的 provisioner Pod 重启或节点被抽干而丢失沙箱创建能力。provisioner 本身无需改动: 带标签的沙箱 Pod 和 Service 就是它唯一的注册表,每个处理器都从 API server 回读,create 也已能容忍并发创建者产生的 409 AlreadyExists。chart README 与 provisioner README 记录了副本数所依赖的前提,以及单副本时永远不会渲染该预算的 事实。两个预算的 minAvailable 都经由同一个辅助函数渲染:它会保留 "50%" 这类百分比(此前直接做 int 强转——既有 的 gateway 预算同样如此——会悄悄把它变成 0,一个什么都保护不了的预算),并对 0、"0%" 及其他不支持的值让渲染 失败,报错信息中会指出对应的 values 键。(#6543)

  • 存储: 文件系统 blob 写入成功后,即使为元数据 sidecar 分配失败也保持 成功。blob 存储把 JSON sidecar 视为附属信息,但其临时文件的分配位于尽力而为 异常处理之外,那里的 OSError 会在内容已原子发布之后仍报告写入失败;对已 存在而缺失 sidecar 的内容重试时,还会泄漏原始 OSError。现在 sidecar 分配 失败保留既有的警告,临时文件仅在已分配的情况下清理;内容分配或发布失败仍 抛出 BlobWriteError。(#6538)

  • support-bundle: 当检出目录的 .env 无法读取或不是合法 UTF-8 时, support-bundle 收集仍保留线程诊断信息。此前 PermissionError 与 UnicodeDecodeError 会从 _thread_data_roots 逃逸,恰恰在本地配置损坏、 最需要排查的时候中止收集。现在先完整读取 .env 再解析,文件系统或解码失败 回退到继承的路径设置与默认存储布局;不会应用残缺的 dotenv 赋值,不导出任何 机密;合法文件的解析与启动器优先级不变。(#6536)

  • llm-text: 推理标签清理现在在带引号的属性之外定位开定界符,形如 <think note=">"/> 的空标签不再吞掉其后的答案。此前两个清理函数都把第一个 带引号的 > 当作开始标签的结尾,把这个标签视为未闭合的推理段,对 <think note=">"/>{\"answer\":\"yes\"} 这类输入返回空字符串。现在开定界符 在单引号与双引号属性之外查找;带此类属性的自闭合标签会保留其后的答案;共享 的前向扫描器保持既有的标签名边界与悬挂块行为。(#6535)

  • 上传: 上传列表现在会跳过在目录扫描与 stat 调用之间消失的文件,而不是让 整个请求失败。list_files_in_dir 先做一次 os.scandir 扫描、再对每个幸存 条目执行 stat,因此任何并发删除——另一个 worker 的删除请求,或暂存清扫回收 过期的 .part——都会变成逃出处理器的 FileNotFoundError,让列表端点对整个 线程返回 500。容错范围限定在预期竞态的 errno (ENOENT/ENOTDIR/ELOOP),与 chmod 路径已有的替换竞态策略一致; EACCES 等操作性错误仍然向上传播,真实的权限问题保持可见。(#6533)

  • 前端: 对话导出现在下载完整的持久化历史,而不只是当前视图中已加载的轮次。 聊天页头此前只导出 thread.messages(已加载的历史窗口),侧边栏导出 getState().values.messages(上下文压缩后会缩小),因此重新打开长对话后不 向上滚动就导出——或在压缩之后导出——会悄悄漏掉更早的轮次,同时仍报告成功。 现在两个入口在生成 Markdown 或 JSON 文件之前都读取全部持久化的 /messages/page 分页(按端点每页最多 200 行),复用既有的按时间分页合并与 消息同一性去重:重复身份保留最新内容,同时 mergeMessages 恢复其最早的 可信序号位置。分页失败或游标不再前进会让导出失败,而不是下载残缺的记录; 读取期间导出控件禁用(聊天页头控件还要等流式结束);公开/静态演示保留其 本地 fixture 导出路径。(#6530)

  • e2b: E2B grep 的文件名过滤器现在作为单个未经改动的参数传给 shell。此前 --include 参数由调用方的 glob 过滤器不加引号拼出,因此限定 release *.txt 的搜索在真实的 release notes.txt 上失败,内嵌引号导致 shell 解析错误,$ 还可能经变量展开悄悄改变过滤器。现在过滤器用 shlex.quote 加引号,与 OpenSandbox 的既有处理一致;通配符匹配与以根目录 为基准的目录范围限定不变。(#6529)

  • 渠道: 过期的 ChannelService.start() 不再能在停机完成后复活服务。此前 start() 只在 await manager.start() 之前检查 _stopping,然后无条件设置 _running=True,因此 stop() 若在 manager 启动的 await 期间完成,就会把 一个活着的 manager 留在停止流程之外——以今天无 await 的 manager 启动并不会 触达该路径,但这正是 #6503 评审指出的检查/执行间隙。现在启动时捕获一个停机 世代号,stop() 一开始就递增它;manager.start() 之后发现启动已过期则 拒绝,即使 _stopping 已被清除也一样;并通过 manager.stop() 排干刚启动的 manager,而不是把服务标记为运行中。(#6521)

  • 子智能体: 在 SubagentBatchService.stop() 仍在清理时,并发的 start() 不再能另起一个替代的批次 poller。此前 stop() 在 await 旧 poller 的拆除 之前就清掉 self._poller,竞争的 start() 因此看不到 poller,清除共享停止 事件并启动一个可能比原 stop() 活得更久、与持久批次领取相重叠的 worker。 现在旧 poller 引用在其停止清理完成之前保持被持有;停止期间的 start() 抛出 明确的生命周期错误;栅栏在停止被取消或失败后依然存在,后续 stop() 可以 重试清理。(#6517)

  • 运行时: 对刚启动的运行执行带回滚的取消,不再删除线程的检查点。此前 cancel?action=rollback(或 multitask_strategy="rollback")在运行离开 pending 之后、worker 捕获运行前回滚点之前到达时,会留下 rollback_point 为 None 且 snapshot_capture_failed 仍为 False 的状态——这与从未有过检查点的线程无法 区分,而后者是唯一允许回滚合法调用 adelete_thread 的情形——于是长期对话被重置 为空状态。现在捕获从一开始就是 fail-closed:snapshot_capture_failed 初始化为 True,只在 _capture_rollback_point 完成后才清除,因此在捕获之前或期间发生的 取消会跳过回滚(检查点得以保留),运行仍以 error/“Rolled back by user”终结。 捕获完成但返回 None 时,保留文档所述的重置为空语义。(#6513)

  • 智能体: 异步文件门等待者不再让默认执行器饥饿。此前并发文件调用可能把执行器 填满等待同一个先读后写门的调用者,而持门者自己也需要该执行器来检查文件或附加 读取标记,双方都无法推进,无关的 Gateway 上下文用量请求也排在其后。现在异步 等待者等待各自的、与事件循环无关的信号,同步与异步调用保持相同的 per-scope/path 互斥;取消等待者会移除其信号,而不会释放另一个调用者的门;已派发 的检查与读取标记工作仍会在解锁前处理完毕。先读后写策略与工具结果不变。(#6511)

  • scanner: 文件扫描器的解码文本预览缓存现在原子地发布。此前 _cache_text_file 就地写入 sha256(virtual_path) 键下的条目,而调用者把该路径 当作文件内容对外提供,因此一次中途失败的写入会留下截断的条目,之后每次读取都把它 当作预览返回,没有任何环节重新校验。现在发布经由临时文件加 os.replace 完成 ——仓库对这类失败会损坏的文件已在使用的模式;发布失败时保留原有条目。(#6510)

  • llm: 推理标签清理不再截断名称仅以“think”开头的无关标签。此前 <think\b 边界会匹配更长标签名内部的标点,因此合法的 suggestions 负载 ["How does <think-tank> work?"] 在解析前就被切断,suggestions 路由返回空列表; 在润色草稿中,无关标签还可能一路吞掉可见内容,直到后面某个真实推理块的闭合标签。 现在边界要求精确的 think 名称之后是空白、> 或输入结束;属性处理、大小写 不敏感清理、未完成开头摘要的抑制以及前向扫描器都保持不变。(#6505)

  • 渠道: 在 ChannelService.stop() 正在关闭服务时,渠道重启不再能发布新实例。 此前 stop() 先把服务标记为已停止、再等待管理器/渠道拆卸,而 restart_channel() 仅由 per-channel 锁串行化,因此一次重启在某个 await 之后恢复 执行时,发布的渠道是停止流程从未快照过的,服务可能在仍有一个渠道运行的情况下 返回已停止。现在引入显式的 _stopping 生命周期状态,在关闭工作开始前进入、只在 完全停止后清除,并在设置之前和即将发布新实例之前都对 start() 与 _start_channel() 形成栅栏;上一次关闭未完成时 start() 会被拒绝。(#6503)

  • 渠道: 出站消息只投递给其派发开始时已接纳的监听器集合。此前 MessageBus.publish_outbound() 在等待每个回调的同时遍历活跃监听器列表,而每次 await 都会让出事件循环,因此派发中途注册的监听器会收到一条派发早于其订阅的消息。 现在每次派发都基于调用开始时取得的快照进行,监听器计数日志与回调投递都使用该 快照。(#6502)

  • 认证: 登录锁定现在按客户端 IP 在所有 Gateway 副本间只计一次。POST /api/v1/auth/login/local 此前把失败计数 存在进程私有的字典里,因此同一个负载均衡器后挂 N 个副本时,攻击者能获得 N × max_login_attempts 次尝试机会, 一个副本上的锁定对其他副本不可见。该计数现在由 LoginThrottleStore 管理:新增的 auth.local.throttle_storage 选择器(默认 auto)在 database.backend 为 sqlite 或 postgres 时把计数保存在共享的 login_throttle 表中 (迁移 0035_login_throttle),没有可共享的数据库时(memory 数据库,或已配置但引擎未初始化的数据库)回退到 进程内计数并记录警告;memory 强制沿用历史上的进程内行为;db 强制使用表,配置的数据库引擎不可用时拒绝启动。 Gateway 在启动时、紧随持久化引擎之后解析一次该存储;独立应用则在第一次限流调用时解析。失败通过一次原子 upsert 计数,同时保留生效中锁定的开始时间与已提交时长(判定措辞是“锁定开始后 N 秒”,而非“最后一次尝试之后”);锁定 期间策略变化时仍以锁定时提交的时长为准;登录成功会清除该 IP 在所有位置的计数;已生效的锁定与空闲计数按有界 批次清扫;声明了多实例部署却仍使用 memory 的安装会在启动时记录警告。登录端点的状态码和消息不变; max_login_attempts 与 lockout_seconds 仍为实时读取。config_version 现为 57。(#6501)

  • 网关: 带键(keyed)resume 的重试现在是幂等的。运行执行让 command.resume 优先于 input,而幂等复用此前只比较存储的与重试的 input,因此两个 input=null 的不同恢复决定可能复用同一个运行。现在带键 resume 的准入会在新增 可空列 runs.idempotency_request_json(迁移 0036_run_idempotency_request)中 持久化一个带版本的 strict-JSON SHA-256 标识,并比较实际的恢复命令:该标识保留 JSON 类型(true、1、1.0 互不相同),规范化对象键序,拒绝非有限数、循环引用 和非字符串键,且绝不持久化恢复负载。SQL 在对活跃线程行应用 interrupt 或 rollback 之前,先解析并锁定已存在的进程级幂等键;新写入方让公开的 kwargs.input 保持 null,新读取方对旧版标识形态做净化,因此混合版本的滚动升级 不会暴露摘要或合成 input;无法持久化该标识的旧式自定义 RunStore 会在持久准入 之前以 503 fail-closed。(#6499)

  • 模型: MindIE 的同步流现在遵循该提供方的异步兼容契约。此前公开的同步流继承 通用路径,绕过了 MindIE 的消息归一化、XML 工具解析和转义换行处理;启用工具的 回退路径继承了 streaming=True 默认值,尽管 MindIE 不支持原生的工具调用流。 无工具的同步流保持原生流式与感知围栏的换行解码器;启用工具的同步与异步流显式 请求非流式的后端生成并复用现有的模拟分块器,保留工具调用与终止用量。(#6497)

  • vllm: 旧版 thinking 归一化器不再修改调用方拥有的 extra_body。此前复用 同一个请求字典并把 thinking 从 true 切换为 false 时,第二个请求仍会发送 enable_thinking=true,且请求构造会改写模型保存的默认值。现在归一化在请求自有的 副本上进行;显式 enable_thinking 保持优先,无关字段得以保留。(#6496)

  • 技能: 通过一个 Gateway 副本做出的技能变更,现在会进入其他所有副本系统提示词中的技能列表。SkillStorage 每次 调用都会重新扫描磁盘,但提示词层按进程缓存已启用技能列表和渲染好的 <skill_system> 段落,而安装、编辑、删除、 回滚或启停技能(以及 POST /api/skills/reload)只刷新处理该请求的进程,因此在多个 uvicorn worker 或多个共享同一 home 卷的 Pod 下,其他副本会一直提供旧技能列表,直到各自重启。现在上述每一种变更还会在共享的 extensions_config.json 旁发布 .extensions_config.json.skills-cache-reset.json,使用与配置文件相同的原子替换和 跨进程锁;每次返回缓存条目前至多每秒一次地比对该标记的签名;只针对某个用户自定义技能的标记只会让该用户的 条目失效。/api/skills/reload 在标记写入成功时报告 scope: shared_config,在无法解析 extensions 配置路径时报告 scope: process,运维者不再需要在每个 Pod 或 worker 上单独调用。MCP 缓存重置的标记现在与它共用同一个 deerflow.config.shared_reset_marker 辅助函数。(#6495)

  • 记忆: DeerMem 派生的 SQLite FTS5 检索索引现在可以放在记忆根目录之外,Gateway 实例也能看到其他实例写入的事实。 此前每个用户的索引是 {storage_path}/.retrieval 处一个 WAL 模式的 SQLite 数据库,多个共享同一 home 卷的 Gateway Pod 会经网络文件系统打开同一个 WAL 文件,而 SQLite 不支持这种用法;每个 Pod 启动都会在其他 Pod 仍在使用时清空并重新 填充共享索引,一个 Pod 的损坏恢复会删掉其他 Pod 仍打开的文件,而同伴写入新事实之后,Pod 仍继续提供自己那份旧 副本。新增的 memory.backend_config.retrieval_index_path 可把索引目录放到别处(留空保持现有位置;相对路径基于 storage_path 解析),启动重建与损坏恢复只触及该本地索引;搜索现在会对自本进程上次索引以来 memory.json 修订号 发生变化的作用域重新同步,因此同伴的事实会出现在下一次搜索中,而本实例自己的写入不会触发重建。声明了多实例 部署(deployment.multi_instance: true / DEER_FLOW_MULTI_INSTANCE=1)却仍把索引放在 storage_path 内的安装会在 启动时记录警告。Helm chart 现在把 Pod 本地的 emptyDir 挂载到 /var/lib/deerflow/memory-index,将该配置项指向它, 并删除了 Gateway 每次启动都会丢弃并警告的遗留 memory.storage_path: memory.json 一行。config_version 现为 56。 (#6494)

  • 部署: $DEER_FLOW_HOME 不可写时,scripts/deploy.sh 现在会快速失败并给出 可操作的提示。此前开发栈(make docker-start)可能以 root 创建 backend/.deer-flow,之后 make up 在持久化生成的 BETTER_AUTH_SECRET 时深入 运行中途才以一句单纯的 Permission denied 中止。现在紧跟 mkdir -p "$DEER_FLOW_HOME" 之后执行可写性预检,失败即退出码 1 并指明属主不匹配 及 chown -R 1000:1000 的恢复办法;down 不做该预检,因此拆除栈永远不会被权限 阻塞。(#6488)

  • MCP: 有效的 MCP 配置变更现在只退役受影响的部署 stdio 会话,而不是整个会话 池。此前 extensions_config.json 的自动失效会在任何变更时拆掉所有持久会话,哪怕 只是展示用的元数据编辑,把无关服务器一并断开。现在绑定通过 build_server_params() → normalized_connection_fingerprint() 调和:未变更的 服务器保留其绑定、会话与属主,变更或移除的服务器推进其 epoch 或收到墓碑,工具 列表变更仍会失效并重建工具缓存。已应用的 MCP 修订与已发布的工具快照分开跟踪, 因此发现处于 pending、失败或已取消时,连续编辑仍会推进 epoch;首次认领与共享 重置的竞态会原子地退役未列入的部署绑定,包括首次工具发现之前建立的持久任务绑定; validate_mcp_task_config_snapshot() 在安装 epoch 前拒绝被 Gateway 冻结的任务 服务器修订,并回退到保守的完整重置;同步的 get_cached_mcp_tools() 读取方在 等待进行中的发现之前先完成已退役池的清理。仅涉及技能的变更,以及等价的配置、 路径与传输写法,仍不会触发退役。(#6487)

  • support-bundle: 线程清单现在能在配置的运行时主目录中定位线程。此前支持包 收集只搜索检出本地的 .deer-flow 目录,而运行时遵循 DEER_FLOW_HOME,因此设置 自定义主目录时,报告会声称存在的线程缺失,或选中检出处一份过期的副本。现在非空 的 DEER_FLOW_HOME 被独占地用于跨 legacy 与用户作用域布局的线程查找,相对主目录 从检出根解析;覆盖未设置或为空时,两种检出布局仍继续使用。路径设置从检出的 .env 读取但不导出其中的密钥(shell 导出保持优先;主目录未设置时遵循 DEER_FLOW_PROJECT_ROOT),外部主目录显示为 {DEER_FLOW_HOME} 标记而不是其 宿主机绝对路径。(#6486)

  • check: make check 的依赖诊断现在能在非 UTF-8 主机上处理 Unicode 输出。 此前脚本用 locale 编码解码工具与 pnpm 输出,因此一条含 Unicode 的 pnpm 失败诊断 会让检查器崩溃而不是报告失败原因,继承的限制性 PYTHONIOENCODING 还可能让其 Python pnpm 运行器完全不输出诊断。现在工具与 pnpm 输出以 UTF-8 捕获,无效字节用 替换字符处理,Python 运行器把输出编码固定为 utf-8:backslashreplace,同时保留 环境的其余部分。(#6485)

  • 模型: MindIE 无工具的原生流式现在能在围栏代码内保留转义换行。此前 _astream 对每个 chunk 独立应用 _decode_escaped_newlines_outside_fences,而该 辅助函数在其输入上配对 分隔符,token 大小的 chunk 几乎不可能同时包含两个 分隔符,因此流式回复中的每个字面 `\n`——包括围栏内的代码——都在流和持久化的 消息中变成了真实换行,与非流式及工具模拟路径不一致。现在流在一个补全的各 chunk 之间携带围栏状态,并在围栏外复用同一个一次性辅助函数,对跨 chunk 边界分裂的末尾 不完整 或 \ 先行扣留,等下一个 chunk 再决定;在未闭合围栏内结束的回复保留 其转义。(#6478)

  • 事件: 数据库运行事件存储在运行被删除后不再重复发放序号。此前 DbRunEventStore 从幸存行的最大值计算下一个 seq,因此 delete_by_run() 之后 的下一次写入会复用已发放的 seq,对用 after_seq 翻页的客户端不可见。现在由 持久化的 per-thread 高水位表 run_event_thread_seq(迁移 0034_run_event_seq_watermark,从现有 run_events 回填)以 max(row_max, watermark) + 1 供 put、put_batch 和 put_if_absent 使用; delete_by_run() 保留高水位,整线程删除(user_id=None 的 delete_by_thread) 会移除它,使重建的线程从 seq 1 重新开始,与 memory 和 JSONL 后端一致。(#6477)

  • 渠道: 并发的 IM 附件上传不再互相覆盖。此前入站摄取在下载附件之前扫描既有 文件名,因此另一个上传在下载期间占用同名文件时,第一个文件被截断——包括 Gateway 刚发布的文件——而两个调用者都报告成功。现在 IM 写入用 O_CREAT | O_EXCL 认领文件名,并经既有的后缀辅助函数重试冲突,以最终名称返回 元数据;飞书和钉钉的内嵌上传对竞争发布使用同样的独占写入重试,带 Gateway 临时 转换链接的普通文件也计为冲突。其他调用者保持共享写入器的默认替换行为,该行为仍 拒绝硬链接。(#6476)

  • MCP: 携带不可用 expires_in 的令牌响应现在回退到文档所述的一小时默认值, 而不是令牌获取失败。此前 OAuthTokenManager._fetch_token() 只防护 TypeError/ValueError,因此两种由服务端控制的形态会抛出未捕获的 OverflowError,MCP 服务器始终收不到其 Authorization 请求头:一是非有限的 expires_in(json.loads 接受 Infinity,int(float("inf")) 会溢出),二是 超出 timedelta 可表示范围的 JSON 整数。现在 OverflowError 加入转换防护,超出 timedelta 范围的有效期视为不可用;合法值不受影响。(#6473)

  • 上传: 上传文档的大纲现在跳过根级 HTML 注释块。此前注释内容会变成大纲条目, 注释内的代码围栏可能压制注释之后的真实标题,隐藏标题还会耗尽大纲上限。现在流式 抽取器会跳到第一个闭合标记行:注释内容不影响围栏代码状态,围栏代码内的注释标记 不会开启注释块,标题样式、物理行号、文件字节与大纲上限都保持不变。(#6470)

  • 项目: 在原生 Windows 且 LongPathsEnabled=0 时,文档存储现在可在 MAX_PATH 之外工作。此前内容寻址的文档布局在深层工作区或长文件名下会超过 MAX_PATH,导致暂存、下载、恢复与清除以 WinError 3/206 失败。现在项目文件 系统根在拼接文档目录与文件名之前改用绝对的 Windows 扩展长度 drive/UNC 路径 ——POSIX 路径、持久化的 stored_relpath 值与 Docker 挂载路径保持原写法,因此 无需存储迁移——启动调和也用同样的写法枚举用户根,深层工作区不再残留过期的暂存 文件。已有的 \\.\ 设备与 \\?\ 扩展命名空间原样通过,symlink 约束保持不变, 在 Windows 上被跳过的 12 个 MAX_PATH 阻塞 I/O 锚点重新运行。(#6467)

  • 上传: 大小写变体的上传文件名在大小写不敏感的文件系统上不再冲突。此前文件名 认领辅助函数按大小写敏感比较名称,因此在大小写不敏感的主机上,Report.txt 之后 的 report.txt 会静默替换第一个文件,而两次保存都报告成功。现在大小写变体(包括 带数字后缀的候选名)都视为冲突——第二个 report.txt 变为 report_1.txt——并有 入站附件回归测试验证两份负载仍可在其报告的名称下取用。(#6466)

  • 沙箱: sandbox.environment 现在能作用于默认本地提供方的沙箱。此前该字段 ($VAR 引用从宿主机环境解析)只被 AIO 提供方消费,因此在 LocalSandboxProvider 上,随附的 video-generation / image-generation 技能没有 文档所述的途径获取其提供方 API key,在默认部署的仪表盘中无法使用(环境继承按 策略被清除,且两个技能都未声明 required-secrets)。现在 LocalSandboxProvider 在初始化时一次性解析该映射,LocalSandbox 经既有的 build_sandbox_env(injected=...) 授权注入通道叠加它——与请求作用域密钥相同的 语义,因此注入条目能在 *KEY*/*SECRET*/*TOKEN* 清除中幸存,os.environ 中的 平台凭据仍绝不泄漏,键冲突时请求作用域的值获胜。未配置时保持原先的纯清除行为。 (#6463)

  • 中间件: 非有限的 Retry-After 提示不再让整个运行中止。此前 _extract_retry_after_ms 的数值分支只防护 TypeError/ValueError,因此形如 1e999 的数值型但非有限的提示(float() 将其解析为 inf)会让 int(float(raw) * multiplier) 抛出 OverflowError;由于该辅助函数运行在重试循环 的 except Exception 处理器内,新抛出的异常也一并逃逸,运行没有回退到抖动退避 而是直接中止。现在该分支也捕获 OverflowError,与其他畸形提示一样将其视为不可 用;同一辅助函数中的日期解析回退此前已防护该情况。(#6455)

  • 网关: Redis 流桥接不可达时,GET /health/ready 现在会报告未就绪。桥接的 Redis 客户端是惰性连接的,此前没有任何 逻辑去 ping 它,因此 Redis 宕机的 Gateway 照常启动,对 Kubernetes 就绪探针和 Compose healthcheck 都回答 200 ready, 并继续接收流量,而每次运行的首个发布都在失败;在多实例门槛已把 Redis 桥接设为必需之后,这样的副本完全无法对外 提供服务。StreamBridge 新增 ping();响应体新增 stream_bridge 判定(内存桥接为 not_configured),不可达时端点 翻转为 503;另新增仅报告、不影响状态码的 provisioner 判定,来自沙箱 provisioner 自己的 /health(设置了 sandbox.provisioner_url 时),由于所有副本共享同一个 provisioner,它永远不会改变状态码。现在只有超出端点时限的 探测才会被标记为不可达,公开探测的门闩改为每种探测一把锁。(#6447)

  • 上传: Gateway 启动时对孤立的 .upload-*.part 暂存文件的清扫现在会跳过 24 小时内的新文件。此前清扫会删除找到的 每一个暂存文件,这对单个 Gateway 没问题,但对共享同一 home 卷的多个副本不成立:滚动更新期间启动的副本会删掉另一个 副本仍在写入的上传的暂存文件,该上传随后在原子提交时失败。分块写入会刷新暂存文件的 mtime,因此进行中的上传始终 比该阈值年轻;崩溃残留则由 24 小时之后的第一次启动回收,与项目文档暂存已在使用的防护一致。已经与已发布上传共享 inode 的暂存文件(原子链接与删除暂存名之间发生崩溃所致)在下次启动时仍会被无条件回收,以免该目标文件在下次替换时 无法通过多链接安全检查。(#6445)

  • 沙箱: AIO 沙箱在凭据放置与容器复用上不再信任未经验证的状态。provisioner 能力探测失败不再被解读为“broker 关闭” (此前会在负缓存 TTL 期间把明文 lark 凭据目录 bind-mount 进无 broker 的 Pod);broker 模式按 Pod 依据 provisioner 的 原子观测加以证实;lark 供应配置冲突会携带能力刷新标记,使部署变更在下一次 acquire 时自愈;隐式 shell 结局不明确的 容器由持久化的隔离区({DEER_FLOW_HOME}/sandbox-quarantine)在热复用、重新发现与重启之间设防,按既有的所有权与 拆除围栏回收,其记录只有在确认容器确实不存在之后才退役。(#6436)

  • MCP: 被取代的 stdio 服务器绑定现在在会话池边界处被围栏隔离。此前 stdio 工具包装器可能比创建它的配置纪元活得更久:旧包装器可以在服务器的连接身份 被取代后仍指向该服务器;进行中的创建者可以在绑定变更后才完成初始化并发布 会话;持有旧单例的包装器可以在重置后向已退役的池创建新会话。现在每个 (ownership_domain, server_name) 都携带一个不透明的 ServerBinding 纪元 (stdio 身份是 transport、command、args、配置的 cwd 与配置的 env 的 SHA-256 摘要,不暴露解析后的值);get_session() 在准入、所有者晋升和 await 之后的最终检查三处都拒绝已被取代的绑定;reconcile_bindings() 在池锁 内原子地安装新纪元与墓碑、分离匹配的所有者,teardown reaper 在锁外保留被 分离的所有者;reset_session_pool() 在替换单例之前先退役旧池。缓存初始化把 其代际声明绑定到特定池,因此被取代的初始化者无法用陈旧指纹为替代池播种。 (#6435)

  • 调度器: 通知投递工作器的生命周期现在被串行化。此前 stop() 会在该轮询器 真正退出之前就对其快照并清掉 self._task,并发的 start() 因此可能看不到 任何持有的任务、清掉共享的 stop 事件,并在旧一代仍在运行时启动第二个轮询器 ——两个投递循环同时持有 outbox,在重启或关闭竞态期间出现成对的并发 claim/send 循环。现在 start() 和 stop() 在一把异步生命周期锁上串行执行, self._task 持续指向当前轮询器,直到该轮询器确实完成或被取消;且只有当被 停止的任务仍是所持有的那一代时才清除持有关系。(#6433)

  • voice-input: 对会裁剪最终结果的提供方,听写分段之间保持分隔。此前相邻的 已定稿结果不加分隔符直接拼接,因此 hello world + today is 在输入框里 变成 hello worldtoday is。现在对以空格分词的书写系统,每对相邻的 final/interim 分段都用空格连接,同时保留中文/日文边界和提供方自带的空白; 起始标点、闭括号和百分号不加分隔符直接附着(done + , maybe → done, maybe,up to 50 + % → up to 50%),句读标点之后与引语前后仍 保留空格(Done. + Next → Done. Next)。(#6432)

  • skillscan: 位于命令位置的 env 现在会被正确分类,而不是一律读作环境 变量转储。此前每个 env 都触发 MEDIUM 级的 shell-env-dump 发现,于是 常见的 env VAR=value COMMAND 启动形态成为误报,而更严重的 curl ... | env bash(会执行下载的内容)却从未落入 shell-curl-pipe-shell。现在在选项、赋值、重定向、引号、命令替换和真实 续行之后,裸 env 与 env COMMAND 得以区分;curl/wget 管道之后由 env 启动的 shell 解释器会被识别,而非 shell 命令、注释、heredoc、无关 管道、输入重定向覆盖以及 env 的仅退出选项保持安静。两条规则由同一个共享的 有界线性分词器驱动,因此不可能互相矛盾,覆盖 GNU/BSD 带值的长短形式、集群 与附着值、-S/--split-string,以及对抗性的长操作数链。(#6428)

  • 沙箱: 端口分配现在在 TCP 端口上限处停止。靠近 65535 的合法 AIO 起始 端口可能耗尽剩余端口,随后 PortAllocator 会尝试 65536 并抛出未被捕获的 OverflowError,而不是文档声明的耗尽错误。现在排他搜索的终点被限制在 65536,搜索耗尽时抛出 RuntimeError;末端口分配、预留、释放以及 max_range 限制均不变。(#6424)

  • 智能体: 结构化工具输出的预览现在有界。一个带 20,000 字符字段名的 JSON 结果渲染成预览时会从 20,007 字符膨胀到 62,020 字符,因为该键在 shape、 summary 和 example 路径中反复出现,破坏了外置节省上下文的初衷。现在 JSON 显示标签用既有的 80 字符标量摘录预算缩写,路径用 420 字符文本摘录预算缩写; 渲染出的概要正文(含截断提示)被限制在 max(840, preview_head_chars + preview_tail_chars) 内;聚合预算放在公共 渲染器上,其他被识别的格式和解析回退无法绕过。完整引用与 read_file 指引 仍在被裁剪的正文之外,原始 head/tail 采样契约与外置文件不变,最初那个复现 用例现在输出 2,263 字符。(#6422)

  • 运行时: 派生的运行摘要现在会略去开头的推理内容。此前带行内 <think> 推理开头的回复在运行预览和定时任务通知里显示的是思考而非答案;思考很长时, 日志的 2,000 字符上限还会把答案整个丢掉。现在先抽取答案、再对摘要限界; 原始响应事件、消息和 token 用量不变,正文与代码中的字面标签保持原样,只有 思考内容的响应仍保留之前有用的摘要,已存储的旧摘要不做迁移。重新生成既有的 内容匹配回退同时接受历史原始摘要和新的可见摘要,基于事件的查找仍优先。 (#6419)

  • harness: 重试单个失败条目不再恢复已暂停的批次。暂停屏障是一项调度契约 ——操作者暂停批次是为了不让新工作被认领——但 retry_item 此前会无条件把 批次翻回 queued,而调度器只从 queued/running 批次中认领,于是未被改动的 兄弟条目会连同被重试的那条一起被认领。现在在已暂停批次中重试不会改动批次 行:该条目重置为 pending 并等待,暂停状态跨重启保持,显式恢复之前不会 调度任何新工作。在未暂停批次中重试的行为与之前完全一致。(#6417)

  • 网关: 定时任务的关闭现在与其他生命周期钩子一样有界。此前 ScheduledTaskService.stop() 被 await 时没有期限,挂起的轮询或分发收尾 可能无限期阻塞 Gateway lifespan,使 worker 无法干净地终止。现在关闭走与其他 Gateway 服务相同的有界关闭策略,钩子超过宽限期时记录日志并继续收尾。 (#6409)

  • integrations: Lark 集成的安装现在在取消时也会被排干。安装器把持久化的 技能包写入放在工作线程中执行,且只有该工作线程返回后才刷新技能 system-prompt 缓存,因此在线程启动后取消请求,安装会照常落盘却跳过缓存 刷新,使运行时状态与持久化状态不一致,直到之后的刷新或重启。现在安装与 缓存刷新是同一个由取消持有的操作,在取消传播之前先行排干,持久化状态与 进程内技能缓存因此不会分叉。(#6408)

  • support-bundle: 子进程诊断现在能在非 UTF-8 输出下存活。此前读取线程用 平台默认编码解码命令输出,因此在 ASCII/GBK locale 下,一行 UTF-8 诊断会 引发解码错误并留下空结果;严格解码会丢失含单个无效 UTF-8 字节的输出;继承 的 ASCII PYTHONIOENCODING 会让 Python 辅助脚本在打印 doctor 的 Unicode 状态符号时失败——bundle 恰好丢掉了它本要收集的诊断。现在命令的 stdout/stderr 显式按 UTF-8 捕获,无效字节被替换且保留原始退出码;复制出的 子进程环境设置 UTF-8 的 Python stdio,父环境不变;既有的脱敏、cwd、超时 和命令缺失处理均保留。(#6391)

  • 渠道: 被取消的配置重载不会再发布过期的渠道配置。ChannelService 通过 asyncio.to_thread(self._load_channel_config, name) 在事件循环之外重载 提供方配置,而旧实现允许该工作线程自行改写 self._config[name]:如果在 线程仍在读盘时重启被取消,每渠道生命周期锁已被释放但线程仍在继续,之后的 configure_channel() 便可能在锁内发布更新的运行时配置,而被弃置的旧线程 随后在锁外用过期的磁盘快照覆盖它。现在 _load_channel_config() 对共享状态 只读,加载到的快照只在工作者汇回 _restart_channel_locked()、且每渠道 生命周期锁仍被持有时才发布。(#6390)

  • 微信: 轮询状态的持久化现在在取消时也会被排干。长轮询游标在内存中推进, 并由 _poll_loop() 经 asyncio.to_thread(self._save_state) 持久化;取消 轮询任务只会取消等待者,因此已经启动的文件工作者可能在任务与渠道生命周期 都已收尾后继续写入;令牌过期分支在重置游标之后对认证状态持久化也有同样的 持有缺口。现在两处游标写入都经由仓库现有的取消安全 await_drained() 排干, 过期认证路径复用现有的 _save_auth_state_drained() 辅助函数。(#6389)

  • 配置: 共享同一份 extensions_config.json 的每个 Gateway 进程,现在都能看到其他进程做出的 MCP 与技能变更。此前 解析结果按进程只缓存一次,只有处理写入的进程会重新加载,因此在多个 uvicorn worker、或共享卷上的多个 Pod 之下, 其他进程一直沿用启动时的副本:智能体可用的 MCP 服务器,以及更重要的、由文件系统 MCP 服务器推导出的 local-bash 绝对路径 allowlist,在各副本之间并不一致,直到每个进程重启。get_extensions_config() 现在每次读取都会按文件的路径 与内容签名重新校验缓存实例,与 get_app_config() 对 config.yaml 的做法一致,缓存 AppConfig 的 extensions 快照 也随之更新。截断或无效的修订(例如 bind-mounted 文件上非原子覆盖回退进行到一半时)会保留上一个配置并只记录一次 日志;启动时遇到损坏的文件仍然会明确报错失败。(#6386)

  • 记忆: 无效和原始的助手工具调用现在被排除在记忆采集之外。DeerMem 与 mem0 此前只过滤解析后的 tool_calls,但函数参数格式错误的真实 OpenAI 响应 会变成一条 tool_calls 为空、invalid_tool_calls 非空的 AIMessage,于是 其可见的规划文本——例如试图 memory_add、写下“I will save the nightly production deployment preference”的那段内容——被当作最终回复采入:DeerMem 将其排入抽取队列,mem0 把它作为助手对话内容提交。现在两个后端都会略去带有 非空 parsed、invalid 或 raw tool_calls、或 legacy function_call 意图的 助手消息;空的调用视图仍是普通回复,用户输入、有效的澄清回答、上传处理以及 既有的 user/agent/thread 范围不变。(#6379)

  • tool-output: CSV/TSV 表格概要现在统计逻辑记录数,而不是物理行数。合法 的带引号字段可以包含换行,而概要此前统计的是非空物理行,因此一张六条三列 记录的表会被报告为十二个数据行。现在概要在既有的表格识别门槛之后,从完整 输入统计非空逻辑记录,迭代 csv.reader(..., strict=True) 且不保留每条记录; 无效或被截断的引号以及解析器字段上限失败会得到未确定的计数。识别样本、表头 与行一致性检查、首行渲染、全空记录过滤、全局 CSV 字段上限、原始预览/恢复 引用以及 5,000,000 字节输入护栏均保留。(#6369)

  • 扩展: 扩展操作失败时,现在通过临时文件加 os.replace 恢复 pyproject.toml、uv.lock 以及必要时 config.yaml,被中断的回滚或失败的发布 不再截断目标文件。恢复还会保留操作前捕获的权限位:此前 mkstemp 以其默认 0600 模式发布,会去掉组或公共读权限。快照捕获现在从同一个打开文件读取字节 和模式,恢复在发布前把该模式应用到临时文件;写入、chmod 或发布失败时,当前 目标文件保持原样,临时文件被清理。(#6366)

  • Helm: 默认的 Helm 安装现在又能创建沙箱了。chart 启用沙箱 provisioner 并把 config.sandbox.provisioner_url 指向 它,但没有任何环节渲染 PROVISIONER_API_KEY:provisioner Deployment 没有该环境变量,<release>-app Secret 没有该 键,内嵌配置也没有 provisioner_api_key。自 provisioner 开始强制要求该密钥(#4116)以来,只要密钥为空或不匹配,其 verify_api_key 中间件就会对每个 /api/* 请求回答 401,因此每一次沙箱创建都会失败。现在 app Secret 会生成一次该 密钥,并像其他 app 密钥一样在升级时保留;gateway 与 provisioner Pod 都从这一个 Secret 读取(provisioner.enabled 为 true 时,用户自管的 existingAppSecret 必须携带该键);默认 config 设置 sandbox.provisioner_api_key: $PROVISIONER_API_KEY,chart README 的配置示例现在也保留了这一行。provisioner.enabled: false 时,gateway 使用运维者 通过 secrets 或 existingSecret 提供的密钥(外部 provisioner);否则其启动命令把该变量默认为空字符串,因此即使用户 自管的 provider Secret 只包含模型密钥,默认 config 也能加载,gateway 照常启动。docker-compose 不受影响:它从 .env 读取该密钥。(#6365)

  • 智能体: ReadBeforeWriteMiddleware 的读取标记现在绑定到执行读取的那个 工具调用。Agent 工具可以返回携带自身结果和对其他消息更新的 LangGraph Command,而中间件此前不加区分地取最后一个 ToolMessage,于是一次失败或无 内容的自定义 read_file 之后若紧跟一条成功的历史结果,当前文件哈希会被盖到 那条无关结果上,后续写入无需成功的当前读取即可通过。现在标记只附加到所请求 的 tool_call_id,直接结果与 Command 携带的结果一视同仁;找不到匹配时跳过 检查,并发出带所请求路径与调用 ID 的调试诊断(不含工具内容、不做文件检查), 而当前结果的成功/出错/无内容检查仍是权威判定。无关元数据、Command 状态与 路由、调用方历史,以及既有的版本、加锁、鉴权、取消与 fail-open 策略均保持 不变。(#6363)

  • 模型: Codex 函数工具现在保留调用方设置的 strict 值。此前 CodexChatModel 在发送 Responses 请求前静默删除该字段,包装字典也会在 bind_tools 中丢失它,抹掉了 OpenAI 函数调用指南所区分的显式 strict: false 与省略之间的差别。现在无论直接调用还是经 bind_tools,包装、扁平与裸函数 字典中的显式布尔值都会保留;缺失或为 None 的 strictness 视为未设置,为 程序化填充的 null 保留提供方默认值。包装字典与转换后的 BaseTool schema 共用 同一个转换器,调用方的 schema 与定义不会被改动,该契约已写入 README 与模型 指南。(#6360)

  • 扩展: Gateway 关闭时现在会先排空已提交的扩展通知,再重置通知循环。工作 线程提交的即发即忘观察记录在 _pending_dispatches 中跟踪,而关闭此前不加 等待就清掉该集合,因此在所属 Gateway 生命周期开始拆除扩展资源之后,扩展代码 仍可能继续运行。现在关闭会通过取消安全的 await_drained() 辅助函数等待挂起 的 future 排空,然后再重置进程级的通知循环绑定。(#6357)

  • discord: Discord 线程映射的持久化现在能在取消之下完整完成。频道到线程的 映射此前用裸 asyncio.to_thread(self._persist_thread_mappings) 写入:线程创建 后写一次,stop() 期间再写一次;取消所属协程只会取消等待方,已经启动的文件 系统工作线程可能在关停或消息处理退栈后继续写入,而且第一次写入尚未结束时, 被取消的关停还可以被重试。现在每一次异步映射写入(包括关停时的刷盘)都经由 取消安全的 await_drained() 辅助函数排空。(#6356)

  • 持久化: PostgreSQL 启动恢复的重试路径现在在取消之下也会排空引擎释放。 init_engine() 处理数据库缺失时会先建库,再在构建替代引擎之前释放第一个 引擎,而重试路径上的这次释放此前仍是裸 await _engine.dispose()——取消发生在 这一边界时,初始化器就可能被提前释放,而进程级引擎的释放尚未完成,尽管临时 维护引擎与正常关闭早已排空。现在这次释放经由既有的 await_drained() 辅助 函数执行。(#6349)

  • 沙箱: 沙箱提供方的直接释放现在在取消之下也会排空完成。两条拆除路径在无 租约持有者可用时用裸 asyncio.to_thread(provider.release, sandbox_id) 直接 释放提供方;取消所属协程只会取消等待方,已启动的工作线程可能在请求或执行 持有者退栈后继续释放沙箱。SandboxMiddleware._release_sandbox_async() 中的 直接释放与沙箱工具中的直接释放回滚,现在都经由既有的取消安全 await_drained() 辅助函数排空。(#6348)

  • 部署: Helm chart 与 docker-compose 不再自带多副本陷阱。chart 的默认 config 启用 run_ownership.heartbeat_enabled 和 run_events.backend: db(这样启动中的 Pod 只会认领属主确实已消失的运行,消息历史跨 Pod 共享);按 gateway.replicas 或显式的 gateway.multiInstance 为 Gateway 的启动门槛导出 DEER_FLOW_MULTI_INSTANCE(用 kubectl scale 或 HPA 扩容前 先设置它,chart 看不到这类扩容);生成并注入共享的 AUTH_JWT_SECRET(此前每个 Pod 各自的 .jwt_secret 文件会让用户 在副本之间被登出;gateway 为多实例时该密钥为必需,避免并发启动的 Pod 竞相写入不同的密钥);声明先扩后缩的滚动 策略;为多实例 gateway 渲染 PodDisruptionBudget;并在 90 秒终止宽限期内约束 uvicorn 的优雅关闭 (gateway.uvicornGracefulShutdownSeconds,10 秒;0 会被遵守),空闲 SSE 流不会再把记忆落盘拖进 SIGKILL。 existingAppSecret 现在被所有消费者遵守(gateway 和 frontend 此前一直引用并未渲染的生成版 <release>-app Secret, 导致 Pod 根本无法启动)。两个 compose 文件也绑定同样的 uvicorn 超时并设置 stop_grace_period: 90s(Docker 默认的 10 秒会打断 30 秒的记忆落盘;把每个有界拆除钩子都算上,lifespan 的最坏情况约 61 秒,测试会直接从 Gateway 读取这些 界限)。chart README 与 compose 注释不再引用早已关闭的 issue #3948 作为保持单副本的理由,改为列出真正仍需单实例的 部分(IM 渠道、微信扫码登录、浏览器工具)。升级早于 AUTH_JWT_SECRET 的发布版本会生成新密钥,并把所有浏览器会话 登出一次;chart README 说明了如何先把旧密钥写入 Secret 以保留会话。(#6347)

  • MCP: 轮换式 OAuth refresh token 现在在工具发现与持久 MCP 任务调用之间 共享。这两条路径此前各自解析一份部署配置副本,启用轮换时发现可能把 refresh token R0 换成 R1,而 Gateway 的调用方仍持有 R0,第一次任务提交在到达远端工具 之前就以 invalid_grant 失败。启用 task_toolsets 的部署服务器现在持有一份 私有运行时状态——缓存的 access token、轮换后的 refresh token 与跨事件循环的 刷新锁——生命周期沿用现有的 Gateway 启动快照,发现、普通工具和持久的 submit/status/cancel 调用都复用它,工具缓存重置之后也不例外。冻结配置检查 保留:真正的连接或认证修改仍需重启,非任务连接仍可热加载。个人发现与个人 持久调用方即使运行时服务器名冲突也保持与部署状态独立;共享仅限进程内—— 轮换后的凭据不会写入磁盘。(#6342)

  • 模型: Claude 提示缓存不再把缓存断点放在 thinking 块上。启用提示缓存时, ClaudeChatModel 此前把每条近期消息字典都当作断点候选,历史上的 thinking 或 redacted_thinking 块可能带上 cache_control,这是 Anthropic 明令禁止的, 会使下一次扩展思考或工具后续请求失效。现在最后四个断点只从符合条件的块中 选取,排除两种 thinking 块类型,同时完整保留其内容、签名、脱敏数据与顺序; 文本、图像/文档、工具与 system 缓存沿用现有策略和四断点上限,既有的清理仍在 选取前移除过期标记。(#6339)

  • 模型: MindIE XML 工具调用解析现在保留 JSON 参数的原始转义。转义换行的 兼容转换此前在 XML 工具调用提取之前就跑遍整个响应,因此形如 {"text":"line1\nline2"} 的 JSON 参数可能以未转义的控制字符收场、退化为普通 字符串,字面反斜杠也可能被静默改变。现在先提取工具调用,再解码剩余回复文本 中的转义换行,JSON 与 Python 字面量参数按原始转义解析;字面 \n 的解码只对 围栏代码块之外的非 JSON 原始字符串回退值保留(并修剪首尾空白),“有意字面 转义”与“网关转义换行”之间无法消除的二义性已写入文档。原生工具调用、混合 调用、不可解析的 XML 内容以及数值上溢/下溢保护均保持不变。(#6334)

  • 技能: 自定义技能的历史现在记录真正被覆盖的内容。update_custom_skill 与 rollback_custom_skill 此前在安全扫描结束后立即于事件循环上捕获 prev_content,却把写入放进排空后的持久化尾部执行,因此落在这段窗口内的并发 修订——另一个管理员的 PUT,或智能体通过工具编辑技能,两者都是受支持的 流程——在磁盘上被覆盖,却不在历史链中:后续回滚永远到不了那个修订,审计 记录也不再反映磁盘上实际发生的事。编辑、删除与回滚现在共享一把变更锁,其 临界区覆盖完整的读取 → write_custom_skill → append_history 序列(投影锁 此前在 write_custom_skill 内获取、在追加历史前释放)。锁以解析出的属主用户 存储根为键而非进程级:无关用户的变更不会排在某个用户缓慢的文件系统操作 后面;即便每次请求与热加载配置构建都会新建存储对象,同一用户也始终只对应 一把锁;被扫描器拦下的回滚分支留在临界区之外,因为它不覆盖任何内容;工作 线程在自己的 with 块内完成加锁与解锁,调用方被取消也不会把锁卡在持有状态。 状态码、取消排空行为与提示缓存刷新顺序均不变。(#6153)

  • MCP: 池化的 stdio MCP 会话现在按属主域隔离。部署与个人 stdio 连接此前 共用一个进程级会话池,键只有服务器名、线程范围和所属事件循环,因此一旦两个 属主域并存,相同的运行时服务器名和范围就可能复用错误的会话,定向断连与 服务器清理也没有属主命名空间来约束该淘汰哪个资源。现在活跃与在途的池标识 都带有显式的 deployment | personal 域:个人 Agent 的 stdio 包装路由进个人 域,持久任务的 submit/status/cancel 调用按持久化的 connection_scope 路由, 定向的会话/服务器清理与断连淘汰都被限定在所请求的域内;thread/scope/global 级拆除保持跨域,因为这些操作拥有整个线程或进程生命周期。部署调用的现有 默认调用形态不变。(#6133)

  • 中间件: 外置工具输出的文件名现在从工具调用 id 派生。宿主机磁盘与沙箱 两条外置路径共用 _build_externalized_filename 以保证命名一致,它此前忽略 tool_call_id、追加随机 uuid,因此同一次调用每次都生成不同的文件,同一输出 外置两次并不幂等。现在文件名来自调用 id(bash-call_abc123.log),并按工具 名相同的方式净化——id 来自模型或提供方,不得携带分隔符或目录穿越成分, ../../etc/passwd 这样的 tool_call_id 无法逃出输出目录。(#6107)

  • 前端: 冷恢复的 Agent 运行在无关历史读取失败时仍可远程取消。活动运行恢复 此前对任何 SDK 错误都重试,因此一次缺少元数据的历史失败或来自其他运行的 过期错误,可能让该标签页丢失所恢复线程与运行的取消指针。现在只有 SDK 错误 元数据与被恢复的线程和运行精确匹配时才重试,不匹配的失败会保留取消目标, 真正的 join 失败仍按有限次重试处理。(#6075)

  • 模型: MiniMax 的 reasoning_content 响应现在会被保留。MiniMax 可能在该 字段返回推理内容,而适配器此前会丢弃它,完整响应与流式增量都不例外。现在 该字段在两条路径上都得以保留;reasoning_details 优先级与内联 <think> 处理 不变,仅含空白的流式增量也会保留,并覆盖了空字段回退。(#6071)

  • 模型: Claude 扩展思考预算现在按真实约束校验,不再拒绝合法请求。自动 80% 思考预算可能低于 Anthropic 的 1024 token 下限,普通思考也要为回答留出 空间,而“凡等于或超过输出上限的预算一律拒绝”挡住了带工具的合法手动 interleaved 请求(max_tokens=8192 配 9000 token 预算)。interleaved 例外现在 只适用于带工具、支持手动思考且实际启用 interleaved-thinking-2025-05-14 beta 的模型,整数下限与普通思考上限维持 不变。复制的自动预算、null 视为未设置、严格整数校验与手动退出均不变,且 校验只在 auto_thinking_budget=True(默认值)时生效。(#6060)

  • 模型: MindIE XML 工具参数现在解析 JSON 数值语法。带符号、小数与指数 数字(-3、3.14、1e-3)此前保持为字符串,且数值加固会用 ast.literal_eval 重试 JSON 转换失败,静默把嵌套的下溢数字变成零。现在数值 语法会被识别,格式错误的数值字符串保持原样;当转换因浮点上溢、下溢或 Python 整数位数上限而拒绝时,整个参数原样保留(包括嵌套的 JSON 列表和 对象),Python 字面量回退只在 JSON 语法错误时运行,绝不在数值转换失败时 运行。(#6030)

  • 持久化: 整数元数据过滤器匹配时不再因存储值溢出而失真。存储的 JSON 元数据可以持有超出有符号 64 位过滤输入契约的整数,这些值会让本已合法的搜索 出错或不可用:SQLite 把存储的 2**63 报告为 JSON 整数却提取成 SQL REAL,转成 INTEGER 又被截到 2**63 - 1,因此搜索最大整数会返回无关线程(负边界有同样 问题);PostgreSQL 的 BIGINT 转换则抛出 NumericValueOutOfRangeError,哪怕 过滤条件只是普通的 42,整个搜索也会失败。现在 SQLite 要求提取出原生 SQL 整数再直接比较,PostgreSQL 把 JSON 整数文本与按字符串绑定的整数比较(零也 覆盖 -0 写法),对任意存储数字都无需 BIGINT 或 NUMERIC 转换。整数过滤器仍 排除小数与指数写法,溢出保证只覆盖整数过滤器——PostgreSQL 的浮点路径仍把 存储的数字文本转换为 DOUBLE PRECISION。(#6000)

  • 网关: 纯时长 checkpoint 的分类不再依赖 Postgres 会丢弃的标记。时长写入器 在自己的叶子 checkpoint 上盖 metadata["writes"]["runtime_run_duration"] 戳, memory 与 SQLite 都能原样往返,但 Postgres 保存器把元数据经 langgraph 的 get_serializable_checkpoint_metadata 处理,行落库前 writes 就被弹出——因此 在生产后端上每个时长叶子的 is_duration_only_checkpoint 都返回 false,而按 仅元数据时长 checkpoint 过滤的读取路径(重放基线谱系遍历、重新生成的按时间 回退、分支点扫描与重新生成历史过滤)会把时长叶子当作可寻址的对话状态,最坏 情况下把更早一次运行的叶子当作重放基线交回,使对话回退到过期状态。分类器 现在分两层:writes 键存在时以它为准,并按写入器精确匹配 (runtime_run_duration、runtime_interrupt_title、渠道写入);writes 缺失 时回退到写入器幸存的戳记(source == "update" 加 run_durations/run_message_ids)——这在读取路径上可以接受,因为每个带戳 叶子要么逐字复制父级消息,要么恢复某个祖先的精确消息状态。 enforce_thread_retention 会删除数据,并传入 stamp_fallback=False,因此它只 通过父形状检查扩大该类别,绝不会扫到盖了继承戳记的标题或提升了 channel_versions 的回滚叶子。(#5988)

  • 社区工具: Jina 网页抓取现在跟随 API 重定向。httpx.AsyncClient 默认 follow_redirects=False,因此 Jina API 的临时重定向会以 Error: Jina API returned status 307 的形式出现,而不是返回抓取到的页面。 现在 Jina 请求会跟随重定向,同时保留现有的代理、环境与超时设置,临时重定向 以 POST 方法原样到达目标。(#5808)

  • 目标: 外部 state 和运行输入在写入前拒绝缺失或无效的目标文本、错误计数及 reducer 控制标记,保持合法的稀疏 goal 状态兼容。重放历史 checkpoint(包括重新生成回答)沿用当前 goal 和已用续跑 预算,不会恢复旧目标,并移除旧回答的达成记录;不需要前端改动或数据库迁移。

  • 运行时: JSONL 事件存储在末行写坏后不再丢失事件。写入中途被打断会让文件缺少结尾换行,下一次追加会接在残缺记录后面, 两者合成一行无法解析;被截断的多字节字符还会让整个文件解码失败,所有完好记录都读不出来,序号恢复也随之失效。现在追加前 若文件末尾不是换行会先补一个分隔符,读取时逐个物理行单独解码、只跳过损坏的那一行,批量追加失败时仍会截回原始大小。 (#6520)

  • 渠道: 在启用 Gateway 认证(默认配置)时,从 IM 渠道发送的 /goal <目标> 和 /goal clear 恢复正常。此前这两个写请求只携带内部认证令牌,而 Gateway 的 CSRF 检查不会 豁免内部认证,因此返回 403,渠道回复“Failed to set goal.”或“Failed to clear goal.”。 现在它们会发送与渠道 SDK 客户端相同的 CSRF Cookie 和请求头。/goal 状态查询不受影响。(#6537)

  • 认证: 在 Docker 部署中,某个客户端输错 5 次密码不会再让所有用户 5 分钟内无法登录。登录失败按客户端 IP 计数,而 Gateway 只在 TCP peer 属于 AUTH_TRUSTED_PROXIES 时信任 X-Real-IP,compose 文件却从未设置该变量;所有浏览器请求都经由 nginx 容器到达 Gateway,因此所有登录共用 nginx 的地址和同一个锁定。现在 AUTH_TRUSTED_PROXIES 也接受主机名,在事件循环外解析并 缓存 10 秒(包括解析失败),两个 compose 文件默认将其设为内置的 nginx 服务。nginx 在每个转发到 Gateway 的路由上都用 $remote_addr 覆盖 X-Real-IP,客户端无法自选地址。.env 中设置的 AUTH_TRUSTED_PROXIES 仍然优先,make docker-start 也是如此:它现在会像代理变量一样导出该值供 Compose 插值。位于其他反向代理之后的 部署还需要为该代理配置 nginx 的 real_ip 模块,详见 .env.example。(#6519)

  • 调度器: 在一次性任务的执行时间之前点击“立即运行一次”,不会再取消原定的执行。此前这次试运行被当作任务本身的 执行:任务被标记为 running,试运行结束后又按其结果把任务终结为 completed、failed 或 cancelled。轮询器只认领 enabled 的任务,因此到了 run_at 也不会执行,尽管 next_run_at 仍显示该时间。现在在执行时间之前启动的试运行 不会改变任务的状态和 next_run_at,与周期任务的试运行一致,其结果只记录在试运行自己的运行记录上。执行时间已过之后 的试运行仍算作任务本身的执行。若试运行的启动记录在轮询器认领已到期的任务之后才写入,也不会再清除该认领的租约;此前这会让 该认领的入队失败,任务停留在 running 且没有任何待执行的运行。(#6512)

  • 技能: /技能名 激活现在在模型调用重试时不再丢失。此前激活在调用模型之前就被标记为已完成,因此调用失败 (限流、过载、超时)或返回空响应而重试时,重试请求不再包含 SKILL.md 正文,而该技能的工具限制仍然生效。现在重试 会携带与首次尝试相同的激活内容,不会重新读取技能或重复记录激活,重试得到的响应也保留技能使用记录。(#6506)

  • 持久化: scripts/migrate_user_isolation.py 现在会把每个 legacy 线程移动到其所属用户下。此前脚本在 {base_dir}/deer-flow.db 中查找线程归属,而 DeerFlow 从不创建这个文件(数据库是 {sqlite_dir}/deerflow.db 或 PostgreSQL),因此归属映射始终为空,所有 legacy 线程都被移到 users/default/。现在归属从 config.yaml 所配置数据库的 threads_meta 表读取;若存在 legacy 线程但无法读取该表,脚本会在移动任何数据前退出。对于 从未记录过线程归属的安装,可传 --allow-missing-thread-owners,此时所有 legacy 线程归入 default。 已运行过旧脚本的安装可按 docker/provisioner/README.md 中的步骤恢复线程。(#6450)

  • 沙箱: e2b 沙箱中失败的命令现在会保留输出和退出码。e2b SDK 在退出码非零时抛出 CommandExitException,而不是返回结果,因此 E2BSandbox.execute_command 返回的是 Error: Command exited with code N and error: ...:stdout 被丢弃,Exit Code: N 标记从未追加,_bash_evidence_status 退回到报告 success 的 deerflow_tool_meta, 失败的 pytest 可能满足 tests_passed 验收条件。该异常本身带有命令的 stdout、stderr 与退出码,现在会按返回结果的方式格式化。stderr 中恰好包含 "sandbox not found" 的失败命令 也不再把沙箱标记为已回收。e2b 的 list_dir 也会处理同一异常:目录不存在时抛出 FileNotFoundError,超过 500 条被截断(SIGPIPE 141)的列表会正常返回,不再以 OSError 失败。(#6441)

  • 前端: 附件上传失败后重试发送时,现在会保留原先附带的上下文。输入框此前在发送开始时(附件上传之前)就清除了 引用、对话引用、已暂存的项目文件和已保存的草稿,因此上传失败后文字和文件虽仍在,重试发送却缺少这些上下文。 现在这些一次性状态只在发送真正派发(上传完成)后才清除;若上传完成时用户已切换对话或离开页面,只清除该次发送 携带的已保存草稿和暂存文件,保留此后保存的草稿或附加的文档。(#6412)

  • 客户端: DeerFlowClient.list_threads(limit) 现在限制的是线程数而不是检查点数。此前 limit 被传给跨所有线程的 检查点扫描,而一轮对话会写入多个检查点,因此一个较长的对话就会占满上限:TUI 线程选择器和 --resume <标题> 只能看到 最近一两个线程,较早线程的标题无法解析。现在 SQLite 和 Postgres 通过检查点索引列出线程,每个返回的线程只加载首个和最新 检查点;Gateway 分支也会列出,排序按检查点写入顺序,因此仅写入目标也算作活动。新增的 sort_by="updated_at" 选项让 --continue 继续恢复最近活跃的线程;默认顺序仍为按创建时间从新到旧。(#6426)

  • 客户端: TUI 和内嵌 DeerFlowClient 在 SQLite 和 Postgres 检查点后端上现在可以正常使用目标(goal)。这两种 同步检查点保存器定义了异步方法,但调用时会抛出 NotImplementedError,而目标读写只要异步方法存在就会调用它,因此 /goal 只会显示 "Could not set goal.",get_goal/set_goal/clear_goal 则直接抛出异常。现在目标读写在这类保存器上 会改用同步方法。Web UI 不受影响。(#6448)

  • community: Browserless 的 web_fetch 提供方现在可以通过认证。此前它把配置的令牌放在 /content 请求的 JSON 正文里, 而 Browserless 只从 token 查询参数或 Authorization 请求头读取令牌,并在读取正文之前完成校验。按照配置指南设置 BROWSERLESS_TOKEN 后,每次 web_fetch 都返回 Browserless HTTP 401(对未设置 TOKEN 启动的实例则因正文模式拒绝 未知字段而返回 HTTP 400),而使用同一令牌的 web_capture 正常工作。现在两个工具都以查询参数发送令牌。(#6484)

  • mcp: 配置了 task_toolsets 的 MCP 服务器在工具发现时无法连接或超时,不再导致所有 MCP 工具丢失。此前发现阶段以空 工具列表跳过失败的服务器,随后任务工具集检查把它的 submit、status、cancel 工具报告为缺失,抛出的错误连同所有正常服务器 的工具一起丢弃;由于缓存从未发布,每次构建智能体都会重新发现全部服务器,重启 stdio 服务器并重新请求 OAuth 令牌。现在发现 失败的服务器与其他失败服务器一样被跳过;已应答但缺少所配置工具的服务器仍按配置错误处理。(#6481)

  • 前端: 侧边对话发送失败时不再清空输入框。侧边对话的提交处理在弹出错误提示后仍以成功返回,输入框据此视为成功, 因此在创建侧边对话或上传附件失败时,已输入的文字和附件都会丢失;发往新侧边对话的第一条消息也会在排队时(实际发送前) 就被清空。现在提交处理会在提示后抛出错误,排队的首条发送也以其自身结果完成提交,因此草稿会保留以便重试,只有消息 实际发出后才会清空。(#6407)

  • 前端: 刷新页面后的首次重连失败时,现在会在同一标签页中重试。SDK 只根据标签页的 lg:stream 指针重连一次,出错时保留该指针;而活动运行恢复会跳过指针匹配的运行, 因此实时流在再次刷新前一直无法恢复。现在该重连失败时(包括流中途断开),恢复逻辑会释放指针; 若服务端仍报告该运行处于活动状态,则按现有的有限重试(立即一次,随后 1s、2s)重新加入。 提交失败的运行行为不变。(#6400)

  • 渠道: 在 IM 渠道会话的运行尚未结束时从网页打开它,用户消息不再显示两次。渠道运行输入此前不带消息 id, Gateway 在运行记录中按原样保存无 id 的消息,而检查点中的副本会被分配新的 uuid;网页客户端重连时按 id 合并这两份副本,因此在运行结束前两条都会显示。现在渠道发送的人类消息自带 id,两份副本共享同一 id。(#6401)

  • 部署: 在全新检出的仓库上,make up、make down 与 make prod-logs 不再因 env file .../.env not found 而中止。.env 与 frontend/.env 已被 gitignore,make up 也不会创建它们,但生产 compose 文件此前要求两者都存在。 现在其 env_file 条目与开发 compose 文件一样为可选:文件存在时加载,不存在时跳过。此写法需要 Docker Compose 2.24 或更高版本,即 README 已注明的最低版本。(#6370)

  • 记忆: 安装 jieba(memory-zh 扩展)时,DeerMem 相关度排序不再把标点计为查询词。 tokenize() 只过滤了 jieba.cut 输出中的空白,而 jieba 会把 ,、。、,、! 切成独立 token,因此同样含逗号的查询与无关事实也会得到非零分,近似去重的相似度被抬高, 标点还会在后续查询词之前耗尽 128 个 token 的预算。现在不含字母或数字的 token 会被丢弃, 与无 jieba 的回退分词及 FTS5 查询过滤保持一致。(#6388)

  • 网关: 经过会压缩响应的代理时,运行流现在会逐帧到达。所有 SSE 响应 (POST /api/threads/{id}/runs/stream、GET .../join、 GET/POST .../runs/{run_id}/stream 与 POST /api/runs/stream)改由同一个 辅助函数发送 Cache-Control: no-cache, no-transform,不再只是 no-cache。 因此会压缩响应的代理(例如不经 nginx、直接 pnpm start 时的 Next.js 改写代理) 不再先缓冲整段流、再成批送出。X-Accel-Buffering: no 与 Content-Location 保持不变,使用 nginx 的部署不受影响。(#6393)

  • 中间件: 工具输出预算不再让子智能体证据丢失失败的 shell 退出码。长度介于 externalize_min_chars(12,000)与沙箱上限(20,000)之间的 bash 结果会被替换为 以 Access: 页脚结尾的预览,结尾的 Exit Code: N 不再位于最后, _bash_evidence_status 退回到报告 success 的 deerflow_tool_meta,输出里仍写着 12 passed 的失败 pytest 可能满足 tests_passed 验收条件。现在 ToolOutputBudgetMiddleware 会在预览之后重新追加原始结尾的 Exit Code: N / Command exited with code N;存储不可用时的回退截断也像沙箱截断一样,从 fallback_max_chars 预算中为它预留位置。仅影响 bash 与 bash_tool 的结果, 落盘的完整输出保持不变。(#6354)

  • make: make clean 现在会说明它删除的内容,并拒绝在运行中的 Docker Gateway 下执行。make help 此前称其清理"临时文件",但它实际删除 backend/.deer-flow:本地数据库、用户、线程、上传、记忆和密钥。 两套 Docker 栈都把该目录挂载进 deer-flow-gateway 容器,而 make stop 不会停止它,因此数据可能在 Gateway 运行时被删除。现在帮助文本和删除前的提示会写明该目录;当该容器仍在运行时,make clean 会在停止任何服务之前退出,并提示先执行 make down 或 make docker-stop。(#6351)

  • TUI: 新的提示不再与同一会话中被中断的运行发生竞争。按下 Ctrl+C 后,该运行的工作线程会 一直执行到当前步骤返回,因此正在进行的工具调用(例如耗时较长的 shell 命令)仍会完成并写入检查点, 而下一条提示已在同一线程上运行;最后写入检查点的运行会成为会话历史,新的一轮可能因此悄然消失。 现在在被中断的工作线程返回之前,在该会话中发送提示会显示提示信息而不会启动第二个运行; 其他会话仍可通过 /new 和 /resume 使用。(#6350)

  • 沙箱: 启用 host bash 时,本地沙箱不再把线程固定在上一个受限 Agent 的技能视图上。 该视图只在 host bash 关闭时维护,但 LocalSandboxProvider 只要它存在就会挂载,导致该线程之后 不受限的运行仍沿用旧的 allowlist,读不到 /mnt/skills 下其他已启用或新增的技能。现在这些运行 使用共享技能视图;线程视图会保留,并在关闭 host bash 后重新生效。(#6344)

  • 沙箱: 本地沙箱不再改写换行符。read_file 此前把 CRLF 转成 LF,导致 str_replace 编辑 CRLF 文件后整份文件都变成 LF,且读后写校验无法察觉仅改动换行符的 变更;在 Windows 上 write_file 会把 LF 内容写成 CRLF,使 bash run.sh 等脚本失败。 现在本地读写与远程沙箱一致,按原样保留换行符;当文件使用 CRLF 时,str_replace 会把 以 \n 书写的 old_str/new_str 转为 CRLF,远程沙箱上的多行编辑也因此能匹配 CRLF 文件。 本地 grep 与 read_file 一样只在 \n 处断行,即使文件含有单独的 \r,命中的行号也与按行号范围读取的结果一致。 同样在 Windows 上,保存到 outputs/.tool-results/ 的超长工具输出此前也被写成 CRLF,与其 blob 引用不再一致,未配置 blob 存储时会在下一次模型调用时被删除;现在按原样逐字节写入。(#6343)

  • 技能: 编辑自定义技能时,不再在事件循环上执行文件系统操作。 PUT /api/skills/custom/{name} 此前在事件循环上构建用户级技能存储、探测自定义、 内置、旧版共享与集成目录、把草稿写入临时目录以校验 frontmatter,并读取将被替换的 内容;只有最后的写入与历史追加已移出事件循环。现在这些步骤都在工作线程中执行, 与回滚路由一致。自定义技能的删除与归档安装路由,以及智能体的 skill_manage 工具(其存储查找每次调用都会 stat config.yaml)也改为在工作线程中构建存储。(#6332)

  • 网关: 知识检索目录加载自定义 Agent 配置时不再阻塞网关事件循环。 GET /api/knowledge/retrieval-catalog/datasets 与 .../datasets/{id}/documents 此前在事件循环上通过同步 Agent 存储读取 Agent:file 后端为文件 IO,db 后端 为一次同步 SQLAlchemy 往返,磁盘或数据库变慢时会拖住所有其他请求。现在与其他 读取 Agent 配置的网关路由一样,在 asyncio.to_thread 中加载;响应内容以及未知 Agent 返回的 404 保持不变。(#6313)

  • 网关: 删除工作区较大的线程时,移除文件期间不再冻结 Gateway 的其他所有请求。 DELETE /api/threads/{id} 此前在事件循环上对线程目录执行 shutil.rmtree, 因此其他请求与进行中的 SSE 流都要等到整棵目录树删除完毕(本地 SSD 上 20,000 个 小文件约 0.7 秒,挂载卷上更久)。现在移除在文件 IO 线程池中执行;请求被取消时, 线程预留会保持到移除完成,因此文件仍在删除的线程上不会启动新的运行。(#6319)

  • 沙箱: 中风险审计警告不再让子智能体证据丢失失败的 shell 退出码。 SandboxAuditMiddleware 原先把警告追加在结尾的 Exit Code: N 标记之后,并只用 四个字段重建 ToolMessage,导致 _bash_evidence_status 找不到标记,退回到报告 success 的 deerflow_tool_meta,失败的 sudo pytest -q 可能满足 tests_passed 验收条件。现在警告插在结尾的 Exit Code: N 或 Command exited with code N 之前,整段只有 Command exited with code N 的输出保持原样,结果保留 deerflow_tool_meta、artifact 与 id。(#6307)

  • 持久化: 另一个实例正在执行 PostgreSQL 模式迁移时,第二个 Gateway 实例不再 因 TimeoutError 启动失败。引导期 advisory lock 此前在应用引擎上以阻塞的 pg_advisory_lock 获取,而该引擎的 asyncpg database.command_timeout(默认 30 秒)同样作用于这条语句,因此任何超过该时限的迁移都会让等待中的实例中止。 现在获取改为轮询非阻塞的 pg_try_advisory_lock:等待时长与持锁方的迁移一致, 每次尝试仍受 command_timeout 约束,等待只记录一次日志。(#6306)

  • 项目: 在 SQLite 上首次读取书架文档时,文档转换期间不再阻塞所有其他数据库 写入。此前懒转换在 BEGIN IMMEDIATE 事务内运行 pymupdf/markitdown,以便与移入 回收站和彻底删除串行化发布;而 SQLite 的这把锁作用于整个数据库,运行状态、线程 元数据与调度器的写入都要等待整个转换完成,超过 30 秒即报 database is locked。 现在转换在任何事务之外写入 .staging/,只在重新校验文档行并原子重命名输出时 持锁,期间被移入回收站或彻底删除的文档仍不会发布任何内容。同一文档的并发首次读取 共享一次转换,不再各自占用文件 IO 工作线程。(#6305)

  • 网关: 单次运行的读取现在能返回 IM 渠道所有者的数据。start_run 用原始 受信所有者 ID(例如 feishu:owner-777)标记运行行与运行事件,但多个运行级路由 按内部调用方规范化后的 ID 过滤,因此在 SQL 存储上,只要所有者 ID 含有 [A-Za-z0-9_-] 以外的字符就匹配不到任何数据: GET /api/threads/{id}/runs/{rid}/messages 与 /events 返回空列表, /workspace-changes 报告没有变更,/artifacts/archive 返回 404,重新生成的 源运行查找退回兜底路径或返回 409。这些读取现在使用与线程消息路由(#5448)相同 的数据身份,所有事件存储的 list_messages_by_run() 都接受 user_id。浏览器与 API 会话仍保留按用户过滤。(#6282)

  • 运行时: 多 worker 部署中已成功结束的运行,不会再在其 worker 仍在收尾时被 当作孤儿运行回收为 error。配置了事件存储时,worker 先在内存中记录终态,直到 journal 刷新、交付回执、工作区扫描与时长 checkpoint 写入完成后才写入运行存储。 此前租约续期会跳过本地状态已是终态的运行,收尾一旦超过租约加宽限期(默认约 30–40 秒),其他 worker 或本 worker 自己的回收器就会接管这条仍处于活动状态的 记录。心跳现在会持续续期直到这次延迟写入被执行,若期间被其他 worker 接管则隔离 本地运行。仅影响启用 run_ownership.heartbeat_enabled 的部署。(#6263)

  • 渠道: 通过 /connect 绑定的 Buzz 作者在已参与的话题中回复时,无需再次提及 机器人。开启 channel_connections.enabled 后,管理器只在连接仓库中记录已绑定作者 的话题映射,而 Buzz 的话题跟随判断只读取 JSON 渠道存储,导致机器人正在回复的话题 中所有未提及的回复都被静默丢弃。现在 Buzz 会在提及判断之前解析连接,并通过与管理器 相同的辅助函数(lookup_thread_id)查找话题,因此已绑定作者的已参与话题就是管理器 将复用的话题,旧的 JSON 映射也不再对该作者生效。管理器的斜杠技能白名单检查中有同一读取逻辑 的副本,也一并修复:尚无话题的已绑定用户此前会按同一会话旧 JSON 话题的智能体进行检查, 可能被告知已启用的技能不可用。(#6232)

  • 记忆: 读取 DeerMem 智能体记忆时,不再因另一写入同时删除事实而失败。 load()、reload() 与全量 rebuild_index() 扫描在不持有存储锁的情况下列 出事实文件,若删除恰好在列出之后、打开文件之前提交,就会对完好的数据抛出 MemoryStorageCorruption:记忆 API 返回 HTTP 500("Stored memory data is corrupted"),提示词注入在该轮丢弃整个记忆块(在 failure_policy.read: fail_closed 下则使运行失败),全量索引重建则把该事实计为失败。列出后消失的事实现在被视为已删除;仍然存在但无法读取的条目(例如悬空符 号链接)仍会报告为损坏。(#6255)

  • 记忆: DeerMem 记忆重新加载不再把旧文档固定在缓存中。reload() 此前先读取文档、后计算缓存签名,若两者之间有写入提交(例如后台记忆更 新器),旧文档就会以新签名写入缓存,之后每次 load() 都返回过时的记 忆,直到下一次写入。reload() 现在与 load() 一样先计算签名,竞争写 入只会触发重新读取。(#6238)

  • 渠道: Discord 的渠道连接数据库操作现在在 Gateway 事件循环上执行。 discord.py 在客户端线程的私有事件循环上投递消息,而 Discord 适配器此前就在该 循环上 await 连接仓库,但仓库的 SQLAlchemy 引擎与连接池属于 Gateway 循环。在 PostgreSQL 上开启 channel_connections.enabled 时,Gateway 使用过连接池之后的 第一条 Discord 消息会以 got Future … attached to a different loop 失败并被 丢弃;在 SQLite 上,耗尽连接池的突发流量会以 Queue … is bound to a different event loop 失败,且等待队列会一直绑定在 Discord 循环上,导致 Gateway 自身的查询随后也以同样方式失败。现在身份查询与入站 提交一起执行,/connect 绑定单独执行,二者都与 Telegram、飞书、钉钉一样经由 _submit_threadsafe_coroutine 在 Gateway 循环上运行;绑定回复经 Discord 循环 发回,stop() 也会在关闭客户端前排空这些任务。输入中提示仍在交接前注册,查询失败时会跳过确认表情, 并在同一目标没有其他消息依赖时停止该提示,被丢弃的消息不会让机器人显示为仍在处理。(#6214)

  • 社区工具: 共享 SSRF 校验现在拒绝所有非全局地址,包括原先的标志位检查放行的 100.64.0.0/10 共享地址段。该地址段包含 CGNAT 与 Tailscale 主机以及阿里云 100.100.100.200 实例元数据端点,因此 web_fetch(crawl4ai、Browserless、 fastcrw)、web_capture、智能浏览器和个人 MCP 连接此前都能访问它们,包括 DNS 应答可以携带的 IPv4 映射形式 ::ffff:100.100.100.200。原有的标志位检查仍然保留, 因为部分非公网形式(例如元数据地址的 NAT64 写法)依然被判定为全局地址。有意通过这些 工具访问 tailnet 或 CGNAT 主机的运维人员现在需要设置 allow_private_addresses: true。(#6202)

  • 浏览器: 智能浏览器不会再因为 SSRF 检查之后发生变化的 DNS 应答而被引向 内网或云元数据主机。导航检查和逐请求守卫会解析主机名进行筛查,但 Chromium 建立连接时会再次解析,因此重绑定 DNS 服务器可以对检查返回公网地址、对连接返回 内网地址。现在每个启动的浏览器的所有 TCP 连接都经过一个按会话创建的本地回环 SOCKS5 代理: Chromium 把主机名交给代理,代理按相同的 allow_private_addresses 策略只解析一次, 并且只连接筛查通过的地址。回环流量同样经过代理。WebRTC UDP 不经过代理,不在覆盖范围内。 通过 CDP 连接的 Chrome 不受影响; 委托抓取服务(crawl4ai、Browserless、fastcrw)仍在其自身一侧解析,Gateway 无法固定。(#6201)

  • 渠道: Discord 现在会在智能体生成回复期间真正显示"正在输入"提示。_start_typing() 调用的 channel.trigger_typing() 已在 discord.py 2.0 中移除(项目要求 >=2.7.0),而其循环吞掉了 所有异常,因此每次都抛出 AttributeError,提示从未发送。现在改为 await 2.x 的 channel.typing() 发送一次提示。每个输入提示循环的首次失败以 WARNING 级别记录(缺少权限或持续限流 在默认日志级别下即可见),之后的失败以 DEBUG 级别记录,而不是直接丢弃。(#6138)

  • 社区工具: SSRF URL 校验在解析主机名时不再阻塞 Gateway 事件循环。 validate_public_http_url 通过阻塞的 socket.getaddrinfo 解析主机名,而 crawl4ai 与 Browserless 的 web_fetch、web_capture、browser_navigate、 Gateway 浏览器导航路由以及 Live 流的导航输入和 seed 都在异步代码中直接调用它, 因此模型或用户选择的 URL 一旦遇到缓慢的 DNS 响应,整个查询期间其他请求和流都会停滞。 逐个检查重定向和子资源的 Playwright 请求守卫也在共享的浏览器事件循环上同步解析, 会让所有浏览器会话的 Live 画面与输入一同停滞。这些调用方现在通过 asyncio.to_thread 运行校验,放行与拒绝的结果不变。 严格的阻塞 IO 检测新增 socket.getaddrinfo 规则,因为 Blockbuster 默认只包装 socket 方法,不包装模块级解析函数。(#6140)

  • 智能体: 循环检测的整数阈值现在会拒绝 YAML 布尔值,而不是把 true 静默转换为 1。此前若配置 warn_threshold: true 和 hard_limit: true,第一组工具调用就会达到硬上限并强制终止智能体; 跟踪窗口、工具频率和按工具覆盖项中的布尔值也会把对应限制缩小为 1。现在所有整数阈值字段都会在配置加载阶段按字段名报错,同时保持 有效整数和数字字符串的既有行为。(#6017)

  • 智能体: 应用配置的整数设置现在会拒绝 YAML 布尔值,而不是把 true 静默转换为 1。此前若配置 recursion_limit: true,所有未自带递归上限的 Gateway 运行都会在第一个 LangGraph 超级步就触达递归上限;llm_call 下的 整数字段(retry_max_attempts、max_concurrent_calls 与两个退避延迟)中的 布尔值也会把重试次数和并发上限缩小为 1。现在全部七个整数字段都会在配置 加载阶段按字段名报错,同时保持有效整数和数字字符串的既有行为。(#6171)

  • 上传: 文档转换时现在会记录原文件与 Markdown 的归属关系。 list_uploaded_files 只隐藏归属已验证的转换文件,文档大纲也只读取 记录中指定的 Markdown;用户自行上传的同名文件会正常显示,不会被 误用作其他文档的大纲。旧版本生成的转换文件没有归属记录,无法与 用户自行上传的 Markdown 安全地区分。升级后,它们会作为独立文件 出现在历史列表中,原文档也不再从同名文件推断大纲或预览。启用 自动转换后重新上传原文件,可以生成有归属记录的新转换文件。(#6101)

  • 智能体: 计划模式下被重试的模型调用不再丢失 TodoMiddleware 已为其排队的待办完成提醒。该中间件在 wrap_model_call 中取出提醒;由于 LLMErrorHandlingMiddleware 包裹着它并通过再次调用自己的 handler 来重试, 第二次尝试在没有提醒的情况下发出,而该 run 已经为它消耗了两次提醒额度中的一次。 现在 TodoMiddleware 会在 handler 抛出时把取出的提醒放回队列,且不重复计数, 从而让重试带上提醒,上限仍允许发送第二次提醒。成功调用的行为不变; 若该 run 的提醒状态在此期间已被清除,提醒不会被恢复。(#6132)

  • 渠道: 停止或重启 Slack、飞书、钉钉、Discord 渠道不再冻结 Gateway 事件循环。 SlackChannel.stop() 直接调用 SocketModeClient.close(),它会 join SDK 的消息处理线程 (每次约 0.7 秒),并等待正在执行的事件监听器,而监听器中阻塞的 Slack Web API 调用 最长可持续到客户端超时;飞书与钉钉则直接以 5 秒超时 join SDK 线程,由于这些线程 只在致命错误时退出,这次 join 通常会等满 5 秒;Discord 直接以 10 秒超时 join 客户端线程, 客户端关闭超时或清理缓慢时会用满这段时间。期间 Gateway 上的所有运行、流和渠道 都会停顿,POST /api/channels/{name}/restart 也不例外。现在这些清理都在工作线程中执行; Slack 的 close 会被 shield 并跟踪,因此被取消的关闭流程不会中断它,重试的 stop() 会等待同一个 close,而不会再关闭一次。(#6134)

  • 中间件: _externalize_to_sandbox 现在校验外部化工具输出的完整字节大小, 而不再仅使用 test -s 检查文件是否非空。此前当远程沙箱写入中途被截断时(如磁盘满 或管道故障),残缺的文件也会通过校验并把错误路径交付给模型;现在会严格核对字节数与负载一致, 发生截断时返回 None 并回退到内联截断。(#6112)

  • Gateway: 含非 ASCII 字符的 CSRF token、GitHub webhook 签名、内部认证 token、 OIDC state 或 provisioner X-API-Key 现在按常规返回 403/401,而不是 500。 hmac.compare_digest 遇到含非 ASCII 字符的 str 参数会抛出 TypeError,而 Starlette 以 latin-1 解码请求头字节,一个 0xE9 字节就能让比较崩溃。Gateway 现在通过统一的 app.gateway.utils.constant_time_equals 比较 UTF-8 字节,独立部署的 provisioner 则在原处编码后比较。此前不存在绕过,请求本就会失败,只是状态码错误。(#6076)

  • 智能体: 上下文压缩的 fraction 触发器与 fraction 保留量现在使用当前运行 模型的上下文 profile;单独配置的 summarization.model_name 只负责生成摘要。 这避免运行模型与摘要模型的窗口不一致时压缩过晚或过早。中间件发布身份现在 分别记录 profile_model 与 summary_model,任一归属变化都会有意刷新该身份。 (#5566)

  • 事件: 在 JSONL 后端上,按 run 读取时,若 run ID 无法用作文件名,不再返回 500。GET /api/threads/{thread_id}/runs/{run_id}/events、.../messages 与 .../workspace-changes 会把 URL 中的 run ID 原样传给事件存储;在 run_events.backend: jsonl 下,run.1 这类 ID 会抛出 ValueError,而内存与 数据库存储返回空结果。现在 JSONL 的读取与删除把这类 ID 视为不存在的 run,写入 仍会拒绝它。(#6070)

  • 智能体: 运行上下文中带有 "max_total_subagents": null 时,现在会使用配置的 subagents.max_total_per_run,而不是抛出 TypeError。该键存在时 dict.get(key, default) 会返回 None,构建 SubagentLimitMiddleware 时该次运行 便以内部错误失败(Web UI 从不发送该键,但 API 与嵌入式客户端调用方可能发送)。 Gateway lead agent、DeerFlowClient 与系统提示词现在通过同一个辅助函数解析上限: null 视为未设置,并限制在 1-50,因此面向扩展的 host policy 与 release policy 报告的也是实际执行的上限,而非超出范围的请求值。(#6088)

  • 调度器: 固定小时的 cron 任务在夏令时回退(DST fall-back)当天不再重复运行两次。 croniter 会返回模糊本地时间的两个实例(首个为 fold=0,第二个为 fold=1)。 对于分和时字段不包含通配符的固定任务,现在会跳过第二个重复实例(fold=1),保持每天只运行一次 的契约(Vixie cron 规范),同时通配符计划(如 0 * * * *)仍会在重复的小时内每小时正常触发。(Issue #6052, #6066)

  • 持久化: SQLite checkpointer.connection_string 若写成 file: URI,现在会在 启动时报错,而不是悄悄写到别处。LangGraph 的 SQLite checkpointer 与 Store 打开 连接串时未传 uri=True,SQLite 会把 URI 当作字面文件名: file:checkpoints.db?mode=rwc 会在工作目录创建同名文件, file::memory:?cache=shared 会持久化到磁盘,file:///... 则无法打开。就绪探针 却会解析 URI,因此检查的文件与运行时实际使用的不同,并把内存 URI 报告为 not_configured。现在四个 SQLite checkpointer/Store 工厂都会拒绝 file: URI, 错误信息会指明该配置项,/health/ready 也会将其报告为不可达。请改用文件系统 路径或 :memory:。(#6069)

  • 配置: make config-upgrade(make dev / make start 也会执行)现在升级的是 Gateway 实际加载的 config.yaml。当 <checkout>/config.yaml 与 backend/config.yaml 同时存在时,脚本升级的是 backend/ 下的副本,而 Gateway 读取的是 checkout 根目录的副本,因此实际使用的文件仍停留在旧版本,升级却显示 成功。脚本还会忽略 DEER_FLOW_PROJECT_ROOT 以及 .env 中设置的 DEER_FLOW_CONFIG_PATH,并在 DEER_FLOW_CONFIG_PATH 指向不存在的文件时退回到 其他文件。现在脚本通过 harness 的解析器 (AppConfig.resolve_config_path)确定文件;DEER_FLOW_CONFIG_PATH 不存在或 DEER_FLOW_PROJECT_ROOT 无效时,会以 Gateway 相同的错误失败,而不是升级 回退文件。(#5991)

  • 沙箱: 在中间件(ToolOutputBudgetMiddleware 与 ReadBeforeWriteMiddleware) 中解包 Overwrite 包装的沙箱状态。在 delta checkpoint 模式下,分叉或回滚 的对话交付的 sandbox 通道状态会被 LangGraph 的 Overwrite 包装。此前直接判断 isinstance(sandbox_state, dict) 会返回 False,导致大工具输出无法外部化到沙箱而退化为内联 硬截断,以及写前读锁作用域丢失有效沙箱 ID。(#6015)

  • doctor: make doctor 现在检查 Gateway 实际加载的配置文件。此前它 固定检查 <checkout>/config.yaml,忽略 DEER_FLOW_CONFIG_PATH 与 DEER_FLOW_PROJECT_ROOT:指向不存在路径、会让 Gateway 无法启动的覆盖值仍会 显示 ✓ config.yaml found 与 ✓ config.yaml loadable,而指向其他有效文件的 覆盖值则会让它检查错误的文件。现在 doctor 通过 harness 自身的解析器确定路径, 并像 make dev 一样处理这两个位置变量:.env 中的值覆盖 shell 中的值(未加 引号的开头 ~ 会展开),DEER_FLOW_PROJECT_ROOT 未设置或为空时取仓库根目录。 Gateway 会拒绝的覆盖值会让 config.yaml found 失败并给出 Gateway 的错误, 其余配置检查随之跳过。(#5987)

  • 前端: 文件上传完成后,乐观显示的用户消息气泡不再丢失引用与对话 引用标签。上传完成时的更新会用仅含已上传文件的对象替换气泡的 additional_kwargs,因此在服务端回传该消息之前这些标签会消失;随本次 发送一起暂存的项目附件在上传中和上传后也都不会显示在气泡中。实际提交的 消息始终完整。乐观副本(上传前后)与提交现在通过同一个辅助函数 buildHumanMessageAdditionalKwargs 构建 additional_kwargs。(#5982)

  • 调度器: 修改间隔任务的标题或 prompt 不再返回 500。编辑对话框总是把 schedule_spec 与被修改的 字段一起发送;当节奏没有变化时,PATCH /api/scheduled-tasks/{id} 会沿用任务原有的 next_run_at—— 而这个值是仓储以 ISO 字符串序列化后返回的。ScheduledTaskRepository.update() 原样把它赋给 DateTime 列,于是 SQLite 抛出 StatementError("only accepts Python datetime"),Postgres 抛出 DataError;只有改变节奏才能成功,因为那条路径会重新计算出 datetime。现在仓储在 update() 中 会像 update_after_launch() 一样,把接收到的所有序列化时间戳转换回来。(#5964)

  • 网关: GET /api/skills、GET /api/skills/custom 与 GET /api/skills/{name} 不再在事件循环上 遍历技能目录。三者此前都内联调用 load_skills():它会解析调用者的存储、扫描所有公共与自定义 技能目录并解析每个 SKILL.md,工作量随已安装技能数增长。#5747 已经为自定义技能内容路由把同一 调用移出事件循环并注明原因,但这三条路由被遗漏了,因此严格的 Blockbuster 门禁会在它们上抛出 BlockingError;在生产环境中,技能树很大或磁盘很慢时,扫描期间该 worker 上的其他所有请求都会 停顿。现在三者通过一个共享辅助函数用 asyncio.to_thread 卸载加载过程。(#5945)

  • 网关: GET 与 PUT /api/user-profile 不再在事件循环上执行文件系统操作。这两个处理器 此前在循环上直接解析按用户隔离的 USER.md 路径(每次调用都会构造绝对路径)、stat、读取、 创建用户目录并写入文件,而自定义智能体路由里的其他所有处理器都通过 asyncio.to_thread 卸载这类工作。在严格的 Blockbuster 门禁下这一对处理器会抛出 BlockingError;在生产环境中, 磁盘变慢时会让该 worker 上的其他所有请求随之停顿。现在两者都把整段 解析-stat-读取 / 解析-mkdir-写入 的流程卸载到线程。(#5935)

  • Docker: 通过统一入口上传超过 1 MB 的项目文档不再被 nginx 直接以 413 拒绝。 POST /api/projects/{id}/documents 是 multipart 上传,Gateway 接受至多 uploads.max_file_size(默认 50 MiB)的文件,但没有任何 nginx location 匹配它,于是请求 落到 /api/ 兜底块,被 nginx 默认的 client_max_body_size 1m 在到达 Gateway 之前拒绝—— 一个 2 MB 的 PDF 会被拒,而同一个文件上传到会话里却没问题。三份维护中的配置(Docker、 make dev、Helm)现在都为 /api/projects/{id}/documents 单独设置 location,沿用会话上传的 设置(100M 上限、请求体流式转发)以及兜底块原本给予的读超时;兜底块自身保持 nginx 默认值。(#5934)

  • Docker: 生产栈(make up / scripts/deploy.sh)现在可以在禁用 IPv6 的主机上启动。 docker/nginx/nginx.conf 同时监听 [::]:2026;在以 ipv6.disable=1 启动的内核上,这条监听会让 nginx 在启动时退出,容器因此反复重启,make up 永远无法就绪。开发用 compose 文件自 #2027 起 会在 /proc/net/if_inet6 不存在时去掉 IPv6 监听,Helm chart 也做了同样处理;生产用 compose 文件是唯一还没有这层保护的启动器。现在它使用相同的启动脚本,并以 exec 启动 nginx 使其成为 PID 1。(#5900)

  • 部署: make up / scripts/deploy.sh 现在会采用写在仓库根目录 .env 中的 BETTER_AUTH_SECRET 与 DEER_FLOW_INTERNAL_AUTH_TOKEN。此前脚本只检查 shell 环境,随后 就重新加载已持久化的密钥或生成新密钥并 export;而 Compose 插值时 shell 变量优先于 --env-file,于是部署文档让运维写进 .env 的值被悄悄替换:会话用的是运维从未选择的密钥, 在栈外运行、持有配置 token 的 Gateway worker 则收到 401。现在 .env 提供的密钥交由 Compose 自行读取(优先级 shell → .env → 持久化文件 → 新生成)。.env 是否提供了值由 Compose 自己决定:脚本用 docker compose config 渲染一个只含 ${KEY} 的桩项目并读回结果, 因此 KEY: VALUE 写法和值内的 ${VAR} 插值在所有 Compose v2 客户端上都按 Compose 的规则 计算;解析结果为空的值(例如已 export 但为空的 shell 变量)仍会触发生成,因为 Compose 否则 会把空值直接传下去。(#5928)

  • 技能: skill_manage(action="remove_file") 与 write_file 现在可以处理二进制支持文件, 并会干净地拒绝目录。.skill 压缩包可以包含 assets/logo.png(安装器只拒绝可执行二进制), 但这两个操作在改动文件之前会先把原有内容按 UTF-8 文本读出(仅用于历史记录),于是二进制 成员会抛出 UnicodeDecodeError,文件既不会被删除也不会被覆盖。像 assets 这样的裸支持目录 也能通过路径校验并抛出 IsADirectoryError。现在非文本内容会记录为“无原有文本”,目录路径 则作为校验错误返回,而不是崩溃。(#5893)

  • 模型: 在旧路径(没有 reasoning: 块)上,when_thinking_enabled 与 when_thinking_disabled 不再整体替换模型档案的 extra_body。两个模板此前都用浅层 dict.update 套用,因此档案里与 when_thinking_enabled.extra_body.thinking / when_thinking_disabled 模板并列的 extra_body: {tool_stream: true}(config.example.yaml 里大多数基于 extra_body 的示例都是 这种写法)在开与关两个方向都会丢失 tool_stream,而合成的禁用载荷和契约路径早已是深度合并。 现在两个旧路径模板同样深度合并。合并语义为:永不删除键,模板只能新增或覆盖,嵌套映射会继承 档案里的其他键,冲突时以模板值为准。合并还会让模板的 vLLM 开关在两种拼写之间保持权威:当档案 与模板对开关的拼写不同(chat_template_kwargs.enable_thinking: false 并列旧别名 thinking: true)时,模板的值会镜像到档案的拼写上,因此 VllmChatModel 与普通的 OpenAI 兼容 类都会在服务端实际读取的那个键上发送模板的意图——旧路径与契约路径、开与关两个方向均如此。 非映射类型的模板值按原样转发。迁移提示:由于键永不删除,when_thinking_disabled 模板不再能 清除档案基础 extra_body 里设置的键——基础 extra_body.thinking: {type: enabled, budget_tokens: 4096} 在关闭思考时会以 {type: disabled, budget_tokens: 4096} 发到 provider,Anthropic 风格的 API 会拒绝。 budget_tokens 之类仅在开启时有意义的键应放在 when_thinking_enabled 里,而不是基础 extra_body; 合成的禁用载荷与契约路径此前已是这一行为。模板在合并时会被深拷贝,因此构造参数不会与缓存的 档案共享对象。(#5894)

  • 项目: 会话文件视图不再为尚无标题的成员会话显示空标题。会话的 display_name 在标题 生成运行之前(或从未运行时)在接口上为 null,但文件分组类型将其声明为必填字符串并原样 渲染,因此在首次回复之前上传的文件会挂在一行空白之下。这类分组现在显示为“未命名”,与 项目的会话列表一致。(#5879)

  • 技能: 删除包目录为运维方管理的符号链接的自定义技能现在可以成功。存储契约允许 custom/ 下的一级链接,但 delete_custom_skill 用 shutil.rmtree 删除包目录,而它拒绝 符号链接:删除在写入历史记录之后以 OSError 失败,退出时清空了用户的技能投影视图, DELETE /api/skills/custom/{name} 返回 500。现在只移除链接本身,绝不触碰其指向的外部目录。(#5881)

  • 开发: 当同级 worktree 的路径包含空格时,make stop / make dev 现在也能回收它占用的 开发端口。serve.sh 用 awk '{print $2}' 解析 git worktree list --porcelain 来构建 worktree 根目录列表,而该输出中的路径不加引号,因此 .../deer flow two 被记录成了 .../deer;从那个 worktree 启动的 Gateway 或前端永远不会被识别为 deer-flow 的进程, 启动会以“端口已被占用”中止。现在会保留整条路径。(#5856)

  • 上传: 运行消息元数据中格式错误的 files[*].size 不再导致整个运行失败。 UploadsMiddleware 对客户端提供的文件条目的其他字段都做了容错校验,唯独把 size 直接交给 int(),因此 "abc" 或列表这样的值会在调用模型之前从 before_agent 抛出——而且由于该条目 会被原样带入,之后每次编辑或重新生成这条消息都会再次失败。该字段只用于 <current_uploads> 里的可读大小;现在无法使用的值会回退为 0,与缺失时一致,数字字符串仍然有效。(#5855)

  • 发布: 版本升级不再把 backend/uv.lock 落下。scripts/bump_version.sh 会改写 backend/pyproject.toml、frontend/package.json 与 Helm chart,但 lockfile 同样记录了 根包自身的版本(uv 保留其 PEP 440 形式,因此 2.1.0-rc0 存为 2.1.0rc0),于是文档给出的 发布步骤产出的提交会被 lock 相关 CI 拦下:uv lock --check 判其过期,uv sync --locked 也会拒绝该工作区;装了 pre-commit 时还会更早在 uv-lock-check 钩子上失败。现在该脚本会用 uv lock 刷新 lockfile,并在缺少 uv 时于修改任何文件之前退出,而不是留下一个只改一半的 工作区。实际改动仅涉及根包的那一行版本号。(#5859)

  • 前端: 子任务渲染状态不再在 MessageList 渲染过程中被就地修 改。子任务同步从渲染阶段移入 effect,因此即使任务上下文尚未发布 更新,卡片也能立即拿到纯派生自消息的快照——修复了最终流式参数与 task_started 同时到达、但卡片仍保留部分标题或空 prompt 的回 归。消息参数提供标题、prompt 与子智能体类型;实时状态保留生命周 期、模型、用量与步骤历史,终态快照仍以 status/result 为准。 (#3157)

  • 技能: 技能启用/禁用请求失败时会在设置页明确呈现,而不是被当 作成功。前端 skills 辅助函数解析响应 JSON 时没有检查 response.ok,因此 skills 列表或切换端点返回的 4xx/5xx 会被当作 成功处理,即便后端已拒绝变更,UI 仍会翻转开关。非 OK 响应现在会 留在错误路径上并展示后端错误信息。(#3823)

  • 智能体: 无人值守运行的提示词现在与工具策略保持一致。非交互 运行会从工具集中移除 ask_clarification,系统提示词却仍要求模型 在遇到歧义时调用它,导致 GitHub、webhook 与定时运行既无法提问也 无法继续。现在由共享的 RunInteractionPolicy 同时驱动工具集与提 示词——webhook 从 issue 与事件上下文推断,上下文不足时以结构 化的阻塞信息退出;scheduled/autonomous 做最小且可逆的假设, 并将高风险歧义报告为阻塞。(#4919)

  • MCP: 持久化 MCP 任务认领的生命周期现在是取消安全的。在获取 认领之后调用方被取消,可能让相关行一直被租约占用直到过期、使已启 动的释放被中途抛弃,或让清理失败覆盖原始的 CancelledError;例 行停机也会被记成任务失败。现在补偿逻辑覆盖认领之后的每一次 await,语义不明确的操作在前台截止时间之后仍由服务方负责完成,每 个认领的 lease token 会原子性地拒绝过期代际的写入(堵住一个 SQLite 竞态)。迁移 0026 新增可空的 lease-token 列;MCP 公开 API 无变化。(#4966)

  • 前端: 流式输出的答案文本不再进入 Thinking 面板。一条同时携 带推理与答案文本的流式 AI 消息,此前会被归入处理过程的折叠面板直 到本回合结束,长时间运行时答案可能一直滞留在 Thinking 面板里;这 类消息现在会立即渲染为助手气泡,而普通的工具前解说仍保持折叠归 组。bash 工具对模型可见的契约也写明了跨平台本地环境探测方式—— 先执行 uname -s,在 Darwin 上使用 sw_vers,遇到被阻断的绝对 路径时用仅命令式探测恢复。(#5001)

  • 运行时: 加固 provider 边界处的模型响应恢复。空的已完成响应 可能让 run 在没有可用答案的情况下结束,被长度上限截断的响应则可 能把截断的工具调用带入循环。真正的空停止现在会在模型边界重试一次 (每次 run 共享一次重试);重试耗尽时以同一步骤内可见的 fallback 收场,而不是隐藏的图级恢复回合;检测到长度上限时会丢弃该响应中的 所有工具调用,即使参数已解析成功。DeepSeek 思考模式的历史会在 assistant 工具回合上保留 reasoning_content。(#5080)

  • 沙箱: 沙箱所有权租约 TTL 溢出现在会在配置校验时被拒绝。 renewal_interval_seconds 与 ttl_multiplier 各自都校验了有限 性,但二者的乘积可能溢出为 inf,直到第一次写入租约才失败,或悄 悄改变承诺的时序。非有限的派生 TTL 与亚毫秒的 Redis TTL 现在都会 被拒绝,Redis TTL 上限为有符号 64 位毫秒范围的一半,且小数毫秒向 上取整,使存储的租约绝不会短于校验值。(#5104)

  • 子智能体: 在释放运行时资源之前先关闭子智能体图流。协作式取 消可能在未显式关闭活跃的 agent.astream() 迭代器的情况下返回终 态结果,导致执行器在图流拆除仍在进行时就释放沙箱租约并发送任务停 止通知。现在图流会被保留并在终态化与释放之前关闭;若关闭也失败, 则以原始的流错误或宿主取消为准;拆除超过 10 秒仍未完成会记录告 警。(#5221)

  • composer: 将 context 保留为斜杠命令别名。 /context compact 是 composer 的内置命令,但 context 此前未在 共享 slash-skill 契约中保留,因此某个技能可能抢占该别名并将其遮 蔽。共享契约现在保留 context,前端与后端的斜杠解析经由它保持一 致,IM 渠道的命令分类不受影响。(#5279)

  • 调度器: 一次性任务的执行时刻在编辑标题与 prompt 后保持不 变。调度控件在挂载时会把存储的时间戳转换为分钟精度的本地时间再转 回去,因此在纽约 2026-11-01T06:30:00Z 会被保存成 05:30:00Z, 普通时间戳则会丢失秒与毫秒——一次仅改元数据的编辑就悄悄移动了运 行时刻。编辑表单现在会快照原始时间戳、本地输入值与时区,并在时间 或时区变更之前始终重新发出原始的 run_at。(#5330)

  • 调度器: 一次性调度表单现在会拒绝被夏令时跳过的墙上时间。纽 约的 2027-03-14T02:30 在转成 UTC 再转回来后变成 01:30,因此 创建与编辑可能保存下与预览不同的时刻。表单现在会校验“本 地→UTC→本地”的往返一致性,不一致时给出本地化的内联提示并把输 入标记为非法,在校正之前发出空的 schedule spec,并保持 Create (以及编辑保存)禁用。(#5348)

  • 沙箱: 在异步重绑期间排空(drain)上一个沙箱的释放。异步重绑 路径此前通过裸的 await asyncio.to_thread(provider.release, ...) 释放上一个沙箱 客户端;取消调用方只会取消等待者,让每线程的生命周期串行器在旧释 放仍在运行时就继续往下放行,于是后续同一线程的生命周期转换可能与 旧释放重叠。三条异步的“释放上一个”路径现在改经具备取消安全性的 run_sync_lifecycle_operation() 辅助函数,它会持有串行器直至旧 释放完成,然后再传播原始的 CancelledError。(#5498)

  • 子智能体: 关闭被停止的 run 留在委派台账中处于进行中的条目。 点击 Stop 时,task 工具会取消子智能体并重新抛出取消异常,因此 不会写入 ToolMessage,台账中的委派也就一直停留在 in_progress——此后每次模型调用都会被告知该工作“已委派;请勿 再次委派”,并去等待一个永远不会到来的结果。当 run 以一条新的用 户消息启动时,DurableContextMiddleware 现在会把这类条目标记为 cancelled(“已取消的尝试;可在调整计划后重试”)。恢复的 run、共享同一 run id 的目标延续以及已有结果的条目均不受影响。 (#5507)

  • 前端: 让澄清(clarification)文本不再落入执行步骤面板。伴随 ask_clarification 调用的回答文本此前渲染在带边框的执行面板内 部,提交一条隐藏回复后,先前已完成的回答气泡又会在续流期间被挪回 面板内。澄清结果现在充当已完成 run 的边界,因此更早的回答在续流 与重连期间都能留在会话中;伴随文本则与原始消息一起在面板外渲染一 次,消息与工具结果的关联以及用量统计均保持不变。(#5508)

  • projects: 在 Gateway 关机被取消时排空(drain)回收站对账的 文件工作线程。取消启动期的回收站保留清扫只会取消 asyncio 等待 者,其 executor 工作线程仍会继续遍历/stat/unlink 文件,因此 _shutdown_startup_trash_sweep() 可能在游离的工作线程仍在改动 projects 目录树时就完成 Gateway 的拆除。两个对账用的 run_file_io() 操作现在都经由具备取消安全性的 await_drained() 辅助函数排空:已启动的工作线程先到达终态,然后取消才传播,后续阶 段不再启动。钩子超时仍是宽限完成的预算,而非取消后的硬上限。 (#5511)

  • MCP: 裸文件名重写改为按字面插入,而不是当作正则替换模板。 _rewrite_unique_bare_filenames 此前把关联得到的 /mnt/user-data/... 路径作为模板传给 Pattern.subn,因此文件名 中的字面反斜杠(在 POSIX 上向 stdio MCP 服务器传一个 Windows 风 格路径,会创建一个字面命名为 screenshots\q3.png 的文件)要么使 工具调用转换抛出 re.error: bad escape,在服务器已经写完文件之 后让整个调用失败;要么——对 re 接受的转义——把转义字节(\r 变成 CR)静默替换进返回的路径,随其进入对话记录与 checkpoint。替 换现在改经可调用对象,路径按原样插入。(#5522)

  • nginx: 允许绑定模型的 /api/ 与 /api/skills 请求存活超过 60 秒。兜底的 location /api/(例如 POST /api/runs/wait—— nginx 掐断上游连接时还会连带取消该 run——以及 POST /api/input-polish)与 location /api/skills(.skill 安 装要用串行 LLM 调用扫描每个归档文件)此前仍沿用 nginx 60 秒的 proxy_read_timeout 默认值并以 504 失败。这两个 location 现在 都设置 proxy_read_timeout 600s,与绑定模型的 location 一致;适 用于 Docker、make dev 与 Helm 的 nginx 配置。更具体的 location 不受影响。(#5524)

  • 子智能体: 在调用方被取消时排空(drain)其自有的 batch-service 停止过程。SubagentRuntime.stop() 此前在等待 SubagentBatchService.stop() 之前就清掉了自己的 batch-submitter 标志;一次被打断的等待会让 runtime 报告没有 submitter——后续的 stop() 调用立即返回——而其自有的服务仍在停止途中,因此 runtime 自有的批量工作可能比 runtime 的生命周期所有权活得更久。 停止现在在一个强持有的任务中运行,该任务被 shield 并在反复取消下 排空;调用方第一个 CancelledError 只在自有停止到达终态之后才传 播。(#5525)

  • 记忆: 在 lifespan 被取消时排空(drain)Gateway 的记忆关机工 作线程。flush 与后端关闭此前是两个独立的 asyncio.to_thread() 等待,因此取消 lifespan 任务只会取消等待者:manager.close() 可 能在 flush 工作线程仍在运行时启动——在一条进行中的 flush 之下关 闭后端连接——而反复取消则可能让关闭工作线程彻底脱离。Manager 解 析、shutdown_flush() 与 close() 现在组合为单一的自有关机操 作,经具备取消安全性的 await_drained() 辅助函数排空;调用方第 一次取消只在已启动的工作到达终态之后才传播。(#5531)

  • 沙箱: 把恰好填满的沙箱搜索结果报告为完整。AIO、E2B、 OpenSandbox、Tenki 与 BoxLite provider 中的 glob 与 grep 此 前把“收集到 max_results 个匹配”当作 truncated=True,于是一 棵恰好含有这个数量合格匹配的树会让智能体误以为搜索不完整,从而徒 劳地重跑或收窄搜索。过滤后匹配的截断判断现在会多看一个匹配再下结 论——真正被丢弃的匹配仍报告为截断——匹配本身与远端原始输出的上 限均不变。(#5534)

  • threads: 线程删除时安全清理持久化记录。 DELETE /api/threads/{id} 此前会移除线程目录、checkpoint、浏览 器会话与 meta 行,却把 run_events、历史的 runs 行和 feedback 留在原地——删除之后消息流仍可读(owner 检查在 meta 行缺失时通过),重建同一线程 id 会继承被删线程的消息。删除现在还 会移除 runs、run events 与 feedback,按 owner 限定并在既有的删除 预留内尽力而为;run 清理只删除 operation_kind == "run" 的行, 因此进行中的删除预留得以幸存。持久化事件存储采用显式的每线程变更 围栏(fence),并发获准的写入者再也无法在删除返回后为已删除的线 程提交行。(#5535)

  • threads: 重新打开聊天后重新加入进行中的 run。SSE 重连路径此 前只知道存于当前标签页 sessionStorage 中的 run id,因此重新打 开浏览器或新开标签页会丢失该指针,而服务端的 run 仍在继续。现在 打开一个没有重连指针的线程时会发现最新的 pending 或 running run,在加入之前先持久化指针(使停止/取消仍指向正确的 run),并重 新加入其可恢复的流;恢复失败会以有界的 1s/2s 延迟重试两次;指针 一致时会抑制重复加入。(#5536)

  • 渠道: 在启动被取消时回滚已部分启动的渠道服务。 start_channel_service() 此前在等待 ChannelService.start() 之 前就发布了全局单例,因此在部分获取资源后被取消会直接退出而不停止 服务,留下一个启动从未完成的单例。启动失败/取消现在会回滚部分启 动的服务,并用 await_drained() 在反复取消下排空回滚;单例只在 清理成功后才清除,清理失败则保留它,供后续关机重试。(#5537)

  • Docker: 新增 make prod-logs 作为生产栈的日志入口,并修复 make docker-logs 在生产环境启动后静默退出的问题。make up 运行的是 compose 项目 deer-flow,而 make docker-logs 跟踪的是开发项目 deer-flow-dev,此时后者没有任何容器, 因此命令什么也不打印。scripts/docker.sh logs --prod 现在用同一个 compose 文件、--env-file ../.env 以及 deploy.sh 导出的插值默认值来跟踪生产栈(缺少这些默认值时, 在没有 .env 的检出上 compose 无法解析生产环境的 volume 定义);开发栈日志在没有任何容器时会打印一条指向 make prod-logs 的提示。(#5538)

  • 前端: 推理内容抽取现在会保留围栏、缩进与行内代码中的字面 <think> 标签,包括流式输出期间尚未写完的代码。 此前解析器只保护紧跟在反引号之后的起始标签,因此一段讲解 <think> 标签的代码示例会被移入推理折叠区—— 破坏渲染出的回答与复制出的文本——而一个字面的未闭合起始标签会吞掉 其后的说明文字。 字面代码之后真正的已闭合或流式推理仍会被正常抽取, 且真实推理内部的 Markdown 分隔符不再妨碍定位其闭合标签, 因此推理中未闭合的围栏无法再藏住回答。(#5540)

  • 上传: 构建文档摘要时会处理上传 Markdown 中的 UTF-8 BOM。 行首 BOM 会让大纲抽取器无法识别第一个标题或首个代码围栏—— 在围栏场景下,代码注释会被当成文档标题、 闭合围栏会被误认为起始围栏,从而遮住真实章节——而仅由 BOM 组成的首行还会占用一个兜底预览槽位。大纲与预览现在改用 utf-8-sig 读取,它会消费可选的 BOM,同时保留普通 UTF-8 内容、内嵌的 U+FEFF 字符、物理行号以及原始上传字节。(#5541)

  • 网关: 准备重新生成(regenerate)线程时, 会保留已确认的澄清卡片答案。重新生成端点此前只挑选可见的 human 消息,而澄清卡片答案是以带 human_input_response 元数据的隐藏 HumanMessage 对象存储的,因此它可能跳过已确认的答案、 改为重放更早的可见提示。现在一个专用于重新生成的谓词会接受 human_input_response 结构合法的隐藏消息, 并保留其隐藏与请求关联元数据;摘要、目标控制消息、 格式非法的隐藏响应以及无关的内部消息仍会被拒绝。(#5544)

  • 沙箱: 把 remote_list_dir_command 与 remote_search_command 探测脚本包进子 shell,使其末尾的 exit 无法杀掉 AIO 沙箱隐式的常驻 shell。裸 exit 会终止会话 shell,服务器对该 exec_command 请求的响应因此永远无法完成, 客户端无限挂起——这使得每一次 list_dir/grep/glob 调用都成为确定性的卡死点,并让发出并行 [ls, bash] 工具调用的整个 run 陷入死锁。输出标记与退出码仍会从子 shell 正常传递;发生变化的只有探测脚本。(#5546)

  • 上传: 删除的是请求的上传条目, 而非符号链接解析出的目标文件。delete_file_safe 此前会先解析请求路径再删除解析结果,因此放置在上传名上的符号链接 (沙箱将上传目录映射为可写)会让 DELETE /api/threads/{id}/uploads/{filename} 删掉目标文件及其配套 .md、把悬空链接留在原地, 并为错误的文件返回 200。现在会检查并删除请求的条目本身: 符号链接与其他非普通文件条目一样返回 404; 指向上传目录之外的链接仍返回 400;配套 .md 的名字取自请求的文件。普通文件的删除行为不变。(#5547)

  • 扩展: 让扩展服务的关闭在宿主取消之下仍能完整排空(drain) 到底。Gateway 用 AsyncExitStack 注册扩展服务清理,却直接 await stop_services(),因此当扩展的 stop() 阻塞期间落入取消时,清理回调会被中止,栈展开在扩展持有的资源仍然 存活时继续进行,破坏了该栈本应提供的所有权顺序。 关闭现在经由可取消安全的 await_drained() 辅助函数执行, 后续运行时拆卸会等扩展服务在每服务超时预算内到达终态。(#5549)

  • 记忆: 按键名报告非法的 memory.backend_config 取值, 而不是抛出不具名的 traceback。Honcho 后端通过仅判断假值的 or {} 回退读取 failure_policy、workspace_overrides 与 user_peer_overrides,因此最自然的 YAML 写法—— 不加引号的字符串形式 failure_policy: fail_closed—— 会以真值非映射的形态留存,随后以 AttributeError: 'str' object has no attribute 'get' 崩溃, 既不指明键名也不提 backend_config。现在, 非映射取值会作为配置错误被拒绝并指明键名;假值仍表示未设置。 mem0 与 OpenViking 后端也为数值开关(top_k、timeout_seconds 等)加上同样的键名保护,取代会漏出不具名 TypeError 的裸 int()/float() 转换。(#5555)

  • MCP: 按线程化身(incarnation)限定 MCP 会话与持久任务(durable task)的访问范围。删除后重建同一线程 ID 会复用上一个生命周期的持久 MCP 会话作用域, 而且由于这些操作只按用户与 thread ID 限定作用域, 替身线程可以列出、读取并请求取消旧生命周期创建的持久任务。 服务端持有的线程化身现在会传入智能体运行时:非遗留 MCP 会话获得带版本的 (user, thread, incarnation) 作用域, 持久任务的列表、详情与取消绑定到调用方捕获的化身与当前化身, 并且在远端提交与本地持久化之间线程被删除或重建时, 提交会被原子地拒绝。显式的遗留 NULL 化身保留激活前的作用域, worker 轮询与取消仍通过创建会话正常工作, 化身字段也不会出现在公开的任务响应中。(#5556)

  • 子智能体: 远程子智能体验收探测现在能识别空的普通文件。GNU stat -c %F 会把零字节文件报告为 regular empty file, 而远程大小与可读性探测此前只接受 regular file, 因此空工件会被当作 NONREGULAR 拒绝,即使文件存在且可读, exists、non-empty 与 file_written 也都停留在 UNVERIFIED。现在两种标签都被接受:存在且可读的空文件可以满足 exists 与 file_written,non-empty 则给出确定性的否定结果。 符号链接、FIFO 与路径包含检查不变。(#5559)

  • 前端: web_fetch 工具步骤标题现在取自抓取所得 Markdown 首个非空行。extractTitleFromMarkdown() 此前只接受开头字符正是 # 的文档,因此第一个标题之前出现的空行或最多三个空格的缩进—— CommonMark 允许、crawl4ai 后端也会原样透传—— 会让会话记录失去步骤标题并静默回退到原始 URL。 缩进的代码块仍不计为标题;空标题则不产生标题,而不是留下字面的 #。(#5560)

  • 沙箱: 阻止 E2B 对账(reconciliation)复活预热池中的沙箱。 每一轮对账都会对停放在预热池中的沙箱发起 Sandbox.connect()——SDK 会把它规范化为一次 300 秒的超时更新, 把远端过期时间不断向前推——并且第一轮就会把预热沙箱收回活动集合, 使其永远不再被释放;空闲但计费的 VM 因此无限期存活并占用容量槽位。对账现在把本地跟踪的沙箱(无论活动 还是预热)视为权威,绝不用 connect() 探测它们, 通过缓存的客户端刷新活动沙箱的 TTL,并清扫停放超过 sandbox.idle_timeout 的预热条目,释放其所有权租约、 挂载结果与容量跟踪。预热沙箱现在会真正按配置的 sandbox.idle_timeout 过期。(#5562)

  • utils: 对无内容的消息返回空文本,而不是字面字符串 "None"。message_content_to_text(None) 此前会把参数字符串化,因此无内容的子智能体最终消息绕过了 No response generated 哨兵,无内容的结构化 LLM 错误回退会隐藏其 error_detail, 任务连续性归档也未能跳过空文本。只有 None 内容被视为空—— 字面字符串 "None"、0、False 与列表块转换均不变。(#5563)

  • doctor: make doctor 现在会校验基于 CLI 的模型凭据内容。 此前只要凭据路径存在,检查就会把 Codex 与 Claude 认证报告为健康,因此空文件、已登出、格式非法或已过期的文件, 都会得到绿色对勾,而模型加载器实际拿不到可用令牌。Codex auth.json 现在会针对全部三种受支持的令牌布局进行校验,Claude .credentials.json 则通过 claudeAiOauth.accessToken 校验并计入运行时的一分钟有效期缓冲;文件缺失、是目录、非对象 JSON、空令牌与无效的有效期取值都会按提供方分别报告失败, 并附上既有的修复提示。该检查仍只用标准库、离线且经过脱敏; 环境变量与文件描述符来源仍是不消费的存在性检查。(#5567)

  • 中间件: 让触到长度上限的回合干净收尾,而不是让 TodoMiddleware 重新唤起模型。当模型在发出 write_file 调用的同时达到单次响应的输出上限(finish_reason=length), 被抑制的调用仍会触发 todo 完成提醒跳转(jump_to=model), 把同样超限的调用再次发向同一个上限——最多产生三次徒劳、 携带垃圾碎片的响应,而非一条干净的截断提示。现在当 model_length_termination 标记存在时 TodoMiddleware 会跳过提醒跳转;即使有部分文本留存也会追加长度提示(顺带修复了 append_visible_text 会静默丢弃字符串内容的潜在缺陷);并且 write_file 的模型可见描述会标注配置的 max_tokens 输出预算,以免模型把 80 KB 流式天花板当成单次写入上限。(#5569)

  • RAGFlow: 文档选择现在按每批最多 100 个 ID 分批校验。在 1000 篇文档的 scope 上限内,单个数据集选超过 100 篇本就合法, 但此前的校验把全部 ID 放进一个请求发送,提供方会拒绝,导致 knowledge_search 在检索开始前就失败。 现在选择按提供方可接受的批次大小校验,保持输入顺序, 并对缺失或不可搜索的文档保留 fail-closed 行为, 同时在多个数据集之间共享同一个并发预算。(#5572)

  • 持久化: PostgreSQL 引导期 advisory 解锁现在能跨取消完成。 _postgres_lock() 的 finally 中该解锁此前是直接 await 的, 因此当 pg_advisory_unlock 仍在执行时到达的启动/lifespan 取消可能中止释放,并把仍持有引导互斥锁的连接归还连接池。 现在释放通过取消安全的 await_drained() 辅助函数, 在获取锁的同一连接上完成。(#5573)

  • 持久化: Gateway 关停时引擎处置现在能跨取消完成。 close_engine() 此前直接 await AsyncEngine.dispose(), 因此处置被阻塞期间的取消会让关停提前返回,而进程级全局的 engine 与 session factory 仍指向一个被部分处置的资源。 现在处置通过取消安全的 await_drained() 辅助函数完成, 全局引用在完成前保持发布,过期的清理也无法再抹掉替换后的新引擎。 (#5576)

  • 渠道: 发给 Telegram 机器人的照片和文件现在能下载并送达智能体。两个 bug 叠加: 下载在管理器事件循环上新建连接,以笼统的 NetworkError 失败; 且落盘的文件被写成 0600 root:root,非 root 的沙箱用户读取时报 Permission denied。下载现在经由绑定到该事件循环的下载 bot, 入站文件获得沙箱可读的权限,失败时会记录底层原因并对含 token 的 Bot API URL 打码。(#5581)

  • 模型: 顶层不是 JSON object 的 Codex 认证文件现在按“此来源无凭据”处理。~/.codex/auth.json(或 $CODEX_AUTH_PATH)中的数组、字符串或数字此前会在 CodexChatModel 构造时抛出 AttributeError,抢在文档记载的 Codex CLI credential not found 错误之前; 加载器现在会记录日志并继续回退。(#5584)

  • 记忆: 显式的 confidence: 0.0 不再被当作 0.5 默认值参与评分。两处读取点用 or 0.5 兜底该字段, 无法区分“显式为零”与“未设置”,因此零置信度的事实被默认权重抬高, 排名也因写入路径所用的索引不同而不一致。现在无论走 FTS5 索引还是子串回退,零都严格排在 0.5 之下;缺失或 null 的 confidence 仍默认为 0.5。(#5586)

  • 技能: 现在能加载带 BOM 保存为 UTF-8 的 SKILL.md。 front-matter 锚点此前会拒绝记事本和 PowerShell 的 Set-Content -Encoding UTF8 写入的前导 U+FEFF,导致 Windows 上编写的技能无声地从目录中消失、没有任何告警。 该标记现在由共享的编译锚点消费,不会进入技能元数据、 渲染后的指令或 skill-context 描述,且该模式在加载器、 校验器与中间件中的三份副本也不会再漂移。(#5588)

  • 模型: 跳过 expiresAt 非数字的 Claude Code 凭据来源。 字符串、null、列表或对象值会让加载器抛出 TypeError 并中断查找循环,因此 $CLAUDE_CODE_CREDENTIALS_PATH 处的一个损坏文件会让所有 ClaudeChatModel 永远到不了 ~/.claude/.credentials.json; 现在该来源会像同类守卫一样被跳过并记录 debug 日志。(#5591)

  • 技能: 显式为空的 allowed-tools 现在渲染为无工具。 技能元数据渲染器此前对该字段做真值判断,因此声明 allowed-tools: [] 的技能会展示 Allowed tools: (all), 而工具策略却剥掉所有业务工具——模型在规划时依据的正是这些会被拒绝 的工具。现在空声明渲染为 (none); 省略或非空的声明仍按原样渲染。(#5593)

  • 网关: 将 Windows 的 image/svg MIME 别名归类为活动内容。 在 Windows 上,MIME 数据库把 .svg 映射为 image/svg 而非标准的 image/svg+xml, 而共享的活动内容分类器只识别标准类型,因此 SVG artifact、 .skill 归档成员和项目文档可能被内联提供而不是强制下载。 现在所有主机均视该别名为活动内容。(#5594)

  • 模型: 在 Codex 的 account_id 进入请求 header 前容忍 null。or 链式兜底让 JSON null 以 None 形式穿过加载器, 导致 CodexChatModel 构造在 self._account_id[:8] 处崩溃; 数字型 account_id 则稍后因 httpx 拒绝非字符串的 ChatGPT-Account-ID header 而失败。null 现在回退到未知账户值 "",其他非字符串会被字符串化。(#5601)

  • LLM: 熔断器探针结算现在按代次与归属加围栏。 LLMErrorHandlingMiddleware 此前无条件结算成功与失败, 因此一个在较旧闭合代次下准入的请求, 可能在较新的恢复探针被取走之后才完成,进而在故障期间闭合熔断器、 重新打开它并丢弃一个健康探针,或释放另一个调用的探针。 现在每次准入都会捕获熔断器代次,且成功、 失败或释放只有在代次匹配——半开状态下还要求探针 token 精确匹配——时才会改动熔断器。(#5602)

  • 记忆: 拒绝永远无法解析的 Honcho base_url。该字段此前 读取时没有任何校验,因此内部主机的自然省略 scheme 写法 (base_url: honcho:8000)在启动时被接受——httpx 把主机名 解析成了 scheme——随后记忆的每次操作都以不支持的协议错误 失败,或者在 Gateway 一路绿灯的情况下静默返回空结果; api_key 明文 HTTP 防护同样被跳过,因为它只匹配字面的 http:// 前缀。现在格式错误的值会在 Gateway 启动时快速失败, 与文档声明的行为一致;而今天能够解析的每一个 URL 都继续照常 工作。(#5607)

  • 子智能体: 显式为 0 的批量上限现在按名字上报, 而不再替换为默认值。SubagentBatchService.submit() 此前 用 or 解析 max_live_items / max_running_items, 调用方传入的 0 会被当作"未提供"并静默换成配置的默认值 ——一个要求零并发的 batch_task 实际上每次同时跑三个—— 而配上较小的 max_live_items 时,请求还会因一个它并未 违反的上限被拒绝。两处解析现在改为与 None 比较,显式的 0 会到达范围守卫并按名字被拒绝;键缺省仍然表示 "使用配置的默认值"。(#5609)

  • 沙箱: 远程 list_dir 输出现在会丢弃被忽略的目录。 本地沙箱会跳过 node_modules、.git 等忽略模式, 但同一调用在远程沙箱中会把它们下面的每一条路径都列出来, 直到 500 行上限——尽管远程的 grep 与 glob 早已 把这些路径视为不可见。共享的远程解析器现在会丢弃 包含被忽略目录名的条目,并像本地遍历那样相对列举根进行匹配: 显式列举一个被忽略的目录仍然可行,完全被忽略的目录则返回 空而不是 FileNotFoundError。(#5612)

  • 中间件: 上下文压缩时会丢弃过期的 todo 提醒。 todo 提醒注入之后,压缩可能把旧的 task 状态摘要进去, 或把过期提醒留在保留的消息尾部,这也会阻止 TodoMiddleware 从当前 todos 状态重建任务上下文。 自动与手动压缩现在都会在选择摘要与保留分区之前 排除 todo_reminder 快照,当前 todo 状态得以保留: 当不再有可见的 write_todos 调用时,中间件会在下一次 模型调用前重新生成提醒。(#5614)

  • 沙箱: Tenki 的 read_file 现在支持 start_line/end_line。Tenki provider 此前只接受 path, 因此区间读取——智能体继续被截断读取的常规方式——会抛出 TypeError: ... unexpected keyword argument 'start_line', 并被渲染成笼统的读取错误。它现在与同类 provider 一样进行切片, 负数边界的钳制方式与本地沙箱一致。(#5616)

  • 持久化: PostgreSQL schema 引导连接关闭现在能跨取消排空完成。 ensure_postgres_schema_async() 此前用一个裸 await 关闭 专用的 psycopg 连接,因此 lifespan 拆除期间到来的取消 可以打断关闭并直接返回,而连接仍然存活。 关闭现在经过取消安全的 await_drained() helper, 它会把调用方的取消推迟到持有的连接完成关闭之后。(#5617)

  • 运行时: 运行时 provider 的拆除现在能跨取消排空。 checkpoint-cache 与 stream-bridge 的 provider 上下文管理器 持有自己的后端,并在上下文退出时直接 await aclose()/close(),因此拆除中途到来的宿主取消 可以打断关闭并返回,而 memory 或 Redis 后端仍然存活。 memory 与 Redis 后端的这两处关闭现在都经过取消安全的 await_drained() helper;取消只在持有关闭完成后传播。(#5622)

  • Helm: 启用共享 home PVC 时,provisioner 的状态根目录 现在与 Gateway 对齐。chart 此前只设置 Gateway 的 DEER_FLOW_HOST_BASE_DIR,provisioner 仍停留在 运行时默认的 /.deer-flow,因此 Gateway 合法的技能投影 路径落在 provisioner 允许的 base 之外,在 USERDATA_PVC_NAME 模式下可能在创建沙箱前就被拒绝。 provisioner 现在收到相同的逻辑根目录,投影因此映射到 PVC 上的 deer-flow/<suffix>;persistence.home.enabled=false 时 该变量仍不设置。(#5625)

  • client: 内嵌客户端现在会尊重智能体的 MCP 插件选择。以 mcp_plugins: [installation-A] 保存的智能体通过 DeerFlowClient 仍会收到所有已启用 MCP 服务器的工具, 空的选择同样被忽略,委派任务也不继承智能体的选择。 客户端现在在加载工具时应用保存的选择,并在每次 run 时 将其传给委派任务(包括缓存图 run); 选择以无序集合的形式加入图缓存键,reset_agent() 之后 保存的配置变更仍然生效。(#5630)

  • 持久化: Alembic 迁移 worker 现在能跨取消排空。 bootstrap_schema() 在 stamp/upgrade 于 asyncio.to_thread() 中运行期间持有 SQLite 进程内 互斥锁或 PostgreSQL advisory lock,但这些 await 都容易 被取消打断:取消宿主任务可能在工作线程仍在变更 schema 状态时从引导临界区返回,让第二次引导与 进行中的迁移重叠。现在每个 Alembic worker await 都经过取消安全的 await_drained() helper;worker 完成后取消才传播。(#5631)

  • 沙箱: AIO 沙箱现在会强制执行命令超时。 AioSandbox 此前忽略调用方提供的超时, sandbox.bash_command_timeout 从未生效—— 长时间运行的命令会一路跑完,卡住的持久 shell 请求 也会一直占着沙箱级串行化锁,阻塞同一客户端的后续操作。 受支持的 AIO 镜像上的命令现在携带服务端 hard_timeout (被冻结的旧 :latest 镜像只获得有界的主机端等待), 结果不明确时绝不重放,sandbox.bash_command_timeout 现在成为 provider 默认值。(#5634)

  • 渠道: Slack Socket Mode 的连接失败现在会被记录, 而不是被丢弃。SlackChannel.start() 通过 run_in_executor() SocketModeClient.connect() 并丢弃返回的 future, 因此被拒的 app_token(或连不上的 Slack)让机器人无声死亡—— 日志显示 Slack channel started,异常只在垃圾回收时以 asyncio 的 "Future exception was never retrieved" 形式浮出水面。 连接失败现在连同 traceback 一起记录,与 Telegram、Discord 渠道已有的做法一致。(#5640)

  • MCP: stdio MCP 服务器配置的 cwd 现在会被尊重。 McpServerConfig 以 extra 字段接受了该设置,但 build_server_params() 在任何一条启动路径看到它之前 就把它丢掉了——因此配置了工作目录的服务器在脚本路径 为相对路径时可能发现失败,或用绝对路径发现成功、 随后在池化工具调用中的相对文件读取上失败。 现在该目录在非空时(包括环境变量引用)会转发给 stdio 连接, HTTP/SSE 则省略;省略、null 或空值保持默认行为。(#5643)

  • 技能: SkillScan 现在从 AST 读取 Python 密钥赋值。 secret-env-assignment 规则此前用 name[:=]value 正则 扫过所有文本文件,这会误读 Python:带类型的可选参数默认值 (token: Optional[str] = None)和 api_key = os.getenv("...")——该规则自己文档写明的整改 方式——都被报告为硬编码凭据,使未改动的内置技能也过不了 评审门禁。Python 源码现在从其 AST 分析,只报告真正的字面量值; 非 Python 文件的覆盖不变,并有一个测试钉住 内置技能保持干净。(#5648)

  • 持久化: 数据库自动建库维护引擎的释放现在能跨取消排空。 _auto_create_postgres_db() 此前用一个裸 await maint_engine.dispose() 释放一次性引擎, 因此首次启动缓慢时到来的取消可以打断 dispose, 让一个针对 postgres 的活跃服务器会话存活整个进程生命周期—— 这个泄漏无法自愈,因为下次启动走的是数据库不缺失的路径, 永远不会重试释放。dispose 现在经过取消安全的 await_drained() helper;首次启动成功时行为不变。(#5649)

  • 技能: 每用户技能安装的目录创建不再占用事件循环。 UserScopedSkillStorage.ainstall_skill_from_archive 此前 在任何实际工作派发之前就用阻塞的 os.mkdir 在事件循环上创建每用户自定义目录,因此 POST /api/skills/install 会卡住 Gateway 事件循环; 既有的 blocking-IO 锚点没有覆盖到它, 因为它只覆盖 host 作用域的基类。mkdir 现在与周围的解压和校验一样 在工作线程上运行,锚点也覆盖了用户作用域路径。 安装语义与磁盘上的布局不变。(#5650)

  • 沙箱: 在 e2b、OpenSandbox 与 BoxLite provider 中像 LocalSandbox 那样对 read_file 的负数区间边界做钳制。 此前负值会落入 Python 负索引切片:start_line=-1 会静默返回文件末尾几行,end_line=-1 会返回截断的区间, 而不是从第一行开始读取、返回空区间。工具层早已拒绝负数边界; 直接使用 Sandbox 的调用方会踩中这一缺口。(#5655)

  • 技能: 让内置 Vercel 部署技能的包目录与其声明名称一致, 并把部署命令指向真实挂载路径 /mnt/skills/public/vercel-deploy/scripts/deploy.sh。 该技能此前存放在 vercel-deploy-claimable,却以 /vercel-deploy 激活,且其说明引用了 DeerFlow 从不挂载的脚本路径,因此一次部署请求可能选中正确的技能, 却拿到一条指向不存在文件的命令。(#5656)

  • 前端: 技能导出面板对显式为空的导出要求不再显示空白值。 导出对话框此前用 join(", ") 格式化 allowed-tools 与 required-secrets,显式空声明会渲染成空字符串, 与省略声明无法区分——尽管二者策略语义不同;现在未声明显示为 "Not declared",显式为空显示为本地化的 "None"。(#5659)

  • 沙箱: 为 AIO list_dir 增加专属的目录级截止时间。 目录列举此前在沙箱级锁下运行,仅有 600 秒无变化超时并继承 SDK 传输预算,因此卡住的 relay 或僵死的 find 可能让同一沙箱上的后续操作阻塞数分钟。在受支持的镜像上,远端 find 现在带 60 秒服务端 hard_timeout,外面再套一个 65 秒不重试的宿主级信封:hard_timeout 抛出 TimeoutError 并保持 shell 代际可复用;结果不明时抛出 OSError 并将其隔离。 未新增配置。(#5662)

  • 运行时: 在记忆 run 存储中, 一次原子线程操作现在共享同一个变更位置。 create_thread_operation_atomic 此前按行推进变更时钟, 一次中断并替换会把被中断行与替换行落在两个 change_seq 值上, 按 (change_seq, run_id) 翻页的 list_changed 读取方可能只看到原子集合的一半; 现在每个被认领的行和新行都携带同一位置,与 SQL 存储一致。 无持久化数据库的默认部署选择的就是该存储。(#5663)

  • 运行时: 同步 checkpoint 变更现在跨取消排空。goal 写入与回滚卸载会通过 asyncio.to_thread() 把同步 checkpointer 移入线程执行,此前取消调用方只会停止等待,而 put、 put_writes 或 delete_thread 工作线程仍在运行—— 调用方已观察到取消,变更却仍可能在之后提交。 这些回退路径现在经由取消安全的 await_drained() helper 排空; 读回退 get_tuple 仍可直接取消。(#5664)

  • 智能体: 智能体装配指纹现在会区分技能未声明与显式为空的 allowed-tools。此前两种声明状态都哈希为空列表,把技能从旧的 allow-all 切换为无业务工具策略时, 运行时工具可用性变了而指纹不变;现在未声明的 allowed_tools 按 None 哈希,显式 allowlist 仍保持为排序列表。Frontmatter 解析与运行时工具策略均未改变。(#5669)

  • 上传: 删除文档时不再连带删除其转换生成的 Markdown 伴生文件。delete_file_safe 此前会重新计算 stem.md, 因此共享词干的两个文档(a.docx、a.pdf) 在删除其中一个时会把另一个的转换文本一并删除—— 或者毁掉用户自己放在无关 report.pdf 旁的 report.md。 DELETE /api/threads/{id}/uploads/{filename} 现在只删除被请求的文件;孤立的伴生文件仍保留在列表中, 可单独删除。API 形态与配置均无变化。(#5673)

  • 沙箱: 远端 ls 列举现在先剪除被忽略的条目, 再应用列举上限。此前的 500 条上限先于忽略路径的剔除生效, 依赖树可以填满整个窗口并把旁边的普通文件挤掉, 返回列表有时只剩根目录;现在被忽略的后代在共享 find 命令内、 head 之前剪除,深度与输出限制保持不变。(#5676)

  • 持久化: database.postgres_schema 现在也会作用于 DB 后端自定义智能体与托管子智能体所用的同步 SQLAlchemy 存储。异步 ORM 与 LangGraph 存储早已遵循所配置的 schema,但同步 agent-store 查询会回退到 public,找不到应用表;连接 URL 现在在保留既有 libpq 选项的同时追加 search_path。(#5678)

  • 日志: 所有非绝对路径的 Redirecting 槽位现在折叠为 /<redacted>。#5225 的 URL 脱敏流程此前对不以 / 开头的槽位原样保留,因此 download?sign=… 这类不带斜杠的相对 Location 引用,以及仅含 query、仅含 fragment、network-path 与 data: 的目标,会在 DEBUG 重定向日志中原样暴露签名 query; 现在只有位置 0 处带 scheme 的目标才交给通用绝对 URL 流程处理, 非 URL 的 -> /path 沙箱挂载箭头仍原样通过。(#5680)

  • 渠道: 钉钉入站文件被跳过时现在会记录日志, 而不是静默丢弃附件。此前返回空内容的下载会不带任何日志地消失, 与消息本就无附件无法区分;现在跳过时会输出一条中性的 guard 日志并点名文件,与微信渠道的形态一致,准确原因仍由下载 helper 内部记录。行为无变化。(#5683)

  • 网关: 就绪探针的连接关闭现在跨取消排空。/health/ready 此前用裸的 await connection.close() 关闭 SQLite 与 PostgreSQL 探针连接,teardown 期间的第二次取消可能将其打断, 让探针在连接仍存活时返回,使数据库清理脱离请求生命周期; 现在关闭经由取消安全的 await_drained() helper 排空。 响应形态不变。(#5684)

  • 日志: 含空格的 Redirecting 槽位现在折叠为 /<redacted>。 Round 15 此前对带 scheme 的槽位原样保留、交给通用绝对 URL 流程, 但该流程的模式在空白处截断,因此形如 https://mirror.example/other page?sig=… 的槽位只会重写空格前的文本,把其余部分泄露进 DEBUG 日志; 现在只有当槽位被一个不含空白的绝对 URL 完整填满时才原样保留, 普通的绝对重定向对仍保留其 host。(#5687)

  • 技能: SkillScan 的 secret-env-assignment 规则重新报告绑定 在普通语句赋值之外的密钥。#5648 引入的 AST 遍历此前只识别裸常量 赋值,因此关键字实参(connect(api_key="..."))、参数默认值、海 象运算符赋值、字面量拼接以及全常量 f-string 都能让硬编码凭据躲过 HIGH 级别闸门。常量折叠改为迭代式:递归实现可能抛出 RecursionError,而扫描器会把它记为按文件错误,丢弃该文件的全部 发现。#5648 带来的精确性(注解、os.getenv 调用)保持不变。 (#5691)

  • 社区工具: Browserless web_fetch 现在像 web_capture 一样 读取文档中声明的等待设置。wait_for_timeout_ms 改用宽容的 _as_int 辅助函数解析,因此 2s 这类取值会回退到默认值,而不是 让调用以 invalid literal for int() 失败; wait_for_selector_timeout_ms 则从工具配置读取,而非硬编码的 5000(默认值不变,既有配置行为一致)。两个键现在均已在 config.example.yaml 的 web_fetch 条目中补充文档。(#5702)

  • 社区工具: SearXNG 超过一页的 max_results 现在会被遵守。 SearXNG API 没有 limit 参数,客户端此前发送了一个被忽略的 limit,只请求第 1 页并从已截断的列表中切片——max_results: 20 会静默只返回一页(默认 10 条)。search 现在逐页遍历 pageno 并累积结果,直到达到上限、某页返回为空或某页没有新增内容(按 URL 去重)为止,最多 5 页;无效的 limit 参数不再发送。默认的 max_results 仍只需一次请求。(#5705)

  • 沙箱: 身份不明确的 AIO 会话创建现在既不能重放也不能执行。 shell 与 bash 创建平面通过 pending/owned/absent/tombstoned 状态 机跟踪每个尝试过的 id:超时、传输错误、5xx 响应与格式异常的 HTTP-200 响应都会把该 id 标记为 tombstone——不在其上执行、不重放 创建、仅做有边界的尽力清理且从不清除 tombstone——同时该平面上的新 创建会被阻断。脏沙箱通过既有的 teardown 路径释放,而不是回到预热 池;shell 的模糊不影响 bash 创建。(#5711)

  • 模型: 通过 Settings 添加的 DeepSeek 官方模型现在使用原生 PatchedChatDeepSeek 适配器。托管 profile 及其连接探测此前总是 构建 ChatOpenAI,导致添加 deepseek-flash 时连接测试以 HTTP 400 Thinking mode does not support this tool_choice 失 败,流式工具调用会丢弃 reasoning_content,并以 max_completion_tokens 代替 DeepSeek 原生的 max_tokens。思考 模式仅在受限的强制工具探测中被禁用——正常对话设置不受影响——且匹配 仅限 api.deepseek.com,第三方兼容端点保持原行为。既有已保存的 profile 无需目录迁移即自动获得该适配器。(#5718)

  • 社区工具: Browserless web_fetch 现在从工具配置读取 reject_resource_types 与 reject_request_pattern。这两个参数 此前只是声明并传给客户端,却始终被赋值为 None,因此配置的值被 静默忽略,资源类型/请求模式拒绝——控制自托管 Browserless 渲染延迟 与带宽的主要手段——根本无法开启。取值接受 YAML 列表或逗号分隔字符 串;不可用的值会直接省略该参数而非发送。默认值不变,两个键均已在 config.example.yaml 中补充文档。(#5719)

  • 技能: 自定义技能回滚路由不再在事件循环上读取技能的编辑历 史。POST /api/skills/custom/{skill_name}/rollback 此前在请求路 径中内联构造存储、探测存在性并解析 custom/.history/<name>.jsonl,而每条历史记录都携带完整的旧版与 新版技能内容,一次回滚就会让这次解析拖住 worker 上的所有其他 run 与流。该工作现在移到工作线程执行,与相邻的历史处理器保持一致;状 态码与响应结构不变,且路由已有 blocking-IO 锚点守护。(#5729)

  • MCP: 持久 MCP 任务调用现在把连接建立与 MCP 初始化约束在与进 入 session 相同的 session_init_timeout 截止时间内。该超时此前 只在进入 session 上下文之后才开始计时,因此一个接受 HTTP 却从不 发送 MCP endpoint 事件的 SSE server 会让调用无限等待。初始化成功 后该截止时间即失效,改用既有的独立工具调用超时;None 仍可关闭 初始化限制。(#5733)

  • 技能: required-secrets[].optional 现在必须是 YAML 布尔 值。形如 optional: "false" 的带引号取值此前能通过 frontmatter 校验,之后却按 Python 真值语义解析,于是 bool("false") 把必填 密钥记录为可选,导致缺失密钥的诊断与导出的需求元数据出错。校验现 在拒绝非布尔取值;对已挂载或历史技能,运行时解析器会失败关闭为 optional=False 并给出警告(仅含取值类型与密钥名)。技能评审分 析器将该形态标记为 structure.invalid-required-secrets-optional。(#5738)

  • 技能: 技能评审提取资源引用时不再保留句末标点。句末的裸路径 (如 “See references/setup.md.”)此前会连同尾部句号一起被提取, 产生误报的 resource.missing 发现,并为真实文件制造一个虚假孤儿 引用。提取的引用现在会剥掉尾部的 .、? 与 !,而 references/config.yaml 这类真实的点号文件名与 references/v1.0.md 这类链接目标保持完好。(#5739)

  • 上传: 分阶段上传的 .part 文件现在只在文档转换描述符释放后才删除。启用 uploads.auto_convert_documents 时,提交环节会在转换仍持有已打开描述符的情况下先行 删除分阶段文件,因此在 Windows 上所有可转换的上传(PDF/DOCX/PPTX/XLSX)都会以 WinError 32(500)失败,而 POSIX 上只是被静默容忍。分阶段文件名现在随描述符的释放 在同一轮清理中移除;中止路径的清理改为尽力而为,避免二次删除失败掩盖原始错误。(#5740)

  • composer: IME 组合中的 Enter 不再触发斜杠技能目录。主输入框的联想处理器缺少兄弟 调用点都有的 isIMEComposing 守卫,因此在 IME 激活且 / 目录打开时,用于提交 CJK 组合内容的 Enter 会被当作选中高亮技能,正在输入的文本随之丢失。被 IME 消费的按键现在 落入正常的提交路径;普通 Enter 仍选中高亮技能。(#5741)

  • 沙箱: BoxLite 的同步桥接等待超时后,其提交到事件循环的任务现在会被取消。 run_coroutine_threadsafe(...).result(timeout) 向调用方抛出异常,却让已提交的协程 继续在私有事件循环上运行,沙箱可能在操作已被上报为超时之后仍在变更状态或持有 acquire 所有权;现在会先取消未完成的 future 再重新抛出超时,已完成的 future 则保持不动。 (#5748)

  • MCP: 仅当生效的 MCP 配置真正变化时才使 MCP 工具缓存失效。此前失效逻辑以整个 extensions_config.json 的签名为准,切换一个无关的技能也会重建缓存工具并退役所有 stdio 连接池会话。缓存现在对比已启用服务器解析后设置的快照(按声明顺序)与全局 mcpInterceptors,只有发生变化才退役;当没有启用任何服务器时,非初始化的刷新会清理 过期状态而不引入额外发现开销;配置损坏时的错误日志也不再输出已解析的凭据。(#5750)

  • 网关: 请求被取消时,artifact 更新现在会先完成再释放所有权。取消 PUT /api/threads/{thread_id}/artifacts/{path} 会在线程操作预留与沙箱租约尚未完成 非挂载远程同步的情况下被拆除,导致远端与主机副本字节不一致,也让后续工作与被放弃的 变更重叠。完整的远程同步/本地替换事务现在会先完成排空再释放所有权,回滚语义保持不变, 主提交失败会先记录日志再回滚。(#5755)

  • 上传: 分阶段上传清理失败时保留原始 link 错误。在转换描述符尚未关闭的 link 提交 失败场景中,Windows 会以共享冲突拒绝错误处理器中的 unlink,这个次要错误顶替了真正的 端点错误;通用 link 失败路径现在遵守 unlink_staged=False,所有分阶段清理均为尽力 而为。link 成功之后的清理失败也不再对已发布的目标返回 500。(#5756)

  • 运行时: 调用方被取消时,数据库 provider 上下文退出会先完成再传播。异步 SQLite/PostgreSQL checkpointer 与 Store provider 使用裸 async with,若取消恰好在 后端 __aexit__() 阻塞时到达,退出本身会被取消,provider 在数据库/连接池清理不完整的 情况下结束——六条 provider 路径全部如此。新增的 drained_async_context() 辅助函数在 把退出排空至完成的同时保留异常与抑制语义。(#5757)

  • 渠道: Buzz 已读事件存储路径的解析移出事件循环。ChannelService._start_channel 内联构造默认 seen_event_store_path,在 POST /api/channels/{name}/restart 可达的 路径上于事件循环内解引用 Paths.base_dir(一次 realpath);解析现在与其他步骤一起 放入工作线程,并有 blocking-IO 锚点将其固定在严格门禁之下。(#5759)

  • 沙箱: AIO 沙箱 acquire 锁路径的解析移出事件循环。 _discover_or_create_with_lock_async 把 acquire 的每一步都放到了线程中,唯独内联的 锁文件路径构造没有,它在每次沙箱工具调用的热路径上解引用 Paths.base_dir(一次 realpath);解析现在并入周边步骤一起在工作线程执行,兄弟的 ownership-publish 锚点 也重新覆盖生产路径层。(#5760)

  • threads: LangGraph 运行被拒绝时不再留下孤儿会话。独立 run 准入会在 LangGraph 复核 assistant 归属前预创建缺失的线程,因此对他人非系统 assistant 发起的运行虽按预期 返回 404,却已经持久化了一个属于调用者的空闲线程。准入现在在 Threads.put 之前校验 assistant 的服务端来源(期间暂时清除环境中的认证上下文);普通用户仍可为注册的系统 assistant 隐式创建线程。(#5762)

  • setup: pre-commit 钩子改经 uv 运行,make install 在 uv 的工具 bin 目录不在 PATH 上时不再失败;Node.js 前置检查也变得可操作:nvm 用户会得到安装命令,其他用户会 得到官方下载页链接。(#5767)

  • 调度器: 在 SQLite 写锁之下读取派发租约的属主。release_dispatch_lease 是唯一 绕过 _lock_task 的写路径,而 SQLite 会忽略 FOR UPDATE,其守卫判断的是过期快照: 落在派发窗口内的暂停可能被覆盖,任务保持 enabled 并再次触发,即使 API 已经确认了暂停。 读取现在先取写锁;PostgreSQL 行为不变。(#5777)

  • utils: 初始化 Article.url,to_message() 对包含图片的页面不再抛出 AttributeError。ReadabilityExtractor 此前从不向 Article 传入页面 URL,经 urljoin(self.url, …) 解析相对图片地址时必然崩溃;该属性现在默认为 "",抽取器会 传入真实 URL。(#5785)

  • 工具: DDG 网页搜索的结果上限现在会从环境变量做类型强制转换。 max_results: $DDG_MAX_RESULTS 此前保留替换后的字符串, DDGS 9.14.1 在为其 worker 定容时抛出 TypeError, 搜索包装层把每次查询都上报为 No results found。配置的上限现在 接受数字字符串;非法或非正值记录警告并回退到默认值 5, 整数配置、默认值以及"配置优先于调用参数"的次序均保持不变。 (#5792)

  • 网关: 空白的 GATEWAY_HOST 或 GATEWAY_PORT 现在视为未设置。 compose 文件中的 GATEWAY_PORT=(或 PORT 未设置时的 GATEWAY_PORT=${PORT})会把空字符串交给 int(),让 Gateway 在 uvicorn 绑定端口之前的导入阶段就崩溃。空白值现在回退到声明的默认 值(0.0.0.0 / 8001);GATEWAY_PORT=0 仍会向操作系统申请空闲 端口,所有非空值的解析与之前完全一致。(#5801)

  • 渠道: 渠道运行时配置现在以 UTF-8 持久化。该存储读取 JSON 时 用 UTF-8,写临时文件时却使用主机默认编码且 ensure_ascii=False,因此在非 UTF-8 主机(CP1252、GBK)上, 非 ASCII 取值——例如配置的 Buzz 中继 URL——可能保存失败, 或写成存储自身无法读回的字节。临时写入现在显式指定 UTF-8; 原子替换、加锁与 chmod 行为不变,旧编码文件不做迁移。(#5803)

  • setup: make setup-sandbox 现在接受 Apple Container 的成功 镜像拉取。在安装了 Apple Container 但没有 Docker 的 macOS 上, 脚本此前会在镜像已经拉取成功之后,继续落入 Docker 可用性检查并以 "Neither Docker nor Apple Container is available" 退出 1。 Apple Container 独立完成的拉取现在计为成功并继续输出默认镜像 提示;Docker 的行为——包括 Apple Container 失败后的回退——保持 不变。(#5804)

  • 运行时: 智能体流的拆除现在能在反复取消下保持存活。 close_agent_stream() 此前在调用方任务中 await stream.aclose(),拆除期间的第二次 Task.cancel() 会把关闭本身 取消,让调用方放弃释放 graph、provider、沙箱与工具资源; 来自关闭自身的 CancelledError 还会让损坏的关闭看起来像被中断 而非失败。关闭现在在专用的 shielded 任务中运行, 调用方的第一次取消被推迟到关闭完成之后, 来自流自身的关闭取消则上报为关闭失败,使 run 以失败终态收场。 (#5806)

  • 工具: DDG 图片搜索套用 #5792 的数字字符串强制转换。 image_search_tool 此前把环境变量替换出的 max_results 字符串直接交给 DDGS,后者在为 worker 定容时抛出 TypeError, 因此 max_results: $IMAGE_MAX_RESULTS 会让每次查询都返回 No images found;0 会移除上限,负值则截断结果。 数字字符串现在可以工作,非法、零或负值会警告并回退到 5, 整数配置与默认值保持不变。(#5807)

  • client: 恢复的内嵌客户端流不再重放之前的回合。 在线程恢复后,DeerFlowClient.stream() 此前会把更早回合的消息 增量(包括工具结果)重新发进 messages-tuple 事件,并把它们的 token 计入 end.usage,虚增了每回合用量。历史消息仍保留在 values 全量状态快照中,但会被排除在当前回合的增量与用量之外, 以当前用户消息上的每 run run_id 为边界;Gateway SSE 不受影响。 (#5811)

  • 持久化: postgres schema 建立连接的退出现在跨取消排空。 _ensure_postgres_schema_with_pool 此前仍用裸 async with 获取连接,调用方的取消若在连接 __aexit__ 阻塞时到达, 会打断退出本身,连接从此不再归还连接池,随后被排空的 teardown 会带着一个仍被占用的连接关闭。该连接现在与其余 checkpointer provider 一样经过 drained_async_context();正常路径行为不变。 (#5812)

  • MCP: 裸文件名关联流程不再破坏反斜杠路径。stdio MCP 调用创建 page.yml 之后,结果中提到的 C:\outside\page.yml 的 basename 会被改写为指向新的工作区文件,因为该流程把反斜杠当作裸文件名的 边界。反斜杠现在在关联名的两侧都作为路径分隔符处理, 真正的裸文件名仍能解析,而未解析的 Windows、UNC 或相对路径中的 名称保持原样。(#5815)

  • MCP: MCP 文本结果中含空格的生成文件路径现在能被解析。 名为 artifact with space.txt 的文件此前在自由文本工具结果中 保留其绝对主机路径,后续的 read_file 以 Permission denied 失败,因为空白让完整文件名对 token 匹配器不可见。 含空白的绝对路径与工作区相对路径现在都会与该调用创建的文件关联, 并在当前线程的 user-data 目录树内校验;树外路径与有歧义的后缀 保持原样。(#5819)

  • 中间件: ThreadDataMiddleware 现在保留 human 消息的元数据。 追加 run 元数据此前会从碎片重建最后一条 HumanMessage, 静默丢弃未复制的字段(如 response_metadata);中间件现在复制 既有消息,只更新其默认名称与 additional_kwargs。(#5823)

  • 上传: 文档转换的 stat() 与 Markdown 回写不再占用事件循环。 convert_file_to_markdown 此前虽把转换本身卸载到了线程, 却仍在事件循环上执行大小检查的 stat() 与转换后 Markdown 的 多兆字节 write_text,阻塞大文档的上传摄取。两者现在都经由共享的 run_file_io 线程池;小文件内联转换的阈值不变。(#5830)

  • MCP: 初始化本身抛错时,惰性 MCP 工具发现现在只运行一次。 get_cached_mcp_tools() 里针对无事件循环的 except RuntimeError 回退把发现自身的错误也一并捕获:没有 运行中的循环时,一次失败的调用会拉起每一个 stdio server 并 重复获取两次 OAuth token——由于失败的初始化从不把缓存标记为 已初始化,之后的每次调用都会重演;而在运行中的循环里,日志 会把真实原因埋在 "asyncio.run() cannot be called from a running event loop" 之下。现在初始化恰好驱动一次并记录真实失败;出错仍返回 [] 且缓存保持未初始化,留待下次重试。(#5836)

  • 配置: request_admission 的整数字段现在支持 $VAR 替换。 requests_per_minute 与 max_queue_size 声明为 strict=True, true 与 60.0 仍会被拒绝,但像 requests_per_minute: $RPM 这样的环境变量替换会以字符串 "60" 到达,使整个 config.yaml 加载以 int_type 失败——保存在环境中的配额根本无法为模型 设定节奏。现在一个 BeforeValidator 会在严格检查之前把纯 十进制字符串转换为 int;布尔、浮点、"60.0"、空值与非数值 仍被拒绝,gt=0 保持不变。(#5838)

  • 工具: 调用 SDK 之前先对 Exa 配置的结果上限做类型强制转换。 $VAR 替换解析出来是字符串,因此 max_results: $EXA_MAX_RESULTS 会让每次 web_search 调用失败 ——exa-py 拒绝字符串形式的 num_results—— contents_max_characters 也会以 "maxCharacters": "1000" 发出。现在两者都会被解析为整数(仅接受非布尔整数或整数形式 的字符串);空白、非数值、带小数、布尔、零或负值会记录警告 并回退到默认值(5 与 1000),而不是让搜索失败,与 DDG、 SearXNG provider 保持一致。(#5839)

  • 网关: 空白的 GATEWAY_WORKERS 与空白的 Lark broker 端口或超时现在视为未设置,扩展了 #5801 的空白环境变量规则。 微信扫码登录守卫中的 int("") 会把空白的 GATEWAY_WORKERS(如 -e GATEWAY_WORKERS=)在每条 QR 路由上 变成 503 "requires a single Gateway worker",尽管 Gateway 实际只运行一个 worker;空白的 DEERFLOW_LARK_BROKER_PORT/ _TIMEOUT 则会让 Lark broker 在启动时中止。守卫现在读取 GATEWAY_WORKERS or WEB_CONCURRENCY or "1",对多 worker 或非数值仍然 fail-closed;broker 则回退到自身默认值。 (#5840)

  • 技能: 技能评审的资源图现在会从 code-span 资源引用中 剥离章节锚点。Markdown 链接目标此前已在解析前丢弃 #fragment,但形如 references/faq.md#pricing 的 code-span 引用会保留锚点、按字面路径解析,即使文件确实 存在且被引用,也会抛出误报的 resource.missing 警告。 code-span token 现在以同样方式切分 fragment 并去掉尾部 句读标点;references/v1.0.md#notes 这类带点的文件名会 保留其扩展名中的点。(#5841)

  • 技能: 技能评审的 Markdown 链接扫描在对抗性输入下保持线性。 提取器曾对每个候选 opener 重复扫描同一目标串,一长串未闭合的 [ 或 ]( 密集的非法目标都会让评审退化为平方复杂度——#5714 在 256 KiB 的 [ 下实测 19 秒,而资源图会扫描每个 text 成员, PackageLimits.max_file_bytes 为 64 MiB,单个大 SKILL.md 完全在影响 范围内。扫描现在只遍历一次候选 opener,经与基准正则的差分 fuzz 验证, 与 finditer 完全一致;已匹配区间改按位置清除,不再使用 str.replace,避免误删后方相同的文本。(#5884)

  • 配置: 配置缓存签名的应是它实际解析的字节,而不是 第二次读取。app-config 加载器此前打开 config.yaml 两次 ——先解析一次,再对一次全新的读取做哈希来记录 (mtime, size, sha256) 签名——若一次写入恰好落在两次读取 之间,旧内容就会带着新签名被缓存,get_app_config() 从此不再重载,静默供应过期的配置,直到某次后续编辑。加载 器现在通过共享的 file_signature.read_config_with_signature() helper 只读取一次,并对返回的字节精确签名;竞态编辑最多 只多付出一次重载。(#5848)

  • 沙箱: glob 与 grep 不再在搜索根目录(或其任一祖先目录)命中忽略模式(如 build、 dist、logs、node_modules、coverage、target)时返回空结果。此前各远端沙箱把 should_ignore_path 用在绝对路径上,而该函数会检查路径的每一段,于是树上任意位置的忽略名 都会把整份结果隐藏,智能体刚 ls 出来的目录却被搜索告知“无匹配”。现在忽略模式按搜索根的 相对路径生效,与 list_dir 已有的做法一致:忽略名依然隐藏自己的子孙目录,但以被忽略目录 为根、或位于被忽略祖先之下的搜索会正常返回其内容。(#5667)

  • sandbox: HTTP 上传路由获取的临时 sandbox 租约现在会释放。在 remote/provisioner 部署中,POST /api/threads/{id}/uploads 会临时获取线 程 sandbox 以把上传文件同步进非挂载 sandbox,但该租约从不释放——成 功、413 与失败路径都一样;飞书文件同步也是同样的不释放模式,导致被获 取的 sandbox(一个 Pod 加 NodePort Service)永远回不到 warm pool。两条 路径现在都会在同步后释放,且只有最后一个持有者退出时才会把 sandbox 放 回 warm pool:同一线程已有活跃 run 的 sandbox 时,释放临时租约只减少计 数,不会把使用中的 sandbox 停进 warm pool。(#3261)

  • frontend: 初始 run 创建请求在结果不明确的失败后不再重试。LangGraph SDK 默认会对失败的 HTTP 请求重试,而 POST /runs/stream 并非幂等:后端 已接受请求之后,中间层仍可能返回 504 之类含糊的网关错误,重试就会把 同一个 run 创建两次,重复用户可见的工作。现在 run 创建以禁用重试的方 式发出;读取、显式 join stream 以及 SSE 恢复——对服务器提供的 Location 发出携带 Last-Event-ID 游标的 GET——仍保留正常重试行 为。(#4725)

  • gateway: stream bridge 故障不再被当成客户端断开而取消健康的 run。 两个创建者侧消费方——SSE POST /runs/stream 响应与 POST /wait—— 此前都在无条件的 finally 中应用创建者的 on_disconnect 策略(取消), 于是 StreamBridge.subscribe() 的任何退出,包括基础设施异常或订阅在终 止事件前耗尽,都会被视作客户端断开,可能取消仍在运行的 agent。该策略 现在只作用于真实观测到的断开(request.is_disconnected()、SSE 生成器 关闭、请求任务被取消);bridge 异常原样传播且不取消 run,订阅提前耗尽 则抛出服务器错误。(#5623)

  • logging: 对 urllib3 "Failed to parse headers" 警告中携带凭据的头部 值做脱敏。该转储包含第一条畸形行之后的原始头部块,而 UrlRedactionFilter 没有任何一轮能匹配到它,于是 Set-Cookie/ Authorization/WWW-Authenticate/Location 的值原样进入日志——还出 现两次,因为 exc_info 的 traceback 会重复它们。新增的脱敏轮以 urllib3 自身的字面量为门控,按转储的逻辑换行拆分后仅折叠携带凭据的字 段值,保留字段名与非敏感字段,日志仍能说明失败原因;异常文本经预置的 exc_text 走相同流程,JsonTraceFormatter 也改为遵循 exc_text 而不 是自行渲染异常(此前 logging.enhance.format=json 模式下泄漏依旧)。 (#5752)

  • uploads: 上传暂存辅助函数 _link_staged_no_overwrite 的链接失败清 理分支不再删除调用者仍持有的暂存名。该分支此前硬性执行 os.unlink() 且只容忍 FileNotFoundError;在转换路径(unlink_staged=False)上调 用者仍持有暂存 inode 的描述符,Windows 上删除会抛出 PermissionError, 覆盖掉调用者需要看到的原始 os.link 失败,并遗留 .part 文件。现在只 有本调用拥有该名字时才会通过尽力而为的 _remove_staged_file 删除,删 除失败也绝不会掩盖链接错误;unlink_staged=False 时由调用者经 _abort_upload_temp 自行清理。(#5763)

  • 事件: 取消后的 JSONL 快照不再在文件扫描仍在进行时释放每线程写锁。 find_latest_ai_message_run_ids() 会在每线程写锁内做一次完整的 thread-log 读取,但扫描此前通过裸 await asyncio.to_thread(...) 执行:取消只会取消 等待方,_read_thread_events() 还在读文件时锁就已释放,写入方可能在快照 中途进入。现在扫描作为任务(名称 jsonl-snapshot:{thread_id})经由 _await_owned_task() 辅助函数排空完成后才释放锁;第一次调用者 CancelledError 仍是主结果,读取失败会作为其原因链式保留。(#5795)

  • 沙箱: 调用者被取消时,AIO 跨进程 flock 现在会等其生命周期 worker 结束后才 释放。_discover_or_create_with_lock_async() 此前通过裸 asyncio.to_thread() 执行发现、注册与创建——取消只会取消等待方,worker 仍在变更沙箱生命周期时 flock 就已释放,另一个进程可以进入同一 discover/create 临界区。锁内的同步 步骤现在经由 run_sync_lifecycle_operation() 排空,整个异步创建包在 await_drained() 中运行以便新建沙箱仍能完成就绪与注册,flock 本身改为可 取消的非阻塞重试,被取消的等待者可以立即退出。(#5820)

  • Helm: chart 现在可以把可选的 lark-cli 沙箱运行时镜像传给 provisioner。 provisioner 会读取 LARK_CLI_INIT_IMAGE 与 LARK_CLI_BROKER_IMAGE,Compose 与 根 README 也都写了这两个变量,但 chart 没有对应的 value——照着 README 操作的 Helm 用户无从设置,沙箱会静默地不带 lark-cli 启动,直到沙箱内报 command not found 才暴露。新增 value provisioner.larkCliInitImage 与 provisioner.larkCliBrokerImage(broker 优先于 init)仅在设置时才在 provisioner Deployment 上渲染对应环境变量;保持未设置则完全不渲染,既有发布 的 Pod spec 渲染结果不变。(#5835)

  • Tenki: 通过环境引用配置的 sticky(例如 sandbox.sticky: $TENKI_STICKY 配合 TENKI_STICKY=false)不再意外开启主机绑定。环境替换后该字段仍是字符 串,原先的 bool(...) 转换会把任何非空字符串——false、0、off、 no——当作 True 传给 Client.create,等于让 provider 开着运维者已关闭 的粘性。现在改用 Pydantic 的布尔校验器解析:大小写不敏感的 false/0/off/no 关闭绑定,true/1/on/yes 开启,省略或 null 保持关闭,YAML 原生布尔值不受影响,非法值在 provider 初始化时直接 报错。(#5849)

  • 配置: stream_bridge.recovered_stream_cleanup_delay_seconds 现在与受 控的同级字段 heartbeat_interval_seconds 一样,在加载时拒绝布尔值、非有 限数值及超过 86400 的取值。该字段此前只有 ge=0,手改的 config.yaml 写 …: true 会被静默转成 1.0(重连宽限窗口缩到 1 秒),…: .inf 或 1e30 则被原样接受——END 之后的清理任务会一直休眠,孤儿 run 的 Redis stream 只能等滚动的 stream_ttl_seconds(默认 86400)过期,而不是按配置的宽限 时间删除。0 仍然有效(END 发布后立即删除),文档默认值 60 不变。 (#5850)

  • 社区工具: 当 config.yaml 里的 max_results 是布尔值或小数时,DDG web_search 现在会告警并回退到默认的 5 条结果——README 早已如此约定, image_search 自 #5807 起也是这个行为。web_search 的 _coerce_max_results 直接走 int(),会把 3.5 截成 3、把 true 当成 1,结果集被悄悄缩小而没有任何非法配置提示。4.0 这类整数值浮点和数字字 符串的行为不变。(#5852)

  • LLM: strip_think_blocks() 在模型文本包含大量未闭合 <think> 标签时不再出现平方 级变慢。该辅助函数负责清理用于 goal 评估、建议和输入润色的模型文本,原先的完整块 正则会在每个开标签处重试剩余后缀,12,000 个重复 <think> 标签约需 3.1 秒且开销仍在 增长。现在一次前向扫描同时处理开/闭标签,每个匹配块只消费一次,并在第一个未闭合 的开标签处停止;大小写不敏感匹配、开标签属性、闭合 > 前的空白以及两种 truncate_unclosed 模式均保持不变。(#5854)

  • 沙箱: 只存在分类挂载时,ls /mnt/skills 不再报 "Directory not found"。 默认技能投影下 provider 挂载的是 /mnt/skills/public、custom、legacy 与 integrations 四个分类,并没有 /mnt/skills 根目录本身的映射,因此 LocalSandbox.list_dir 会把根目录解析成字面宿主路径,宿主扫描在虚拟子目录 叠加逻辑得以列出分类之前就抛出 FileNotFoundError。现在当请求的容器路径内 至少挂载了一个映射时,list_dir 会把宿主路径缺失视为空列表,由叠加逻辑列出 四个分类;内部没有任何挂载的路径仍照旧抛出 FileNotFoundError。(#5857)

  • 示例: Jev 抓取内容筛查示例不再让行为异常的筛查端点或特殊部署绕过其 fail-open 与 数据外发保证。深度嵌套的应答体(RecursionError)或超大的 noul 整数 (OverflowError)此前会逃出 fail-open 路径,并在逐消息任务组中取消其他消息的筛查 请求、丢掉该工具调用的全部标记;现在解析在 fail-open 块内进行,两类错误都会被兜住。 16 KiB 响应上限此前按解码后字节计数,压缩应答会先膨胀超限;请求现在携带 Accept-Encoding: identity 并拒绝编码响应。纯 HTTP 的 loopback 端点(校验器允许的 唯一纯 HTTP 形式)此前会跟随 HTTP_PROXY 和 ALL_PROXY,把 bearer key 和摘要以 明文发给代理;这类端点现在以 trust_env=False 构建客户端,HTTPS 端点保留代理 设置。根 README 也不再声称摘要总是做 PII 脱敏:它会经过净化,仅在启用 pii_redaction 时才脱敏。(#5858)

  • 调度器: 一个调度器迟到的完成写入不再会覆盖另一个调度器对同一 run 的新鲜认领。 ScheduledTaskRunRepository.update_status 此前先用普通 session.get 读行,再据此 校验 expected_lease_owner(及 protect_terminal 回填):当过期的 launch 租约被 重新入队并被另一个调度器认领(lease_owner=B)后,第一个调度器受旧数据守护的迟到 写入仍能通过校验,清掉 B 的租约 fencing 并把 launching 状态覆盖为 failed。现在 会先取父任务的写锁再重读该行——与本仓库其他变更路径相同的 task → run 锁顺序——并加 populate_existing=True 避免 identity map 返回加锁前的旧行;Postgres 侧相应增加 FOR UPDATE 行读取。(#5860)

  • 工具: 绑定后的 task 或 batch_task 工具现在经绑定运行时自己的提交 器与执行容量执行同步调用,而不是落到进程级全局兜底。get_available_tools 会就地为共享工具单例包一层同步 func,但 bind_task_tool 与 _bind_batch_tool 只重绑副本的 coroutine,因此每个副本继承的是围绕未绑 定协程的同步包装:其同步路径要么落到进程级全局提交器(正是绑定契约排除的 兜底),要么在从未同步包装过单例的全新进程里抛出 NotImplementedError。 两个绑定函数现在都会用同一个 make_sync_tool_wrapper 重绑 func,绑定副 本的两条调用路径因此都执行绑定后的协程。(#5861)

  • 模型: token 字段不是字符串的 CLI 凭据文件不再被当作有效凭据加载。 credential_loader.py 的加载器此前校验了容器字段、expiresAt 与 Codex 的 account_id,却没校验 token 本身:手改或脚本改写的文件里 accessToken/access_token 写成数字、列表或对象也能通过真值检查——来自 $CLAUDE_CODE_CREDENTIALS_PATH 的坏 Claude override 会遮蔽有效的 ~/.claude/.credentials.json 回退,坏 Codex token 则被拼进 Authorization: Bearer … 请求头,最后以一个莫名其妙的 401 暴露。现在这 类 token 会被跳过(字符串会去除首尾空白,空白视为缺失),非字符串的 refreshToken 与 account_id 一样降级为空。(#5864)

  • MCP: 在启用 user_auth 的 HTTP/SSE 服务器上,持久化 MCP 任务的状态与 取消调用现在以任务记录的归属用户完成鉴权。此前任务在已鉴权的 Agent 回合内 提交成功,后台调用却全部失败:McpTaskToolCaller 没有把存储的 owner 提供给 凭证拦截器,缺少运行中的请求上下文时凭证选择落到 default,任务服务只会重 试这一鉴权失败而不读取远端任务状态,从 SQL 重建的服务同样失败。现在启用 user_auth 的后台 HTTP/SSE 调用会通过既有 user ContextVar 绑定一个仅含 ID 的 owner,并在 finally 中恢复原上下文;提交、stdio 及未启用 user_auth 的调用保持原上下文,并发 owner 互相隔离,后台调用也仍不触碰 headers_from_context 的请求密钥。(#5870)

  • 智能体: 循环检测不再把分页或内容变化的通用工具调用误判为重复。此前的 调用键只保留第一个显著参数(path/url/query/command 等),MCP fetch 用 start_index 续页、用 page 翻页的搜索、或 list_uploaded_files 跟随 cursor 都会折叠成同一个键,第五个不同调用就以 stop_reason=loop_capped 硬性终止运行。现在键由全部参数构成;只有 bash/ls/glob/grep 的叙述性 description 字段仍被排除(改写它依然无法绕过检测),其他工具的 description 因可能是操作载荷而继续参与键值。(#5872)

  • 中间件: 输入净化不再丢掉被净化用户消息上的元数据。 InputSanitizationMiddleware 之前用四个选定字段重建净化后的 HumanMessage,content/id/name/additional_kwargs 之外的任何字段——尤 其是携带网关来源的 response_metadata——在文本与多模态两条路径上都会被 丢弃。现在从原消息复制、只替换 content 与 additional_kwargs,元数据、 标识字段和消息子类都得以保留,原始请求消息仍保持不变。(#5875)

  • 社区集成: config.yaml 中的 InfoQuest 超时配置现在会同类搜索/抓取提供方一样先做 归一化。deerflow.community.infoquest.tools 此前把五个文档化的数值选项(timeout、 fetch_time、navigation_timeout、search_time_range、image_search_time_range) 未经校验直接传给 InfoQuestClient,而 $VAR 引用会从环境变量原样替换—— timeout: $FETCH_TIMEOUT 因此以字符串抵达,客户端的 > 0 比较抛出 TypeError, 使 web_fetch、web_search 或 image_search 整体失败,错误信息还指向客户端而非 配置键。现在这些值先经过一步强制转换:整数、整数值浮点数或整数形式的字符串按配置 生效;布尔值、小数、空白/非数字字符串或 None 回退到文档默认的 -1(该过滤禁用) 并记录一条点名配置键的警告。已经合法的整数配置产生的请求逐字节不变。(#5883)

  • 沙箱: AIO acquire 中已启动的 worker 现在会在同键序列化器释放前结束,即使 调用者已被取消。在按 (user_id, thread_id) 序列化的区域里,进程内缓存复用与 warm pool 回收此前是裸 asyncio.to_thread() 调用——取消会在 worker 仍在运行 时释放作用域,同一 user/thread 的重试 acquire 可能与之重叠。这两步现在经由 一个辅助函数执行:仍在排队的工作会被取消(与旧行为一致),但已启动的 worker 会保留序列化器所有权并在反复取消下排空;flock 等待、就绪轮询与 skills 投影 步骤仍保持可取消。(#5888)

  • 技能: 托管集成技能的发现现在在每个技能包边界处停止下钻。与公开和自定义技能加载 器不同,集成目录遍历在找到 SKILL.md 后仍会继续深入,导致自带 SKILL.md 的嵌套 评测夹具可能成为运行时技能——若 frontmatter 名称与公开技能相同,还会把它顶替掉。 自身没有 SKILL.md 的 provider 与命名空间目录仍会正常暴露其嵌套技能。(#5890)

  • 模型: 开启 prompt caching 的 Claude 会话在几轮之后不再因 HTTP 400 而 无法继续。ClaudeChatModel._apply_prompt_caching 之前把 cache_control 标记直接写进请求与线程共享的 block——langchain-anthropic 对 Claude 原生 block(带 source 的图片或文档、搜索结果)按引用传递——标记因此随消息 一起被 checkpoint,从第三轮起每个请求都超出 Anthropic 的四个断点上限, 线程再也无法恢复。现在标记写在副本上,且每个请求都会先剥掉已存在的标记 (无论缓存开闭),checkpoint 里已带陈旧标记的线程下一轮即可自愈,无需任 何迁移。(#5892)

  • 子代理: 当最后一个活跃项的租约在最后一次允许尝试中过期时,持久化批次 不再永远停留在 running。claim_items 会把这样的项标记为 failed,却不刷 新父批次,于是末项以此收场的批次既没有 completed_at,也无法观察到终态。 租约恢复现在会在同一事务内刷新批次:没有任何成功项的批次变为 failed,有 部分成功的变为 completed,失败项计数保持可见,显式重试某个项仍会照旧重新 打开批次。(#5897)

  • 智能体: 提供方在同一 thread 的不同 run 间复用 tool_call_id 时不再破坏 委托账本。账本此前按原始 ID 去重并沿用早先的 run_id:后一个 run 的同 ID 委托可能覆盖先前条目——任务从持久上下文中消失、max_total_subagents 漏计 该 run——其回复也可能被当成对更早孤儿任务的应答。现在捕获、保留窗口与终态 保护都改用 (run_id, tool_call_id) 标识;新用户回合的孤儿关闭以该 run 的 首条消息为界,没有新用户消息的续跑 run 会保留已应答的条目。(#5898)

  • 沙箱: 沙箱网络策略变更现在会在调用者取消时排空完成。 consume_network_policy_events()、deny_pending_network_policy_events() 与 decide_network_policy_request() 的异步包装此前用裸 asyncio.to_thread() 执行 这些有状态的 trusted-proxy 变更——取消等待中的工具/agent 任务会在策略状态仍在 变更时离开生命周期边界。三者现在都经由取消安全的 run_sync_lifecycle_operation() 辅助函数:已提交的 worker 先结束,随后才传播 原始的 CancelledError。(#5899)

  • 追踪: Langfuse 追踪现在标注实际运行的模型。model:<name> 标签此前在 模型解析之前注入,默认模型的 run 可能完全没有模型标签,回退 run 则保留被 拒绝请求的模型名。LeadAgentAssembly 现在以 effective_model 暴露解析后的 模型;Gateway worker 在组装完成后注入追踪元数据,嵌入式客户端也先解析 agent 再注入,两者都标注最终选定的默认或回退模型,调用方提供的元数据仍然 优先。(#5902)

  • 运行时: 纯图片的用户输入现在每次 run 只记录一次。RunJournal 此前用 可选的文本摘要判断输入是否已捕获,而无文字的消息摘要为 None,导致之后的 每次模型调用都追加一条 llm.human.input 事件并虚增 message_count;批量 回调还可能在最新的图片输入之后补记更早的 prompt。现在用一个独立于可空摘要 的标志跟踪捕获,一旦记录就停止扫描批次;媒体内容与空的展示文本保持不变。 (#5903)

  • MCP: 为原子的通知启动预留铺路,让 MCP 任务通知的故障恢复可以安全回 滚。此前 worker 在异步 run 查询与启动之后复用批次的扫描时间戳,导致重试延 迟过早开始、仓库过期判定拿陈旧时间作比较;单条记录的意外失败也要等整批 gather() 结束后才处理,一条记录的重试节奏被最慢的兄弟记录拖住。现在查找 与启动结果使用不早于认领时间的完成时间,单条失败在各子任务内释放,仓库可 以读取、恢复、轮询并结算 launching 状态的通知(保留其快照与幂等键),而 生产路径尚不写入该状态——回滚到旧版 Gateway 仍然兼容;通知处于 launching 期间前端会继续轮询。(#5908)

  • 子代理: 被取消或超时的子代理执行会保留已完成模型调用上报的 token 用 量。模型可能在中间件仍在等待、图尚未发布下一个 values 帧时完成并上报用 量;这个窗口内的取消会绕过结果处理分支,外层超时/取消包装只能拿较旧的进度 快照定稿结果——两次完成响应分别花 15 和 30 token 时只报了 15。现在流清理排 空之后、选定终态之前,执行器会发布 token 采集器的最终累计快照;首次终态写 入语义、取消传播与父运行日志去重均保持不变。(#5910)

  • 路径: 路径隔离校验器现在拒绝带结尾换行的 ID。Python 的 $ 也会匹配到 末尾换行之前,而 _validate_user_id、_validate_project_id 与 _validate_integration_id 用的都是 .match(),形如 "alice\n" 的 ID 能 通过文档约定的受限字符集检查,生成严格语法本不允许的文件系统 bucket。三处 现在都改为 fullmatch,只接受到字符串结尾都符合字符集的 ID。(#5911)

  • 上传: 文件名中含 NUL 字符的上传不再以 500 失败。规范化此前放行了内嵌 NUL,文件名随后进入 os.lstat() 并抛出未处理的 ValueError (lstat: embedded null character in path),中断整个上传请求。 normalize_filename 现在会直接拒绝这类名称;多文件请求中非法文件被跳过, 其余有效文件照常保存。(#5915)

  • 目标: 智能体现在每次模型调用都能看到激活的 /goal。此前 ThreadState.goal 只到达目标评估器:通过 PUT /goal、 DeerFlowClient.set_goal() 或 TUI 设置的目标会被忽略,压缩还可能把陈述目标 的唯一消息一并移除。DurableContextMiddleware 现在在其隐藏的 <durable_context_data> 消息开头渲染 <active_goal> 元素(HTML 转义、与 goal 路由一致的 4000 字符上限、开启时做 PII 脱敏),持久上下文契约追加静态 例外:智能体以用户请求的优先级推进该元素,且它不携带 system 或 developer 权限。active_goal 加入输入净化器的标签黑名单,用户输入与远程结果无法伪造 它;子智能体的状态不含 goal,不受影响。(#5917)

  • 前端: 带信息串的围栏行(如 ```python)不再关闭已打开的代码块。 CommonMark 要求闭合围栏不得短于起始围栏、且不能带信息串,但三处围栏 扫描中只有 Mermaid 归一化两半都检查了——渲染路径 (stripLeakedSystemTags)和导出路径(stripInternalMarkers)只比较围栏 字符与长度,块因此提前结束、其后内容被当作正文:渲染时示例代码内的 系统标记标签被剥除、内容直接暴露,导出 thread 时整个 <project>/<documents> 区间被删除、用户文本丢失。两处现在共用移入 core/streamdown/fences.ts 的 isClosingFence 谓词。(#5919)

  • 技能: 技能发现不再因符号链接成环而反复扫描同一棵目录树。load_skills() 此前跟随目录链接但不检测指回祖先的链接,命名空间里两个成环链接会重复展开 同一棵树——真实扫描超过了 3 秒诊断时限,而普通外部链接不到 1 毫秒即可完成。 现由共享的 walk_skill_directories 辅助函数剪枝"解析后路径已在当前遍历分支 上"的目录,覆盖本地与用户级存储的 public/custom/integration/legacy 各类 发现;指向同一外部树的多个独立别名仍可被发现。(#5921)

  • 后端: asyncio 子进程在原生 Windows 上恢复可用。自 #3236 起, backend/sitecustomize.py 会把所有 sys.path 含 backend/ 的 Python 进 程切到 WindowsSelectorEventLoopPolicy,而该循环无法创建子进程: invoke_acp_agent 工具立即失败且错误信息为空(裸 NotImplementedError), stdio MCP 服务器和本地沙箱命令执行也撞上同一堵墙。这一强制的前提(异步 psycopg)已不成立——postgres 驱动是 asyncpg,本就没有 Windows wheel,默 认的 SQLite 后端在两种循环下都能跑。该文件已删除,Windows 恢复 Python 默 认的 Proactor 循环;postgres 基准脚本仅在自身的 postgres 场景下设置 selector 策略。(#5937)

  • 沙箱: 非有限的 OpenSandbox 超时值现在在 provider 构造时即被拒绝,而不是拖 到首次获取沙箱时才失败。这些选项是 provider 特有的扩展项,YAML 的 .nan 与 .inf 能通过 SandboxConfig 校验,provider 此前只检查符号,把失败推迟到获取 沙箱时的 SDK 或 timedelta 转换,抛出底层运行时错误。request_timeout 与 ready_timeout 现在必须为有限正数,sandbox_timeout 必须为有限非负数(0 仍表示显式清理);config.example.yaml 已注明该要求。(#5944)

  • 子代理: 上下文快照不再丢弃 Anthropic 原生的 document 块。以 context_mode="snapshot" 委派时,document 不在可识别的媒体类型里,块会被 静默丢弃,子代理可能因此失去父代理已掌握的要求。现在可序列化的文档会作为 历史用户角色数据保留,并在捕获时对其文本字段(title、context、text 来 源的 data、content 来源的文本块及 cited_text 等引用文字)套用既有的保 留标签与用户输入中和,移除 cache_control,并与父历史做嵌套副本隔离。中和 在捕获时进行,因为隐藏的快照消息不经过输入净化中间件;不可序列化的文档退 回既有的历史媒体省略提示。(#5948)

  • 沙箱: 中间件应用网络审批响应的路径现在会在取消时排空完成。 SandboxMiddleware.abefore_agent() 此前用裸 asyncio.to_thread() 执行 _apply_network_policy_response()——取消等待中的 agent 任务可能在已启动的 decide_network_policy_request() worker 仍在变更沙箱网络策略状态时离开中间件 边界,绕过了已被 #5899 改为取消安全的 provider 包装。异步桥接现在经由 run_sync_lifecycle_operation();同步的 before_agent() 路径保持不变。 (#5949)

  • 运行时: subagent 步骤事件的 flush 现在能在宿主取消下存活。flush 会先把批次 从 _pending 移出再等待 RunEventStore.put_batch(),且只有普通失败会被重新 入队——CancelledError 属于 BaseException,会绕过 except Exception 栅栏, 于是取消可能在持久化仍在进行时拆掉 flush,之后写入再失败就会静默丢失该批次。 现在每笔已提交的写入都在独立的 shielded 任务中运行并吸收反复取消;若排空后的 写入失败,批次会先重新入队再传播原始 CancelledError,普通存储失败仍照旧 记录日志并重新入队。(#5950)

  • 上下文: 同一模型响应中的两个不同 task_note 新增调用不再会静默删除已有笔记。 此前每个调用读取同一份状态快照后各自合并更新,已有 7 条笔记时合并结果只保留最后 8 个键,而两个调用都返回 saved。现在剩余槽位会按本批次的工具调用顺序为互不相同的 新键预留:超出的新增在发出状态更新前就返回 note_capacity,已有笔记得以保留;对 同一新键的重复写入共享一个槽位;结构非法的兄弟调用不占用容量。被兄弟删除或失败的 调用释放的槽位在下一批次可用。对启用了 task_continuity.enabled 的部署而言,这 改变了此前文档所述并行溢出时"保留最后 8 条"的行为。(#5954)

  • MCP: 删除线程现在会一并销毁该线程的持久化 MCP 会话。此前 DELETE /api/threads/{id} 清理了检查点、运行、事件、反馈与实时浏览器会 话,却从未触及线程的持久化 stdio MCP 会话(close_scope 在生产代码中没有 调用方),有状态服务器(如 Playwright)会保留服务端状态,同用户重建同 id 线程时可能继承陈旧的页面与 Cookie,泄漏的 owner 任务及其子进程也继续占用 内存和文件描述符。现在删除会在浏览器会话清理之后以尽力而为方式调用新增的 MCPSessionPool.close_thread_scope(),匹配该用户/线程的旧版作用域与所有代 数(含进行中的创建),不影响其他线程;清理失败时线程仍照常删除。(#5956)

  • 工具: 社区搜索工具不再因无穷大的 max_results 而失败。此前的类型转 换守卫只捕获 TypeError 与 ValueError,而 int(float("inf")) 抛出的是 OverflowError;yaml.safe_load(.inf 是合法的 YAML 浮点数)与 json.loads(接受 Infinity)都可能把无穷值递给守卫——结果不是回退到默认 值,而是转换崩溃,SearXNG 的 web_search_tool 每次搜索都返回错误负载。 brave、serply、serper、searxng、groundroute、sofya 的守卫(max_results, 另含 Sofya 的内容上限)现在同时捕获 OverflowError,无穷值与其他非法输入 一样回退到默认值。(#5957)

  • 模型: 全空白的 Codex CLI 凭据现在会在模型初始化时被拒绝,而不是生成 非法的 Authorization 请求头。加载器此前接受任意字符串 token, auth.json 写成 "access_token": " " 也能成功构造 CodexChatModel, 配置错误要拖到 provider 请求真正失败时才暴露。现在字符串 token 会先去除 首尾空白再走原有的类型与空值检查,空白值视为缺少凭据(沿用既有的 "Codex CLI credential not found" 报错),带空白的合法 token 去除空白后照 常可用。(#5959)

  • 智能体: 只差一个结尾换行的 agent 名称现在返回 422,而不是 500。Gateway 路由此前用 .match() 校验 AGENT_NAME_PATTERN,而 Python 的 $ 会匹配到 末尾换行之前,于是 POST /api/agents 传入 {"name": "reviewer\n"} 能通过 路由检查,却在 FileAgentStore.create() 的 fullmatch 处抛错——可读的错误 信息只留在服务端日志里,调用方拿到的是内部错误。DeerMem 的 validate_agent_name 同样放行该名称,使 agent_facts_directory() 建出自身 校验器拒绝读回的目录。两处校验器现都改用 fullmatch;此前合法的名称不受 影响。(#5960)

  • 网关: Gateway 关闭时现在会关闭池化的 MCP 会话,而不是将其遗弃。此前 lifespan 清理只关闭了浏览器会话管理器,从未触及 MCPSessionPool,而池化会 话的传输层 __aexit__ 只能由其 owner 任务执行——事件循环一旦停止,stdio MCP 子进程与 SSE/HTTP 连接就完全失去有序关闭。现在 lifespan 关闭会在浏览器 会话步骤之前 await MCPSessionPool.close_all(),受共享的关闭钩子超时约束且 尽力而为:超时或失败只记录日志,其余清理继续进行。(#5961)

  • TUI: 无头模式 deerflow --print 和 --json 运行失败时不再泄漏 Python traceback。此前会话创建、thread 解析和流式迭代没有任何错误边界, 任何抛出的异常——包括文档记载的 CheckpointModeMismatchError——都会从 main() 逃逸,且没有稳定的非零退出码。现在 --print 向 stderr 输出一行 Error: <message> 并以退出码 1 结束;--json 追加一条终态 {"type": "error", "data": {"message": ...}} 记录——即使流中途失败, stdout 每行仍是合法 NDJSON——并以退出码 1 结束。KeyboardInterrupt 和 SystemExit 仍照常传播。(#5963)

  • MCP: MCP 任务通知现在会在创建 Agent 运行之前原子地预留启动。此前 worker 会先启动 run 再持久化任何启动阶段,一旦所有权在这段副作用期间过期 或被回收,另一个 worker 可能并发启动同一条通知。预留是一次条件 UPDATE (begin_notification_launch),由 lease owner、每次认领的 token、租约到期 时间与 dispatch version 共同把关;未启动的陈旧快照遇到更新事件会在任何副作 用前被拒绝,恢复出的 launching 行保留原事件与幂等键。确定性的启动拒绝 (400、401、403、404、裸 409、422、501)立即进入死信,歧义的 408/429/500 或取消则保留 launching 快照等待对账,不消耗重试预算。无需数据库迁移。 (#5965)

  • MCP: 在 Web 编辑器中创建的 MCP 连接现在归属于登录用户,而非部署全体。 此前编辑器写入部署级配置,用户用自己的凭证添加的连接会变成全体共享的 Agent 能力,普通用户却无法维护自己的连接。新增仅限本人的 CRUD 接口 /api/mcp/personal/config,持久化到 .deer-flow/users/<user_id>/integrations/mcp.json;能力中心把部署指南与共 享连接归入 "Platform provided",个人连接归入 "My plugins";工具装配只组合 共享工具与当前用户的连接(个人发现不进入全局工具缓存),持久化任务会记录 归属范围,即使部署服务器名与个人运行时名冲突,提交、轮询与取消也沿用该范 围。个人 HTTP/SSE 端点只允许连接通过公网策略校验的地址,并保留原 Host、 TLS SNI 与证书校验;特权个人定义会在发现、调用与持久化任务的提交/轮询/取 消环节重新校验当前管理员身份(降级即失效)。config.yaml 工具与既有 extensions_config.json 条目仍为共享。(#5966)

  • 中间件: PII 脱敏改写用户消息时不再清空 response_metadata。中间件 此前用 content/id/name/additional_kwargs 手工重建脱敏后的 HumanMessage,凡内容被改写的消息,其网关来源等元数据都会被静默重置; 脱敏本身一直是正确的。现在从原消息复制、只替换 content(深拷贝,因为 未改动的 block 是按引用保留的)与 response_metadata,id、name 和消 息子类得以保留,且不与原消息共享可变状态。占位符与脱敏行为不变。 (#5968)

  • 智能体: 用户消息上调用方附加的 response_metadata 现在能在动态上下文 注入后保留。DynamicContextMiddleware 此前把副本重建为只含 content/id/name/additional_kwargs 的新 HumanMessage,其余字段 (包括 response_metadata)在进入模型的消息列表里被静默丢弃,而同链的 PII 脱敏与 thread-data 中间件早已使用 model_copy。现在改用 original.model_copy(update={"id": ...}) 构造副本,文档约定的 {id}__user 换 ID 语义不变;UploadsMiddleware 重写用户消息时也做了同样处理。(#5977)

  • 持久化: PostgreSQL DSN 查询串里的字面 + 在注入 schema 时不再被改 写。设置 database.postgres_schema 后,dsn_with_search_path() 此前用 parse_qsl/urlencode 往返处理既有参数,而 parse_qsl 会把 + 解码成 空格——这是 libpq 并不采用的 HTML 表单规则——于是 application_name=web+app 到达服务端时变成 web app, options=-c timezone=UTC+8 退化成 -c timezone=UTC 8,不再是一条完整的 -c 设置。现在既有参数按原字节逐字保留,只追加(或合并进)一条百分号编 码的 options=-c search_path=<schema>;关键字形式 DSN 与 postgresql+asyncpg:// 的规范化保持不变。(#5978)

  • 目标: 工件投递失败时活动目标现在会保留。此前一个 run 可以先写出并展示输出、得到 满足的 goal 评估、随即删除目标——之后投递校验才把同一 run 标记为错误(终止性投递 回执写入失败也会造成同样的不一致),让线程在尚有未完成工作时失去了目标。满足的 评估现在只是一个完成候选:worker 会等投递、回执持久化和终止性取消仲裁成功并确认 状态持久化成功之后才清除目标,同时在既有的 checkpoint 写入预留下重查目标、可见 对话和 checkpoint,让较新的已准入 run 获胜。任何失败都会保留原目标与计数器,且不 增加模型调用;正常完成(包括没有新输出的 run)仍照常清除目标。(#5980)

  • 配置: 以带 BOM 的 UTF-8 保存的 extensions_config.json 现在可以正常 加载。运行时加载器(ExtensionsConfig.from_file)与支撑 Gateway skill 开关的原始读改写读取器此前都按普通 UTF-8 打开文件,编辑器写入的 BOM 会以 Unexpected UTF-8 BOM (decode using utf-8-sig) 失败——既阻断 MCP/skill 配置加载,也让开关无法保存本就有效的配置。两个读取器现在都接受可选的前 导 BOM;写入仍是不带 BOM 的 UTF-8,原始 $VAR 引用依旧保留。(#5983)

  • 智能体: read_file 的非有限行号边界不再让运行在循环检测中崩溃。模型 输出的 start_line: 1e999 会被解析为 float("inf"),_coerce_line_number() 里的 int() 转换在 after_model 路径上抛出未捕获的 OverflowError,run 以 难懂的内部错误收场,而不是由 read_file 自身的 schema 校验返回正常的工具 错误。该转换器现在把 OverflowError 视为其他不可用输入:边界回退为 None, 调用按既有的开放式读取键处理;正常的整数边界行为完全不变。(#5990)

  • 集成: 远程 provisioner 模式下,完成 Lark 授权不再把集成面板的运行时 卡片翻成"未就绪"。此前变更响应只验证授权、从不探测沙箱运行时就绪,而 前端将其整体写入缓存,面板于是显示一个刷新页面才会纠正的状态。现在 安装、配置完成、凭据切换和授权完成共用同一个变更状态构造器,与 GET /lark/status 路由一致地探测运行时就绪,且构造发生在释放每用户 凭据锁之后(此前探测在持锁状态下运行,可能让其他凭据操作阻塞一个网络 超时的时长)。状态 API 新增 sandbox_runtime_probed,前端只在变更响应 报告未探测时保留先前确认的运行时字段。(#5994)

  • 渠道: Feishu 和 DingTalk 收到的附件现在可被沙箱读取。在 AIO/Docker 沙箱模式下,沙箱以非 root 用户访问 bind 挂载的 thread 目录,而这两个 渠道自行落盘的入站文件权限为 0o600——agent 拿到的 /mnt/user-data/uploads/<name> 路径读出来是空的,来自这两个渠道的附件 因此静默不可用。现在两者都在自行写入后立即授予 group/other 读权限,与 渠道管理器和 HTTP 上传路径一致(Telegram 已由 #5581 覆盖)。本地沙箱 行为不变。(#5998)

  • 数据库: DatabaseConfig 现在严格校验 pool_size、pool_recycle 与 command_timeout。此前 YAML 布尔值会被静默转换为 1/0: pool_size: true 会把连接池限到单个连接,command_timeout: true 相当于 1 秒的语句超时;pool_size 还接受非正数(0、-1),command_timeout 接受 inf(YAML .inf 或 "1e999"),等于永不超时。现在 pool_size 与 pool_recycle 强制要求正整数,command_timeout 拒绝布尔值与非有限浮点 数,null 仍表示禁用超时。(#6009)

  • 社区: Firecrawl 工具现在会关闭每次调用构建的异步 HTTP 连接池。异步化 改造之后,_get_firecrawl_client() 每次调用都会新建一个 AsyncFirecrawlApp,而它会立即创建一个由 httpx.AsyncClient 支撑的连接 池,且不公开任何关闭接口,于是每次 web_search_tool / web_fetch_tool 调 用都遗弃一个未关闭的池。两个工具现在都在 finally 中通过尽力而为的 _aclose_firecrawl_client 关闭它:缺少内部 _v2_client.async_http_client 句柄时退化为空操作,关闭失败只记日志,不会掩盖工具本身的结果或错误。 (#6013)

  • 能力中心: 管理员可以在能力中心再次管理共享 MCP 插件。#5966 之后 Platform provided 区块对管理员和普通用户渲染同样的只读视图,无法新增、编辑、 启用/停用或删除,而仅限管理员的 /api/mcp/config 接口本就支持这些操作。 管理员(非静态模式)现在在 Platform provided 下恢复 MCP 编辑器,改动写入对 所有用户生效的共享部署配置;普通用户仍为只读,My plugins 继续管理个人配置。 MCP 查询同时区分作用域与用户 ID,控件的可访问名称带上本地化的归属分区, 重名的共享与个人连接也能区分。(#6018)

  • 网关: 托管子代理的存储变更现在会在请求取消时排空。/api/subagents 的 create/update/delete 端点此前用裸 asyncio.to_thread() 承载持久化写入:取消请求 只会取消等待方,已启动的文件/SQL 存储变更可能在请求生命周期结束后仍在提交。三个 写入现在都经由既有的 await_drained() 辅助函数(封装为 _run_store_mutation),已 准入的变更先到达终态,调用方的取消随后才传播;目录读取与校验保持原有取消语义。 (#6023)

  • 网关: 仅管理员可用的托管模型保存在请求取消时现在会排空。PUT /api/managed-models 此前用裸 asyncio.to_thread(_save, body) 持久化共享的加密模型目录:取消请求只会 取消等待方,已启动的 ManagedModelStore.save() 可能在请求生命周期结束后仍在提交 目录状态。保存现在经由既有的 await_drained() 辅助函数排空;取消后发生的工作线程 失败只记录异常类型即被消费——存储错误可能携带凭据。目录读取与连接探测保持原有取消 语义。(#6024)

  • 配置: 一批此前会静默接受坏值的数值配置字段现在带上了范围校验与布尔 拒绝,与此前 stream-bridge 心跳和数据库连接池的守卫一致。 sandbox.port 限制在 1-65535,不再以晦涩的 Docker 端口绑定错误暴露; circuit_breaker.failure_threshold 要求至少为 1(0 会在检查本身就熔断); summarization.trim_tokens_to_summarize 要求至少为 1,不再用 max(1, ...) 把误配悄悄变成单 token 摘要; mcp_servers.*.tool_call_timeout/session_init_timeout、 models.*.stream_chunk_timeout 与 run_events.max_trace_content 拒绝会 让每次调用或流立即中断、把 trace 截成空的 0/inf; mcp_servers.*.oauth.refresh_skew_seconds 拒绝负数。以上字段均不再把 YAML 的 true 强转成 1;文档允许的取值(如 sandbox.idle_timeout: 0、 null 超时)保持不变。(#6026)

  • 前端: 在 Windows 上,pnpm dev 现在默认绑定 127.0.0.1。此前开发 服务器监听 0.0.0.0,当 Hyper-V/WSL2/winnat 保留了覆盖 3000 端口的端口 段时,启动会报 Error: listen EACCES: permission denied 0.0.0.0:3000,而 同一端口绑定 loopback 却能成功。显式传入的 -H/--hostname 照常透传, 需要局域网访问时可用 pnpm dev -- --hostname 0.0.0.0 (DEER_FLOW_DEV_ALLOWED_ORIGINS 用法不变)。默认 make dev 流程不受 影响——dev nginx 上游本就指向 127.0.0.1:3000——macOS/Linux 行为不变。 (#6034)

  • 网关: Windows 上的内置解释器修复提示不再显示双反斜杠路径。内置 MCP server 指向另一个 Python 解释器时返回的 HTTP 400 detail 用 {sys.executable!r} 插值路径,repr() 会转义反斜杠,提示因此显示成 'D:\\...\\python.exe'——既没法照抄进终端,放进它让你编辑的 JSON 里也是 噪音。现在 detail 会把路径渲染成可直接用于 server 配置的 JSON 字符串,并 附上供终端使用的原始路径;POSIX 路径只是引号来源变了。(#6040)

  • 渠道: Slack 入站文本现在会解码 Slack 一律转义的三种实体,像 if a < b && c > d: print("R&D") 这样的消息可按原样到达 agent,而不是 if a &lt; b &amp;&amp; c &gt; d: print("R&amp;D")。&lt;、&gt; 和 &amp; 在剥除 bot 提及之后解码——真实提及是原始的 <@U…>,而用户手动 输入的 <@U…> 到达时已转义、仍是纯文本——其中 &amp; 最后解码,转义 实体恰好只解码一次。此前被破坏的文本会误导 agent 推理,可能把 &lt; 写进文件,引用回复时还会被二次转义。出站转义不变。(#6045)

  • 沙箱: 带目录前缀的 glob 模式现在相对搜索根锚定,** 也变为递归。 path_matches——glob 工具与各 provider 的 grep glob= 过滤器共用的辅助 函数——此前使用 PurePosixPath.match,在 Python 3.12+ 上该方法从右侧匹配且把 ** 当作单段 *:src/**/*.py 会漏掉 src/top.py 与更深的文件,而 docs/*.md 还会误中 vendor/docs/b.md。含 / 的模式现在从搜索根开始逐段 匹配,** 跨越零个或多个目录;仅基名的模式仍在任意深度匹配。(#6046)

  • cli: 当运行以 LLM 错误回退收尾时,deerflow --print 和 --json 现在以退出码 1 结束。提供方故障不会被抛出——LLMErrorHandlingMiddleware 会把它们转成带有 deerflow_error_fallback 标记的 AI 消息——因此无头 运行在凭证过期这类场景下会打印回退文本并以 0 退出,调用脚本把一次失败 的运行当成了成功。两种模式现在遵循与抛异常相同的失败契约:--print 仍把回退文本写到 stdout,但在 stderr 追加 Error: LLM request failed (error_type=…, error_reason=…);--json 在 流式事件之后追加一条终态错误记录。运行中途出现回退、随后仍有真实最终 回答的情况依旧以 0 退出。(#6056)

  • 前端: 运行时长现在作为推理折叠区的标签,不再是单独的脚注行。此前 已完成的回答在回答上方显示一个泛化的推理标题,又在仅悬停可见的操作 工具栏下方显示一行时长,工具栏隐藏时就留下一段无解释的空隙。折叠区 现在显示 Took 31s(用时 31 秒)并带紧凑箭头,仍可用鼠标和键盘展开, 可访问名称中保留本地化的视觉隐藏推理提示,并绑定到答案气泡、处理过程 组和子代理组各自的所属运行;单独的时长脚注随之移除。没有时长的流式与 历史消息仍用泛化标签,恢复的历史推理默认折叠,实时推理仍在完成一秒后 自动收起。(#6057)

  • 沙箱: 沙箱中的每条 bash 命令现在都通过 DEERFLOW_USER_ID 携带已鉴权的用户 id。skill 脚本以 bash 子进程形式在沙箱内运行,无法触达 Gateway 鉴权上下文, 用户级 skill 因此无从得知当前服务的用户(issue #3919)。现在命令会加上 export DEERFLOW_USER_ID=<quoted> 前缀(id 不可用时为 unset DEERFLOW_USER_ID),POSIX 本地与远程沙箱两条路径均生效,并位于既有 DEERFLOW_CHANNEL_USER_ID 前缀之前;Windows 则改为通过子进程环境注入。该变量 仅供参考——命令可以覆盖它——鉴权仍由服务端解析。(#6058)

  • 渠道: 被拆成多条连续消息的长 Discord 回复现在可以精确还原。此前 _split_text 在拆分点的换行符之前结束当前块、再从剩余文本开头剥掉该 换行符,每个 2000 字符边界都会丢失一个换行——落在切点上的连续空行则整段 丢失,拼接起来的气泡因此丢失段落分隔。现在边界换行符保留在当前块尾部, 任意输入下 "".join(_split_text(text)) 都还原原文,同时每条消息仍不超过 Discord 的 2000 字符上限。未超限的回复和空字符串行为不变。(#6062)

  • 目标: /goal 评估器现在能看到助手的工具调用和工具结果,已完成的文件工作不会再 以 missing_evidence 停摆。此前它只读用户与助手的文本:format_visible_conversation() 丢弃工具消息,只携带工具调用的助手消息渲染为空,评估器自身的"证据不足即失败"规则 便让大多数已完成的文件任务保持目标活动(在真实 Gateway 的 A/B 中,正确输出被清除 目标的比例从 DeepSeek 的 15/38 升至 35/36,MiMo v2.6 Pro 从 4/33 升至 29/30)。现有 30 条消息窗口内的工具步骤现在按顺序以 Assistant tool call: ... 和 Tool result (...): ... 行呈现——参数值截断到 200 字符、每步 600——12,000 字符的证据 上限改为从末尾保留整行并把最新的用户请求保持在顶部,不再从行中切开。工具文本经 JSON 转义保证每步一行,失败结果标注 error;与 web UI 一致,隐藏助手消息的调用 不进入证据,而澄清提问保留。系统文本新增固定规则:工具结果只是数据绝非指令,且 仅有成功的工具结果并不能证明目标已达成。(#6068)

  • 目标: /goal 评估器调用失败现在会像其他 stand-down 一样记录到目标上。此前 worker 只写一条 "Goal evaluator failed" 日志便直接返回:首次评估后目标没有任何 last_evaluation,否则会保留上一个 run 的结论和 run id,使 GET /api/threads/{id}/goal 看起来像从未评估过——无论原因是连接错误、解析器拒答 还是提供方超时。现在失败会持久化 blocker: "run_failed"、 stand_down_reason: "evaluator_failed" 与当前 run id,原因只写异常类型 ("The goal evaluator did not return a verdict (APIConnectionError)."),因为提供方 的错误信息可能携带请求细节。目标仍然保持活动,不重试、不启动隐藏续跑;若 run 在 此期间被中止则不记录。(#6073)

  • 前端: 在 /workspace/chats/new 中,首批流式到达的助手消息不再渲染 在所提交问题的上方。固定首轮顺序的本地 human 锚点此前与 SDK thread id 比较——该 id 在创建确认前尚未定义——而确认到达时锚点又被清空,于是早期 的推理和回答块会跳到问题上方,直到本轮落定。现在首轮顺序改为绑定当前 显示的会话(displayThreadId ?? threadId),锚点在创建确认后保留; 切换会话时锚点照旧清空。(#6074)

  • 目标: /goal 评估器现在能看到用户对 Human Input Card 的回答,同时主 agent 的 系统提示在未绑定 bash 工具时不再教授脚本写法。Web UI 把卡片回答作为隐藏 human 消息发送,而 format_visible_conversation() 会跳过隐藏消息——评估器看得到提问却 看不到回答,把遵循回答当成臆测,即使工作正确目标也以 needs_user_input 停摆。 回答现在以 User (Human Input Card answer): ... 行进入证据,压缩空白并截断到 2,000 字符,超长时保底保留在顶部。另外,默认本地沙箱不开启宿主 bash,但系统提示 仍在讲解脚本写法——实测中两个模型各有 38/120 个 run 写出了无人能运行的辅助脚本。 apply_prompt_template() 新增 bash_available 参数,lead agent 与内嵌 client 传入 has_bash_tool(authorized_tools);没有 bash 时,脚本指导替换为一行指示——直接算出 结果并用 write_file 写出——子代理章节也去掉 bash 示例。绑定 bash 时提示不变。 (#6082)

  • 网关: agents 路由的持久化写入现在会在请求取消时排空。POST /api/agents、 PUT /api/agents/{name}、DELETE /api/agents/{name} 和 PUT /api/user-profile 此前用裸 asyncio.to_thread 承载存储写入:断开连接会取消 handler 任务,要么取消 尚未开始的 worker——写入静默丢失——要么与已在运行的 worker 脱钩,其失败被静默丢弃 (handler 的 except,即 500 加错误日志,永远不会执行)。四个写入现在统一经由基于 既有 await_drained() 的 _drained_write 辅助函数:已启动的 worker 先完成,调用方 的取消随后才传播;丢失的失败只记录异常类型,而 AgentExistsError 等预期领域错误仍 会以 409 返回给仍在连接的调用方。读取保持可取消语义——放弃它们不会有任何损失。 (#6087)

  • 前端: APNG、AVIF 和 WebM 工件在工作区工件列表和查看器中现在显示 对应的媒体图标,而不是通用文档图标。这三种格式本可在浏览器中预览,但 getFileIcon 没有对应的扩展名分支:apng 和 avif 现在映射到图片 图标,webm 映射到视频图标,与既有预览支持一致。未知扩展名仍回退到 文档图标。(#6089)

  • 前端: 保存的 Dockerfile 和 Makefile 工件现在可以作为源码打开并 编辑,而不再落入不支持的文件下载视图——尽管这两个名字本就在语言映射表 里。getFileExtension 此前对整个路径按 . 切分而不是先取基名,无扩展名 的名字——或位于带点父目录下的文件——永远解析不到对应的映射项。类型识别 现在基于基名进行;既有的后缀名和隐藏文件映射保持不变,流式写文件预览 也仍使用自己的文本回退。(#6091)

  • MCP: 个人 MCP 配置写入现在会等已受理的变更落盘,再向调用方传递取消。 此前写入路径用裸的 asyncio.to_thread(_mutate, ...) 执行持久化文件变更, 请求在 worker 启动后被取消时,路由可以直接退栈,而已开始的持久化变更仍在 运行、缺乏请求侧的生命周期归属。现在变更经取消安全的 await_drained() 辅 助函数排空:路由存活到已开始的写入结束,随后原始 CancelledError 照常传 播。只读路径保持既有取消语义。(#6093)

  • 沙箱: 本地容器后端的所有文本模式 subprocess.run 调用现在固定 encoding="utf-8" 与 errors="replace"。在非 UTF-8 区域设置的主机(如 cp936 中文 Windows)上,此前按平台默认编码解码会丢失整段输出—— stdout/stderr 返回 None,导致 JSON 解析调用点崩溃或静默返回空结果。 (#5905)

  • 上传: 取消上传现在会先等待已开始运行的文档转换工作线程结束,再删除其 临时源文件,清理不会删掉转换器仍在读取的文件。因此取消的耗时可能与转换本身 一样长;取消仍会向外传播,且被取消的转换不会写出 Markdown。(#5999)

  • 智能体: 读前写拦截消息现在会给出目标文件的行数和具体的 read_file 范围建议,取代笼统的"先读最后约 30 行";倒置的范围现在返回 (invalid line range: start_line must be <= end_line),取代容易被模型 误解为"文件比 start_line 短"的"no lines in range"文案。带内无内容的 read_file 结果也不再盖读标记,避免误开写入门。(#6020)

  • 前端: 工件编辑现在要求完整文件内容:截断的字节范围预览不再被用作编辑 基线(预览与完整响应可能携带相同 ETag),修复了"还原编辑仍提示未保存"、 "删除预览之外的内容会被悄悄恢复"两类问题。已有草稿可跨部分重载保留,重载 处于加载、截断或失败状态时"退出编辑"仍然可用。(#6036)

  • 模型: vLLM 提供方在非流式响应、流式增量及后续工具调用轮次中,若 reasoning 字段缺失或为 null,会回退读取旧版 reasoning_content 字段, 旧版 vLLM 兼容端点返回的推理内容不再丢失;两个字段同时存在时保留 reasoning。(#6048)

  • Discord: 缺少 discord.py 时,Discord 频道的启动报错现在指向可选依赖 extra(uv sync --extra discord),而非会改写 pyproject.toml 的 uv add discord.py——启动时会以 uv sync --locked 重新同步整个依赖树。 (#6054)

  • Telegram: 出站 Telegram 消息的分段(及流式预览截断)现在按 UTF-16 代码单元计量——Telegram 的 4096 字符上限正是以该单位计算——而非 Python 码点。此前表情密集的回复虽通过码点检查却超出真实上限,收到确定性的 400 "Message is too long" 并在重试后丢失;非 BMP 字符也不会再被拆到两个 分段中。(#6067)

  • 技能: 自定义技能变更(编辑、删除、回滚、安装、启用/停用)现在会在 请求取消时先排空持久化尾部:写入、历史记录与技能提示词缓存刷新会一起 完成后再传播取消,已取消的请求不会再留下缺少对应历史记录的技能文件或 过期的提示词缓存。(#6078)

  • 子智能体: return_direct=True 工具(包括扩展中间件贡献的工具)的 结果现在会返回给调用者,不再被前置的助手消息顶掉:直返输出按工具调用顺序 返回;直返批次中任一工具失败时,任务会按既有 subagent_error 契约标记为 失败,同时保留批次输出(包括成功的同级结果)。(#6083)

  • 记忆: 记忆变更接口(清空、事实的创建/更新/删除及导入)现在通过 await_drained 在请求取消时先排空持久化写入:已提交的写入会先完成再传播 取消,不会在存储工作线程仍在运行时直接中止。(#6092)

  • 路径: 创建宿主机可见路径时,现在会在所有操作系统上拒绝 Windows 保留 设备名(CON、PRN、AUX、NUL、COM1-9、LPT1-9)以及以空格或点 结尾的路径段,覆盖上传文件名、本地沙箱工具与 bash 路径、自定义技能支持 文件路径。已以此类名称存储的现有文件仍可读取和编辑。(#6102)

  • 前端: 网页抓取结果标题不再显示结尾的 ATX 闭合井号:# Release Notes ### 在工作区链接中现在显示为 "Release Notes"。字面井号会原样保留(如 C#、转义井号、后跟非 ASCII 空白的井号),提取改为一次向后扫描,避免 长标题上的二次回溯。(#6103)

  • 智能体: 外部化的工具输出现在通过兄弟临时文件写入、再原子重命名发布: 写入中途失败(磁盘写满、请求中断)时不会在最终文件名下留下半截文件。 外部化文件名改为由清洗后的 tool call id 派生(而非随机后缀),宿主机与 沙箱路径对同一次调用使用同一文件名,重复外部化同一输出也不再遗留两个 文件。(#6109)

  • 认证: 当 IdP 签发的 ID token 携带非 ASCII 或非字符串的 nonce 声明 时,OIDC 登录不再以 500 失败。这类声明会走正常校验路径被拒绝,回调与其他 nonce 不匹配一样重定向到 sso_failed;匹配的非 ASCII nonce 仍按原样 接受。(#6115)

  • 沙箱: Lark broker 模式下,AIO 的一次性 Bash 运行会关闭继承的管道 stdin,读取默认输入的命令立即得到 EOF 而不再挂起;持久 Shell 命令不受 影响,显式管道、heredoc 与文件重定向仍正常提供输入。broker shim 仅在 EOF 后转发管道/文件输入,输入空闲超过窗口时以退出码 124 中止且不执行 (DEERFLOW_LARK_BROKER_STDIN_GRACE_SECONDS / DEERFLOW_LARK_BROKER_STDIN_TAIL_SECONDS,默认 2 秒);broker 日志只记录 参数个数、退出码与耗时,不记录参数值。(#6117)

  • 上传: 匹配保留暂存命名 .upload-*.part 的文件名现在会在写入任何 文件或获取沙箱之前被拒绝,返回 400 并提示改名;整批文件先检查后落盘, 保留名称不会再留下与临时暂存文件冲突的部分上传。嵌入式 SDK 在复制前 校验整批文件名并抛出 ValueError;项目资料库遵循同样限制,附加带保留 名称的旧资料库文档现在返回 400 及改名提示,而非 500。 行为变化: 此前可上传的这类文件名需要先改名再上传,已有的旧上传 文件不会被迁移。(#6122)

  • 前端: 重新持久化的消息保持在会话中的原位置。此前去重时,以更晚日志 序号重新持久化的历史行会把消息推向会话末尾;现在保留副本会重新锚定到该 身份最早出现的序号,并优先取最早的可见副本,隐藏的控制行不会带动其可见 副本移动。内容仍收敛为最新副本。(#6128)

  • 前端: .jl 文件重新解析为 Julia 语言。此前代码文件扩展名映射把 Julia 按语言名而非 jl 扩展名作为键,导致 Julia 文件无法命中代码文件 检测,编辑器收到的语言标识是扩展名 jl 而非 julia;映射现在同时提供 jl 与 julia 两个键,均指向 julia。(#6130)

  • 持久化: RunEventStore.list_messages 现在同时遵循两个游标:同时给出 after_seq 与 before_seq 时,memory、JSONL 与数据库后端都会先应用两个 开区间边界再裁剪 limit,按升序返回窗口内最早的消息。此前 JSONL 后端在 设置 before_seq 时会忽略 after_seq 并返回窗口末尾;固定 before_seq 并前移 after_seq 即可在有界历史范围内向前翻页。(#6136)

  • 路径: 上传、新建技能支持文件和新的本地沙箱路径现在在所有平台上拒绝 Windows 上标数字设备名——Windows 将 COM¹/LPT¹ 至 COM³/LPT³ (含 com².txt 之类的带扩展名形式)视为保留设备,此前以此类名称创建的 文件在 Windows 上不可用。COM⁴.txt、COM¹notes.txt 等普通名称仍允许, 读取与删除路径保留原有豁免。(#6148)

  • 上传: 保留暂存名检查现在同样拒绝 Win32 别名:去掉末尾点号或空格、 忽略大小写后会变成 .upload-*.part 的名称(如 .upload-notes.part.、 .upload-notes.part 、.UPLOAD-NOTES.PART)会在整批文件落盘前以 400 拒绝——否则 Windows 会将其作为同一个暂存文件打开。项目资料库的上传、 晋升与附加沿用同一检查;磁盘上已有的旧别名文件仍可见,不会被清理。 (#6149)

  • 智能体: 并发外部化同一工具输出时不再共享同一个临时文件。每个写入者 以独占方式创建各自的 .tool-output-*.tmp 兄弟临时文件,关闭后通过原子 重命名发布,互相不会截断或删除对方的待发布输出,最后成功发布者的完整 内容胜出。发布文件沿用 Gateway 正常的 umask 权限,以其他 UID 运行的挂载 沙箱仍可读取;非正常停机仍可能留下 .tool-output-*.tmp 残留,系统不会 自动清理。(#6150)

  • 前端: 名为 constructor、__proto__ 或 data.constructor 等点分形式的 工件文件不再被误判为代码文件:语言检测现在只检查语言映射自身的键,而不是 其继承属性,这些文件名继续走现有的下载回退,而不会在打开编辑器时失败。 (#6152)

  • 模型: Codex 提供方在收到 response.completed SSE 事件后立即返回已完成 的响应,不再等待连接关闭,服务端保持打开的流不会再把已完成的回答变成读取 超时失败。response.failed、response.incomplete 与 error 事件现在会携带 提供方的错误码/消息或不完整原因抛出,部分输出不会被当作成功答案返回。(#6155)

  • nginx: 通过 http://127.0.0.1:2026 或 http://[::1]:2026 访问时,现在会以 301(仅限安全方法)重定向到 localhost 上的同一 URL,这样在一个回环地址下 创建的登录会话在另一个地址下也可见。此前浏览器按主机名隔离 Cookie,会把 会话拆分到两个源。API 调用与 WebSocket 握手原样通过,非回环主机名不受 影响。(#6158)

  • 配置: 面向 Google 官方 OpenAI 兼容端点的 Gemini 示例配置现在改用标准 reasoning 契约(thinking: required、dialect: none、受支持的 reasoning_effort 取值),运行在 gemini-3.1-pro-preview 上;旧示例发送的 顶层 thinking 字段会被端点以 HTTP 400 Unknown name "thinking" 拒绝。 文档中的迁移说明覆盖了从旧示例复制而来的配置。(#6161)

  • sandbox: Tenki 云沙箱提供方现在要求 SDK 1.4.0+,该版本可能在退出码为 零时返回 timed_out=True。超时命令现在会保留部分输出并报告 Error: command timed out 与 Exit Code: 124,不再伪装成功;命令不会重试, 沙箱仍可继续使用。预热池健康探测即使输出中含有 ok 行,也会拒绝超时或 失败的探测结果。(#6162)

  • 上传: 转换上传文档的大纲与预览现在除设备号、inode 和大小外,还会校验 原文件的修改时间戳,因此对已上传文档做等长覆写后,不再展示描述旧内容的 大纲。行为变更: 缺少原文件时间戳的旧归属记录会被拒绝——启用 uploads.auto_convert_documents: true 后重新上传原文件即可恢复转换;文件 本身仍保留,未通过校验的 Markdown 会在历史列表中作为独立文件显示。(#6165)

  • tui: TUI 输入框现在是多行编辑器,粘贴的代码、堆栈跟踪和多段 prompt 会保留换行与缩进,Enter 发送完整内容。上/下方向键先在输入框内移动 光标,到首/末可视行(含软换行)时才切换到历史输入;斜杠命令面板与 PageUp/PageDown 转写滚动保持不变。(#6168)

  • 配置: tool_output 的字符/条数预算与所有 tool_overrides 每工具取值 现在都要求非负整数:YAML 布尔值会被拒绝,而不再被强转为 0/1;负数 override(此前例如 tool_overrides: {web_fetch: -1} 会静默关闭该工具的 外置)现在会让配置加载报错,并指明字段与工具。数字字符串、整数值浮点数 与显式 0 仍然有效;每工具 override 为 0 时仍只关闭外置。(#6172)

  • telegram: 富文本消息的长度检查现在按 UTF-16 码元(Telegram 实际执行的 计量方式)衡量回复,而不是 Python 码点。此前表情符号较多的回复按码点数 能通过检查,却被 Telegram 拒绝并退回分段纯文本,丢失回复的 Markdown 排版,还浪费一次 API 往返。(#6174)

  • 社区: Firecrawl 与 fastCRW 的 web 搜索工具现在会先把配置的 max_results 规范化再交给搜索客户端。此前该 YAML 值未经验证就直接作为 请求的 limit 发出;布尔值、3.5 这类小数、零、负数、非数字字符串和 显式 null 现在会记录警告并回退到默认值 5,而 "8" 这样的整数字符串 仍会被接受。(#6175)

  • 前端: HTML 工件预览现在会忽略并非真实元素的 <base> 文本。此前 用正则判断文档是否已有 base 标签,注释、脚本文本或 <template> 内的 base 标签也会命中,导致这些报告跳过注入的工件基础 href,相对资源改为 按应用源解析,图片与样式随之失效。现在检测在惰性 <template> 中解析 文档,只认可真实的 <base> 元素;不含 base 文本的文档完全不构建 DOM。 (#6176)

  • Artifact: CSV/TSV 预览现在能正确处理字节区间在 CRLF 行尾中间截断的 样本。此前截断样本恰好切在 CRLF 中间时,解析器会把引号字段后残留的 CR 当作语法错误并拒绝整个预览;现在解析前会去掉这个多余的 CR,完整行 照常渲染,不完整的末行仍按原样丢弃。先 LF 后 CRLF 的文件同样覆盖; 纯 CR 行尾的完整文件与真正的引号错误行为不变。(#6180)

  • 前端: 输入框中的行内引用现在可用 Backspace 或 Delete 整体删除,原生 撤销也能恢复。已完成的引用是 contenteditable 对象;光标相邻时,删除会先 选中整个对象,由浏览器一步移除,撤销也能原样找回,包括对话引用的智能体 绑定。删除引用的 @ 分隔符后移动光标不再重新打开提及选择器,后续编辑 也会保留对话引用的标题与智能体绑定。(#6184)

  • 调度器: 定时任务的 PATCH 请求携带显式 assistant_id: null 时,任务 现在会重置回 lead_agent。此前更新处理器用 exclude_none=True 构建变更 集,null 被静默丢弃,任务一旦绑定自定义智能体便无法解绑。现在按请求 实际发送的字段判断:显式 null 重置为 lead_agent 且不再解析智能体配置; 省略该字段则保留当前智能体,即使该自定义智能体已被删除。 (#6185)

  • 配置: title.max_words 与 title.max_chars 现在拒绝 YAML 布尔值。 此前 Pydantic 会在范围校验前把 true 强转成整数 1,紧邻布尔开关写串的 true 会悄悄把生成的线程标题限制在一个词或一个字符;现在两个字段都会 在校验时报错 "must be an integer, not a boolean"。"80" 这样的数字 字符串仍然有效。(#6187)

  • 上传: 上传 Markdown 的大纲不再把缩进的粗体行当作标题。转换得到的 PDF 文本常包含 **PART II** 这类示例;四空格或制表符缩进的行此前 会被识别为粗体标题,把真实章节挤出标题预览,并在正文出现之前耗尽大纲 配额。现在所有标题样式都会跳过根级缩进代码,而保留最多三个前导空格的 PDF 风格粗体标题。(#6194)

  • Artifact: 输出文件被外部原子替换后,工件内容摘要现在会刷新,即使 替换保持了相同的大小与修改时间。此前预览/保存用的 SHA-256 按路径、 mtime 和大小缓存,沙箱同步这类外部替换会沿用陈旧摘要,刷新预览也拿不到 可用于保存的新版本。缓存键现在包含文件身份(设备、inode 与变更时间); 超过 2 MiB 编辑上限的常规文件改用基于 stat 身份的 ETag,不再为每个 区间请求整文件哈希;条件区间请求必须匹配 ETag,日期形式的 If-Range 会收到完整的新文件。(#6195)

  • 频道: 配置或断开频道运行时凭据时,现在把 runtime-config.json 的持久化写入与运行中 Gateway 内存缓存 app.state.channels_config 的同步视为同一个提交单元:请求的 CancelledError 只在持久化更新和内存缓存都落定后才送达,客户端在提交中途取消也不会再 造成磁盘状态与运行中的 Gateway 不一致。(#6198)

  • 微信: 微信扫码登录的每次认证状态写入都会等待落盘:iLink 返回 confirmed 后,先把 确认的 bot_token 与 bot id 写入 wechat-auth.json,扫码任务的取消才会继续传播;内存中 的凭据不会再先于持久化副本暴露。(#6199)

  • 网关: 创建运行不再在事件循环上导入 lead-agent 代码栈:start_run() 改为通过专用 assembly 线程池解析 agent 工厂,启动后首次运行(导入需数秒)不会再卡住并发的其他请求、 SSE 流和频道消息;工厂导入失败仍会阻止运行被受理。(#6206)

  • MCP: 五个 MCP 配置端点(更新配置、创建/更新/删除 server、切换 server 状态)现在把 extensions_config.json 的读-改-写与 MCP 工具缓存重置作为一个取消感知单元执行:被取消的 请求不会再脱离持有配置锁的 worker,也不会跳过缓存重置(此前可能让工具缓存与新配置不一致); 两者落定后取消才继续传播。(#6210)

  • 通知: 通知投递的认领现在带有按次生成的 fencing token。当卡住的 worker 在 stale-sending 超时把投递重新分配后恢复时,它迟到的 mark_sent/mark_failed 结果会被 忽略,不会再把当前 worker 正在 sending 的投递改回 pending 并多消耗一次重试;投递离开 sending 状态时认领 token 会一并清除。(#6211)

  • 频道: 飞书和钉钉频道重启不会再遗弃仍然存活的 SDK worker。当 stop() 的有界 join 超时而 provider 线程仍在运行时,频道会报告停止失败(ChannelStopTimeout), ChannelService 保留旧实例并推迟替换,不再把频道当作已停止;该卡死会记为 WARNING 日志, 而不是每次重试都输出完整堆栈。(#6215)

  • 运行: thread 与 stateless 的 wait 端点现在会对已完成的失败运行返回 SDK 兼容的错误 信封。此前在写入新检查点前就失败的运行会把上一次的回答留在 thread 头部,等待它返回的是 过期答案且 SDK 不抛错;现在等待会报告当前运行的 status: error,Python LangGraph SDK 默认将其抛出(传 raise_error=False 可改为查看状态与错误)。(#6217)

  • 智能体: lead agent 的 token 预算不再漏计已完成的子任务。在 token_budget.enabled 与 token_usage.enabled 都开启时,已完成子任务的用量会在预算校验前回填到派发消息上(包括 最后一批),运行会在配置的预算处停下而不是超支;当前步骤的用量归属仍位于各守卫之后, 终态结果会被标记以避免重复计入。(#6218)

  • TUI: 在输入框中按 Down 时,若输入历史导航未激活,不会再清空未发送的草稿;从历史 恢复草稿后连续按 Down 也不会丢弃之后的编辑。切换到较新历史条目的正常导航与草稿恢复 (包括空草稿)行为保持不变。(#6219)

  • 沙箱: 当 warm-pool 空闲回收线程仍在执行清理时,关机不再拆除沙箱 provider。若回收 线程在 5 秒 join 后仍存活,_stop_idle_checker() 会判定失败,AIO、BoxLite、Tenki、 OpenSandbox 四个 provider 会重置关机守卫使关机保持可重试,而不会在回收线程仍占用资源时 关闭 provider、再把失败的关闭当作已完成。(#6231)

  • 模型: Codex 适配器在转换消息时现在会省略 tool_call_id 为空或纯空白的工具结果, 避免把此类无效的 function_call_output 发给 provider;非空 ID 原样保留,输入历史不会被 改写。(#6234)

  • 沙箱: E2B 关闭在维护线程仍存活时保持可重试:提供方现在会先确认租约续期与 对账线程确实退出再执行清理;若任一线程超出 join 等待仍在运行,则保留全部已 登记的沙箱状态、继续封闭准入(acquire 返回 reason: shutdown),由后续的 shutdown() 完成清理。信号处理器与 atexit 只记录待完成的清理而不抛错,信号 触发的关闭仍会转发原始的 SIGTERM/SIGINT/SIGHUP 动作。(#6244)

  • 技能: SkillScan 的 secret-env-assignment 规则现在会报告通过解包绑定的 Python 秘密——元组与列表目标按位置与右侧值配对,host, api_key = "internal", "sk-…" 不再绕过该门禁——并逐一检查列表、元组、字典与条件表达式中的字面量。 字典键只作为结构标签:仅当键本身匹配已知的令牌格式才报告,而持有运行时取值的 "access_token" 这类标签不会被误报。(#6247)

  • TUI: 输入历史导航在内容没有变化时不再破坏草稿、光标与撤销历史:没有输入 历史时按 ↑ 不再改动输入框;召回的历史条目(或恢复的已存草稿)与当前输入相同 时跳过重新加载,保持光标位置与撤销状态。(#6248)

  • TUI: 运行进行中现在会保护会话切换:/resume、/threads 与 /switch 会 提示等待,而不是切离正在输出的会话;线程选择器在应用选择时会重新检查运行状态; 中断并切换后,来自旧线程的迟到流事件会被丢弃。无效的 /resume 引用只显示错误 行,当前会话与 TUI 本身保持可用。(#6249)

  • 产物: 保存已编辑产物时,对现有文件的读取限制在 2 MiB 编辑上限加 1 个检测 字节以内;大小检查之后文件若被增大或替换,会以 413 拒绝保存,而不是把超限文件 整体读入内存。(#6250)

  • 网关: 多 worker 启动安全门禁现在也会读取 uvicorn 的 WEB_CONCURRENCY—— 在不传 --workers 的启动方式(backend/Dockerfile、scripts/serve.sh)中, worker 数由它决定。行为变化: WEB_CONCURRENCY>1 且未用 Postgres 数据库、 启用了 scheduler 或浏览器工具的部署现在会启动失败,而不是在门禁失效下多进程 运行;每条拒绝信息都会指出实际生效的变量。(#6252)

  • 网关: 其余的 worker 数读取方现在与启动门禁使用同一解析逻辑:渠道入站去重 存储的警告与运行时浏览器门禁在不传 --workers 时也会尊重 uvicorn 的 WEB_CONCURRENCY,其警告与拒绝信息会指出实际生效的变量。(#6253)

  • 上传: Markdown 大纲识别不再把加粗分段的财务表格行误判为标题:当节号之后 的任何块仅由数字、标点或货币符号组成(括号年份、带符号数值、$€£¥ 金额)时, 该行会被跳过,这类行不再挤占大纲的标题配额。含实际文字的标题——包括带标点或 非 ASCII 的标题——仍然可以识别。(#6266)

  • 技能: eval 固定样例路径的识别现在会检查包路径中的每一段 evals/fixtures/, 而不只是第一段 evals:位于 evals/cases/inner/evals/fixtures/ 下的样例文件 会被 SkillScan 豁免,不再以嵌套 SKILL.md 为由阻断整个包。非样例的嵌套 SKILL.md 仍为 CRITICAL。(#6271)

  • 渠道: 流式 IM 渠道收到后端 error 事件帧时,现在走与传输故障相同的路径: 渠道会记录错误类型、消息与线程 ID 便于诊断,回复通用错误文案,并且只在发出该 最终回复之后才释放入站去重键,使平台重新投递可以重试,而不是把失败的运行当作 普通的无回复完成。(#6272)

  • 扩展: agent-teams 的响应大小门禁改为按字符串的原始 UTF-8 字节、结构化中断 响应的 UTF-8 JSON 计量:中文或表情符号答案只要不超过文档规定的 8,000 字节上限 就会被接受,不再因 JSON 转义放大计量的体积而被拒绝。含未配对 Unicode 代理对 的响应会得到正常的校验错误,等待中的任务保持不变。(#6274)

  • 技能: SkillScan 的 secret-env-assignment 文本扫描现在会报告键名带引号的 凭据:JSON 中的 {"api_key": "…"} 与 YAML、.env、shell 写法同等识别;凭据 词前带分隔符的键(access_token、client_secret、MY_API_KEY)同样匹配。 仅包含关键词的普通单词(tokenizer、secretive)保持不报。(#6276)

  • 模型: CLI 凭据文件(Codex auth.json、Claude Code 凭据)现在按 UTF-8 解码 并容忍 BOM,不再依赖主机区域设置:包含非 ASCII 文本的有效 JSON 文件在 cp1252 类主机上不再读取失败。无效编码按不可读来源处理——Claude Code 在覆盖文件无效时 仍会回退到默认文件——make doctor 也接受相同的文件。(#6277)

  • 路径: 上传文件、新的本地沙箱写入路径和新的技能支持文件现在也会拒绝 Windows 控制台设备别名 CONIN$ 与 CONOUT$(全平台生效)。共享的保留名检查 在首个点号前不区分大小写,因此 conin$.txt、assets/CONOUT$/icon.png 会被 拒绝,而 CONIN$notes.txt 这类普通名称仍可使用。(#6279)

  • 技能: SkillScan 现在按解析后的 URL authority 而非正则截取来分类 HTTP 端点:http://LOCALHOST:8080 这类大写本地主机不再被误报为外部明文 HTTP, 括号形式 IPv6 回环([::1])按本地处理而外部 IPv6 端点仍触发网络发现, userinfo 无法隐藏真实主机(http://localhost@Example.COM 仍视为外部)。 云元数据主机名检测同样不区分大小写。(#6280)

  • 脚本: 手动导出 Claude OAuth 令牌的 export_claude_code_oauth.py 现在会 在任何导出动作前校验 Keychain JSON:容器必须是对象,claudeAiOauth 须为 对象且其 accessToken 为非空字符串。此前损坏的容器会直接抛出 traceback, true 等非字符串令牌会被原样导出;现在两者都以既有的令牌缺失错误失败, 且不会回显容器内容。有效令牌原样导出,不做裁剪。(#6281)

  • 沙箱: BoxLite 关闭时会先等待进行中的沙箱获取完成再拆除私有事件循环, 并拒绝关闭期间新到的获取请求。若进行中的获取在 5 秒内未结束,关闭会失败并 保留仍被占用的资源、可以重试,而不再让循环线程静默运行。(#6283)

  • 浏览器: Live 浏览器流现在会在释放浏览器会话租约前,跨越取消等待自身 负责的 stop_screencast 清理完成。清理中途到达的网关关闭或客户端取消不再 会遗留 screencast 管理器 pin、占用浏览器会话容量;清理完成后会重新抛出首次 取消原因。(#6284)

  • 项目: 项目文档架上的显式名称——上传的 name 与提升的 shelf_name—— 现在在任何字节落盘前都要经过与普通上传相同的 normalize_filename 校验。含 NUL、Windows 保留设备名(如 CON.txt)、结尾点号等不可移植名称会以 400 被拒绝且不留暂存文件;既有文档行在读取时不重新校验。(#6287)

  • acp: ACP 代理的 timeout_seconds(默认 1800)现在是一个覆盖初始化、 会话创建与 prompt 的共享预算,自子进程启动后计时——此前它只约束 prompt, 在 initialize 或 new_session 阶段挂起的代理会让工具调用无限阻塞。超时后 DeerFlow 会中止调用并终止子进程;工作区/MCP 准备与子进程清理不计入该预算。(#6292)

  • 技能: SkillScan 的 shell-env-dump 规则现在只在 env、printenv 或 export -p 出现在 shell 代码的真实命令位置时才触发。此前脚本中这些词的任何 出现都会报该发现,因此可移植 shebang #!/usr/bin/env bash、 https://env.example.com 这类 URL、--env 之类旗标、参数(echo env)、 变量(${env})、注释以及 heredoc 正文都会被误报为环境转储。(#6297)

  • 代理: 在 agent-teams 扩展中,被未回答的澄清提问或中断阻塞的排队任务在 等待期间不再占用全进程共享的 8 个活动任务预算,因此一个团队停滞的会话不会 让其他团队的任务一直排队。已准入的任务——包括启动确认丢失的——仍会占住 其槽位。(#6298)

  • 代理: 模型生成的 write_todos 调用若带有畸形 status(例如列表或字典), 不再导致 token 归属崩溃。计划模式的 token 归属现在会在集合成员判断前跳过 非字符串 status,由工具层返回其常规校验错误——代理可以据此纠正——而不是 让整个运行中断。(#6299)

  • tui: 中断之后,上一个运行的迟到流动作不再泄漏到下一个运行的显示、 用量、标题或完成状态——即使下一条提问使用同一会话。每个运行现在拥有自己的 取消标志与投递身份;若运行无法启动,TUI 会报出可重试的错误并回到空闲状态, 而不是卡在忙碌状态。(#6302)

  • 频道: 每个频道的生命周期变更——就绪启动、重启、运行时配置与移除—— 现在都在同一把按频道划分的锁上串行执行;此前就绪检查使用单独的锁,重启、 配置与移除会与之竞争。并发提交的配置现在以 epoch 计数,过期的快照不会再在 启动途中覆盖较新的凭据。(#6304)

  • 上传: 伴随记录现在能解析大写后缀的转换 Markdown 文件:转换出的 report.MD 不再因 .md 后缀比较区分大小写而丢失其 Markdown 伴随文件与 提取的提纲,也不会回退到原始文件。(#6308)

  • 微信: 出站图片与文件现在在读取时即执行 max_outbound_image_bytes / max_outbound_file_bytes(默认 20 MiB 与 50 MiB),包括解析之后仍在增长的文件。超限的读取会在加密/上传前被拒绝并 记录跳过日志,而不会发出截断的前缀;非正数限制仍表示关闭对应上限。(#6309)

  • 技能: SkillScan 的 shell-curl-pipe-shell 警告现在覆盖真实安装脚本 常见的变体:curl | sudo [flags] bash、绝对解释器路径(如 /bin/bash 与 /usr/local/bin/sh)、更多 shell(zsh、dash、fish),以及下载 与解释器之间任意位置的反斜杠换行续行。管道到 jq、tee 等非 shell 工具仍不触发警告;无管道的下载在遇到连续反斜杠文本时也不会再拖垮匹配 器。带取值的 sudo 选项(如 sudo -u root bash)仍不属于这条警告级启发 式的范围。(#6312)

  • 项目: 并发读取项目文档时,不再因共享字符数缓存的更新竞争而抛出 KeyError。一个文件 I/O 工作线程刚查到缓存计数,另一线程可能在它调用 move_to_end() 之前就把该条目逐出,错误沿 document_char_count() 传播, 使本可正常读取的 read_project_document 失败。现在缓存的查找/晋升与 插入/逐出都在短锁内进行,完整文件扫描仍在锁外,互不相关的读取保持并发; 256 条 LRU 上限与按内容标识的键保持不变。(#6315)

  • 子代理: 关闭常驻的子代理隔离事件循环时,若限时的线程 join 未能确认 工作线程已终止,不再丢弃其所有权。原关闭路径在 1 秒 join 后就清空全局的 循环/线程句柄,仍然存活的工作线程因此失去可重试的清理句柄,而后续调用 还可能另建一个并行的新循环;启动超时路径也有同样的隐患。现在所有权保留 到工作线程真正退出为止:清理未决期间新的提交会收到可重试的 shutdown-pending 错误,退出后由下一次提交回收并关闭旧循环,再创建替代 循环。(#6316)

  • 沙箱: AIO 沙箱关闭时,若租约续期线程在限时停止 join 超时后仍在运行, 现在会以失败收场。原来的 _stop_lease_renewal() 无视 join 结果,继续 分离并销毁所有受跟踪的 warm-pool 条目,而续期线程还活着,重试时已没有 可清理的所有权。现在先停止续期、再分离受跟踪的所有权;join 超时会抛出 异常,重试仍拥有全部活跃与 warm 条目。信号处理器遇到关闭失败会记录日志 并照常转发原信号,而不是因异常中断。(#6317)

  • 模型: 无法解码的 CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR 凭据交接 不再导致 Claude 模型构建失败。描述符读取此前只捕获 OSError,非 UTF-8 字节(如 UTF-16 编码的 token 或被截断的序列)会以 UnicodeDecodeError 从 load_claude_code_credential() 抛出,而不是回退到凭据文件。现在解码 失败返回 None 且不写入缓存,本次及后续加载仍会尝试 CLAUDE_CODE_CREDENTIALS_PATH 或 ~/.claude/.credentials.json,警告 日志不包含 token 内容。(#6323)

  • Telegram: 停止 Telegram 通道时,轮询工作线程若在限时的关闭 join 之后 仍在运行,不再被丢弃句柄。原来的 stop() 只记一条警告就清空 self._thread,仍然存活的线程失去可重试的停止句柄,worker 继续轮询时 通道却可能看似已停止。现在会抛出 ChannelStopTimeout——与 Feishu、 DingTalk 已有的保留契约一致——且仅在线程引用仍指向该 worker 时才清空。 (#6333)

  • 模型: Codex 完成响应在 usage 为 null、缺失或为空时,仍会保留其 文本、推理与工具调用。解析器此前使用 response.get("usage", {}),显式的 JSON null 会得到 None,后续 token_usage 计算抛出 AttributeError, 使本已完整的回答被丢弃。现在不可用的 usage 会归一化为空映射: AIMessage.usage_metadata 保持 None,usage 字段报告零值;而有值的 usage 映射(包括零计数与缓存/推理 token 明细)不受影响。(#6335)

  • Discord: channels.discord.allowed_guilds 现在按 config.example.yaml 文档所述的方式解析为公会 ID。此前直接迭代配置值会把标量 "321" 拆成 公会 1、2、3,未加引号的 YAML 整数会以 TypeError 使通道构造崩溃,而 静默丢弃无法解析的条目会让已配置的限制退化为“允许所有”,把机器人开放 给它加入的每个服务器。现在单个 ID(加引号与否均可)视为一个条目,无效 条目在警告中丢弃,而配置了却得不到任何有效 ID 的值会拒绝所有公会并记录 错误;未设置、null、[] 或空白字符串仍允许所有公会。 allowed_channels 获得同样的标量处理。(#6338)

  • 发布: v* 发布门禁现在会拦下过期的 backend/uv.lock。此前 scripts/verify_versions.sh 只比较 Chart.yaml、pyproject.toml 和 package.json,手动改这三处就能通过门禁,而 lock 里记录的根包版本仍是旧版本。 backend 镜像用 uv sync --locked 安装依赖,因此打标签后 chart 以及 frontend、 provisioner 镜像都已发布,backend 镜像却构建失败;chart 版本不可覆盖,修复只能 换一个新版本号。现在该脚本还会在 backend/ 中运行 uv lock --check(PEP 440 规范化交给 uv,2.1.0-rc0 仍与 2.1.0rc0 匹配),缺少 uv 时直接失败; verify-versions.yml 会安装与 backend 镜像相同的固定 uv 版本。(#6588)

  • TUI: TUI 拒绝发送的消息现在会保留在输入框中。此前提交时会先清空输入框再检查能否发送, 因此在运行进行中、被中断的运行尚未停止,或运行无法启动时输入的提示会被一条提示信息取代, 只能在输入历史中找回;命令行传入的启动消息在运行无法启动时则会完全丢失。 现在输入框会按输入历史记录的文本恢复消息,把光标放在末尾并保持命令面板关闭, 按 Enter 即可重新发送; 被接受的消息和本地斜杠命令仍会像以前一样清空输入框。(#6666)

  • TUI: deerflow --print 和 --json 不再丢弃未加引号消息中的词。此前这两个 选项只接收一个值,也从不读取位置参数,因此 deerflow --print summarize this repo 只会发送 summarize,deerflow summarize this repo --print 在终端中会以退出码 2 要求提供消息,而有管道 stdin 时命令行上输入的词会被悄悄忽略。现在两个选项会接收 其后的所有词,并像 --cli 一样在没有值时使用选项之前的词;命令行上输入的消息 优先于管道 stdin。消息被选项分隔在两侧时(deerflow hello --print there), 由于词序无法还原,会以退出码 2 退出并提示加引号。同时传入 --print 和 --json 现在会以退出码 2 退出,而不是运行 --print 并丢弃 --json 的消息。(#6636)

  • 前端: 工作区会话失效后重新登录,现在会回到原来打开的页面,并保留查询参数和锚点。 此前在没有有效会话时打开工作区页面(例如会话过期后点开定时任务通知链接 /workspace/scheduled-tasks?task_id=...)会跳到不带参数的 /login,登录后只能 回到默认工作区;工作区布局无法读取请求 URL,因此现在改由浏览器发起跳转,在 next 中保留完整地址。客户端会话检查(AuthProvider.refreshUser、 useRequireAuth)此前保留了路径但丢掉了查询参数,共享 fetcher 则丢掉了锚点;现在都保留完整地址。(#6681)

  • 沙箱: AIO、E2B、OpenSandbox 提供者和 ACP agent 不再对 sandbox.environment / acp_agents.<name>.env 的值做第二次解析。加载 config.yaml 时已替换每个 $VAR,因此本身以 $ 开头的已解析密钥(如 $2b$12$... 形式的 bcrypt 哈希或生成的密码)会再次被当作变量名查找,传入沙箱 或 agent 时变成空字符串;若同名主机变量存在,则变成该变量的值。现在这些值按 加载结果原样传递,与 BoxLite 和本地提供者一致。(#6673)

  • 前端: 侧边栏和对话页加载更多对话时,不再重复请求已加载的对话。侧边对话 不在这些列表中显示,因此一页 50 条可见对话可能跨越更多服务端行;页面原先把这个 偏移量作为数组上的隐藏属性保存,TanStack Query 的结构共享和列表自身的缓存更新 都会把它丢掉。重命名、运行结束或任何列表刷新之后,下一页会从过早的位置开始, 取回重叠的行;从满页中移除一条对话也会让列表在重新获取前停止加载更多。现在每个 缓存页都会记录自己隐藏了多少行,以及服务端是否还有更多。(#6702)

  • 前端: run_at 只有日期(如 "2026-12-07",对话中创建的任务可能如此)的单次定时 任务,现在显示为任务时区当天的零点,与服务端实际执行时间一致。此前界面把末尾的 -07 误认为 UTC 偏移量,显示成 UTC 零点:在 Asia/Shanghai 显示为 08:00,在 UTC 以西的时区则显示为前一天晚上;任务列表、详情、对话卡片和编辑表单的预填时间都受影响。(#6692)

  • 子代理: 持久批处理工作器在不再监管仍在运行的子代理时,现在会取消它,并在它 停止后从注册表中移除对应条目。此前,在长期运行的进程中停止批处理服务(SDK 的 SubagentRuntime 停止)会让每个仍在运行的 SubagentResult(包括完整对话 记录)永久留在进程级注册表中;轮询时的存储库错误会把条目标记为失败或重新排队, 却让其子代理在无人监管的情况下继续运行,因此重试可能把同一条目执行两次。始终不 停止的条目会在其租约过期后被移除。(#6785)

  • 网关: 子代理批次 JSONL 导出 (GET /api/threads/{thread_id}/subagent-batches/{batch_id}/results.jsonl) 现在每次读取 10 行已存储结果,而不是 500 行。每行最多可包含 max_result_chars 的报告和同等长度的证据 JSON,而导出会先把整页载入内存再开始输出,因此默认上限下 单个请求约占用 280 MB(上限 1,000,000 时约 1.7 GB),并且每页会阻塞事件循环 半秒以上。导出的行及其顺序不变。(#6706)

  • 网关: 子代理批次条目列表 (GET /api/threads/{thread_id}/subagent-batches/{batch_id}/items,批次运行时工作区 每 2 秒轮询一次)不再为了只返回预览而载入每个条目的完整报告、证据 JSON 和提示词。 默认 max_result_chars 下,一页 100 个条目约占用 34 MB,并阻塞事件循环约 70 ms (limit=500 时为 170 MB 和 300 ms);现在不到 1 MB。响应内容不变。(#6781)

  • 渠道: 在渠道设置页保存或断开机器人时,不再覆盖经由其他 Gateway worker 保存的机器人。每个 worker 都保留启动时读入的 runtime-config.json 内容,并用这份 副本重写整个文件,因此在两个 worker 下,一个 worker 上保存的 Telegram 机器人会在 另一个 worker 保存 Slack 机器人时丢失,已断开的机器人也会重新出现。现在每次保存 都会在跨进程共享的锁下重新读取文件,只修改自己的 provider。(#6844)

  • 持久化: 同一用户同时对同一个运行首次评分(两个标签页、两台设备或客户端重试)时, 不再有一个请求返回 500。两个请求都会查不到已有记录并插入,第二个插入会触发 (thread, run, user) 唯一约束;在 Postgres 上,80 个这样的请求中有 26 个失败。 IM 会话绑定中的同类竞争(例如首条消息与 /new 同时到达)也不再导致消息处理失败。 落后的写入现在会更新另一方创建的记录。(#6852)

  • MCP: 在 SQLite 上取消后台 MCP 任务时,取消请求现在会在下一个轮询周期发往远程服务器, 而不是最多延迟 mcp_tasks.lease_seconds(默认 120 秒)。由于 SQLite 上 FOR UPDATE 不起作用,在轮询器认领任务期间提交的取消请求所清除的轮询租约会被写回;取消工作器 随后要等该租约过期,任务还会被多轮询一次,而结果会被丢弃。现在每次认领在写入租约时 都会重新确认任务仍可认领。Postgres 不受影响。(#6859)

  • 持久化: 启动时的 schema 升级不再为已与模型一致的列记录虚假的 safe_add_column ... drifts from the model definition 警告。该检查拿模型的通用类型名 和默认值写法(STRING、BIGINTEGER、DefaultClause('0'))去比较数据库自己的写法 (VARCHAR、BIGINT、'0'),因此 revision 0023 之前创建的 Postgres 或 SQLite 数据库升级时都会记录一条,丢失 head stamp 的 schema 会记录 29 条(SQLite)或 36 条(Postgres),淹没了这个警告本应发现的真实类型不一致。现在两侧都按所连接 数据库的写法进行比较。(#6865)

安全

  • skillscan: LLM 之前的静态门禁现在按形状匹配 fork 炸弹,而不再只匹配 :(){ :|:& };: 这一规范写法。resource-fork-bomb 和 shell-destructive-command 的 fork 炸弹分支此前只匹配那一种写法,因此 #6651 报告的改名或改空格的载荷—— bomb(){ bomb|bomb& };bomb、f(){ f | f & };f、带点号和多行函数体的变体——不产生 任何发现并通过阻断门禁,尽管运行时沙箱审计中间件早已能阻断一般形状。现在由共享的 _FORK_BOMB_PATTERN 支撑这两条规则,它与中间件的规则保持一致(函数体经管道递归 进入后台任务的函数),静态扫描因此绝不会放行运行时会被沙箱阻断的载荷。(#6689)

  • 工具: 延迟工具的正则发现不再无界运行,因此模型生成的 (a+)+$ 之类模式无法再 让发现在长工具名或长描述上卡住数秒。正常发现和 +required 正则排序此前都使用无界 的标准库匹配;四个独立复现在进入搜索后各自超出了外部 8 秒期限。现在查询在候选与 排序之间共享 100 毫秒的执行预算,并通过新声明的 regex 依赖做可中断匹配;模式限定 256 个字符,每个可搜索的名称或描述限定 65,536 个字符。预算耗尽时返回重试提示且 不做部分晋升,无效模式仍回退到字面匹配,select: 保持精确、区分大小写且不设上限。 (#6676)

  • 社区工具: 委托式 web_fetch/web_capture 后端默认失败关闭。 Browserless、Crawl4AI、Firecrawl 与 fastCRW 在后端自己的网络命名空间中解析 目标 URL,针对目标 URL 的 SSRF 屏障无法端到端生效。现在每个入口在委托之前 先筛查其解析出的后端 base URL(配置键、环境变量回退与默认值):公网后端 照常工作;回环、私有或无法验证的后端会被拒绝,除非运维确认其出网隔离。 升级提示: 使用文档默认配置的部署(Browserless 位于 http://localhost:3032、Crawl4AI 位于 http://localhost:11235,或经 base_url/CRW_API_URL 自建的 Firecrawl/fastCRW)会在 web_fetch/web_capture 上遇到委托错误,直到把后端指向其公网地址,或在 隔离后端出网之后在每个工具配置中设置 network_isolation_confirmed: true。 make doctor 会对被拒绝的后端配置发出警告,包括 CRW_API_URL 覆盖。 allow_private_addresses 仍然只控制目标 URL。参见部署指引。 (#6531)

  • 路径: 上传、新建沙箱路径与新建技能支持文件路径现在拒绝扩展名前用 ASCII 空格 填充的 Windows 保留设备名主干(NUL .txt、COM1 .log)。此前共享的文件名可 移植性防护在第一个点处切分、把空格留在主干里,因而不再匹配保留名称集合,放行了 Windows 视为保留设备、无法用作普通文件的名称——让上传与支持文件树通过了它们在 Windows 上无法存活的可移植性校验。现在只裁剪用于比较的主干:report .txt 这类 普通文件名原样返回,非 ASCII 空格不做归一化,既有的读取/删除豁免保持不变。 (#6458)

  • skillscan: 现在能检测 GitHub 细粒度 PAT 和 Google API key,且 .zsh 脚本按后缀当作 shell 扫描。secret-cloud-token 规则此前识别经典 GitHub 令牌(ghp_/gho_/...)和 sk- 密钥,但不识别 github_pat_... 或 AIza... 密钥,而规范的 PII 检测器本就同时标记这两族,SkillScan 因此是两道 防线中较弱的一道;IM 出站清洗器(notification_text.py 中的 _EGRESS_SECRET_PATTERNS,文档写明与 SkillScan 检测器保持一致)存在同样的 缺口,于是在安装时被拦截的密钥仍可能未经脱敏就发往第三方 IM 平台。.zsh 文件此前只有带 shebang 才算 shell,去掉 shebang 或改名就会跳过所有 shell 规则——反弹 shell、破坏性命令、环境变量转储——尽管安装器早已把 .zsh 当作 代码。现在两族令牌在 SkillScan 和出站清洗器中都能匹配,.zsh 仅凭后缀即 归类为 shell。(#6423)

  • skillscan: 新式 sk- API 令牌现在能被检测,并从外发通知中清洗。此前 sk- 模式拒绝 - 和 _,SkillScan 因此漏掉新式 OpenAI 与 Anthropic 密钥 (sk-proj-、sk-svcacct-、sk-admin-、sk-ant-api03-);定时任务通知 清洗器共用同样的窄模式,这些密钥因此继续出现在发往外部 IM 平台的摘要里; 占位符先于真实凭据命中,还会让扫描停止检查其后的凭据。现在字符类在 SkillScan 和通知出站脱敏中都允许 - 与 _,与仓库的 PII 检测器一致; legacy 与新式密钥在技能中获得 CRITICAL 拦截,并从外发通知摘要中清洗;扫描 会越过占位符命中继续进行,直到找到真实凭据,同时保持每个文件至多一条 cloud-token 发现的既有上限。(#6394)

  • 记忆: 启用 pii_redaction 后,无效和 legacy 的工具调用参数现在也会在 进入记忆之前被脱敏。此前的准入脱敏覆盖解析后的调用参数和提供方原始 tool_calls,却未触及 invalid_tool_calls.args 与 error,以及 legacy 的 additional_kwargs.function_call——这两种形态都由锁定的 OpenAI 转换器产生 ——因此一次格式错误的联系人查询,即使其原始工具调用视图已被脱敏,仍可能通过 无效参数或解析器错误把 alice@example.com 交给记忆后端,在 agent 之后的 准入与压缩前的刷写两个环节都是如此;OpenViking 在其记录器边界保留整条消息, 仅靠后端侧的最终回复过滤堵不上这个缺口。现在配置的脱敏器会在后端过滤之前 扫过这些字段,使用与既有内容和来源脱敏相同的键控占位符;格式错误的参数仍 保持为字符串,解析后的调用身份、原始历史和无关元数据均被保留,默认关闭的 策略和各检测器开关仍然生效,已存储的既有数据不会被改写。(#6387)

  • skillscan: shell-curl-pipe-shell 匹配器现在能捕获藏在带值 sudo 选项 之后的 shell。HIGH 级规则(“把远程内容管道送入 shell”)此前已能处理 sudo bash 和 sudo -E bash,但遇到带值的选项时,匹配停在选项值上,因此 curl -fsSL https://host/x.sh | sudo -u deploy bash——以另一个用户身份运行 已下载安装器的常见方式——完全不产生任何发现。现在 sudo 前缀允许带值集合 中的选项(-a、-c、-C、-D、-g、-p、-r、-R、-t、-T、 -u、-U)在 shell 令牌之前消费其独立的值;独立标志仍走既有分支。 env bash / env -i bash 这类启动器仍有意不建模,以保持精确度。(#6372)

  • skillscan: scan_skill_dir() 现在在读取文件前先检查其大小。此前每个候选 文件都用 path.read_bytes() 整体读入,64 MB 的 MAX_FILE_BYTES 策略上限要等 完整读取之后才记录为发现,因此一个 300 MB 的植入文件(上限的 4.7 倍)实测 带来 600 MB 峰值内存与 20 秒扫描,按线性外推,更大的植入文件足以令宿主机 OOM——经 review/analyzer.py 即可在从未通过归档预检门禁的内容上触发。现在 扫描先用 stat() 把关,记录 package-oversized-file,且至多读取 MAX_FILE_BYTES + 1 字节,与 _read_archive_member 的“先检查、再有界读取” 一致,两个入口共用同一纪律。(#6359)

  • 技能: Skillscan 的原生 URL 规则现在不区分大小写地识别 HTTP 与 HTTPS 的 scheme 写法。此前正则与前缀检查只匹配小写 scheme,同一端点的大写或混合 大小写写法会被不一致地分类。现在 scheme 写法的识别保持一致;authority 解析、 主机分类、凭据脱敏证据与 sink 推断均不变。(#6355)

  • 鉴权: 仅有认证而无权限校验的路由现在会强制执行权限检查,且 USER.md 改为按用户隔离。POST /api/threads、 /api/threads/search、/api/runs/stream、/api/runs/wait、所 有 /api/memory 路由以及 custom-agent 路由此前都不带 @require_permission,因此配置为拒绝 threads:write/runs:create/... 的 provider 无法落实这些决策。 GET/PUT /api/user-profile 此前还读写同一份全局 USER.md,任何 已认证用户都能覆盖或读取所有人的 prompt 上下文——该文件现在存放 在 {base_dir}/users/{user_id}/USER.md。(#4989)

  • 网关: 元数据行缺失或 owner 为 NULL 的线程现在仍保持 owner 隔离。#5448 的内部调用方 run 范围限定会为每个受信任的内部调用方 返回空过滤器,而 ThreadMetaStore.check_access(..., require_existing=False) 又刻 意放行缺失的元数据行(遗留兼容)与 NULL-owner 行,因此代 owner A 行事的内部调用方可经 /runs、/runs/page 或 /runs/{run_id} 列举或获取 owner B 在此类线程上的持久化 run(单 run 请求返回 200 而非 404)。_run_scope_user_id 现在会查询线程元数据存储:已确 立 owner 的线程保持不过滤的读取,缺失/NULL-owner 的线程则回退到 代为执行的 owner 的原始 stamp,使跨用户的 run 保持隐藏。浏览器与 API 会话均不受影响。(#5484)

  • 前端: 工具步骤链接改为经由共享的 isSafeHref scheme 白名单 把关。web_fetch、web_search 与 image_search 工具步骤此前把 URL 直接塞进 <a href target="_blank">,因此取自模型所写工具参 数(或 provider 结果)的 ms-msdt:、search-ms: 或 vscode: 链接会被渲染成可点击——后端 URL 校验永远看不到仍在流式传输或已 失败的 fetch——一次点击加上浏览器的外部应用提示,就把攻击者选定 的 URI 交给了本地应用。不安全的 URL 现在渲染为与 markdown 链接相 同的不可点击“Unsafe link omitted”span;web_fetch 还不再对非 字符串的 url 参数抛异常,且所有工具分支中缺失的工具调用 args 都规范化为 {}。(#5526)

  • 上传: 内嵌客户端的上传不再能穿过符号链接写入。 上传目录从沙箱内部即可写, 因此沙箱中的进程(例如被提示注入操控的进程)可以埋下 notes.txt -> /host/path,让下一次 DeerFlowClient.upload_files——或随之转换生成的 Markdown 伴生文件——以客户端的权限覆盖主机上的任意文件。 上传现在通过一个禁止符号链接的复制辅助函数发布; 不安全的目标会被跳过,并按 Gateway 的契约经 skipped_files 与 success: false 上报。(#5578)

  • 上传: 文档转换现在读取暂存字节的一份私有副本, 而不是它们落盘时使用的名字。上传目录可以从沙箱内部写入, 因此在文档转换前的窗口期内,沙箱中的进程可以把某个上传已提交的 名字换成一个指向任意主机文件的符号链接; 转换器按路径打开并跟随链接,主机文件的内容就以转换出的 .md 附件形式回到了会话中。Gateway 现在转换的是私有临时目录里由文件 描述符支撑的副本,客户端则转换调用方自己的源路径——被调换的名字 不再能重定向读取。(#5611)

  • 鉴权: Live Browser WebSocket 连接现在强制执行 threads:write。WebSocket 此前绕过 HTTP 认证中间件, browser_stream() 会检查认证、Origin、thread 归属与浏览器 可用性,却不解析路由权限——因此策略拒绝 threads:write 的 thread 属主仍能连接该 thread 的 Live Browser、 接收帧并派发输入事件。被拒绝的连接现在会在获取会话之前以关闭码 4403 拒绝;细粒度鉴权关闭时行为不变。(#5621)

  • 网关: 现在会在写入 checkpoint、run 准入和智能体执行之前, 以 HTTP 400 拒绝外来的 system 与 developer 消息。 此前这类消息会被接受并可能被持久化, 在后续回合中并入智能体的系统上下文。 消息表示现在会一次性完成规范化与校验;普通的 user、assistant、 tool 与附件消息以及受信任的内部系统消息均不受影响,既有 checkpoint 也不会被重写。(#5651)

  • 技能: 用户作用域的 .skill 安装现在与继承的压缩包预检使用 同一份 app 配置来解析静态内容扫描。用户作用域的存储覆盖此前未传 入 app_config,内容扫描便回退到热更新的进程级全局值:当某存储 的快照为 skill_scan.enabled: true 而随后的 config.yaml 编辑 将其改为 false 时,预检仍会执行但内容扫描被静默跳过,含 CRITICAL 内容的压缩包(如内嵌私钥材料)照样安装。按文件的 LLM 扫 描现在转发同一份配置,模型选择不再读取全局值。(#5703)

  • 沙箱: 恢复沙箱的引用现在限定在已认证用户与线程范围内。已认 证调用方可以在自己拥有的线程上提交 input.sandbox.sandbox_id, 而 AIO 恢复路径会从进程级活跃沙箱缓存解析 body 提供的 id,却不检 查缓存沙箱的 (user_id, thread_id),因此一个已知的仍活跃的他人 沙箱 id 可能让沙箱支撑的工具调用被派发到它上面。sandbox 现在是 服务端自有的状态:外部 run 输入与线程状态更新遇到它一律以 HTTP 400 拒绝,checkpoint 复用要求作用域匹配,否则回退到以 Overwrite 持久化的规范 acquire(user_id, thread_id),且只有服 务端创建的 fork 路径可以借用父沙箱。(#5736)

  • 日志: urllib3 重试日志行中携带空格的请求目标现在会被脱敏。三个脱敏模式 (Retry: …、Retrying (…) … 与带引号的请求行)都在第一个空格处停止匹配,因此 RFC 9110 允许的、内部含空格的 origin-form 路径会带着完整签名查询串明文进入日志; Retrying 形态以 WARNING 级别输出,无需 DEBUG 就会落入生产日志。这些模式现在一直 匹配到目标的结束引号或行尾,与已加固的 Redirecting 与 increment-retry 槽位保持一致。 (#5745)

  • 认证: POST /api/v1/auth/initialize 现在原子地认领第一个管理员。处理逻辑此前在 一个会话中统计管理员数、在另一个会话中创建用户,两者之间没有任何锁或约束,因此两个并发 的首启请求(不同邮箱)都会成功且都成为管理员——而且毫无声息,因为 setup-status 随后 报告系统已初始化。计数与插入现在共用一个串行化事务(SQLite 上 BEGIN IMMEDIATE, PostgreSQL 上事务级 advisory lock,未知方言在启动时直接失败),落败方收到文档载明的 409 system_already_initialized;顺序请求的行为与响应结构不变。(#5776)

  • 沙箱: view_image 读取现在要求先有 sandbox:execute 授权。 被允许调用 view_image 但被拒绝 sandbox:execute 的角色, 此前仍能读取当前线程允许目录中的图片,图片中间件还可能把这些字节 放入模型请求——这是授权缺口,而非跨用户读取。同步与异步读取现在 都会检查执行授权,中间件在读取已记录于线程状态的图片前也会复查, 角色变更之后同样如此。(#5799)

  • 鉴权: suggestions 端点现在强制执行 model:use。在模型授权 启用的情况下,被拒绝某模型的用户此前仍能通过 POST /api/threads/{thread_id}/suggestions 调用它——显式指名, 或在被拒模型正是配置的默认模型时省略名称——尽管同一个用户请求 GET /api/models/{model_name} 会得到 403。共享的模型详情检查现在 在模型创建之前执行,拒绝时返回 403,并置于尽力而为的处理之外, 权限失败不会再变成返回空结果的 200。(#5816)

  • 沙箱: 已查看图片的主机读取现在限定在当前线程。外部 run 输入 或线程状态更新此前可以提供带有自选 actual_path 的 viewed_images 元数据,在匹配到 view_image 历史之后, 中间件会把该主机路径读入下一次模型请求, 因此一个用户的请求可能携带另一个用户的图片。服务端自有的 viewed_images 与 thread_data 通道的外部写入现在会在 run 准入 前以 400 拒绝;已保存的主机副本只在记录的虚拟路径(按当前用户 与线程解析)与 actual_path 匹配时才被读取,跨线程引用在 view_image 重新执行之前保持不可用。(#5824)

  • 内存: 开启 pii_redaction 后,进入内存提取队列的对话负载现在也会被脱敏, 补上 #5527 刻意留下的最后一块。请求级脱敏刻意在线程状态里保留原文,而 MemoryMiddleware._resolve_add_args() 会把这些原始消息直接传给 MemoryManager.add_nowait(...),因此在 pii_redaction.enabled=true 时,缓存的 提取负载、提取模型的输入以及最终落盘的事实仍可能带着原始 PII。 MemoryMiddleware 现在像摘要/持久上下文/标题路径一样接收 pii_redaction_config, 在入队边界(覆盖两个 agent 钩子的同一入口)通过共享的 redact_text() 完成 脱敏;缓存负载本身即为干净副本,消息通过 model_copy 重建、绝不改动原件。 开关默认关闭,未设置时行为不变。(#5577)

  • 目标: 开启 pii_redaction 后,/goal 评估器的输入现在也会被脱敏。评估器直接 调用模型,不经过 PiiRedactionMiddleware,因此此前会发送原始的用户消息、工具参数、 工具结果和 Human Input Card 回答。现在先对评估器读取的完整消息脱敏,避免证据截断把 标识符切成两半,再对拼好的输入(包括目标原文)脱敏一次;线程状态保留原文。脱敏出错 时这次检查按失败处理(evaluator_failed),不会发送原文。关闭脱敏时输入逐字节不变。 (#6556)

  • 授权: 技能授权现在在装配与激活两个环节强制执行,skills: {allow: ["data-analysis"]} 这类 RBAC 策略因此能真正拒绝某个技能——Phase 2A (#4439)覆盖的是 Gateway 路由,技能此前仍只受 agent 配置白名单约束。 主 agent 的技能目录、子代理 config.skills 加载、describe_skill 与所有 激活门都经由同一个 authorize("skill", "activate") 辅助函数查询 AuthorizationProvider 的 skill 策略;拒绝时返回与成员资格拒绝一致的 提示,provider 错误遵循既有的 fail-closed/fail-open 配置。被拒绝的 read_file 读取 SKILL.md 时会打上 skill_context_denied 标记,持久上 下文、技能 allowed-tools 与自主密钥绑定都不会激活被拒绝的技能。(#4541)

  • 目标: 开启 pii_redaction 后,隐藏的 /goal 续跑消息现在也会被脱敏。 PiiRedactionMiddleware 不处理这条框架消息,因此每次续跑都会把目标原文以及评估器的 理由和证据摘要原样发给 agent 的模型,并留在线程里供之后的模型调用读取。现在先对这些 内容脱敏,再拼成消息;线程保存脱敏后的消息(界面不显示),目标状态仍保留原文。 脱敏出错时这次检查按失败处理(evaluator_failed),不计入续跑次数,也不会发送原文。 关闭脱敏时消息逐字节不变。(#6556)

  • Lark: 可选的 Lark broker 子命令拒绝列表 (DEERFLOW_LARK_BROKER_DENY_SUBCOMMANDS)不再能被以独立 token 传入的选项 值绕过。此前匹配只去掉以 - 开头的 token 并从头比较剩余部分,因此 --profile work config show 中的 work 成为首个位置参数,config show 规则永远匹配不上——而真实的 lark-cli 1.0.65 在这种写法下仍会执行 config show。broker 无法得知哪些选项带值,因此规则现在只要其 token 按顺序 出现在非选项 token 中即视为匹配——这也覆盖了值夹在中间的情形 (config --profile work show),而连续匹配仍会漏掉这种情况。参数值恰好按 顺序拼出被拒绝路径的调用也会被拒绝(fail-closed)。(#6212)

  • 渠道: Telegram 的 allowed_users 列表中若没有任何数字用户 ID,现在会拒绝 所有用户,而不是静默放行所有人。此前无法通过 int() 的条目会被直接丢弃且不 记录日志,而结果为空又被视为"未配置白名单",因此 ["@alice", "bob"] 会让机器 人对所有人开放。单个 ID 现在视为只有一项的列表,而不再被当作字符串逐位拆成多个 用户("123456" 曾放行用户 1–6 并拦截 123456);null 或单个整数也不再导致 渠道启动时崩溃;每个被丢弃的条目(@用户名、浮点数、布尔值)都会记录警告。(#6230)

  • 技能: 位于 scripts/ 之外的 Windows 脚本、HTML 应用程序和 scriptlet 不再逃过 SkillScan。.bat、.cmd、.psm1、.jse、.vbe、 .vbs、.wsf、.hta、.sct 等后缀现在一律视为代码文件(不区分大小 写):含有杂散 NUL 或无效 UTF-8 字节时仍会报 package-undecodable-script 并在有损解码后继续分析,而不是被直接跳过; 安装器的可执行代码策略(warn 即拒绝安装)也适用于包内任意位置的这类 文件。此前带一个杂散字节的 hooks/install.jse 既得不到静态分析,也 不经过可执行代码审查。(#6321)

  • 脚本: 本地 make dev / make start 改为绑定回环地址。此前 serve.sh 和 backend/Makefile 以 --host 0.0.0.0 启动 Gateway,nginx.local.conf 监听所有网卡,Next.js 在 Windows 之外也沿用监听所有网卡的默认值,因此在局域网 或 VPN 中,其他机器可以访问 2026、8001 和 3000 端口,包括首个管理员创建 之前的 /setup。Docker 部署栈和 README 的部署模型原本就只监听回环地址。现在 Gateway 和前端绑定 127.0.0.1,nginx 监听 127.0.0.1 和 [::1],除非设置了 BIND_HOST(与 Docker 部署栈使用同一个变量)。BIND_HOST 无效时,会在停止任何 正在运行的服务之前报错退出。(#6587)

    行为变更: 需要从其他设备访问本地部署时,请设置 BIND_HOST(例如在 .env 中设置 BIND_HOST=0.0.0.0)并使用 2026 入口;Gateway 和前端端口不再对其他 机器开放。

  • 脚本: 本地 BIND_HOST 不再接受会被 nginx 误读的值。此前本地 nginx 辅助脚本只检查字符是否属于地址或主机名,因此 BIND_HOST=0 会生成 listen 0:2026;,nginx 将其解析为 0.0.0.0 并监听所有 IPv4 网卡,1.2.3 则会绑定到 1.2.0.3。[[fd00::1]]、-foo 等 nginx 直接拒绝的值能通过 serve.sh 的预检,要等正在运行的服务被停止之后才失败。现在该脚本只接受 点分十进制 IPv4 地址、IPv6 地址(带或不带方括号)或符合 RFC 1123 的主机名。 改回默认的回环地址时,也会删除之前为其他值生成的 temp/nginx.local.conf。(#6635)

  • 技能: review_skill_package 不再能读取其他用户的技能。此前本地路径 目标只要位于 Gateway 工作目录或 /tmp 之下即被放行,而所有文档化的部署都把 DEER_FLOW_HOME 放在工作目录之下,因此知道他人用户 id 的用户可以传入 .deer-flow/users/<id>/skills/custom/<skill>,在模型响应中拿到该技能的 SKILL.md 与 references/ 内容。该工具始终可用,技能工具策略也无法移除它。 现在本地目标仅限于配置的技能根目录和调用者自己的用户目录;skill:// 与 inline:// 目标不受影响。(#6580)

  • 沙箱: BoxLite、OpenSandbox、Tenki 三个提供者的 glob 与 grep 用 str.splitlines() 切分记录,而该函数还会在裸回车、换页符、垂直制表符、 文件/组分/记录分隔符、下一行符以及 U+2028/U+2029 处断行——这些字符在 Linux 文件名与被匹配文本中都是合法内容。因此名为 notes\x0bdraft.txt 的文件会被 报告成两条互不相关的路径(其中一条并不存在),而形如 const s = "a\u2028b"; 的匹配行会在该字符处被截断。现在这三个提供者只按 " " 切分,与共享解析器 既有的约定以及 LocalSandbox、AIO 后端、E2B 的行为一致。(#6595)

  • 安全: 新增仅从环境变量读取的静态凭据加密密钥 DEER_FLOW_CREDENTIALS_KEY。 按连接存储的 IM 渠道凭据此前虽有加密路径,但没有任何生产代码接入,既无法写入 也无法读取,Slack 因而始终使用部署级 bot token。现在 Gateway 在启动时加载该密钥 (逗号分隔的 Fernet 密钥:第一个用于加密,全部用于解密,以支持轮换;密文带 fernet:v2: 前缀,旧的 fernet:v1: 密文仍可读取),并传给所有渠道连接仓库; 无法解密的值按缺失处理。未设置时单实例会生成 {DEER_FLOW_HOME}/.credentials_key; 声明为多实例且启用 channel_connections 的部署在缺少密钥时拒绝启动,格式错误的 密钥同样会被拒绝且不会回显。Helm chart 将密钥生成到 app Secret 并在升级时保留, make up 将其持久化到运行时目录,两个 compose 文件都会把它传给 Gateway。 .jwt_secret(以及托管模型密钥)现在以独占方式创建并回读,共享卷上同时冷启动的 副本不再各自保留不同的会话签名密钥。 (#6611)

文档

  • 文档: 新增韩语 README(README_ko.md),完整翻译当前英文 README。标题、表格和 链接与英文版一一对应,所有代码块和行内代码保持逐字节一致,只翻译正文。README.md 及其他译本的语言切换行和 AGENTS.md 的译本列表现已链接到该文件。 (#6909)

  • 文档: 配置检查与迁移命令现在在 backend 环境中运行。文档中的示例此前用 系统 Python 执行,无法导入已安装的 backend workspace 包;用 uv run --directory backend 选择环境会改变工作目录,导出的相对路径因此可能 指向错误的运行时主目录并静默漏掉已有智能体;验证期间的裸 uv sync 还会 移除已安装的可选依赖(如 PostgreSQL 驱动)。文档现在从仓库根目录使用 uv run --no-sync --project backend,既选中已安装的 backend 环境,又保留 调用方的工作目录与已安装的 extras;同时补充了安装前置条件、以及复用 Gateway 导出的运行时/配置选择器的说明,先展示迁移试运行(dry run),并对齐两处 迁移脚本用法示例、配置模板、README 与 backend 指引。(#6425)

  • 文档: 修正 Apple Container 的验证说明。 该指南此前指向一个并不存在的 test_container_runtime.py 脚本和旧的运行时实现;现在改为引用 local_backend.py 中的 LocalContainerBackend,说明针对受限网络既有的 Docker 选择, 并用两个现存的运行时选择 pytest 用例(它们会 mock 运行时命令) 作为验证。(#5605)

  • 文档: 将 config.example.yaml 中的 # Sandbox Configuration 横幅移回它所介绍的 sandbox: 键正上方——此前它已漂移到二十行之外的 uploads: 块上方—— 并为 uploads: 增加专属横幅。仅改动注释;由示例生成的 config.yaml 文件不受影响。(#5652)

  • 文档: 在 README 与运维排障指南中澄清既有本地和 Docker 部署的升级流程:make config 与 make docker-init 是首次安装步骤,而非常规的源码升级步骤;make config-upgrade 只在某个版本新增配置字段时才需要执行。(#5654)

  • 文档: 新增连接本地 llama.cpp llama-server 的文档,关闭 #2931。config.example.yaml 与英文/中文配置页面新增 OpenAI 兼容 的 Qwen 示例(use: langchain_openai:ChatOpenAI,指向 http://localhost:8080/v1),并涵盖 --alias 模型名、Docker 可 达的 base_url、启用 llama-server --api-key 时的 API key,以 及 chat-template/工具调用要求。(#5688)

  • 文档: 扩展手册(deerflow-extension-api 0.2.3,中英文)覆盖全栈插件与请求范围的 运行证据。新增 Full-Stack Plugins 章节,文档化 registry.plugin()、 BrowserModule/BrowserAssets、后端 action、模型工具与设置;运行证据章节覆盖 resolve_run_evidence_reader/require_run_evidence_reader 并附带按用户鉴权的路由 示例;Reference、Troubleshooting 与 Operating Extensions 也补充了插件相关小节。 (#5775)

  • 文档: 修正文档中的技能与 MCP 启用/禁用端点。文档站与 backend/docs/API.md 此前指向 POST /api/extensions/skills/{name}/enable 等网关中不存在的路由(404/405); 技能开关实为 PUT /api/skills/{name}(请求体 {"enabled": bool}),MCP 开关实为 PATCH /api/mcp/config(请求体 {"server_name", "enabled"}),两者均需管理员权限, 现已写入中英文技能与 MCP 页面及 API.md,并附可用的 cURL 示例。(#5778)

  • 文档: 修正后端文档示例中引用了不存在 API 的地方。 PATH_EXAMPLES.md 的上传示例此前导入了一个任何模块都不导出的 THREAD_DATA_BASE_DIR 符号,还指向旧版上传桶;现在改为通过 deerflow.uploads.manager 的 get_uploads_dir(thread_id) 解析目录,它读取的正是 Gateway 实际写入的桶。CONFIGURATION.md 也删除了 SandboxConfig 从未声明、也无代码读取的 sandbox.auto_start 键。(#5798)

  • 文档: 让自定义记忆存储指南变得可以照着做。harness 的 memory.mdx / customization.mdx 片段导入了一个并不存在的 模块,展示的存储类既无法被工厂构造、其调用也会被记忆更新器 拒绝,还记载了 storage_class 从不解析的 module:Class 取值形式,并承诺了代码实际会刻意抛错之处的优雅回退。指南 (英文与中文)现在写明真实的模块、匹配真实的调用形态, 并陈述实际的错误行为。(#5844)

  • 文档: 新增十章的 checkpoint 存储手册 (harness/checkpoints),中英双语——快速上手、渠道模式、 快照节奏、历史缓存、恢复与回滚、运维、可观测性、故障排查 与参考。这是文档站首次覆盖双模式 checkpoint 存储:为什么 模式与快照节奏按进程冻结(运行时只有 checkpoint_graph_cache.accessor_graph_max 会重读)、为什么 模式不匹配的请求返回 409、为什么 delta 运行无法 fork,以及 保留的存储究竟如何增长——周期快照保留完整消息列表,delta 只是把二次项除以节奏,而非将其消除。(#5845)

  • 文档: 修正引用了不存在的异步 API 的 harness 客户端示例。 DeerFlowClient 并没有 astream/ainvoke,但《Create Your First Harness》教程与集成指南(中英文)全程围绕它们编写,照抄教程的第一次 调用就会抛出 AttributeError;示例还把覆盖参数嵌在 config={"configurable": {...}} 字典里,而 stream()/chat() 会把它静 默吞进 **kwargs。示例现在改用同步的 client.stream(...) / client.chat(...),以平铺关键字参数传递覆盖项(model_name=、 subagent_enabled=),通过构造器配置 agent (DeerFlowClient(agent_name=...)),FastAPI SSE 示例改为同步生成器, "Async streaming" 一节更名为 "Streaming"。公开 API 无变化。(#5843)

  • 文档: 在 harness memory 页面 (frontend/src/content/{en,zh}/harness/memory.mdx)中补上可插拔的 MemoryManager 架构说明;此前该页只讲配置与存储,想接入独立的记忆系 统只能自行从源码反推契约。新增的 "Memory Manager" 一章解释后端无关的 契约、通过 manager_class / backend_config 选择后端、五个内置后端的 对比,以及 deermem 与 openviking 各自的接入方式;"Implementing your own memory backend" 一章给出最小可运行骨架、可选操作、失败语义 (MemoryConflictError → HTTP 409)与宿主钩子。既有 "Custom storage backend" 一节新增提示框,区分替换 DeerMem 的 storage_class 与接入完 全独立的记忆后端。(#5929)

  • 文档: 修正 MemoryManager 指南中照做即会失败的内容(#5929 的勘误, 中英文同步)。OpenViking 配置按文档写 failure_policy.read: fail_closed 会导致 manager 构建失败——该键只接受 fail_open 或 raise——且 mode: middleware 是必需项而非推荐项。from_config 与 add、 get_context 同为必需,跳过它的后端会解析成 None。工具模式下还需要 实现 get_memory() 与事实 CRUD 方法,因为除非后端设置 requires_passive_writes_in_tool_mode,否则不会安装 MemoryMiddleware;create_fact 返回 (memory_data, fact_id),未知事 实 id 抛出 KeyError(HTTP 404);staleness_review_enabled 默认开启, 且五个开关都位于 memory.backend_config 之下。(#5943)

  • 文档: 为中文 README 补上缺失的「外部聊天消息角色 (External Chat Message Roles)」一节,与英文 README 对应章节对齐,置于 Gateway 管理员 权限等同于代码执行与部署默认值两节之间。内容说明 run 请求与手动线程 状态更新会以 HTTP 400 拒绝客户端提交的 system / developer 消息 (session/PAT 认证不授予 system prompt 权限);该检查不会重写已有的 checkpoint——重启不会移除已持久化的指令;以及如何用 python backend/tests/poc_external_system_message_injection.py --help 在本地验证,包括在一次性线程上运行 --expect vulnerable / --expect blocked。(#6059)

  • 文档: 在智能体指导中加入“扩展优先”评估门槛。AGENTS.md 与 .github/copilot-instructions.md 现在要求编码智能体在修改核心代码之前 先评估能否以打包扩展交付该功能:自包含能力走扩展;核心保留给缺陷修复 以及 agent 循环、记忆、上下文压缩和鉴权相关的变更;既有贡献点无法表达 时,为 extension-api 增加通用钩子并以扩展实现,而不是把业务逻辑硬编码 进核心。(#6178)

  • 文档: 让运行事件与扩展示例文档重新与代码一致。 backend/docs/RUN_EVENT_STREAM.md 现在列出目录与契约中早已存在的 summarize 中间件标签并说明 middleware:summarize 事件,并新增测试将文档中 的标签列表固定为 MIDDLEWARE_EVENT_TAGS;backend/docs/summarization.md 补充该事件缺失的三个 changes 字段。扩展示例不再声称覆盖全部贡献类型: 它演示八种中的五种,README 为其余类型指向观察者指南和 bookmarks 插件示例。(#6582)

内部改进

  • deps: 升级 /backend 中的 lark-oapi:1.5.5 -> 1.7.3。(#6665)

  • 模型: 让推理内容捕获只有一个归属。patched_mimo 和 patched_stepfun 两个 OpenAI 兼容适配器中字节级相同的私有副本 _with_reasoning_content 和 _get_typed_choice_message 移到 models/reasoning_capture.py (with_reasoning_content、typed_choice_message),对“附加而不改动”语义的修复 不会再落进一个适配器而漏掉另一个;推理处理确实不同的提供方保留自己的代码,任何 模块若复述这两个函数体,源码普查测试都会失败。(#6639)

  • 社区工具: 让检索凭据处理只有一个归属。community/lightrag 和 community/ragflow 中字节级相同的 SecretStr 解包、API 密钥脱敏和拒绝 URL userinfo 的副本移到 community/provider_credentials.py(resolve_api_key、 redact_secret、reject_url_userinfo),加固一个提供方不会再让另一个无声地保持 未修复状态;提供方各自的策略(LightRAG 容忍缺失密钥、RAGFlow 拒绝密钥)仍由各 提供方保留,任何提供方包若复述这些规则,普查测试都会失败。(#6638)

  • 测试: 把四个用裸 Path.symlink_to 植入 fixture 符号链接的后端测试改走现有的 symlink_or_skip 辅助函数,未开启开发者模式的原版 Windows 主机会跳过它们,而不再 报 OSError: [WinError 1314]。TOCTOU 沙箱权限测试在安装其 os.lstat 钩子之前会 探测符号链接创建能力,并标记为仅 POSIX。运行时代码无改动。(#6619)

  • support-bundle: 启动器的 dotenv 测试现在能在 Windows 主机上通过。 backend/tests/test_support_bundle.py 的全部 30 个失败都汇于 _local_launcher_home():shutil.which("bash") 解析到 System32 里的 WSL 启动器(套件中仅剩的一处 shutil.which("bash")——#5404 的 require_script_bash() 桩已迁移了其它所有文件,且该桩的 UTF-16 输出破坏了 子进程解码);即使是真的 Git Bash,返回的也是测试无法转回 Windows 目录的 MSYS 路径。该辅助函数现在改用 require_script_bash(),在 shell 脚本内部用 cygpath -w 转换 home 目录(经由 argv 传递可让 MSYS 重写字面量 '${PWD}' 的场景),并为不带引号的 ~/... fixture 固定 USERPROFILE—— Git Bash 与 Python 的 expanduser() 从不同的变量解析它。POSIX 主机不受 影响。(#6544)

  • deps: 升级 /backend 中的 mako:1.3.12 -> 1.4.2。(#6524)

  • deps: 升级 /frontend 中的 next:16.3.6 -> 16.3.8。(#6523)

  • deps: 升级 /backend 中的 langgraph-sdk:0.4.2 -> 0.4.4。(#6514)

  • 测试: 从两个后端测试中移除 Windows 时钟与路径假设。此前 test_dotenv_location_tilde_expands_like_source[quoted-config-path] 断言 POSIX 路径写法,现在使用平台原生的 str(Path(...)) 值; test_scheduled_cleanup_barrier_keeps_lease_renewal_and_remote_cancel_active 用 严格的 > 比较两个紧邻的 UTC 时间戳,Windows 的粗粒度时钟可能使二者相等,现在 改为在 30 秒租约窗口内提前 10 秒建立已确认的截止期限。(#6474)

  • blocking-io: 阻塞 I/O 检测器输出的路径现在以可移植方式比较。JSON schema 测试此前用 str(source_file) 断言输出的 location.path,但 scan_file 通过 relative_to_repo 构造该字段,两个分支都返回 .as_posix()——在 Windows 上 str() 产生反斜杠,测试因此在 Windows 上 失败,而跑在 Linux 上的 CI 看不到这一失败。断言现在改用 source_file.as_posix();在 Linux 上是无操作,检测器、其输出 schema 和 其余测试均不变。(#6427)

  • deps: 升级 /backend 中的 urllib3:2.7.0 -> 2.8.0。(#6404)

  • deps: 升级 /backend 中的 pyjwt:2.13.0 -> 2.15.0。(#6403)

  • deps: 升级 /frontend 中的 next:16.3.3 -> 16.3.6。(#6402)

  • 测试: 在 Windows 上跳过超出 MAX_PATH 的 blocking-io 锚点。 backend/tests/blocking_io/ 下的十二个锚点此前必然失败,报 OSError: [Errno 206] Filename or extension too long:它们在磁盘上演练的文档 布局(users/<uid>/projects/<hash>/documents/<xx>/<sha256>/<id>/original)在 pytest tmp_path 前缀下会超出 Windows 的 260 字符限制。三个受影响文件统一 应用模块级 pytest.mark.skipif(os.name == "nt", ...) 标记,与已合入的主机 限定锚点约定一致;锚点在 POSIX 上照常运行,这些标记在那里是空操作。不改动 生产代码。(#6361)

  • 测试: 为 run-change 时钟修复及其回滚补充历史回归覆盖。从已 发布的迁移图谱在 0023_user_preferences 或 0024_project_documents 处创建的数据库会跳过后插入的 0023_run_change_seq 祖先节点;新增测试重建了这两张受影响的图 谱,针对真实的启动 bootstrap、遗留行与线程删除预留/状态写入做演 练,并验证健康位置能在反复 bootstrap、降级与再升级后保持不变。仅 文档性质——修复本身已在 #5517 落地;无运行时行为变化。(#5518)

  • 重构: 能力画廊与 MCP 连接校验的内部清理: 具名的状态/操作辅助函数配合提前返回,拆出独立的 validate_mcp_connection()(422 响应不变),共享 installationQuery(),并从 catalogForServer() 移除未使用的 display-name 参数。渲染出的 UI 与行为均无变化。(#5580)

  • 依赖: 将 /backend 中的 anyio 从 4.13.0 升级到 4.14.2。 (#5583)

  • 测试: 新增由各沙箱 provider 共享的离线搜索契约测试。新的 112 用例参数化套件针对共享的 fixture 文件系统, 通过全部六个适配器执行相同的 ls / glob / grep 场景, 覆盖被忽略的后代、特殊路径、grep 范围、溢出、 传输失败与不可用的二进制。生产代码未变。(#5677)

  • 依赖: langgraph-checkpoint 升级到 >=4.2.0,<5.0, postgres extra 的 langgraph-checkpoint-postgres 升级到 >=3.1.2,<3.2,使 checkpoint_patches.py 得以移除其 InMemorySaver 补丁,改用上游对“完整 → 增量迁移后首次写入被丢 弃”与普通值增量种子检测的修复。这些下限是关键的:缺少它们时,解 析器可能悄悄装上已不再打补丁的问题版本。行为不变——本地补丁此前只 是在掩盖该 bug。(#5734)

  • 测试: 覆盖增量历史缓存的 full→delta 迁移,并钉住两个仍未修复 的上游 DeltaChannel 缺陷。迁移契约现在会在历史缓存关闭、冷启动 与热缓存三种状态下运行,断言物化后的历史与未缓存 saver 摘要一致; 新增回归用例钉住 langgraph#8382(同一 superstep 的并行写入重放时 脱离实际顺序)与 langgraph#8448(Postgres 分页增量遍历会污染游标, 导致较旧的 checkpoint 水合为空),在上游修复落地前标记 skip。 无运行时行为变化。(#5797)

  • tests: 让 blocking-I/O 的 Lark auth-complete 测试在 Windows 上可用。 此前 CLI 夹具是一个无扩展名的 POSIX shell 脚本,Windows 上的生产 CLI 发 现机制无法解析它,测试还没跑到要覆盖的子进程逻辑就因 HTTP 404 (lark-cli is not installed)失败。现在 Windows 上生成 .cmd 桩 (POSIX 仍为可执行 shell 脚本),放在含空格的目录中并经生产 PATH 查找 解析;植入假的应用凭据让授权完成流程真正到达 CLI,并断言认证成功及返 回的用户名。夹具约定已补充到 backend/tests/AGENTS.md。(#5826)

  • tests: Windows 开发机上跳过 22 个绑定 POSIX 宿主环境的后端测试,而 不是让它们硬性失败。test_extension_dependency_sync.py 的 11 个用例直 接调用 make -n(GNU make 不在文档化的 Windows 安装范围内,报 FileNotFoundError),test_lark_broker.py 的 10 个用例直接执行 #! 脚本(WinError 193),还有一个 boxlite shim 测试在 os.chmod 后 断言 POSIX 的执行位语义。全部采用条件跳过:PATH 上有 make 的 Windows 仍会运行 Makefile 契约测试,纯逻辑测试处处运行,Linux/CI 覆盖 不变。(#5931)

  • tests: tests/test_file_signature.py 的夹具改为按字节写入,避免 Windows 文本模式的换行转换混入。三个测试用 Path.write_text(...) 构造 夹具却断言字节级一致;Windows 上换行落盘为 CRLF,而 read_config_with_signature() 的契约就是原样返回磁盘上的字节,返回值 正确,测试却失败(10 例中 3 例)。夹具现在改用 write_bytes(...),stat 过期测试改为二进制追加;产品行为无变化。(#6042)

  • 网关: agents、subagents 与 managed-models 路由的持久写入现在共用一个 run_drained_write 辅助函数,在客户端断开后先排空写入再传播取消,取代 三份各自为政的路由内实现;托管子代理与托管模型的保存现在只按异常类型 记录意外失败日志。端点的取消与错误契约保持不变。(#6151)

  • gateway: 内存变更路由(清空、事实的创建/更新/删除、导入)现在改用其他 持久化写入共用的 drained-write 辅助函数,移除该 router 私有的包装。线程外 执行与取消排空语义不变;意外失败会额外留下一条仅含异常类型的丢失写入 日志。(#6164)

  • tests: 修复四个在 Windows 开发机上必然失败的后端测试,全部源于测试侧 的平台假设:skill-review 的耗时断言改用 time.perf_counter()(Windows 上 monotonic 约 15.6 ms 的时钟粒度会把线性扫描测成 0.0),personal MCP 的 owner-only 权限位检查在 chmod 无法表达时跳过,project-shelf 的最近优先 测试固定严格递增的 updated_at,不再依赖时钟粒度。无生产代码变更。(#6167)

  • 护栏: TypeSafe 风险闸门评估脚本 (backend/scripts/eval_typesafe_risk_gate.py)支持自定义评分标准并记录 实际评估的策略。新增 --instructions、--criteria-true、 --criteria-false 参数透传 provider 的评分标准覆盖,使阈值、 max_state_chars 与评分标准可在同一循环中校准;JSON 报告新增 policy 对象,记录生效的指令与两项标准——自定义标准的运行不再与默认策略运行 无法区分。provider 内置文本成为公开常量(DEFAULT_INSTRUCTIONS、 DEFAULT_CRITERIA_TRUE、DEFAULT_CRITERIA_FALSE);护栏运行时行为 不变。(#6179)

  • 测试: LangGraph Studio 路由测试在 Windows 上改为优雅停止开发服务器。 那里 terminate() 等价于 TerminateProcess,服务器不会执行任何关闭钩子, 开发持久化的 flush 无法落盘——测试所演练的重启后状态因此丢失。fixture 现在向子进程组发送 CTRL_BREAK_EVENT(uvicorn 按 SIGBREAK 处理),优雅 停止超过十秒时再退回强制杀死。POSIX 行为不变。(#6197)

  • 配置: deerflow/config 中 14 处布尔值拒绝校验收敛为一个共享助手;此前以不带字段名 的消息拒绝布尔的数值字段现在会指出字段名,例如 pool_size must be an integer, not a boolean。(#6226)

  • 测试: 阻塞 I/O 的 Web 工具 URL 校验回归测试不再依赖平台相关的数字 主机解析或外部 DNS。原来的 http://127.1/ 探针依赖各操作系统解析器把 这种简写展开成回环地址;现在改为合成一个 .invalid 主机名,通过在真实 socket.getaddrinfo 包装之下 patch _socket.getaddrinfo 的夹具解析到 回环地址,并断言每条工具路径都真正到达了该夹具。不支持地址族时会失败, 而不是得到伪造的 IPv4 应答;在事件循环上解析仍会被严格门禁判为失败。 (#6314)

  • 测试: cd backend && make test 现在把离线测试套件按时长均衡拆成四个 分片并行运行,任一分片失败即整体失败,取代原先的单进程串行执行。 TEST_JOBS(默认 4)控制分片数,TEST_JOBS=1 可顺序运行同样的分片; make test-shard 与 CI 的分片方式不变,live 与阻塞 I/O 测试仍被排除。 新增测试用离线 worker 替身固定了分片并行启动与“等待全部分片再报失败” 的行为。(#6324)

  • 集成: Lark/Feishu CLI 的输出改为按 UTF-8 解码,不再使用宿主 locale。 lark-cli(通过 @larksuite/cli npm 包分发的原生二进制)与 npm 都会向管道 写入 UTF-8,但 lark_cli.py 中的每一处捕获都只传了 text=True 而未指定 encoding,因此在 ANSI 代码页非 UTF-8 的宿主上(cp936、cp1252)非 ASCII 字段会被静默破坏——auth status --json 返回的 userName 变成乱码,而无法 解码的字节还可能让读取线程异常退出、使 stdout 变成 None,从而把一个正常 的 CLI 报告为不可用。(#6590)

2.1.0 — 2026-09-24

本节累积面向 2.1.0)的工作。 该里程碑随本次发布收尾,共合并 772 个 pull request。

⚠ 不兼容变更(Breaking Changes)

  • 网关: 现在会无条件签发请求 trace id,且每个 Gateway HTTP 响应都会携带 X-Trace-Id header。此前二者均受 logging.enhance.enabled 控制;该配置现在 仅控制日志输出——即日志记录是否包含 trace_id 字段及其格式。此 header 无法关闭;使用默认 enabled: false 的安装在升级后也会开始收到它。定时任务、 MCP 任务通知 run、IM 渠道消息以及内嵌 DeerFlowClient 都会为每个工作单元绑定 一个 id,因此此前没有 trace id 的 run 记录、checkpoint 元数据和 Langfuse trace 现在也会包含它。run 请求的 metadata 或 config.context 中提供的 deerflow_trace_id 现在会被忽略并覆盖,以确保响应 header、日志和持久化 run 保持一致;如需跨服务固定关联 id,请发送 X-Trace-Id 请求 header。logging 仍需重启后生效。未新增或移除任何配置键。(#5119)

  • 技能: 沙箱现在将 /mnt/skills 保留给“仅启用项”的托管投影视图。 DEER_FLOW_HOST_SKILLS_PATH 与 SKILLS_HOST_PATH 不再使用;Docker/AIO 和 hostPath 部署会从 DEER_FLOW_HOST_BASE_DIR 推导投影路径。指向 /mnt/skills 或其子路径的 E2B operator 挂载会被跳过并告警,避免遮蔽托管投影;请将额外内容 挂载到其他容器路径。用户投影会从磁盘重读全局启用状态,使切换在下一次获取沙箱时 跨 Gateway worker 生效。既有 E2B 沙箱在重建前仍保留创建时快照;PVC 模式暂不提供 已禁用技能的文件系统隔离。(#4178)

  • 沙箱: E2B 现在将 sandbox.replicas 作为进程级容量上限来强制执行。默认的 wait 策略会等待 acquire_timeout,随后令当前智能体回合失败。DeerFlow 不会自 动重试该回合。可使用 burst 配合 burst_limit 允许有限地超出额度多开 VM。reject 策略可在返回容量错误前先回收一个预热 VM。(#4391)

  • 技能: 包含 SKILL.md 的目录现在是一个运行时包边界。该包内嵌套的 SKILL.md 文件被视为支撑数据,不再注册为独立技能;若自定义目录结构较为特殊,需将可独立加 载的技能移到不带自身 SKILL.md 的命名空间目录下。(#4098)

  • 记忆: 记忆系统现已可插拔(memory.manager_class 选择后端;默认 deermem 自包含)。DeerMem 私有配置从 memory: 顶层移入 memory.backend_config,/memory/config 响应(以及 client.get_memory_config())的结构也发生了变化。(#4122)

  • 记忆: /memory/config 与 client.get_memory_config() 不再返回扁平的 DeerMem 字段(storage_path、max_facts、debounce_seconds、token_counting、guaranteed_* 、staleness_* 等)。改为返回 {enabled, mode, injection_enabled, manager_class, backend_config},其中 backend_config 是一个由当前后端自行解释的不透明 dict 。记忆数据响应(/memory、/memory/status 的 data)未变。读取旧扁平字段的 外部 API / SDK 客户端需改为读取 backend_config。(#4122)

  • 记忆: 自定义 memory.storage_class 发生迁移:旧的默认路径 deerflow.agents.memory.storage.FileMemoryStorage 已不存在(现为 deerflow.agents.memory.backends.deermem.deermem.core.storage.FileMemoryStorage )。自定义 MemoryStorage 子类的 __init__ 必须接受 config(此前为无参)。 损坏或过期的 storage_class 会记录错误并回退到 FileMemoryStorage(不会崩溃 )——请更新路径与签名以恢复使用。(#4122)

  • 记忆: storage_path 的语义由“文件路径”改为“根目录”。抽象化之前,绝对 storage_path 是共享的记忆文件(不按用户隔离),相对值则是 data base_dir 下的全局文件。现在 storage_path(无论绝对或相对)都是根目录;按用户的记忆存放在 {storage_path}/users/{uid}/memory.json 。若升级后仍保留旧的默认 storage_path: memory.json(一个相对文件名),会导致 按用户的记忆孤立,或在保存时触发 NotADirectoryError。因此旧版迁移会带告警 丢弃以 .json 结尾的文件式 storage_path 值,且当 storage_path 解析到一 个已存在的文件时工厂会抛出异常。如需自定义根目录,请将 memory.backend_config.storage_path 设置为一个目录。(#4122)

  • 记忆: 当 memory.mode: tool 搭配一个未实现 search() 的后端时,现在会在 Gateway 启动时由 MemoryManager 的不变量校验快速失败并抛出 ValueError,而不 是启动成功后在每次 memory_search 调用时静默返回空结果。两个内置后端都实现了 search()(DeerMem 会检索,noop 返回 []),因此仅影响未覆盖 search() 就 接入的自定义后端。这是有意为之——静默返回空比启动期报错更糟。修复方式:切换到 mode: middleware,或覆盖 search()(并设置 supports_search=True)。(#4324)

  • 配置: database.checkpoint_delta_snapshot_frequency 已迁移为 database.checkpoint_delta.snapshot_frequency,默认值从 1000 改为 10。 旧顶层字段仍会在告警后映射到新字段,显式的新字段优先。依赖旧默认值的 delta 模式部署现在会将快照频率提高 100 倍;如需保留原节奏,请显式设置为 1000。(#4516)

  • Docker: 两份 compose 文件发布的入口端口现在默认只绑定回环地址 (127.0.0.1)。依赖旧 0.0.0.0 绑定的部署必须设置 BIND_HOST 才能向其他网卡 暴露服务。(#4618)

新增

调度器

  • scheduler: 定时任务在 once 和 cron 之外新增 interval (schedule_spec.every_seconds)。节奏为 UTC 的 now + N, 不补跑错过的节拍。N 不小于 scheduler.min_once_delay_seconds (默认 60 秒),不大于 30 天。(#5291)

  • scheduled-tasks: 保存任务前即可查看即将到来的 cron 触发时刻。 POST /api/scheduled-tasks/preview-cron 接受五段式 cron 表达式、时区、1–10 的 count(默认 5)与一个可选的带时区(aware)参考时间,返回规范化后的 cron、生效的 UTC 参考时间,以及 UTC 与本地时间下的触发时刻。 它在工作线程中运行调度器自己的计算器,因此 DST 处理与保存后的任务实际 行为一致;它不预留任何资源,也不触碰任何 task、thread 或 run 存储;输入 非法与日期范围失败都会返回 422。(#5381)

  • scheduled-tasks: 可以在服务端按触发状态过滤 run 历史。过去要找一个罕见 的失败,只能把每一页历史都下载下来在客户端过滤,因为埋在大量较新成功记录 后面的失败可能出现在任何一页;GET /api/scheduled-tasks/{task_id}/runs 现在接受 ?status=,取值有 queued、launching、running、success、 failed、skipped 与 interrupted,并在分页之前应用该谓词,因此 ?status=failed&limit=50&offset=0 返回的就是第一页真正的失败记录。未知 取值(包括父任务自身的 completed)返回 422,空结果返回 [], 未过滤时的数组响应保持不变。(#5384)

  • scheduled-tasks: 定时任务详情面板可以向后翻页浏览执行记录。此前它只显示 最新 50 条,尽管端点早已支持 limit 与 offset,更早的记录在 UI 上无法访问。 “较新记录”、“更早记录”与“最新记录”导航现在会在历史中翻页, 并带有本地化的页面、加载、错误与重试状态;每次请求都会多取一行来判断 是否存在更早的页面,而不是凭空编造一个总数;最后一页不满时会禁用“更早 记录”,切换任务会重置到最新页并取消过期的请求,使迟到的结果不会替换新 任务的历史;且只有最新页会轮询。(#5363)

认证

  • 认证: 新增用于程序化 API 访问的个人访问令牌(PAT): POST/GET/DELETE /api/v1/auth/pats 用于管理令牌(仅展示一次,以 SHA-256 摘要存储);默认拒绝的路由策略只允许会话/run 生命周期路由,并进一步受令牌的 threads/runs scope 限制;任何具有取消能力的请求维度(?action=、 multitask_strategy)还额外要求 runs:cancel。(#5041)

  • 认证: 登录限流参数可配置:auth.local.max_login_attempts(默认 5,最小 2) 与 auth.local.lockout_seconds(默认 300),实时解析,配置重载后下次登录即生效 ,无需重启 Gateway——这对多名用户共享同一出口 IP 的企业代理 / NAT 部署是解锁 通道。默认值不变。(#5110)

  • **settings:**账户偏好可以跨浏览器清理或设备更换保留。通知开关、默认模型、对 话模式与推理力度以独立的 (user_id, key) 行存储,位于需会话认证的 GET/PATCH /api/v1/auth/preferences 之后,并在首帧渲染前从服务端拉取,同 时配备标签页本地的发件箱与有上限的退避重试,因此失败的写入不会被静默丢弃; null 会重置某个字段。只有这四个白名单字段会被传输——设备通知权限、显示设置 与线程级模型覆盖都保留在本地,自动的输入框回退也不会作为显式账户偏好上传。现 有的无作用域本地设置不会被迁移,因为它们没有已知归属,因此用户升级后需要重新 选择这四项。(#5397)

智能体与运行时

  • **scheduler:**定时任务可以把 assistant_id 固定为 lead_agent(默认)或创 建者已有的自定义智能体。未知或格式错误的名称返回 422。工作区的创建/编辑表单 提供同样的选择。(#5286, #5288)

  • gateway:GET /api/threads/{thread_id}/runs/page 以 (created_at, run_id) 键集游标翻页遍历线程 run 历史( {data, has_more, next_before_created_at, next_before_run_id})。 GET /api/threads/{thread_id}/runs 仍返回仅含最新 100 条 run 的裸数组,以便 LangGraph SDK 客户端继续工作。(#5282, #5283)

  • 中间件: 新增 TokenBudgetMiddleware,强制单个 run 的 token 预算,在主智 能体与子智能体之间累加共享。(#3412)

  • 中间件: 结构化的工具结果元数据与工具进度状态机,让运行时对多步工具流拥有 一等可见性。(#3601)

  • 上下文: 每个 run 记录生效的记忆身份,并在摘要过程中持久化持久上下文(系统 消息、记忆与工具状态),以结构化运行时元数据的形式发出,压缩(compaction)不再 丢失这些信息。(#3556、#3887、#3906)

  • 运行时: 目标延续(goal continuation)允许 run 跨多个智能体回合朝目标继续 执行,并跟踪、限制 continuation_count。(#3858)

  • 子智能体: 系统维护的委派账本(delegation ledger)防止对在途任务重复委派, 并设总委派上限以约束单个 run 的扇出。(#3877、#4115)

  • 子智能体: 在会话中持久化并展示子智能体的步骤历史。(#3845)

  • 工具: 结构化摘要取代预览中原始的超大工具输出。(#3377)

  • 文件: 文件工具引入确定性的“写前读”版本门控,避免覆盖并发编辑。(#3912)

  • 网关: 感知缓存的成本核算将 token 成本归因到缓存 / 未缓存路径;Redis stream 桥接启用分布式事件流;并向用户暴露手动上下文压缩。(#3920、#3191、#3969)

  • 网关: 可通过 stream_bridge.heartbeat_interval_seconds 配置 stream bridge 的心跳间隔(默认 15 秒),使位于激进 proxy 空闲超时之后的部署可以统一调节 SSE、 /wait 和内部订阅者。(#5017)

  • 运行时: 双模式 checkpoint 存储(基于 LangGraph DeltaChannel)将长调研 / 编码 run 的会话存储从 O(N²) 降至近线性。(#4292)

  • 智能体: 配置声明的主智能体中间件,让部署方无需修改运行时链即可加入自定义 AgentMiddleware 类。(#3964)

  • 智能体: 按智能体的模型与生成设置(temperature、max_tokens、thinking_enabled 、reasoning_effort)可覆盖共享的模型 profile。(#4347)

  • 运行时: 记录终态的 artifact 投递回执,使预期要 present_files 的 run 在 投递失败时不再误报成功。(#4365)

  • 上传: 通过 list_uploaded_files 工具懒加载历史文件,而非注入完整清单。(#4174)

  • 运行时: Delta 模式 checkpoint 历史缓存(内存/Redis)支持 O(1) 增量合成, 通过 database.checkpoint_cache 配置。(#4638)

  • 调度器: scheduler.recursion_limit 可设置定时运行的 LangGraph super-step 上限(默认 1000,与 Web UI 一致,并受 max_recursion_limit 限制)。(#4848)

  • 运行时: 每次工具调用都会携带由运行时签发且可防篡改的工具回执;有界回执账本 会注入模型上下文,使智能体能在报告中引用执行证据。默认通过新的 verification 配置节启用。(#4659)

  • 子智能体: 子智能体委派现在可验证,并按 RFC #4651 分层实现:每份子智能体 报告都必须引用工具回执(例如 [r3 write_file]),并为每个交付物附上可验证 handle;主智能体会将这些引用与子智能体的实际执行记录交叉核对,task 委派的 acceptance_criteria 也会在父侧确定性检查(文件是否存在/非空、记录的测试命令 退出状态),无法判定的项目会报告为 UNVERIFIED,而不是静默通过。(#5076、 #5090、#5109)

  • 澄清: 人工输入卡片支持结构化表单字段,智能体可精确请求所需信息。(#4406)

  • 子智能体: 内置子智能体会接收当前日期上下文锚点,使相对日期任务与主智能体 直接处理时表现一致。(#4797)

  • 子智能体: 设置页新增部署级子智能体目录;自定义智能体可配置显式 worker allowlist,并在 prompt 与执行阶段同时强制执行。(#4887)

  • 子智能体: 并发由统一的进程级容量控制器管理;可选 batch_task 工具可把大量 独立项目作为基于 SQL 的持久、可恢复批次执行,支持租约、有限重试、暂停、恢复与 取消,并在聊天中展示进度。(#4998)

  • 智能体: 注入 lead / 子智能体 prompt 的当前日期上下文遵循可选的 DEER_FLOW_DATE_TIMEZONE 环境变量(IANA 名称,如 Asia/Shanghai),非 UTC 部署的用户在午夜前后不再被告知错误的“今天”;未设置时保持服务器本地时区行为。 (#5154)

  • 子智能体: 被委派的子智能体可以发现此前回合上传的文件:父 run 经校验的 uploaded_files 边界会注入子智能体的图状态,使 list_uploaded_files 可参与 正常的工具策略过滤(持久 batch_task worker 保持禁用)。(#5170)

  • **agents:**读取先于写入的拦截现在会把被阻止的 write_file / str_replace 调用中无用的负载(content、old_str、new_str)从发往模型的请求中剔除。 被阻止的调用从未执行,必须在重新读取后重新发出,因此原始参数只会占用上下文; 已存储的历史、回执与 run 日志都会保留它们。被阻止的结果会与调用出现位置配对 (工具调用 id 可能跨回合重复),而历史被重写的请求会丢弃 OpenAI 的 resp_ 响应 id,使 use_previous_response_id 链无法恢复原始的服务端历史。由 read_before_write.elide_blocked_payloads(默认开启)与 read_before_write.elide_min_chars(默认 2000)控制。(#5329)

  • agents:ToolOutputBudgetMiddleware 现在还会在对话稍后又读取或修改了同 一路径时,把成功的 write_file 调用的 content 从发往模型的请求中剔除。写 入成功后磁盘上的文件才是事实来源,而读取先于写入的拦截会强制在下一次修改前 read_file,因此历史副本是冗余的,长篇的报告撰写 run 会把每个章节携带两遍。 最新的 tool_output.keep_recent_writes 次成功写入(默认 1)始终可见, str_replace 的负载从不改动,已存储的历史、回执与 run 日志都会保留原始参数。 由 tool_output.elide_superseded_writes(默认开启)与 tool_output.superseded_write_min_chars(默认 2000)控制。(#5374)

  • **agents:**自定义智能体的 config.yaml 接受 memory_enabled: false,以把 该智能体作为无状态的执行 worker 运行;省略时默认为 true。该退出选项只把该 智能体从记忆生命周期中移除——召回记忆注入、被动捕获、 memory_search / memory_add / memory_update / memory_delete 工具以及 工具模式的记忆指引全部被抑制,自动摘要与手动 /compact 也会跳过持久记忆刷写。 关闭一个已有智能体只清除 checkpoint 状态中被冻结的、由服务端标记的记忆提醒: 日期提醒、真实的用户消息与未标记的相似内容都会保留,其他每个智能体的全局记忆 设置都不受影响。(#5167)

  • runtime:ToolProgressMiddleware 的干预现在可在持久的 run 事件流中审计。 每次生效的 warn、block、recover 或更晚调用的 reset 阶段转换都会持久 化一条新的 middleware:tool_progress 事件;此前这些重规划提示、工具状态提升 与短路都消失在进程日志中,因此持久化的 run 无法显示模型是自行恢复的还是由运 行时护栏干预的。只记录有界的决策元数据——状态与恢复词汇表来自规范的 tool_result_meta schema,工具参数、结果内容、提示词以及由内容派生的哈希从 不进入事件。子智能体与其他 recorder 归属键在 Gateway 与嵌入式 worker 边界处 都由服务端持有,因此调用方无法伪造持久归属。事件只在 ToolProgress 启用时出现, 而它默认仍处于关闭状态。(#5214)

  • **subagents:**持久化的 batch_task 条目现在接受与普通 task 委派相同的可 选 acceptance_criteria,因此批处理 worker 无法再声称某个缺失的交付物存在却 仍作为成功条目落地、且没有任何记录在案的检查。判据在持久化前会被规范化(20 条可用条目、中性化后 500 个字符;空值变为 null),并在租约恢复后保留;已完 成的执行会复用现有的确定性检查器,对照归属方作用域内的线程文件与已记录的测试 命令证据。条目查询与 JSONL 导出新增可空的 acceptance_criteria 和单独校验的 acceptance_verdict(holds、does not hold 或 UNVERIFIED):一个已完 成、带有报告、缺少 CSV 且含不受支持的质量声明的条目会保持 succeeded,并带 上这三个叶子,也不会被自动重试。没有判据的条目与遗留行不报告任何结论。 (#5289)

  • subagents:task(context_mode="snapshot") 让委派可以携带派发时刻所保留 的父对话副本,而不只是委派的提示词。快照模式在委派校验之后、子项设置之前捕获 对话内容与 summary_text,因此被拒绝的委派不会序列化任何内容,之后父对话的 编辑也不会传到子项。它被渲染为任务之前一条独立的历史 HumanMessage,保留纯 文本、文本块、带匹配结果的历史工具调用描述以及可序列化的媒体输入;不可序列化 的媒体会变成一条显式的省略声明,而父级系统消息、隐藏的框架状态(包括注入的记 忆与 todo 状态)、推理块、执行元数据与待处理的工具调用都被排除。子项保留自己 的角色、模型、工具与技能限制,父级工具帧从不进入它的执行历史,因此父级操作无 法填充子项的回执、执行步骤或 bash 证据。context_mode="isolated" 仍是默认值, batch_task 条目仍要求自包含的提示词。(#5367)

  • **context:**可选的任务笔记与压缩历史召回,由 task_continuity.enabled(默 认 false;配置 schema 新增该小节并带禁用默认值)控制。启用后,task_note、 history_search 与 history_read 会在现有授权与技能策略下对标准主智能体和 DeerFlowClient 暴露,并同时支持同步与异步工具执行。task_note 在共享状态 通道中保存关于约束、决策、失败尝试与下一步的简短检查点笔记,每次写入都强制执 行笔记本容量限制与报告形态,并在持久化上下文的人类消息中渲染为已转义的历史数 据。自动与手动压缩还会把有界的消息和工具文本——包括真正的隐藏澄清卡片答案——保 留到沙箱挂载之外的 user/thread 本地 SQLite FTS5 归档中,随后由 history_search 与 history_read 查询;格式错误的 checkpoint 元数据会报告 为不可用,而不会中断模型调用或压缩,存储故障也不会影响普通压缩。这是单一任务 内的字面召回:它不会恢复 run、跨主机复制归档,也不会创建跨线程记忆,且不引入 任何 embedding 依赖。(#5382)

  • **gateway:**Gateway 的 run 不再硬编码 100 的递归上限。那些常规任务需要更长 智能体循环的部署此前必须让每个 API 调用方都提供请求级覆盖,而未这样做的调用 方会在本来合法的工作上撞到 GraphRecursionError。现在一个可热重载的顶层 recursion_limit 会在请求未指定时提供默认值(100,以保持向后兼容),显式的 请求值仍然优先,无效的请求值回退到配置的默认值,而 max_recursion_limit 会 同时限制已配置与客户端提供的值。(#5390)

  • **gateway:**Gateway run 可以被要求读取一段指定的更早对话。可选的 read_conversation 加上 conversation_references run 字段(最多三个线程 id 或同源的聊天 URL)会授予主智能体对上一段对话的面向模型读取权限,同时继续 当前任务;普通消息文本从不授予访问权,该授权绑定到该次 run 的引用、该 run 的 runs:read 权限以及每个来源的归属。读取复用现有的转录分页与可见性规则,返 回有界的用户与助手文本,并带上来源 id、续接游标以及截断或不可用声明,同时排 除隐藏上下文、推理块与原始工具结果。读取器从不持久化到配置中,并在 run 结束 时释放,因此恢复与重放需要再次提供引用;bootstrap、子智能体与普通嵌入式路径 都不会获得它。文本上限为每条消息 4000 字符、每页 20000 字符。(#5399)

  • **gateway:**对话引用现在可以由 SDK 客户端发送,并被它们检测。 LangGraph JS SDK 的 RunsClient.stream 会丢弃未知的顶层字段,因此浏览器客户 端此前根本无法发送 conversation_references 字段,也没有办法判断读取器是否 启用——在关闭时无从隐藏入口。运行创建、流式与等待请求现在都接受 context.conversation_references,限制相同(最多三个,每个 1–2048 字符), 错误位置也相同,在校验前被提升为规范字段并从 context 中移除,因此它不会到 达 run 上下文或被 checkpoint 的 configurable;同时发送两种写法会返回 422,而 留在 body.config 中的副本仍不授予任何权限。GET /api/features 报告 conversation_references: {enabled, max_references},其中 enabled 遵循已 配置的工具列表,因此 config.yaml 的改动无需重启即可生效。(#5463)

  • tools:list_uploaded_files 接受可选的 query(大小写不敏感的文件名子 串)与 extensions("pdf" 或 ".PDF",两者都表示 .pdf),且两个过滤器 都运行在 20 文件截断之前——而懒加载历史早已应用了该截断。此前上限作用于未 过滤的按 mtime 排序的列表,因此在一个满是较新截图的线程里说“分析我之前上传的 那些 PDF”会把 PDF 推进 omitted_summary,让智能体没有路径可交给 read_file; 现在 total_count、truncated 与 omitted_summary 描述的是过滤后的集合。 省略两个参数会保持当前行为,两个过滤器以 AND 组合,空值或无效值视为不过滤, 过滤后无匹配会返回 No uploaded files matched the given filters.。(#5341)

  • agents: 自定义智能体此前在 Gallery、聊天头部与欢迎页显示仅含 ASCII 的 存储标识符,因此所有者无法用中文或其他非 ASCII 文字为智能体命名。每个 智能体现在可以在其配置文档中,以及 create/update/read/list 各 API 中,携带 一个可选的 display_name——去除首尾空白、至多 100 个字符; 更新时省略该字段会保留原值,null 或空值则将其清除。该标签从智能体 Gallery 的设置按钮进入编辑,按 Unicode 码位计数,并受一个可见的字符预算 约束;若包含控制字符、不可见的格式字符,或仅由记号(marks)、分隔符 (separators)与格式字符(format characters)构成,则会被拒绝;普通多语言 文字与 ZWJ 表情仍被允许。路径、URL、运行时的 agent_name、归属与 React 组件身份仍使用稳定标识符,因此无需迁移任何数据;存储中无效的取值在读取时 会被忽略,而不会破坏 list/detail/bootstrap。(#5324)

记忆

  • 记忆: 记忆合并(consolidation)合成碎片化的事实,并通过 LLM 为每条事实分 配的 expected_valid_days / staleFactsToExtend 进行过期审查,剪除静默过期的 事实。(#3996、#3860、#4143)

  • 记忆: 保证注入纠正事实(并优雅降级),使用户的纠正始终能触达模型。(#3592)

  • 记忆: 精简可插拔的 MemoryManager 接口以方便后端接入——新后端不再需要实现 未被调用的抽象方法,DeerMem 专有的 hook 注入也移出共享工厂。(#4326)

  • 记忆: 增量式按智能体作用域的 Markdown 事实存储,按智能体隔离事实,且更新 单条事实时无需重写或重建整个集合的索引。(#4279)

  • 记忆: 记忆消息处理新增会话水位(watermark)、无意义回合过滤与持久化队列, 使抽取不再每回合都重新喂入完整会话。(#4447)

  • 记忆: 内置 FTS5/BM25 检索适配器,无需外部服务即可对已存记忆全文搜索。(#4360)

  • 记忆: 新增可插拔后端:通过 HTTP 接入 OpenViking 与 mem0,以及作为用户模型 记忆 provider 的 Honcho。(#4509、#4528、#4730)

  • 记忆: 混合事实淘汰策略综合多种信号,决定容量满时应丢弃哪些事实。(#4789)

  • memory: 可选的写入侧防护,避免把已有事实的改写再次存入记忆,因此重复的 抽取不再以重复内容占用记忆容量与 prompt token。写入侧开关是 memory.backend_config.fact_dedup_enabled(默认 false),另有 memory.backend_config.fact_dedup_similarity_threshold(默认 0.7,取值 范围 0.5–1.0)。在当前 user/agent 作用域内, 新的同类事实会与既有事实以有界 token-Jaccard 相似度比较——对拉丁词与中文 二元组(bigram)做词法启发式,而非语义等价判定。合并会保留既有事实的 id、 content 与创建时间,把 confidence 提升到两者中较高的值, 时刷新 source;facts_merged_dedup 计数器记录发生了多少次合并。 成对的更正替换会绕过近似去重,移除提议会把自己的目标排除在匹配之外, 且不会伪造任何确认信号——强化仍然需要真实的人类消息。(#5254)

技能

  • skills: 内置的图像生成技能现在可以使用兼容 OpenAI 的 Images API 进行生成与参考图编辑,端点、模型、尺寸与输出格式均可配置。(#5389)

  • 技能: 原生 SkillScan(阶段一)在加载时静态分析技能包;describe_skill 支 持延迟发现,模型按需获取技能 schema,而非一开始就加载全部技能。(#3033、#3775)

  • 技能: 按用户的自定义技能隔离,并配合沙箱挂载。(#3889)

  • 技能: 选中一个技能后技能列表会重新打开,便于连续附加多个技能。(#4639)

  • 技能: 可直接从“技能”设置页安装本地 .skill archive,并复用现有的按用户 installer 和安全扫描。(#5039)

  • 技能: 播客生成的火山引擎音色可按说话人性别覆盖配置,默认值经过修剪、对空值 安全。(#5156)

  • skills: 可以从设置页导出自定义技能,包括已禁用的那些,因此备份、在多 个安装之间搬迁或发给协作者的,都是实际安装的版本,而非原始上传的版本。 双语预览会列出包大小、分页的文件路径、声明的依赖要求与可操作的阻塞项, 两个新增的仅管理员 manifest 与 download 端点会将该预览 绑定到一个修订摘要——期间被改动的包会返回 409,而不是给出 过期的 ZIP。支持文件、空目录与规范化后的可执行权限能在往返中保留, 而链接、特殊文件、不可移植的路径、嵌套的技能根与可执行二进制会被拒绝。 导出复制的是原始保存的文件,不是脱敏步骤:它既不扫描密钥,也不 运行技能脚本,因此凭据与账号设置仍需单独配置。(#5332)

  • skills: 延迟技能发现现在会对候选排序,而不是把模型的整段自由文本查询 当作一个正则表达式去匹配。设置 skills.deferred_discovery: true 后, 智能体只能看到技能名称,并调用 describe_skill 来选择要加载的内容; 但多词意图此前必须连续匹配:chart visualization 找不到 chart-visualization,analyze Python 也找不到被描述为 “Analyze data with Python, pandas, jupyter” 的技能,因此智能体无法加载 匹配的工作流及其生效的工具策略。 现在查找会规范化 Unicode、大小写与名称分隔符,并按字面意图词的覆盖率 给候选打分,覆盖率相同时名称匹配优先于仅描述匹配,并以目录顺序 打破平局,使结果保持可复现;模型生成的查询限制为 256 个字符与 16 个 不同的词。select: 精确选择与 +required 名称过滤保持不变,且不涉及 任何嵌入、模型调用或遥测。(#5369)

模型与集成

  • 社区工具: 新增网络检索 / 抓取引擎——GroundRoute、Crawl4AI(web_fetch)与 fastCRW provider——并新增 Browserless web_capture 截图工具和 Brave image_search 。(#3675、#3821、#3585、#3881、#3866)

  • MCP: MCP 工具调用支持按 server 的 tool_call_timeout,并提供路由提示引导 模型选用正确的 server。(#3843、#4004)

  • MCP: 新增官方 OpenViking /mcp 示例,通过 DeerFlow 通用 MCP 客户端暴露其 原生工具集。(#4745)

  • 社区工具: 将“智能体化浏览器控制”作为会话的一等能力——基于 Playwright 的浏 览器会话由智能体操作,用户可在工作区中观察或接管。(#4187)

  • 社区工具: Lark / 飞书 CLI 集成打包了运行时安装、官方 lark-* 技能包与交 互式授权流程,使该集成不再依赖手动环境配置。(#3971)

  • 集成: 可在“设置 > 集成”中按用户切换 Lark/飞书应用凭据;新 App ID/Secret 会在写入前校验,成功切换后撤销旧 OAuth token。(#4703)

  • ACP: 支持 MiniMax Code (mcode acp) 作为原生外部编码智能体;ACP thought chunk 不再拼接进工具结果。(#4846)

  • 模型: 新增 Z.AI GLM-5.3-Flash profile,保持 thinking 始终开启并停止通用 reasoning-effort 转发,因为该模型会拒绝关闭 thinking,且只接受自身定义的 effort 值。(#5074)

  • 社区工具: 新增 Serply(支持 news 与 scholar 垂直搜索)和腾讯云 WSA 网络 搜索 provider,并为 DDGS、Brave、Tavily 与 SearXNG 提供统一的原生时间范围过滤 (日/周/月/年)。(#5023、#5057、#5099)

  • 社区工具: 新增 Sofya web_search 与 web_fetch provider——搜索结果直接 携带每个页面的内容,并按结果设上限,使默认搜索保持内联。(#5239)

  • 知识库: 新增可选的只读 RAGFlow 检索,通过已配置的 RAGFlow dataset 暴露 knowledge_search(query) 智能体工具,并提供 dataset ID allowlist,以及错误路径 中的凭据和 dataset ID 脱敏。(#4955)

  • 知识库: 新增可选的只读 LightRAG 检索,作为同一 knowledge 组的另一个 knowledge_search(query) provider——运营方通过配置哪个条目来选择 RAGFlow 或 LightRAG。它调用 LightRAG 的结构化 /query/data endpoint(不做 LLM 生成),将 排序后的 chunk 格式化为带引用编号的文本,可选的 X-API-Key 在所有模型可见路径 中脱敏,服务端错误消息会映射为可操作的工具错误。(#5209)

  • models: 新增按用户区分的收藏层,让常用模型触手可及,且不会改变当前选中 或默认的模型。紧凑的两行模型列表现在在主聊天与 Side Chat(侧边对话)中都是 锚定式下拉,每行带一个行内星标;收藏会排进第一组,但不会选中任何模型, 也不会关闭选择器,并按已登录用户持久化,支持跨标签页同步,以及恢复暂时 不可用的条目。原有的搜索框被移除,这是有意的简化,且没有单独的管理模式。 (#5441)

  • **models:**有界并发并不限制请求速率:多个并发 run 仍可能耗尽某个 provider 的每分钟请求配额,而分发前没有任何机制来对需求限速,因此 429 才是出错的第一 信号。新增可选的 models[].request_admission 配置块,通过进程本地的队列放行 模型调用,接受 requests_per_minute(必填)、可选的 group(默认为模型配置 名)、max_wait_seconds(默认 300)与 max_queue_size(默认 256);在 60 RPM 下放行至少间隔一秒,而空闲期不会积累突发额度。同一个有界 FIFO 由同步 调用方、独立的 asyncio 事件循环以及显式组内的每个模型实例共享,它们的策略必 须一致——冲突的设置会在构造时失败并需要重启。取消与超时会丢弃一个等待者而不消 耗一次放行,队列已满则会在分发前失败。该限制器通过 LangChain 的 BaseChatModel 钩子挂载,因此智能体模型、invoke 与 stream 路径以及工厂创建 的辅助模型都在覆盖范围内;工厂会把策略从 provider 参数中剥离,并把暴露给 SDK 的 max_retries 设为零,使重试无法悄悄绕过钩子——现有的智能体中间件重试 会重新进入放行队列,而非中间件调用方则会失去 SDK 重试。预算按进程计算,因此 运维方必须把账户配额在 worker、副本与其他客户端之间划分;限制器统计的是请求 而非 token,未配置的模型行为与之前完全一致。(#5432)

MCP

  • MCP: 新增持久任务运行时:长时工具任务通过持久 driver 跨 Gateway 重启继续, 进度与完成通知显示在聊天 UI 中。(#4665、#4690、#4833)

  • MCP: 共享 MCP server 可注入按用户区分的凭据;未映射用户默认拒绝,存储的 凭据在 Gateway API 响应中脱敏。(#4868)

  • MCP: 新增按 server 的 tool_name_prefix,让已自行命名空间化工具的 server 保留原始工具名;默认行为不变。(#4624)

  • MCP: “设置 > 工具”现在可以通过定向 Gateway endpoint 新增、编辑和删除 MCP server;复制粘贴 JSON 的工作流会保留高级字段和已脱敏的 secret placeholder。 (#5022)

  • MCP: 共享 HTTP/SSE server 可通过 headers_from_context 将请求作用域的 secret 映射为 header:调用方在 config.context.secrets 中提供每次请求的值, 配置只存储键名,缺失值默认拒绝。(#5010)

  • mcp:extensions_config.example.json 中新增一个可选的 parallel-search server 条目(https://search.parallel.ai/mcp,HTTP,默认 无认证),默认禁用;启用后会暴露 parallel-search_web_search 与 parallel-search_web_fetch,并文档化可选的 Bearer 认证。(#5028, #5501)

  • MCP: extensions_config.example.json 新增可选的 parallel-search server 条目(https://search.parallel.ai/mcp,HTTP,默认无鉴权),默认禁用;启用后会 暴露 parallel-search_web_search 与 parallel-search_web_fetch,并文档化可选 的 Bearer 认证。(#5028)

渠道

  • 渠道: 把 IM 的 channel_user_id 以 DEERFLOW_CHANNEL_USER_ID 暴露给沙箱 命令。(#3926)

  • 渠道: 对同一会话的密集消息进行排队,并在批次间保留话题卡片预览。(#3988)

  • 渠道: 入站 webhook 去重迁移到 Postgres,使多个 Gateway Pod 可同时服务同一 IM 渠道而不重复处理事件。(#4210)

  • 渠道: 钉钉入站消息支持文件与图片附件。(#4423)

  • 渠道: 新增 Buzz (Nostr) 渠道连接器及配套前端体验。(#4649、#4727)

  • 渠道: IM 命令 /agent list 与 /agent use <name> 允许会话切换到 owner 的 自定义智能体:选择会持久化到会话元数据(重启后恢复)并优先于过期的渠道默认值, IM 创建的会话在 Web UI 打开时路由到同一智能体;同时 /agent 在斜杠技能解析器 、前端与 TUI 中保留,避免任何技能遮蔽该命令。(#5168)

认证与防护

  • 认证: 通用 OIDC / SSO 认证,并支持 Keycloak。(#3506)

  • 护栏: 已认证的运行时上下文在 GuardrailRequest 中暴露,安全干预以 run 事 件的形式持久化。(#3665、#3837)

  • 认证: “保持登录”选项,配合统一的会话 cookie 策略(HTTPS 下使用持久化 Secure cookie,公网 HTTP 下使用会话 cookie)。(#4255)

  • 认证: 部署方可关闭本地自注册,将新账号限制为仅通过 SSO / OIDC 开通。(#4311)

  • 鉴权: 内置 RBAC 鉴权 provider 与统一工厂,并在装配期(模型可见前移除工具 )与运行期(拒绝被禁用的调用)双重强制执行工具鉴权。(#4260、#4370)

  • 鉴权: Gateway 路由权限改由已配置的 AuthorizationProvider 推导,不再使用 固定表。(#4439)

  • 鉴权: 模型权限会在 Gateway 路由和智能体运行时双重执行,获取沙箱时还会校验 sandbox:execute。(#4540、#4911)

  • 鉴权: GET /auth/me 现在返回调用方的生效路由权限(RFC #4063 阶段 4),直接 读取认证中间件在每个已认证请求上标记的 AuthContext——不产生额外的 provider 评估——使前端能隐藏调用方角色无法执行的操作。(#5228)

  • **authz:**被拒绝 threads:delete 或 runs:cancel 的角色此前仍会看到线程行 的删除菜单项、侧边删除按钮与一个可用的输入框停止按钮,于是 UI 提供了一个 Gateway 的 @require_permission 守卫只会拒绝的操作。前端现在消费 GET /auth/me 报告的生效权限并隐藏删除入口,并在流式输出时禁用停止按钮,同 时用 aria-label/title 标明权限边界。强制逻辑没有变化——Gateway 守卫仍是唯 一决策点——而缺失、为 null 或尚未加载的权限列表会被当作宽松处理,因此新旧后 端混布的部署绝不会隐藏调用方仍可执行的操作。(#5294)

沙箱与 provisioner

  • 沙箱: 新增 E2B 与 BoxLite(micro-VM)沙箱 provider;BoxLite 自带预热池。(#3883 、#3940、#3951)

  • provisioner: ClusterIP Service 与按技能作用域的 PVC 挂载,并支持配置沙箱 容器端口。(#4016、#3928)

  • 沙箱: 新增云沙箱 provider:Tenki 与 OpenSandbox。(#4382、#4877)

  • 沙箱: K8s provisioner 模式新增可选的 lark-cli 凭据 broker sidecar,将 Lark 应用密钥与 OAuth token 移出沙箱文件系统;沙箱只看到转发命令的 shim。默认关闭。 (#4501)

  • 沙箱: E2B mount 上传的 wall-clock deadline 可通过 mount_upload_deadline_seconds 配置(默认 120 秒)。(#4876)

  • 沙箱: E2B 沙箱创建后会携带结构化的 MountUploadResult(truncated、 reason、上传统计),并在同进程内的 warm-pool 回收后保留——挂载上传因资源限制 被截断的情况可以在代码中观察,而非只出现在 Gateway 日志。(#4884)

  • 沙箱: 本地 Docker AIO 沙箱新增可选的受控出网:sandbox.network.mode 支持 isolated(每个沙箱独立内部 bridge,无出站路由)与 allowlist(同样的 bridge 加上自行解析目标的域名 allowlist HTTP(S) 策略 sidecar,拒绝 IP 字面量与 ECH); 被拒绝的公网域名可通过人工输入卡片批准(临时授权或本沙箱内永久允许),非交互 run 默认失败,受限模式下沙箱 API 也不再直接发布。要求 Docker Engine 28+; open 仍是默认值。(#5152)

扩展与插件

  • 扩展: 新增 out-of-tree Python 扩展系统,可贡献中间件、任务生命周期与系统模型 observer、Gateway 服务和 HTTP 路由,并用 deerflow extensions 管理。(#4636、 #4684、#4780)

  • 扩展: 扩展可观察消息来源、中间件策略、智能体装配指纹、上下文压缩、护栏决策 和工具的 MCP 来源。deerflow-extension-api 升至 0.2.0,0.1 扩展会在启动时被拒绝。 (#4863)

  • extensions:extensions.middlewares 条目除了现有的 module.path:ClassName 字符串外,还可以写成 {class, kwargs},kwargs 会 传给构造函数。需要设置阈值、请求头名称或任何其他参数的运维方管理中间件,不再 必须为了设定一个值而硬编码一个子类。字符串条目仍以无参数构造,空的类路径会在 配置校验时被拒绝,而不是留到智能体创建时才失败,像 apply_to 这样的未知字段 也会被拒绝。(#5312)

  • extensions:deerflow extensions upgrade SOURCE(也以 make extension-upgrade SOURCE=... 暴露)会替换一个受管理的本地快照,或为 已在 extensions 组中的依赖重新固定版本,并沿用现有的 plugins: 记录,使其 私有的 config 与 required 得以保留。迁移到更新的固定版本此前意味着先 remove 再 install,而 remove 会删除整条记录——包括密钥——而已经快照过的 本地目录若不手工编辑受管理的副本根本无法重新安装。普通的 install 没有变化, 仍会拒绝已存在的本地快照;升级未安装的来源会以安装提示的方式失败关闭;失败的 升级会恢复先前的快照,即使并发的 pyproject.toml / uv.lock 编辑阻止了依赖 文件回滚。(#5347)

  • extensions: 新增可选的 RunEvidenceReader 契约,让 Gateway 扩展可以 直接得知自上次查看以来发生了什么变化,而不必再反复对齐整个 thread:由宿主 持有的单调递增 cursor 持久地发现 changed-run,另有按 run 限定范围的事件分 页,以及具备权威性的状态读取。cursor 不透明、带版本、绑定作用域、可安全重 放,对使用数据库支撑的存储而言还是持久的;分页按 (change_seq, run_id) 排 序,而不是从 thread 作用域的事件序号或时间戳推断全局顺序;进度快照与租约心 跳不会推进这个时钟;删除操作不产生 tombstone,因此消费方需要轮询状态,并把 缺失的 run 视为不存在。既有扩展不受影响,因为 ExtensionRuntimeDeps.run_evidence_reader 默认为 None;无法提供某一页的 存储会显式失败,而不会返回一个具有误导性的空页。生产环境的 reader 以 app 为作用域,并为受信任的运营方扩展提供全局跨用户可见性;它会脱敏事件元数据, 但保持内容不变。(#5405)

持久化

  • 持久化: 可通过 postgres_schema 选择自定义 PostgreSQL schema;ORM、LangGraph checkpointer 与 store 表均创建在其中,启动时自动创建。(#3442)

前端

  • 前端: 支持对助手回合进行分支,以及针对引用追问的侧边对话。(#3950、#3934)

  • 前端: 支持重新生成最新回答。(#3637)

  • 前端: 引用来源证据面板、智能体 run 的工作区变更评审,以及可视化的 ask_clarification 卡片。(#3907、#3945、#3956)

  • 前端: 语音输入、方向键回溯 prompt 历史、输入框内容润色,以及“(思考了 N 秒)”思考时长标签。(#4036、#3718、#3986、#3627)

  • 前端: 用 agents_api 特性开关控制智能体 UI 的可见性,并在后端与 UI 中持 久化 AI 回合时长。(#3769、#3663)

  • 前端: 将斜杠技能(slash-skill)激活渲染为内联标签(chip)。(#3981)

  • 前端: 本地化的 AI 辅助声明。(#4374)

  • 前端: 支持置顶最近会话。(#4442)

  • 前端: 在输入框中校验 /goal 目标长度。(#4337)

  • 前端: 实时显示上下文窗口使用量。(#3183)

  • 前端: 可原地编辑并重新运行最近一次用户回合。(#4377)

  • 前端: 澄清卡片待处理时仍可输入并发送回复。(#4530)

  • 建议: 可通过 suggestions.max_suggestions 配置后续建议数量(默认 3)。(#4533)

  • Artifact: 可在 artifact 面板中内联编辑文本 artifact。(#4596)

  • Artifact: Markdown artifact 可在新窗口 reader 中以渲染形式打开(并提供 “查看源文件”和“下载”fallback);一次 run 中展示的所有文件也可根据该 run 的 投递回执打包下载为 zip。(#5056、#5117)

  • 前端: 自定义智能体聊天支持 Browser Live。(#4719)

  • 前端: 新增长会话大纲:超过 5 个用户回合后,紧凑侧边菜单会列出会话中的问题, 支持跳转并跟踪当前章节。(#5025)

  • 前端: 定时任务可复制为可编辑草稿,保留配置但不带 run 历史。(#5064)

  • 会话: 分支会话自动使用 Title (2)、Title (3) 等编号区分标题,最近会话 列表以树形连接线展示父子关系。(#4983)

  • 前端: 支持归档与恢复会话:侧边栏 Archive 操作配 Undo toast、搜索栏上方的 “最近会话 / 已归档”标签页,以及按会话的恢复控件;SQL 与 Memory 存储在分页前 应用归档过滤,同时保留消息、文件、链接、置顶状态与当前 URL。(#5236)

  • 项目: 项目工作区组织会话(Projects MVP 阶段一):侧边栏 Projects 区块支持 平铺 / 分组列表模式,项目详情页带分页的会话列表,项目内新建聊天会预创建线程并 归属该项目,run 永远不会落到所选项目之外;分支继承项目归属、支持在项目间移动 ,并提供项目的创建 / 重命名 / 归档 / 恢复 / 删除。(#5265)

  • Artifact: 已完成的 CSV/TSV artifact 以有界表格预览(最多 200 行 × 50 列, 每页 50 行,吸顶表头,可选首行表头),解析在后台线程进行、复用现有 1 MiB range 加载器——字面字符串、前导零与多行带引号单元格都得以保留,长单元格可在可复制的 对话框中打开,源码视图一键切换。(#5284)

  • title: 仅含附件的首回合现在会得到真正的标题,而不是千篇一律的 New Conversation,因此以文件开头的对话既可区分也可检索。当首个回合只携带 一个通过校验的附件时,清理后的文件名会成为本地标题;携带多个附件时则生成类 似 2 files uploaded 的计数;用户自己撰写的文本仍然优先作为标题来源,而在 没有可用合法文件名时仍回退为 New Conversation。本地标题在配置的标题模型 路径之前返回,因此仅含附件的回合不再需要为一次 LLM 调用付费。文件名只从上 传中间件填充的 uploaded_files 状态中读取,客户端元数据不受信任,并且会做 清洗:剥离控制字符与多余空白,保留普通 Unicode 字符和百分号,长名称在保留 扩展名的前提下截断。这取代了此前 <current_uploads> 标题修复特意保留的 New Conversation 回退行为。(#5304)

  • projects: 项目 MVP 第二阶段让项目真正发挥作用,而不只是给文件夹起个名 字。项目说明此前会被存储、可以编辑,却没有任何东西读取;现在它会在每个成员 线程的 run 中作为有界的 user 角色 <project> 块送达模型,且只出现在该 run 的请求里,永远不会进入系统提示词或持久化历史;重命名会在下一次 run 生效; 超长文本会在写入时被拒绝,而不是被截断。每个项目的文档架新增 Documents 标签页(上传、带来源信息的列表、预览、下载、移入回收站、按内容哈希去重), 智能体可按需通过 list_project_documents / read_project_document 读取 ——这些工具只在项目 run 中注册,且仅支持文本。此外还支持双向提升: Save to project 把任意线程文件连同来源信息复制到文档架, Attach to thread 则通过常规上传管道把文档架中的文档重新摄入一次对话。只 读的对话文件视图会聚合各成员线程的上传文件与产物,项目删除也终于有了回收站 这一层级。(#5443)

  • frontend: 调试模式下可以查看通用调用与 MCP 调用的工具详情。这些步骤此 前只显示一个标签,尽管浏览器早已收到它们的输入与结果,很难看清工具之间究竟 传了什么。Token Usage → Debug 现在会带出一个折叠的 Tool details 面板 ——对没有 token 统计数据的调用同样如此——展示工具名称、call ID、输入,以 及原始结果或明确的错误,并配有复制操作与中英文标签。内容只在展开时才格式化, 文本长度、嵌套层级与已访问的值都有上限,因此大体积载荷不会让面板卡住。 (#5309)

  • frontend: 对话现在可以从输入框中引用。附件按钮旁边新增了 Reference a conversation 按钮,仅当 /api/features 报告该能力时才渲染, 并会打开一个选择器,其列表与侧边栏使用的最近会话列表相同——从不会提供当前 对话,并以报告的 max_references(目前为 3)为上限,超过上限的行会被禁用, 而已选中的行仍可点击以移除。被附加的对话在输入框中显示为可移除的 chip,在 对话记录中则是指回来源的只读 chip。引用是按消息生效的:不随草稿保存,发送 或切换线程时清除,重新生成或编辑某个回合时会不带引用运行,除非重新附加。 (#5465)

  • frontend: 能力管理从设置中迁出,成为工作区侧边栏里独立的 Capability Center。此前 MCP server、已连接应用授权与技能都挤在账号和外观 偏好旁边,既难以找到,也让设置对话框难以扫读;现在它们集中在同一个页面上, 其中 Plugins 合并了 MCP 管理与 Lark/飞书应用的安装与授权,Skills 提供 Built-in、Community、My skills 与 All skills 四种视图,支持可搜索 的卡片、启用开关、文件导入、创建以及自定义技能导出。设置保留其余七个分区, API 契约与权限均未改变,旧的 ?settings=tools|integrations|skills 入口被 移除。(#5468)

可观测性与工具

  • 可观测性: trace-id 关联与增强日志,以及通过 Monocle 实现的智能体可观测性 。(#3902、#4024)

  • 工具: 类 Hermes 的终端工作台(deerflow CLI,基于 DeerFlowClient),以 及脱敏的社区支持包(support-bundle)生成器。(#3760、#3886)

  • 安装向导: 安装向导现在会询问 OpenAI 兼容的 gateway 模型是否支持 thinking ,并新增火山引擎 Coding Plan 快速安装路径。(#3428、#4141)

  • TUI: clear 命令。(#4306)

  • TUI: 支持透明终端背景。(#4631)

  • 网关: 新增 GET /health/ready 就绪探针,执行有界的数据库 SELECT 1, 数据库不可达时返回 503(memory 后端返回 200 not_configured);/health 仍为 纯存活探针,生产 compose 的健康检查也改用就绪探针。(#5166)

  • 可观测性: 延迟工具晋升(routing-hint 自动晋升与显式 tool_search)会以 隐私最小化的 middleware:tool_promotion run 事件持久化(工具名、来源、数量、 智能体归因;不含查询、schema 或结果),且仅在技能策略过滤之后观测,被拒绝的 schema 不会被报告为生效晋升。(#5183)

  • client: 上下文压缩把结果存放在 messages 之外的 ThreadState.summary_text 中,但嵌入式客户端的 values 事件只选择了 title、messages 与 artifacts——因此 Gateway 之外的消费方(例如基准 测试运行器)无法通过公开事件流观察到该摘要。现在每个嵌入式 values 事件都 会携带 summary_text,缺失时为 None,并把更新、重复与清除作为状态快照转 发。消息序列化、AI-delta 去重、工具产物与用量记账均未改变,也没有暴露任何 checkpoint 内部实现。初始快照本身就可能包含恢复出来的摘要,因此仅凭取值变 化并不能断定发生了一次压缩事件。(#5249)

变更

  • 前端性能: 保持公共根页面和本地化文档静态化;懒加载关闭的工作区面板及编辑器/ 高亮依赖;增量推导流式消息状态;限制流式 Markdown 工作量;虚拟化超长消息与聊天 列表;暂停屏幕外装饰效果,并对代表性路由设置 JS/CSS 预算。

  • 浏览器: 协商二进制 Browser Live JPEG 帧,兼容旧 JSON/base64 协议;每次刷新 只呈现最新帧,并撤销已替换的 object URL。

  • Artifact: 普通文本 artifact 支持 HTTP byte-range 流式读取;Web UI 初始预览 限制为 1 MiB,用户显式请求后才加载完整文件。

  • 沙箱: Helm chart 现在默认将每个沙箱的 Service 设为 ClusterIP 而非 NodePort ,因此代码执行沙箱只能通过集群内 Service DNS(http://sandbox-<id>-svc.<ns>.svc.cluster.local )访问,不再绑定到每个节点(包括 GKE / EKS / AKS 上外部可达的)网卡。升级时现 有 chart 安装会从 NodePort 切到 ClusterIP。如需保留原有可达性(外部探测命中 30xxx 端口,或 gateway 不在 K8s 的 Docker-Compose / 混合部署路径),请设置 provisioner.sandboxServiceType: NodePort(必要时配合 provisioner.nodeHost)。provisioner 本身不变(自 #4016 起已按模式感知)。(#4190)

  • 技能: 处于激活态的限制型技能若要委派给子智能体,必须在 allowed-tools 中 显式声明 task。只读发现基础设施(tool_search 与 describe_skill)仍可用, 但无法为被禁用的业务工具授予 schema 可见性或执行权限。(#4098)

  • 记忆: 抽象化之前位于顶层的 memory.* DeerMem 字段(storage_path、max_facts 、debounce_seconds、model_name、token_counting、staleness_*、consolidation_* 等)在加载时会带告警自动迁移到 backend_config,因此升级不会静默地把自定 义配置回退为默认值(model_name -> backend_config.model.model)。将它们移到 config.yaml 的 memory.backend_config 下即可消除告警。(#4122)

  • 记忆: 新增 memory.mode(middleware | tool);tool 模式会注册供模 型直接调用的记忆工具(memory_search/add/update/delete),取代被动的逐 回合摘要。manager_class 解析改为快速失败(未知后端时抛出 ValueError,而非 静默回退)。(#4023)

  • 中间件: 声明式分层中间件构建器;ThreadData 现在先于 Uploads 运行。(#3809)

  • 沙箱: 宿主机到虚拟机的输出脱敏正则现在统一归属,消除重复的模式编译。(#4108)

  • 文档: AGENTS.md 成为智能体指引的权威来源,CLAUDE.md 通过 @AGENTS.md 导入;模块指南同步刷新。(#3770)

  • 记忆: OpenViking 后端改用官方适配器;旧 trusted-mode 的 auth_mode/account 字段会被拒绝,改用绑定凭据的 USER API key。(#4707)

  • 网关: 在 run-event journal 出现之前创建的会话,会在首次新 run 前把 checkpoint 历史回填为 seed event,使旧会话升级后仍可见且顺序正确。(#4590)

  • 智能体: 子智能体委派改按净收益路由;除非并行延迟、专长能力或上下文隔离明确 有益,否则主智能体默认直接执行。(#4384)

修复

  • nginx: 把 600 秒读取超时扩展到其余两个会等待 Gateway 的 location,它们在线程路由的修复 之后仍沿用 nginx 默认的 60 秒。/api/ 兜底 location 之后:无状态的 POST /api/runs/wait 阻塞在同一套运行完成等待上,并在客户端断开时取消该运行,因此等待超过 60 秒的 API 调用方会 同时收到 504 并且运行被取消;输入框的 POST /api/input-polish 则等待一次性模型调用。 /api/skills 之后:安装 .skill 压缩包会对其中每个文件各做一次 LLM 安全扫描,自定义技能的 编辑与回滚各再做一次,它们都没有自己的超时;此前只有同级的 /api/skills/install/upload 拿到了更长的超时,因此同样的安装经由 POST /api/skills/install 会在 60 秒失败。 Docker、本地开发与 Helm 配置均已应用。(#5524)

  • 前端: 项目会话行上的 …(kebab)菜单不再溢出侧边栏。在侧边栏的按项目分组模式下, 缩进的嵌套菜单继承了 SidebarMenu 的 w-full 又额外带着 ml-4,实际宽度是"100% + 16px", 其绝对定位的 right-1 操作按钮因此落到侧边栏边缘之外——活跃项目分组下的行被裁剪,Archived 分组下双层缩进的行则完全看不到按钮;扁平会话列表(唯一不带缩进的菜单)不受影响,这也是问题 此前未被发现的原因。两个嵌套菜单现在改用 w-auto,块级 flex 容器按"剩余宽度减去外边距"填充。 除布局修复本身外,行为、数据与 API 均无变化。(#5682)

  • 持久化: 修复静默跳过了 run-change clock schema 的数据库。0023_run_change_seq 被插到了 已经发布的 0023_user_preferences 修订之前,因此在该修订点(或之后)盖章的数据库会把它当作 已应用的祖先而从不执行——run_change_clock 表与 runs.change_seq 列永久缺失,第一次删除会话 (任何 run-store change-clock 递增)就会以 no such table: run_change_clock 失败。新增的 0025_repair_run_change_seq 修订在升级时重新应用同一份带守卫的 DDL,在形态健康的库上空操作。 RunChangeClockRow 与 UserPreferenceRow 也已注册进 ORM 模型注册表,create_all 与 autogenerate 通过显式导入(而非模块副作用)看到所有表。(#5517)

  • 后端: 为 LangGraph 兼容的 POST /api/assistants/search 增加分页校验。此前 limit 与 offset 直接用于 Python 切片,offset: -1、limit: 0 之类的非法值乃至过大的 limit 都会返回 200 和误导性的结果,而不是在 API 边界被拒绝。现在 limit 必须落在兼容范围的 1~1000 内, offset 必须非负;非法请求返回 422。(#5506)

  • 模型: Claude Code 凭据加载器现在能防护格式错误的 claudeAiOauth 容器。 ~/.claude/.credentials.json 可能是合法 JSON 但 claudeAiOauth 的值不是对象——部分导出遗留的 null、原始字符串、数组或数字——此前提取器会对它调用 .get,从 ClaudeChatModel.model_post_init 抛出 AttributeError 并中断模型构造,与该模块文档声明的 "优雅降级"行为相反(同属模型加载器的 Codex 一侧早已对同样形态做了防护)。现在顶层 payload 或 claudeAiOauth 值不是对象时,会以 debug 级别记录并视为"此来源没有凭据",加载流程继续尝试 下一个来源,所有来源都不可用时最终返回 None。(#5494)

  • 事件: 删除会话时保持 DB 写锁的代际稳定。DbRunEventStore 用一把 asyncio.Lock 串行化 每会话的序列号分配,而 delete_by_thread() 只要发现 lock.locked() 为假就把锁从注册表移除 ——但 asyncio.Lock.release() 会先清除锁定状态、再让排队的等待者恢复运行,删除若恰好落在这个 交接窗口内,就会在已入队的等待者仍引用旧锁时移除注册表项。随后同一会话的新写入者会创建新一代 锁,两个写入者可能各持一把锁并行推进,破坏围绕 max(seq) + INSERT 的单进程串行化保证。现在 注册表改为弱引用,已入队的持锁者/等待者在自己排空之前锁仍可被发现;另用单独的强引用 pin 维持 正常运行中"每会话一把锁"的既有行为,删除只回收 pin。(#5462)

  • setup: BOM 前缀配置中的自定义 sandbox 镜像现在会生效。setup-sandbox.sh 用 ^sandbox: 去匹配仍带着 UTF-8 BOM 的首行,匹配失败后静默选择并拉取默认镜像,而运行时 YAML 加载器其实 接受同一份配置。脚本现在在进入镜像选择流程前剥离首行开头的 UTF-8 BOM,未引入新的运行时依赖。 (#5515)

  • 网关: 关停时的运行排空在重复取消下得以存活。_drain_inflight_runs() 会对 RunManager.shutdown() 任务加 shield 并再次 await,但第二次 Task.cancel() 若落在第二个 shield 尚未完成时,会打断 helper 自身,让 lifespan 在运行任务仍在排空时继续退出——恰好重新 打开该排空机制要防止的资源顺序问题:checkpointer 开始拆除后,运行任务可能仍在写 checkpoint。 现在 helper 会强持有已启动的 shutdown 任务并反复 shield 直至其到达终态,记住首次调用方取消 并在有界排空完成后再传播;排空自身失败时的日志与错误行为保持不变。(#5487)

  • 模型: Codex 的无效工具调用现在与其工具结果成对重放。Codex 模型发出 arguments 不是 合法 JSON 的 function_call 时并不会让本轮失败:调用被记入 invalid_tool_calls,由 DanglingToolCallMiddleware 以占位 ToolMessage 应答,模型看到的是可恢复的工具错误。但 Codex Responses 序列化器只对有效的 tool_calls 生成输入项,占位结果作为 function_call_output 到达 provider 时,同一请求里没有与之配对的 function_call,而 Responses API 要求二者成对——中间件本要恢复的那个场景反而以 provider 报错收场,而不是重试。 Chat Completions 系 provider 经 LangChain 转换器本就会这样重放无效调用,OpenAI 兼容路径的 同类失败也已在中间件层修复;现在 Codex 请求同样把 invalid_tool_calls 作为 function_call 输入项与有效调用并列重放。有效调用路径、解析侧与中间件均未改动。(#5509)

  • nginx: 需要等待模型调用的线程路由不再在 60 秒时失败。浏览器直接调用 /api/threads/*, 而该 location 没有设置 proxy_read_timeout,因此沿用 nginx 默认的 60 秒,而 /api/langgraph/ 允许 600 秒。较慢的 /compact 会返回 504,但 Gateway 仍会继续执行并保存压缩结果,于是 UI 对已经生效的操作显示错误,诱使用户重试并再次压缩。/suggestions 也受同一限制,/runs/wait 则会在 nginx 断开连接时取消其运行。Docker、本地开发与 Helm 配置现在都为该 location 允许 600 秒。(#5505)

  • middleware: 循环检测不再中断正在分段读取文件的智能体。read_file 调用 此前按 200 行的分桶建键,因此短于一个分桶的读取都会塌缩到相邻分桶上:连续 五次 40 行的读取哈希完全相同,会触发硬停止,使 run 以强制最终答复和 stop_reason=loop_capped 结束——而这正是 read_file 自身的截断提示 要求模型去做的分段读取。现在的键使用精确的行区间,省略的 end_line 保持 "读到末行"的开放语义,不带范围的读取与显式 start_line=1 仍共用同一个键。 重复同一区间仍会在原有阈值被拦下,边界抖动的读取循环仍由按工具类型计数的 频率层覆盖。 (#5486)

  • subagents: 让 max_turns 名副其实——表示运维人员所理解的轮次。它此前被 直接当作 LangGraph 的 recursion_limit 传入,而后者统计的是 super-step, 每个图节点算一步;而 create_agent 又会为每个中间件生命周期钩子编译出一个节点,因此在 子智能体的中间件链上一轮要花掉 7~8 步:内置 general-purpose 的 max_turns=150 实际只买到约 18 轮带工具调用的轮次,随后以 turn_capped 结束;每往链上加一个中间件,有效预算还会再缩水一次。现在 执行器会按实际装配出的中间件链的每轮节点数来换算配置的轮次,调高 max_turns 就能得到它所声明的轮次。没有配置项变化;既有的 max_turns 取值现在会获得完整预算,因此原先被截断的子智能体 run 可能变长,其上界 仍由 subagents.timeout_seconds 与 subagents.token_budget 约束。 (#5485)

  • 调度器: 在 SQLite 上同样强制执行全局 max_concurrent_runs,此前该上限只在 Postgres 上成立。 认领排队中的 occurrence 时,会先统计正在执行的行,再把其中一行提升为 launching,Postgres 用 advisory lock 将这两步串行化。而 SQLite 的 deferred 事务直到那条提升用的 UPDATE 才占用 writer, 因此在不同行上并发认领的调用方——手动触发与轮询重叠,或第二个 Gateway 进程共用同一个数据库 文件——会读到同一个过期计数并全部通过预算检查,导致实际运行数超过配置的上限。(#5469)

  • sandbox: AIO 的 glob 不再把"恰好填满"的结果报告为截断。其 include_dirs 分支在收集到 max_results 个匹配时就立即返回, 因此一个只有这么多匹配、后面再无匹配的目录列表也会被标记为被截断, 工具据此告诉模型结果不完整。该分支本就持有整份目录列表, 现在改为多看一个匹配再判断,与同级的 include_dirs=False 分支 一致,后者一直是按完整列表判断的。这里涉及的只是过滤后匹配数上限; parse_remote_search_output 管的是原始输出行数上限,是另一条限制、 有自己的"多放一行"记账方式,其他 provider 的过滤后匹配数上限未作改动。 (#5449)

  • 中间件: 移除工具调用的守卫不再导致 Claude 或 OpenAI Responses 线程之后的每一轮都失败。 token 预算与循环检测的硬停止、subagent 数量限制的截断以及安全终止抑制只清空了 tool_calls, 却把 provider 自身的工具调用块留在消息 content 中。Anthropic 与 Responses API 会重新发送这些块, 导致下一次请求带着没有结果的工具调用而被 provider 拒绝;写入 checkpoint 的硬停止消息还会让之后 每条新消息都失败。现在所有守卫都通过同一个共享 helper 删除对应的 content 块,该 helper 也会保留 clarification 所保留的 Responses 调用。(#5447)

  • 沙箱: 远程 glob 与 grep 的输出被截断时,不再报告"没有匹配"。BoxLite、Tenki、E2B 与 OpenSandbox 会先限制搜索的原始输出行数,再在 Python 中过滤(node_modules 等忽略目录、匹配模式或 glob 范围),但只有达到 max_results 时才报告 truncated。若被截取的行全部被过滤掉,截断位置之后仍有 真实匹配的搜索会返回空结果且显示为完整。现在搜索会多输出一行以判断是否被截断,glob 和 grep 工具对被截断的空结果会说明结果不完整,而不是显示 "No matches found"。(#5427)

  • 沙箱: 当输出用 : 连接主机路径(如 $PATH、$PYTHONPATH)时,主机路径不再暴露给模型。 匹配的路径会一直延伸到列表末尾,导致同一根目录下之后的条目都未被遮蔽;多余的遮蔽轮次每次 恰好补回一个条目,因此短列表掩盖了这一泄露。现在遮蔽时匹配的路径在 : 处结束。 挂载目录内指向所有挂载之外的符号链接,在命令输出和 glob 结果中改为显示其挂载路径,而不是目标的主机路径。(#5418)

  • 沙箱: BoxLite grep 不再忽略 glob 的目录部分。此前只比较文件名,src/*.js 会匹配整棵目录树中的所有 .js 文件。现在 glob 作用于相对搜索根目录的路径,与 glob() 及其他 provider 的范围一致。(#5419)

  • 模型: 通过 CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR 传递 Claude Code OAuth token 时,第一个之后的 Claude 模型不再丢失凭据。每个 ClaudeChatModel 实例都会重新加载凭据, 但文件描述符只能读取一次,导致标题、摘要、subagent 模型以及之后的每次运行都没有凭据,并以 TypeError: Could not resolve authentication method 失败。现在 token 在每个进程中只读取一次并复用。(#5411)

  • 模型: 当 supports_reasoning_effort: true 的模型同时从 profile 获得 reasoning_effort(顶层、when_thinking_enabled 或 when_thinking_disabled 中,或由 extra_body.thinking 的关闭路径注入)时,lead agent 不再构建失败。lead agent 的常规构建总会转发请求的 effort(即使未设置),导致该参数两次传给 provider 构造函数,抛出 TypeError: got multiple values for keyword argument 'reasoning_effort'。现在请求值 按每个 agent 的 model_settings 方式叠加:替换 profile 顶层的值,未设置时保留该值, 最终值仍由 thinking 模式相关设置决定。Codex 保留自己的级别校验。(#5403)

  • 运行时: 带 Idempotency-Key 的 run 重试在 SQL run 存储上不再返回 500。HTTP 准入不会传入 user_id,SQL 存储会把请求用户写入该行,但进程内的 run 记录仍为 None。同一 key 的重试若落到另一个 Gateway worker,或在已完成的 run 被清理后回到 同一 worker,就会比较两边的拥有者,把自己的 run 误判为其他用户的并抛错。同一不一致 还让 HTTP run 被按拥有者过滤的历史读取漏掉,并跳过了 MCP background_tasks 投影, 因此这类 run 的 values 事件现在会包含 background_tasks。RunManager 现在按 SQL 存储的方式用请求用户补全缺省的拥有者,各存储记录的拥有者保持一致。(#5401)

  • 运行时: 跨 worker 的幂等 run 复用不再让复用方 worker 永久阻塞该线程。此前复用会 把从存储中读取的行注册为本地 run 记录,但只有拥有该 run 的 worker 才会结束并清理自己的 记录,因此这份副本会一直停留在准入时的 pending/running 状态:该 worker 上此线程后续 所有 reject 准入都返回 409,直到重启;读取该 run 时持续返回过期状态;若拥有方崩溃, 孤儿回收也会跳过这个 run。发往该 worker 的取消请求还会走本地拥有方路径,把拥有方仍在 运行的行标记为 interrupted。现在复用方 worker 返回不注册到本地的 store-only 句柄, 取消请求也按非拥有方的约定处理。(#5393)

  • Skills: 切换 skill 启用状态时不再把解析后的密钥写入 extensions_config.json。 此前 Gateway 的 skill 开关与 DeerFlowClient.update_skill 通过 ExtensionsConfig.from_file() 读取配置(该方法会把所有 $VAR 值替换为环境变量的 实际值),再把模型整体写回,于是 "$GITHUB_TOKEN" 引用会被持久化为明文令牌,未设置 的变量则被永久写成 ""。DeerFlowClient.update_mcp_config 对 mcpServers 以外的 所有键也存在同样问题。现在这些写入方直接修改磁盘上的原始 JSON,并按运行时的加载方式 校验候选配置后再写入,占位符与手写结构保持不变;MCP 路由也复用同一个原始读取函数。 已被旧版本改写过的文件仍保留明文值,请恢复 $VAR 引用并轮换已暴露的凭据。(#5357)

  • Gateway: disable_clarification 与 github_token 现在与 non_interactive 一样,仅对内部认证的调用方生效。此前这两个键无论调用方身份都会从 body.context 透传,而且不会从被逐字复制进 run config 的自由格式 body.config 中清除,因此任何 会话或 PAT 调用方都能设置它们。其中 disable_clarification 影响更大: ClarificationMiddleware 会把包括 risk_confirmation 在内的所有澄清请求替换为 "无需确认,继续执行",SandboxMiddleware 也把它与 non_interactive 视作同一个 非交互信号。github_token 则会进入 runtime.context,被 bash 工具导出为 GH_TOKEN/GITHUB_TOKEN;若经由 body.config['configurable'] 夹带,还会被写入 checkpoint 存储。定时任务、IM 渠道与 GitHub webhook 渠道走内部请求通道,不受影响。 (#5338)

  • Artifact: PUT /api/threads/{id}/artifacts/{path} 现在严格限制在 /mnt/user-data/outputs 之内。此前 outputs-only 校验只是对原始路径做字符串前缀 检查,百分号编码的 ..(outputs/%2e%2e/uploads/x.txt,nginx 原样转发、Starlette 解码后)可以通过,而路径解析器只把结果限制在 user-data/ 内,因此调用者能覆盖自己 线程里的上传文件或 workspace 文件。现在会先折叠 ./.. 段再做前缀检查,并把解析 后的宿主机路径与解析后的 outputs 根目录再次比对,outputs/ 内被植入的符号链接同样 无法把写入重定向到别处。该规则现在收敛为一个共享 helper,IM 渠道的附件投递也走同 一实现,两处不会再各自漂移。(#5321)

  • 网关: 不再把调用方提供的 deerflow_trace_id 持久化到 run 记录上。body.metadata 会同时到达运行中的 run config(run worker 会重新盖章)和 runs API 原样回显的 run 记录, 此前只覆盖了前者,因此客户端可以让 run 最持久的展示面与同一请求的 X-Trace-Id 及日志行 互相矛盾。现在 id 只在信任边界处盖章一次,config.context 也以同样方式封堵,且会话自身 的 metadata 不再被写入"创建它的那个 run"的 run 作用域 id。(#5119)

  • 网关: 在 Access-Control-Expose-Headers 中暴露 X-Trace-Id。它不在 CORS 安全列表 中,因此跨域拆分的浏览器客户端——同样读不到 Gateway 日志的那一方——无法读到本应写进 bug 报告的关联 id。(#5119)

  • 网关: 未处理异常的 500 响应现在也携带 X-Trace-Id。Starlette 的 ServerErrorMiddleware 通过所有用户中间件之外的原始 send 发出这类响应,因此服务器 bug 的 500——最需要关联 id 的那个响应——成了唯一不带 id 的响应。TraceMiddleware 现在会在 重新抛出异常前自行发送一个携带该 header 的 500;服务端的异常日志不受影响,流中途的失败 也照旧传播。这一回退响应在 CORSMiddleware 之外发出、保持 CORS 不可读,因此跨域拆分的 浏览器客户端在这个响应上读不到 id——与它所替换的 ServerErrorMiddleware 500 一致。 (#5119)

  • 网关: 从持久化的请求回显中剔除伪造的 deerflow_trace_id。body.config 会原样存入 runs.kwargs_json 并由 runs API 返回,因此 config.metadata 或 config.context 中的 伪造 id 会在这一处幸存,而其他所有展示面都带着真实 id。redact_config_secrets 现在会把 该键从两个容器中剔除,build_run_config 则将 run metadata 合并到副本上,服务器盖章的 id 不再能写穿回调用方的请求体。(#5119)

  • 运行时: 会话元数据现在仅在 run 通过启动屏障后才切换为 running,待取消的 run 不再短暂呈现 running 状态;worker 启动期间客户端可能观察到先前的会话状态 。(#4450)

  • 运行时: 通过原子化、感知租约的接管(takeover)claim 重新检查孤儿候选 run ,使扫描后的成功心跳仍保持 run 活跃,且仅有一个 reconciler 上报恢复。(#4424 、#4434)

  • 技能: allowed-tools 只作用于斜杠激活或实际加载的主智能体技能,避免被动 启用的技能与评测 fixture 从每个 run 中移除 MCP、网络、文件与委派工具。(#4095 、#4098、#4192)

  • 模型: 在所有 BaseChatOpenAI 子类(VllmChatModel、MindIEChatModel、PatchedChatMiMo 、PatchedChatStepFun、PatchedChatMiniMax)上生效 api_base,而不只是 ChatOpenAI / PatchedChatOpenAI。此前这五个子类会静默丢弃配置的 endpoint,随后以一个含义 不明的 unexpected keyword argument 'api_base' 让每次请求都失败;它们也未被纳 入未知配置 key 的告警。两者现在都基于 issubclass(BaseChatOpenAI) 判断。(#4146)

  • 智能体: 在 LLM 请求前合并 SystemMessage;保证工具运行后仍有可见响应;避 免在流结束前发起默认的 LLM 标题调用;为省略号预留空间以使本地标题遵守 max_chars ;并将工具输出尾部前移,使回退截断遵守 max_chars。(#3711、#4033、#3885 、#4052、#4017)

  • 智能体: 动态上下文的日期扫描跳过无日期的提醒;从不含 config.yaml 的智能 体目录加载 SOUL.md;update_agent 的旧版智能体守卫要求存在 config.yaml; 并拒绝空的 SOUL.md 更新。(#3685、#4136、#4166、#4219)

  • 中间件: 为循环检测的工具频次计数器加窗口,避免长 run 误触;阻止标题中间件 流式输出 token;修复“同内容列表耗尽时位置回退误取无关 todo”的问题;在 _apply 、before_agent、_clear_run_state 与 _drain_pending_warnings 之间持有 token 预算锁;丢弃孤立的 ToolMessage 以免严格型 provider 返回 400;净化非法 tool-call 参数;并在 dangling 修复中从空的 tool-call 名称与畸形 tool-call id 恢复。(#4072 、#3566、#3709、#3714、#4080、#4193、#4008、#4246)

  • 子智能体: 继承 LoopDetectionMiddleware 与摘要中间件,使工具循环能被打断 、步骤能被捕获;将回合预算上限以 MAX_TURNS_REACHED 暴露并附带部分结果;在累 加式 stop_reason + token_budget 上统一护栏上限;压缩前注入持久上下文;保留 父 checkpoint 命名空间;在 general-purpose 系统 prompt 中禁用 task 工具;flush 失败时重新缓冲子智能体事件以避免丢失步骤;并修复子智能体 run_id 为 None 时 丢失 loop_capped 停止原因的问题。(#3931、#4009、#3949、#3980、#4040 、#4215、#4161、#4082、#4059)

  • 记忆: 加固对 null / 空值边界情况的处理——跳过仅含空白的事实;在更新、检索 与剩余三处原始读取中规整 null 的 confidence / source.confidence;将显式 null 的 backend_config 值视为缺省;修复事实无 id 或事实列表为空时的 KeyError / UnboundLocalError;停止防抖更新队列的忙等(busy-spin);并在优雅关闭时刷写记 忆队列以防丢失。(#3719、#4074、#4076、#4034、#4217、#3993、#3992 、#4073、#4181)

  • 运行: 关闭 run 原子性中的多 worker 归属缺口;在租约续期于截止期前无法确认 时令本地执行快速失败,并在 peer 接管后对迟到的完成写入加围栏;多 worker 下将 cancel 降级为租约接管;让 create_thread 在插入竞态时仍幂等;从运行时上下文读取 stop_reason ;并将 run 时长持久化到 checkpoint 以供历史读取。(#4003、#4064、#4414、#3800 、#4188、#4118、#4431)

  • 运行时: 序列化 SQLite event-store 写入,避免按会话的序列号冲突;在 journal 中跳过隐藏的人类消息;并去掉 _merge_stream_text 中静默丢弃 delta 的行为。(#4077 、#3698、#4085)

  • 网关: 按真实 event_type 关联会话消息反馈;把 artifact 服务、gateway 上 传与 Discord 渠道中的阻塞文件 IO 移出事件循环;限制上传文件的上下文清单;并实 时追踪畸形的 Redis 重连 id。(#3651、#3551、#3935、#3927、#3917、#4012)

  • 上传: 在写入前先占位转换后的 Markdown 配套文件名,使同词干(stem)的两个 可转换上传(或一个可转换上传加一个同词干 .md 上传)不再在同一请求内静默互相 覆盖。uploads.auto_convert_documents 开启时,配套 .md 会得到唯一名称(如 a_1.md );POST /threads/{id}/uploads 与 DeerFlowClient.upload_files 都会在 markdown_file 中返回实际文件名。(#4288)

  • 配置: 将为 null 的对象型配置节规整为默认值;在 store 与 sync checkpointer 中遵循统一数据库配置;并让旧版 DB 回填在已存在的表上补建缺失的 Index 对象。(#3573 、#3904、#3994、#4090)

  • 模型: 将 stream_chunk_timeout 默认值应用到所有 BaseChatOpenAI 子类; 并为 ChatOpenAI 把 api_base 规范化为 base_url,遇到未知配置 key 时给出告 警。(#4102、#3790)

  • MCP: 按 server 隔离工具发现失败;同步 session-pool 单例的生命周期;按配置 内容 + 路径(而非仅更新的 mtime)失效工具缓存;在加载时校验 MCP 工具名,使延迟 prompt 保持惰性;并按来源 server 路由工具,而非按名称前缀。(#3772、#3797、 #4124、#4154、#3812)

  • 技能: 斜杠技能每个 run 仅激活一次,而非每次模型调用都激活;补齐技能安装安 全扫描的覆盖缺口;在评审 CI 中识别被完全删除的技能包,并在 SkillScan 中把剩余 的 requests / httpx 方法识别为网络出口;在无参 skills prompt 段落复用已解 析的 app 配置;并支持不重启 Gateway 即可重新加载已挂载技能。(#4103、#3924 、#4169、#4130、#4160、#4264)

  • 沙箱: 为反向路径翻译与输出脱敏正则加上段边界;在 read_file / str_replace 中处理单边行范围与空文件;对齐 AIO bash 工作目录;在 Windows 的反向 resolve 包 含性检查中使用 os.sep;在 bash 命令中规范化 Windows 反斜杠路径;阻止 glob / grep / ls 暴露被禁用技能的文件;并在沙箱审计中允许合法的 heredoc 命令。(#4035 、#4053、#4078、#4079、#4051、#4058、#3869、#4096、#3786)

  • 沙箱: 同步沙箱 provider 单例的生命周期(含并发回归测试),并让 provisioner 中的 k8s 调用不阻塞事件循环。(#3730、#3941)

  • 沙箱: 将沙箱 artifact 挂载与渠道用户对齐;修复非 root / NFS 主机上的本地 开发(make dev);停止时回收 macOS 上的 nginx 进程;并修复 Docker 中生产环境 Postgres 的 UV-extras 检测。(#3729、#3590、#3828、#3897)

  • 渠道: 在解析渠道 provider 配置前先校验 provider;对 GitHub webhook 重投递 去重,并丢弃冗余的 GitHub review-comment webhook 扇出;把斜杠技能白名单检查限 定在 run 的 owner 作用域;将飞书文件消息批量到一个会话,并以 bot @mention 前缀 分发飞书群命令;在 /connect 绑定码前接受前导 @mention,且不把裸 connect 当 作绑定命令;阻止飞书创建会话话题并限流卡片更新;让 UI 运行时渠道配置优先于 config.yaml ;修复 bot_login / mention_login 仅含空白时 require_mention 的门控;防护 企业微信中为 null 的引用字段;并将入站去重按聊天作用域的工作区建立 key,使 Telegram 、飞书、微信与钉钉在默认(未绑定)配置下不再因重投递重复执行智能体,遇到瞬时失 败时释放去重 key 以便重投递仍可恢复。(#4100、#4104、#4131、#4129、#3753 、#4229、#4222、#4251、#3810、#3674、#4055、#4069、#4287)

  • 前端: 摘要过程中保留消息与持久上下文;流式期间保留 artifact 并稳定 artifact 路径;解析相对 artifact 图片路径;在 header 下拉中保留已展示的 artifact;保持 孤立的工具消息可见;工具步骤期间展示助手文本;客户端导航时重置新会话;防止并发 提交导致流被取消;修复过期 run 的重连与取消处理;修复聊天公式渲染、单波浪线 markdown 、双重 reasoning 渲染、UTF-16 markdown 二进制分类与 Streamdown 中的 <memory> 标签;让最近会话行整体可点击;上传前校验附件上限并修复消息复制中上传文件元数据 ;修复移动端工作区与可访问性阻塞、卡片工具消息 bug 以及侧边聊天工具栏 / 面板按 钮行为;拦截未解析的建议模板占位符;刷新通知权限;仅对已完成回合显示分支操作; 在自定义智能体聊天中启用重新生成;并为被中断的首回合 run 生成兜底标题。(#3826 、#3791、#4094、#4038、#3854、#3880、#4114、#3673、#3878、#3908 、#3557、#4245、#3870、#3966、#4209、#3733、#3900、#3944、#3740 、#3976、#3959、#3961、#3764、#3768、#4147、#3967、#3874、#3644)

  • TUI: /quit 退出前先中断活动 run。(#4235)

  • harness: 当大纲标题数恰好等于 MAX_OUTLINE_ENTRIES 时不再误判为被截断。(#3856)

  • 追踪: 把 Langfuse trace 元数据附加到目标评估器。(#4202)

  • 上下文: 修复 context-compress 的 bug。(#4065)

  • ThreadData: 修复 runtime.context 为 None 时的 AttributeError。(#3989)

  • goal: 修复 stand-down 期间 continuation_count 被重复递增的问题。(#4199)

  • 熔断器: 修复半开探测不可重试后熔断器卡死的问题。(#3991)

  • GitHub: allow_authors 登录名改为大小写不敏感匹配。(#4218)

  • 社区工具: image_search 现在返回全分辨率图片 URL。(#3990)

  • 技能: 把技能历史接口中的阻塞文件 IO 移出事件循环。(#3563)

  • 技能: SkillScan 不再把惰性求值的 PEP 695 类型别名误判为网络出口。(#4315)

  • skills: 技能压缩包的解压现在也按成员数量设限,而非只按未压缩大小。 safe_extract_skill_archive() 是每次 .skill 安装都会经过的常开路径;它限 制了 未压缩字节总量以防御按大小计的 zip 炸弹,却没有成员数量上限, 因此一个装有数万条微小条目的压缩包可以被顺利解出。 scan_archive_preflight() 早已把条目数限制在 4096,但只会在可选的 skill_scan.enabled 开关开启时运行,默认路径因此没有上限。现在解压会无条件 执行同样的 4096 成员上限,直接抛出 ValueError;在启用了 SkillScan 的情况下,结构化的 package-too-many-members 命中仍会优先呈现。 (#4241)

  • 追踪: 从运行时上下文解析 Langfuse trace 的用户。(#3794)

  • 护栏: 将内部 owner 归因透传到护栏上下文。(#3839)

  • 子智能体: 子智能体上限与 MIN_SUBAGENT_LIMIT 保持一致地限幅。(#4081)

  • 子智能体: 加载按用户作用域的技能。(#4356)

  • MCP: 按 server 的 OAuth 预热改为失败即软降级,并持久化轮换后的 refresh token 。(#4084)

  • MCP: 忽略畸形的类路径文本。(#4456)

  • 认证: 邮箱账号改为大小写不敏感解析。(#4101)

  • 认证: 从 setup-status 超时中恢复。(#4371)

  • 调度器: 修复一个调度竞态,该竞态可能为同一个定时任务启动两个 run。(#4105)

  • 渠道: 缓冲并在 busy run 期间排空被排队的 GitHub 评论。(#4133)

  • 渠道: 在转换为 mrkdwn 前先转义 Slack 保留字符。(#4197)

  • 渠道: 飞书卡片 / reaction SDK 调用上检查 response.success()。(#4234)

  • 渠道: 丢弃不携带会话身份的入站钉钉消息。(#4316)

  • 渠道: 支持接收入站 Telegram 附件。(#4392)

  • 记忆: 合并后的事实从其来源继承 expected_valid_days。(#4225)

  • 配置: _memory_config 与 AppConfig 自动重载保持同步。(#4208)

  • Postgres: 用 pool_recycle 与 command_timeout 加固异步引擎,消除陈旧连 接导致的 504。(#4230)

  • harness: 为 invoke_acp_agent 增加超时,避免无限挂起。(#4238)

  • 社区工具: 在 web_fetch(Browserless)中暴露目标页的错误状态。(#4239)

  • 沙箱: 放宽 BoxLite / AIO 的租户哈希范围,并在回收时校验身份。(#4171)

  • 沙箱: str_replace 在任意文件上遇到空 old_str 时改为无操作。(#4256)

  • 沙箱: 序列化 E2B 的释放状态转换。(#4355)

  • 沙箱: 限制 E2B 输出同步的资源占用。(#4364)

  • 沙箱: 在 after_agent 中解包被 Overwrite 包裹的沙箱状态。(#4381)

  • 沙箱: 本地 AIO 流量绕过代理。(#4444)

  • 模型: 暴露因长度截断的模型响应,而非直接丢弃。(#4309)

  • 流式: 保持大文件生成过程的响应性。(#4354)

  • 流式: 把自定义事件暴露给 astream_events。(#4403)

  • 流式: 对回放历史中的缺口发出信号。(#4426)

  • 摘要: 使用 run 模型进行摘要,摘要 provider 失败时回退。(#4361)

  • 运行时: 模型调用后移除临时的图片上下文。(#4267)

  • 运行时: 阻止子图流式帧冒充根帧。(#4407)

  • 运行时: 拒绝不支持的 run 选项与流模式。(#4430)

  • 运行时: 序列化 checkpoint 写入与活动 run、线性化 delta 模式的 checkpoint 恢复,并接受 SDK 默认的 stream_resumable=false,以避免恢复竞态。(#4437、#4460 、#4468)

  • checkpoint: 解包对空 channel 的 Overwrite 首次写入。(#4383)

  • nginx: 允许超长聊天 prompt 通过 /api/langgraph/,不再直接返回 500。(#4277)

  • 网关: 当 header 已设置时,优先使用 X-Trace-Id 而非 metadata.deerflow_trace_id 。(#4283)

  • 网关: 为分支补种 run-events,使继承的历史在分叉后仍然保留。(#4385)

  • 网关: 分支历史补种的 run id 按继承的回合作用域划分。(#4459)

  • 前端: 加固 artifact 与 markdown 渲染。(#4117)

  • 前端: 工作区变更评审中,把“符号链接替换文件”与“删除”区分开。(#4170)

  • 前端: 把工作区变更文本缓存生命周期中的阻塞文件 IO 移出事件循环。(#4268)

  • 前端: 对 artifact URL 的路径段进行编码。(#4278)

  • 前端: 厘清 run 时长的展示。(#4348)

  • 前端: 在分支会话中保留重新生成状态。(#4358)

  • 前端: reasoning-effort 未设置时默认展示为 Medium。(#4373)

  • 前端: 剥离并解析 <current_uploads> 上传上下文标签。(#4402)

  • 前端: 保持前导的孤立工具消息可见。(#4408)

  • 前端: 刷新后保持已完成子任务卡片的稳定。(#4432)

  • 前端: 通过 inline style 应用消息图片的 maxWidth。(#4446)

  • 前端: 恢复 artifact 与侧边面板的可调整大小。(#4469)

  • 前端: 允许非 localhost 主机访问 dev-server。(#4471)

  • 内容安全: 回填空的内容过滤响应,避免污染会话。(#4394)

  • 工具: 从 list_uploaded_files 的 schema 中排除注入的 runtime。(#4376)

  • Artifact: 显式加载完整文件时限定在来源会话内,使其他会话中同路径 artifact 仍保持 1 MiB 预览。(#4634)

  • 沙箱: SandboxAuditMiddleware 改为按命令替换所处位置判断风险:普通输出捕获 不再误拦,而命令位置、解释器代码参数、eval/source、process substitution 与 here-string 中执行下载内容仍会阻止;heredoc 正文继续按数据处理。(#4611、#4623)

  • MCP: 设置页的启停只校验目标 server;允许禁用已不合规目标但拒绝重新启用, 支持规范中的 transport 别名、展示后端校验详情,并以原子方式更新共享配置。(#4574、#4577)

  • MCP: 用按 server 的 session_init_timeout(默认 60 秒,null 可关闭)限制工具 发现与持久 stdio session 初始化,避免挂起 server 阻塞智能体装配或 Gateway 事件循环。(#4657)

  • 运行时: .tool-results 等超大工具输出外置目录不再计入工作区变更和产物检测, 仅外置工具输出的 run 不会再被投递校验误判失败。(#4657)

  • 前端: 回合仍在流式输出时隐藏旧的后续建议。(#3396)

  • 前端: 修复流式渲染抖动:不重复播放逐字动画、稳定步骤文本和消息顺序,并保持 reasoning 位于答案上方。(#4266、#4510、#4513、#4578)

  • 前端: 聊天路由中的 thread id 现在会编码,特殊字符不再破坏导航。(#4302)

  • 前端: 从 React children 正确渲染引用链接。(#4486)

  • 前端: 本地化会话导出失败消息。(#4493)

  • 前端: 拖拽折叠面板时同步侧边面板状态。(#4556)

  • 前端: 每个 run 只渲染一张工作区变更卡片。(#4559)

  • 前端: 活动 artifact 发生变化时刷新其内容。(#4584)

  • 网关: 拒绝 API 请求中的非正读取上限。(#4284)

  • 网关: 解析 thread id 时兼容为 null 的 config.configurable。(#4301)

  • 网关: 统一各 API 路由的 thread id 校验。(#4589)

  • 网关: 合并并发的会话元数据更新,避免相互静默覆盖。(#4489)

  • 网关: 向跨域客户端暴露 run 元数据响应 header,使分离部署的前端能及时获知新 run id。(#4535)

  • 网关: 从稳定 checkpoint 执行“编辑并重跑”,确保编辑后的 prompt 真正运行, 并在重跑后保留手动标题。(#4534、#4539)

  • 运行时: 可从任意存活 Gateway worker 取消 run,停止按钮不再依赖请求路由。(#4500)

  • 运行时: interrupt 或 rollback admission 中途取消时关闭替代 run,避免留下不可见的活动 run。(#4472)

  • 运行时: 重新生成响应时保留当前标题,并支持最近一次尚未写入 checkpoint 的中断响应。(#4480、#4524)

  • 智能体: 将 404 等 web_fetch 错误页识别为错误证据,使重试和停滞保护能够响应。(#4314)

  • 智能体: 规范化澄清选项时兼容 XML-to-dict 形态。(#4527)

  • 子智能体: 委派执行使用隔离 callback 与惰性技能激活,修复跨事件循环错误及被动 技能移除 write_file 等基础工具的问题。(#4497)

  • 沙箱: 初始化沙箱时兼容被 Overwrite 包裹的状态。(#4429)

  • 沙箱: 安全协调 E2B 沙箱:选择首个健康候选、按用户与会话采纳规范实例、延后 处理 peer 的活动副本,并在宽限期后回收孤儿。(#4443)

  • 沙箱: 销毁 readiness 失败的沙箱前先取得所有权,避免 peer 采纳后误杀活动回合。(#4505)

  • 沙箱: grep 支持搜索单个文件。(#4512)

  • 沙箱: 使用 Redis 所有权时在部署范围内强制 E2B 容量上限。(#4575)

  • 技能: 斜杠调用可从托管 integrations 根目录激活集成技能。(#4570)

  • 技能: 更新技能时把阻塞文件 IO 移出事件循环,并序列化并发写入。(#3565)

  • MCP: 忽略过大的类路径文本。(#4582)

  • 记忆: 在创建临界区拒绝重复事实,按条目边界截断 mem0 注入上下文,并阻止 “仅检查”等任务级指令进入长期记忆。(#4599、#4600、#4604)

  • 调度器: 启动成功后的记账若失败,仍保留 run slot 与 run id,防止后续重复启动。(#4504)

  • 配置: 被删除的 extensions 配置文件按不存在处理,工具与技能配置解析仍可继续。(#4275)

  • 配置: 为异步 ORM engine 规范化 postgres:// 短 scheme。(#4293)

  • 控制台: 模型定价混用货币时禁用成本汇总,避免输出无意义总额。(#4564)

  • Browserless: 接受 timeout 配置键并加固类型转换。(#4519)

  • Docker: 仅在浏览器请求升级时发送 Connection: upgrade,修复远程访问 Docker dev stack 时登录页循环刷新。(#4250)

  • 运行时: JSONL 批量事件按 run 分组写入,避免跨多个 run 的批次全落入首个文件。(#4938)

  • 运行时: 恢复独立 LangGraph Studio 兼容:图入口、文件式 app、系统助手发现与 langgraph dev 工作流重新可用。(#4760、#4838)

  • 网关: 只在 /messages/page 中把 turn_duration 标到 run 的最后一条 AI 消息。(#4755)

  • 网关: 跨越 event 分页上限仍保持精确历史归因,旧 AI 消息不再归到后续 run。(#4953)

  • 网关: MCP task worker 停止时以 HTTP 503 拒绝取消请求。(#4963)

  • 中间件: 修复动态上下文目标、列表字符串净化、重复无效工具占位符,以及摘要误 压缩当前用户请求等四个上下文问题。(#4667、#4668、#4693、#4882)

  • 中间件: 恢复向模型说明 write_todos 工具的系统 prompt 注入。(#4735)

  • 智能体: SQL agent-store 签名改为内容敏感,时间戳复用时注册表也不会继续提供旧路由。(#4709)

  • 工具: 使用运行时用户解析待展示文件,避免有效 artifact 被误判在 outputs 外。(#4677)

  • 工具: 强引用延迟子智能体清理任务,防止 GC 销毁待执行清理并泄漏记录与锁。(#4928)

  • 子智能体: 每个后台执行使用服务端 execution ID,复用 provider tool-call ID 的 并发 run 不再覆盖、轮询或取消彼此状态。(#4758)

  • Harness: 把 ACP workspace 创建与 MCP 配置加载移出事件循环。(#4965)

  • MCP: 收到 task snapshot 时拒绝非有限 poll_after_seconds。(#4750)

  • MCP: OAuth token 交换中以配置的 grant_type 为准,extra_token_params 不再能 静默切换 flow。(#4860)

  • runtime: 缓慢或卡死的 stdio MCP server 不再拖住整个 Gateway。智能体装配 此前在事件循环上同步装配 MCP 工具,因此等待一次进行中的 MCP 初始化会阻塞所有其他请求的 SSE 投递、run 取消与定时器, 而不只是阻塞那个正在等待自己工具的调用方。 现在每个异步入口点都会把工具装配派发到工作线程:task_tool 中的子智能 体生成路径、用于持久批处理的 SubagentBatchService._execute_item、 run_agent 的 agent 工厂(它覆盖了 lead-agent 装配中两处 get_available_tools 调用点),以及 checkpoint state-accessor 的构建—— 其冷缓存读取现在在工作线程中承担 MCP 初始化的等待,而不再停住事件循环。这四 处卸载共用同一个有界专用池(utils/assembly_io.py 中的 run_assembly(),8 个 worker,可用 DEER_FLOW_ASSEMBLY_WORKERS 覆盖),而非事件循环的默认执行器,因此一个为等待完整 MCP 超时而停住的 worker 无法把其他所有 to_thread 调用方排到自己身后;contextvars 会跨这次跳转复制,扩展的 build-context 快照因此仍能传播, 而当等待中的装配数超过 worker 数时会以限流的警告记录下来。由于装配 现在可能被长时间挂起,持久批处理会在启动前立即重新检查其条目的持久状态, 因此在装配期间被取消的批处理不会再发起模型调用。 (#5217, #5224)

  • agents: LoopDetectionMiddleware 不再把一个轮次的预算消耗在另一个轮次的 合法工作上。它此前只用 thread_id 界定相同调用哈希与按工具频率窗口 的作用域,因此一个跨轮次复用的已编译智能体——DeerFlowClient 会保留图并为每个轮次分配新的 run_id—— 会把先前轮次的普通调用计入后一个轮次的限额,在默认的相同调用阈值下, 第三个独立轮次就产生一次虚假的循环警告,第五个轮次则被剥掉一次合法的工具 调用。状态现在按 (thread_id, run_id) 界定作用域,同一个 run 内多次进入图 时证据仍会累积(包括隐藏的 goal 延续),并发兄弟 run 则各自拥有独立的警 告队列。LRU 淘汰会丢弃整个 run 作用域,reset(thread_id) 仍会清除该线程 拥有的每个作用域,阈值以及警告/硬停止行为均未改变。 (#5344)

  • agents: 现在,一个 run 的 token_budget.max_tokens 上限在活跃 /goal 的各次隐藏延续中同样成立。worker 会在同一个 run_id 下重新进入图, 而中间件此前会在 after_agent 中清空该 run 的用量, 再把所有既有消息标记为已见,因此每次延续都从零开始计数: 在 max_tokens: 10000 下,一个在 12k 处触发硬停止的轮次 之后跟着一次继续调用工具的延续,run 一边上报 stop_reason: token_capped, 一边花掉了 20k 令牌,并在达到上限后又执行了两次工具调用。 现在用量与警告状态能够在相同 run_id 的多次进入图中存活下来, 循环检测早已如此;而之后的一次用户 run 仍会拿到全新的 run_id 和全新的预算。达到上限后,延续仍会先做一次模型调用, 然后其工具调用才会被剥离。 (#5410)

  • middleware: 人工输入卡片的回复现在会被识别为用户的当前请求。 卡片答案以隐藏的 HumanMessage 形式到达,其中携带有效的 human_input_response,而轮次检测辅助函数使用的是 is_real_user_message,它会无差别地拒绝每条隐藏消息,没有任何豁免—— 因此由卡片回复启动的 run 会把较早的可见请求当作"当前"请求,而答案本身 却被视为框架注入。在摘要中,当前请求的挽救逻辑因此锁在了那条过期的消息 上:压缩之后模型看到的是原封不动的原始请求, 而用户的地区与年份约束只存在于摘要里。在 McpRoutingMiddleware 中,只出现在卡片答案里的路由关键词匹配不到任何东西, 于是被推迟的 MCP 工具永远不会被自动提升,模型只能手动调用 tool_search——同样的答案若以可见消息发送就能完成提升, 可见行为只取决于答案的传输方式。现在两处都改用 is_genuine_user_message,它跳过隐藏消息,除非其携带有效的 human_input_response;没有该字段的隐藏消息仍会被跳过。 (#5416, #5426)

  • goal: 一个已达到 token 上限的 run 所满足的目标, 其后不再跟着一次毫无意义的延续。既然延续现在共享该 run 的 token 预算, 硬停止后排入队列的延续只会做一次模型调用,随后其工具调用便被剥离, 因此无法取得任何进展——然而 worker 仍然会把它排入队列, 每次都付出一次评估器调用和一次模型调用的代价, 直到延续上限或无进展上限终止该目标为止。run_agent 现在会把 run 的 stop_reason 传入目标延续的准备流程,当它是 token_capped 时,目标会以 stand_down_reason: "token_capped" 退场,而不是再排入一次延续。 评估器仍会先运行,因此已达到上限的 run 确实满足了的目标仍会被清除,其他 stop_reason 的行为与之前相同。 (#5424)

  • agents: 被重试的模型调用不再会丢掉守护中间件早已为它排入的警告。 LoopDetectionMiddleware、TokenBudgetMiddleware 和 ToolProgressMiddleware 会在 wrap_model_call 中、调用 handler 之前取空各自已排队的警告或提示;由于 LLMErrorHandlingMiddleware 包裹着它们并通过再次调用自己的 handler 来重试, 第二次尝试是在队列为空的情况下运行的,而那条警告早已被 标记为已发送。一个在某次工具调用上循环、并在携带该警告的请求上因 503 失败一次 的模型从未看到这条警告,径直跑到了强制停止。现在这三者都会在 handler 抛出时把取出的警告重新放回队列头部,从而让重试拿到它们; 成功调用的行为不变,每个 run 的上限仍然生效, 而若 run 在没有再次模型调用的情况下结束,警告仍会在 after_agent 处被丢弃。 (#5433)

  • agents: token 预算对没有 run_id 的 run 重新生效——包括 LangGraph Server、langgraph dev 以及直接调用 create_deerflow_agent 的调用方。 两处缺陷都源于那个 runtime 局部的回退键。子智能体的硬停止被存在 id 字符串下,却用 consume_stop_reason(None) 读回,因为 SubagentExecutor 会传播父级的 run_id 而父级没有,于是被 token 上限截断的子智能体向父级 报告的是干净的 Task Succeeded,而不是上限截断失败。此外,LangGraph 会给每个节点各自的 Runtime 包装器,因此 id(runtime) 在 after_model、 下一次 wrap_model_call 与 before_agent 之间各不相同: 排队的预算警告从未被投递,after_model 错过了 before_agent 的基线而把线程里的每条 AIMessage 都计入, 同一线程上的第二次调用还被计入了第一次调用的令牌。现在,context 中没有非空字符串 run_id 的调用会改用 LangGraph 按 run 作用域的 Runtime.control 对象作为键,也就是循环检测使用的同一个锚点; 而停止原因会严格按给定的 context run_id 存储,包括 None。 (#5436)

  • agents: 取消 read_file 或 write_file 调用不再会让 read-before-write 门的锁被搁置或提前释放。asyncio.to_thread() 的取消只会取消 asyncio 的等待者,不会取消已在运行的 worker,因此一次取消可能让已排队的 threading.Lock.acquire() 一直挂起,或者在跨线程探测仍在运行时就把门释放掉。 现在派发出去的门操作会在取消传播之前先在 asyncio.shield() 之下排空,第一个 CancelledError 会被保留并在重复取消时重新抛出(包括被排空的那个 worker 任务自身被取消的情况),取消之后才成功的锁会恰好释放一次, 而同路径的门会一直被持有到写入检查与读取标记工作完成。 (#5395)

  • agents: create_deerflow_agent 的三项功能现在名副其实。工厂图此前是在 没有 DurableContextMiddleware 的情况下构建的, 而正是它写入 delegations 账本——因此 SubagentLimitMiddleware 始终把此前的委派数计为零,只有 per-response 上限生效——也正是唯一会把 summary_text 放回模型请求的东西, 因此第一次压缩之后模型只看到保留的尾部,摘要就此丢失。另外, RuntimeFeatures(token_budget=True) 构建的 TokenBudgetConfig() 其 enabled 默认为 false,于是每个钩子都提前返回,既没有警告, 也没有硬停止,更没有 token_capped。现在 _assemble_from_features 总会按 lead-agent 链的同一位置添加 DurableContextMiddleware,工厂的模型请求会在图含有摘要、委派或 已加载技能文件时携带隐藏的 durable-context 块,而 token_budget=True 会构建 TokenBudgetConfig(enabled=True)。 (#5488)

  • subagents: 子智能体的验收检查在 Windows 上不再让越界命令算作证据。 _cd_target_in_scope() 此前用宿主机平台的路径模块来规范化 bash cd 目标, 因此 在 Windows 上一个绝对 POSIX 路径或 .. 逃逸 可能被判为安全的相对目标,一次在受检范围之外运行的测试就能满足某项验收标准。 现在 bash 目标与配置的根路径在任何宿主机上都按 POSIX 语义规范化,带盘符的 Windows 路径会被识别并做大小写不敏感的比较, 而盘符或目录不匹配时会失败关闭。 (#5162)

  • sandbox: 一旦并发子智能体数量超过 AIO 镜像的 shell 会话上限,并发子智能 体就会停止工作。每个并发子智能体都会获得自己的持久化 scoped shell,但 AIO 镜像把 MAX_SHELL_SESSIONS 限制为 10,因此第十一个 shell 会在 DeerFlow 仍持有其 scoped id 的情况下淘汰掉最旧的空闲会话——该子智能体的下一条命令随即以 404 Session not found 失败,而在不提高容量的情况下重建会话只会淘汰另一个 子智能体并丢失其 shell 状态。新的本地容器现在会把 subagent_runtime.max_running + 1(多出的那个槽位给 lead shell 留出空间) 作为 MAX_SHELL_SESSIONS,provisioner 模式会把同一个值转发给 sandbox Pod, 而显式设置的、低于所需容量的 sandbox.environment.MAX_SHELL_SESSIONS 现在会在 provider 启动时报错并列出两个数值。如果 scoped 会话仍然丢失 (因超时或外部清理),DeerFlow 会重建它一次,且仅针对结构化的 404 Session not found 响应。(#5178)

  • view-image: view_image 不再为远程沙箱图片提供过期或缺失的图片。 它此前把 /mnt/user-data/... 解析为 Gateway 宿主机路径, 因此字节只有在同步之后才可用,而宿主机上较旧的副本可能顶替当前的图片。 现在只要存在活跃的沙箱,字节就从当前的活跃沙箱读取, 不会仅仅因为存在持久化的沙箱 ID 就去获取一个替代沙箱;轻量的 来源信息——精确的 SHA-256 加上源沙箱 ID——会记录在 viewed_images 中,而不是 把图片字节或 base64 写入 checkpoint。报告图片缺失的替代沙箱只有在与记录的大小 和 SHA-256 都相符时才会回退到同步后的宿主机副本,因此大小相同的过期内容会被 拒绝;缺失文件的判定对 provider 中立,依据显式的 __cause__ 链, 并有意忽略隐式的异常上下文。异步工具调用与模型注入现在会通过 run_sync_lifecycle_operation() 执行阻塞式的沙箱读取,因此取消操作无法在读取 排空之前拆除沙箱租约清理。 (#5306)

  • subagents: 子智能体在自己的上下文被压缩之后仍保留自己的指令。执行器以 system_prompt=None 构建 agent,并把拼装好的提示作为第一条消息放进 state,因此其中承载着角色提示、<report_contract> 引用规则、验收标准 说明、技能索引,以及被推迟的 MCP 工具和路由提示——而压缩是按索引裁剪的, 索引 0 永远落在被摘要掉的部分里。第一次压缩之后的每一次模型调用都是在 完全没有系统提示的情况下进行的,而摘要也替代不了它,因为它是以隐藏数据 注入的,权限契约明确告诉模型不要遵从它。现在动态上下文保留会一并挽救 SystemMessage,以及带标签的提醒和最新的用户消息,并保持它们的顺序, 使提示仍排在最前、且不会被送进摘要器;当提示加上当前请求就是剩下的全部 内容时,直接跳过压缩,而不是把提示摘要掉。lead agent 不受影响——它的提示 是请求的 system message,而不是 state 里的一条消息。(#5454)

  • subagents: 被反复取消的子智能体不再永久占用自己的执行槽位。 SubagentExecutionCapacity.slot() 会在让出之前递增进程级运行计数, 并在异步上下文管理器的 finally 中释放它;但第二次 Task.cancel() 若在释放等待容量锁时到达,就会打断这次清理:任务以取消状态退出, 而 _running 仍处于递增状态。在 max_running=1 下,之后每个原生子智能 体都会排队直到超时或被拒绝,尽管当时并没有任何东西在运行,违反了文档记载 的不变量——取消与超时会释放队列与槽位归属。现在最后一次释放在自己的任务 中运行,不受调用方取消的影响,并在取消传播出去之前跨反复取消完成排空。 (#5477)

  • worker: 被委派的子智能体出错不再导致父 run 失败。当子智能体的模型 调用在重试后仍以错误结束时,执行器报告 task_failed,lead agent 仍然 作答,但 worker 还在根级 task_running 自定义事件里看到了 deerflow_error_fallback 标记——每一个都携带一条带 additional_kwargs 的子智能体消息——并据此把父 run 标记为 error,错误文本取自子智能体。 目标延续随之停止,而一次编辑并重跑把线程回滚,丢弃了编辑后的问题和新的 回答。自定义帧不再参与父级的错误回退检测,而 lead 自身的错误回退仍通过 values、messages 和 updates 帧送达;现在 lead 完成时父 run 以 success 结束。(#5407)

  • sandbox: 规范化掩码输出尾部的分隔符,使虚拟路径在 Windows 宿主机上也 按 POSIX 风格拼写。两个输出掩码器都在输出的正斜杠规范化副本中搜索宿主机 基路径,却从原始文本里切出匹配到的尾部,因此嵌套的反斜杠得以存活进拼接 结果,glob 结果与被掩码的技能读取会以混合拼写返回,例如 /mnt/user-data/workspace/pkg\util.py。深度为 1 的尾部恰好能干净拼接, 这正是 Linux CI 从未发现它的原因;现在两处拼接点都会在把尾部接到虚拟 前缀之前先规范化它。(#5247)

  • sandbox: 反向解析 Windows 宿主机路径的正斜杠拼写。正向解析以 / 拼写已解析的路径,因为反斜杠会破坏 \U 这类 bash 转义序列,但把输出中的 宿主机路径映射回其 /mnt/... 形式的反向扫描器仍把匹配锚定在原生的反 斜杠基路径上——因此每个在命令输出或智能体写入的文件中回来的正向解析路径 都无法匹配,把原始宿主机路径、真实用户名和完整目录树泄露给了模型,而不是 智能体本应引用的容器路径。现在扫描器的匹配与分隔符无关,与沙箱工具早已 采用的同一份契约一致;在两种拼写重合的 POSIX 宿主机上不受影响。(#5373)

  • sandbox: 远程 list_dir 不再把失败报告为空目录。在缺失路径上执行 find ... 2>/dev/null 只产生空的 stdout,而客户端错误被当作 [] 吞掉, 于是 ls 告诉智能体该目录是 (empty)——一个已死的沙箱、一个已关闭的 客户端和一个不存在的路径看上去都像一棵可写的空树,让智能体覆盖已有文件 或跳过恢复。AIO、E2B、BoxLite、OpenSandbox 和 Tenki 现在会在命令或客户端 失败时抛出 OSError,在什么都没列出时抛出 FileNotFoundError,与本地 沙箱对非目录路径的既有行为一致;真正空的目录仍报告 (empty),而 find -H 让带符号链接的根仍可列出。(#5264)

  • sandbox: 拒绝不完整的远程 list_dir 遍历,而不是把它当作完整结果 呈现。find 对缺失的起始路径、以及对打印了部分条目后无法读取的文件或 子目录都退出 1,而解析器两种情况都接受 1——因此一次中途失败的遍历会 把可见条目当作一次成功的完整列目返回,ls 交给智能体的是一棵静默不完整 的树。仍无条目时的状态 1 依旧抛出 FileNotFoundError,而只要有任意 条目的状态 1 现在抛出 OSError,指出遍历不完整并建议换用更窄的路径, 与远程 glob 早已应用的同一份契约一致。每个共用远程 list_dir helper 的 provider 都无需 provider 侧改动即可继承它。(#5422)

  • sandbox: 远程 grep 与 glob 不再把失败报告为“没有匹配”。这些搜索 丢弃了 stderr,并从 head 取管道状态,因此缺失的搜索根、缺失的 grep/find 二进制或不可读的目录树会什么都不打印并以 0 退出——而工具会 告诉智能体 “No matches found”,而本地沙箱在这种情形下报告的是 Error: Directory not found。现在一个共享包装器会记录搜索命令自身的状态: 根缺失时抛出 FileNotFoundError,任何其他失败——包括在已经打印了部分结果 之后的 grep 2 或 find 1,因为调用方无法区分部分结果与完整结果——都抛出 OSError,告诉智能体有些路径无法读取。真正的无匹配仍返回 []。E2B、 OpenSandbox、BoxLite 和 Tenki 在客户端已关闭时也会抛出,而不是返回空。 (#5380)

  • sandbox: 以行为单位报告 read_file 的截断,并点明应从哪一行继续。 该工具按 sandbox.read_file_output_max_chars(默认 50,000)做头部截断, 其标记告诉模型用 start_line/end_line 继续,但切口是按字符偏移切下的, 标记也只报告字符计数——因此切口几乎总是落在某一行中间,模型看到的最后 一行是一个读起来像完整行的片段,且没有任何东西说明该从哪一行继续。 在被要求对截断后的文件做下一次读取时,三个模型在 15 次尝试中有 0 次选对了 start_line,偏差达几十行甚至上百行。现在切口落在预算允许的最后一个行 边界上,标记以行为单位陈述位置并给出确切的续读位置,例如 [truncated: showing first 743 of 1828 lines (49746 of 155704 chars). Continue with start_line=744] 带范围的读取报告的是文件行号,而不是 provider 的切片相对行号。单个超长 行仍然在字符上限处截断,因为丢掉它会浪费掉大部分预算,此时标记指向 bash,而不是一次无法返回该内容的 read_file 调用。(#5474, #5478)

  • sandbox: 强制 PowerShell 使用 UTF-8 控制台,使 CJK 工具输出在 Windows 上不再乱码。命令运行器通过 UTF-8 管道读取器捕获 stdout,但 Windows PowerShell 5.1 除非显式切换,否则以遗留的 OEM 代码页写出控制台输出—— 在 zh-CN Windows 上是 GBK——因此每个 CJK 字符到达时都是乱码,而又因为读取 器是替换无法解码的字节而不是抛错,损坏是静默的。现在每个 PowerShell -Command 载荷都会在用户命令运行前把 InputEncoding、OutputEncoding 和 $OutputEncoding 设为 UTF-8。(#5440)

  • sandbox: 以感知平台的方式校验 Windows 宿主机上托管的 Lark CLI 沙箱 运行时。该检查要求一个 POSIX 可执行位,而 NTFS 并不保留它,因此每个候选 都报告 st_mode & 0o111 == 0,Gateway 抛出 ValueError: Managed Lark CLI sandbox runtime file is not executable ——使托管的 Lark 运行时在 Windows 开发宿主机上、以及把托管的 Linux CLI 运行时挂载进沙箱的 Windows Docker 宿主机上都无法使用。POSIX 保持严格的可 执行位契约不变;在 Windows 上,仅限 Linux 的构件改由内容校验,要求各架构 二进制具备可执行镜像魔数、启动器具备 shebang。(#5442)

  • sandbox: Docker-outside-of-Docker 沙箱的端口绑定在 Docker Desktop 上 默认绑定到回环地址。DEER_FLOW_SANDBOX_HOST 在 DooD 模式下默认为 host.docker.internal,它在容器内解析为 Docker Desktop VM 网关,而把沙箱 端口发布到该地址会让宿主机套接字层以 WSAEADDRNOTAVAIL 拒绝绑定——因此 即使启动成功了,第一个沙箱 shell 动作仍以 ports are not available 失败。 当 Docker server 是 Docker Desktop 且未配置 DEER_FLOW_SANDBOX_BIND_HOST 覆盖时,绑定主机现在是 127.0.0.1, Docker Desktop 会转发它;显式覆盖仍然优先,原生 Linux DooD 不受影响。 (#5446)

  • sandbox: 在取消被当作失败清理之前排空技能同步 worker。默认的 sync_agent_skills_async() 包装器使用裸的 asyncio.to_thread(),因此取消 等待方会在同步的沙箱变更尚未完成时返回,而 SandboxMiddleware 随即在 provider 工作仍在途中时释放执行持有者——破坏了生命周期契约:代表某个 持有者启动的阻塞工作必须在该持有者离开边界之前排空。现在该包装器改经 既有的取消栅栏运行,因此对那些自身不对同步与释放做串行化的第三方和基于 上传的 provider,顺序也能成立。(#5350)

  • frontend: 让人工输入卡片与请求它的回合待在一起。在多轮对话中,一张 既有的 needYourHelp 卡片可能漂到下一条用户消息下方,于是时间顺序变得 误导,一个已回答的请求可能读起来像新一轮的一部分。一条提交前的基线消息 可能被编进新持久化的用户消息之后,一张仅通过 REST 历史确认的卡片可能 永远进不了 checkpoint 基线,而已经为当前 run 持久化的步骤可能在一次中断 之后被抬进上一轮。现在基线消息会恢复在待处理的人类消息之前,经 REST 确认的卡片算作已确立的历史,而当前 run 自身的步骤留在启动它们的消息 之下。(#4892)

  • frontend: 在实时内容合并中保留服务端分配的消息位置。流式更新在替换 消息内容的同时也会丢掉它的 deerflow_seq,于是服务端已经定妥的排序被在 客户端重算并算错——已加载的 1,3,5 历史窗口与 2,5 的实时尾部合并后 渲染为 1,3,2,5,长线程(历史分页,或压缩之后恢复的对话)会以乱序显示 其步骤,直到一次刷新。现在内容与位置分开处理,合并、压缩桥接和渲染账本 共用同一份位置优先级,使上游已排序的结果不会在下游被重新排序,而异常 序列值(null、字符串、NaN、非整数、非安全整数)永远不会覆盖一个已知位置。 (#5293)

  • frontend: 在增量流重连之后恢复用户的输入。在较晚一个回合期间刷新, 可能让重连流在当前人类输入进入持久历史 feed 之前先回放 AI 与工具 chunk, 于是用户的消息短暂消失,其推理步骤被归到上一轮之下。现在活动 run 的输入 会在加入增量流之前先从 kwargs.input 恢复并合并入最新的持久线程状态,按 id 去重;如果元数据或状态读取不可用,则原样沿用先前的重连路径。(#5428)

  • frontend: 即使一个智能体的工具组显式为空,也显示它的技能徽章。徽章 容器的空值合并链停在工具组计数 0 上,因此一个声明为 tool_groups: [] 且 skills: ["data-analysis"] 的智能体在 Agents gallery 中丢掉了徽章—— 尽管显式为空的工具组列表是有效的,并受 Agent API 与 update_agent 工具 支持。现在容器在两个列表中任意一个至少有一项时即显示。(#5326)

  • frontend: 把 composer 的斜杠技能建议限定到活动智能体。自定义智能体 对话可以限制自己能激活哪些技能,包括限定为一个显式空列表,但 composer 仍提供全局的已启用技能目录——于是智能体可能建议一个它无权激活的技能。 现在建议会经过活动智能体的允许列表过滤,显式空列表被视为没有可用技能, 而不是在智能体加载期间短暂泄露全局目录;当一份保存的、带有已选技能 chip 的 composer 草稿被恢复时,也应用同样的限定。(#5451)

  • frontend: 删除侧边栏会话前先确认。在某个最近会话的菜单中选择 Delete 会立即移除该对话及其文件,没有任何确认步骤,尽管删除是不可逆的。现在一个 对话框会点明该对话并警告删除不可逆,聚焦 Cancel,并在 Cancel、Escape 或 关闭时保持会话完好;删除待处理期间操作被禁用、驳回被阻止,而删除失败会让 对话框保持打开并带上底层错误,以便用键盘重试。该对话框挂在虚拟化的侧边栏 行之外,因此一次列表刷新无法在一次部分删除之后移除重试 UI。(#5406)

  • agents: 让 Custom Agent 设置对话框留在视口内。选择 Selected subagents 可能使对话框高于窗口,在一条长长的 worker 描述填满嵌套列表时 把标题、关闭控件和 Save/Cancel 按钮推到屏幕之外。现在头部与操作按钮位于 单一滚动表单区域之外,子智能体描述显示两行预览,并有可通过键盘访问的展开 在不移动焦点或改变复选框状态的前提下滚动进视区,而长名称与描述会换行而 不缩小复选框。(#5458)

  • frontend: 让 MCP 配置对话框留在视口内。在 Capability Center 中打开 一份很长的 MCP server 定义会让 JSON textarea 长过窗口,于是居中的对话框 裁掉了标题、关闭控件和 Save/Cancel 按钮,在较小的窗口上变得难以编辑或 关闭。现在对话框受视口约束、在桌面上有更多横向空间,长 JSON 在编辑器内部 滚动,而标题、Close、Save 和 Cancel 留在滚动区域之外,即使在极小的视口下、 或遇到很长的服务器名称或校验消息时也是如此。(#5492)

  • frontend: 在那些可点击、却仍显示默认光标而使其可操作性不清晰的交互 控件上显示指针光标。现在一个全局选择器覆盖原生按钮、role="button" 元素、 下拉菜单项和命令项,排除原生禁用控件和任何标记了 aria-disabled="true" 的元素;生成的 ui/ 与 ai-elements/ 组件不受影响。(#4921)

  • frontend: 中文本地化文档链接。本地化文档中的 MDX 链接与 Nextra 卡片 此前在改写时并不知道活动的文档语言,于是跟着快速上手链接走的中文读者被 送到了英文页面。现在本地化文档布局通过 context 提供该语言,链接改写会 尊重它。(#5275)

  • runtime: 仅内存部署在计划清理之下保留其 run 历史。cleanup() 此前 无条件淘汰,因此一个按文档默认 store=None 构建 RunManager() 的内嵌 消费者会在清理触发时从历史中丢掉已完成的 run——get(run_id) 返回 None, list_by_thread() 丢掉该记录,因为这些读取没有持久副本可以回退。现在淘汰 以存在后端存储为前提,恢复了仅内存模式此前永久保留的行为;有存储支撑的 manager 仍在宽限期后淘汰。(#5453)

  • events: 读取含有 U+0085、U+2028 或 U+2029 的 JSONL 事件记录。三个 读取器都会把这些 Unicode 分隔符当作记录边界来切分,因此携带其中之一的 记录在读取时会被跳过——这会在重新打开之后复用事件序列号,并把幂等插入 变成重复。现在线程读取、run 读取与序列恢复都按物理换行切分;既有有效 文件无需重写。(#5429)

  • events: 把每线程的 JSONL 写锁一直保留到文件系统工作落定。在其 worker 释放期间取消一次存储调用会在追加仍在途中时丢掉锁,于是稍后的一次删除 可能在被取消的追加重建该记录之前就完成,而一个被取消的混合 run 批可能 回滚掉一次它已经确认的写入。现在被接纳的变更会在传播取消之前,一直持有 其锁跨过文件 I/O、回滚与簿记,覆盖 put、put_if_absent、批量写入和两种 删除方法;排队的调用方仍可在被接纳前取消,无关的线程保持彼此独立。 (#5439)

  • events: 阻止 JSONL 线程变更在一次删除中分裂到两代锁。 delete_by_thread() 在仍持有每线程锁的同时把它从注册表中移除,因此一个 已排在旧锁上的变更可能与一个稍后为同一线程解析出新建锁的变更并发运行 ——在紧跟删除边界处出现重叠的序列号分配与文件变更。现在注册表是一个 WeakValueDictionary,且不再弹出该条目,于是持有者与排队的等待者让同一代 保持存活直到它们排空,之后该条目自行消失。(#5455)

  • channels: 让渠道的首次线程创建只使用一代锁。清理逻辑在当前创建者 退出时无条件移除每会话锁,于是一个失败或被取消的创建者会释放并注销自己的 锁,而一个排队的创建者正通过它进入,一个迟到的到达者还可能装上新锁并发 进入——两个调用方为同一会话创建 Gateway 线程,靠后的映射写入胜出,相邻的 消息被拆到被孤立的线程上。现在手工管理的生命周期被替换为既有的、感知等待者 的键控锁表,于是会话键跨异常和取消保持同一代可发现,空闲键只在最后一个 参与者离开后才被回收。(#5480)

  • gateway: 以数据属主、而非授权身份读取一个线程的 run。列表、keyset 分页和单 run 读取端点按调用方的授权身份过滤 run 行,而 start_run 给它们 盖的是数据身份戳——两者只在本来就是安全的属主取值上重合,因此受信任的 内部调用方即使在自己有权操作的线程上也总是看到一个空 run 列表和 404, 而这个症状很容易被误读为 run 丢失。现在内部调用方会跳过每用户存储过滤器, 而这些端点的线程可见性已经做了授权;浏览器与 API 会话则保持各自确切的每 用户过滤器;两个消息读取端点有同样的混淆,也以同样方式修正。(#5448)

  • gateway: 对消息编辑与重新生成的 helper 路径应用同样的数据身份限定。 那三个 helper 仍在解析授权身份并把它当作数据过滤器传入,因此内部调用方在 自己有权的线程上 regenerate/prepare 与 edit-regenerate/prepare 会以 409 失败。现在它们按与读取端点相同的方式解析自己的过滤器 id,浏览器与 API 会话则保持先前的每用户过滤器、以及对跨用户 run 的 409。(#5483)

  • conversation: 让会话读取器的页面留在内联范围内,并说明何时丢掉了 文本。一个页面此前是靠切掉放不下的最后一条消息来填满其 20,000 字符上限的, 而被切掉的后缀永远无法翻页取回——六条各 3,500 字符的消息中最旧的一条回来 时约 2,500 字符,且 has_more: false。更糟的是,超出默认 12,000 字符工具 输出预算的页面会被 ToolOutputBudgetMiddleware 外置,于是模型只看到一份 摘要加上一份源文本的文件副本,而这是在目标线程里。现在页面按实际适用于 read_conversation 的预算来确定大小(每工具覆盖,否则 externalize_min_chars,以及 fallback_max_chars),仍以 20,000 文本字符 为上限,而放不下的一条消息会完整地开启下一页——于是一项运维设置始终当家, 且 tool_output.tool_overrides.read_conversation 只调整这一个工具。现在 被截断的结果会带上一条提示,要求智能体承认这一省略、并在声称完整覆盖之前 索取缺失的材料。(#5421)

  • goal: 在澄清卡片仍打开时停下目标循环。目标评估器只读取人类与 AI 消息,因此通过 ask_clarification 提出的问题——它以工具结果的形式到达 ——对它不可见;它判定目标未达成,worker 便排入一次隐藏延续,告诉智能体 继续工作、除非真正被阻塞否则不要问用户。智能体于是按自己的猜测行事, 包括对 risk_confirmation 问题,而卡片就那样在 UI 里等着。现在一次尾部的 人类输入请求会在评估器运行之前被检测到,并以既有原因 blocked:needs_user_input 让目标退场;用户的回答是一条新的人类消息,于是 下一次 run 正常评估,而前面没有助手文本的卡片会报告它为何停下,而不是 run_failed。(#5467)

  • client: 发出后又被后续某个节点改写的 AI 消息,其新增文本现在也能流式送 出。LoopDetectionMiddleware、TokenBudgetMiddleware、 SafetyFinishReasonMiddleware、SubagentLimitMiddleware 与 TerminalResponseMiddleware 都会在各自的节点里用同一个 id 改写最后一条 AI 消息,但流只会把首次见到的 id 记下来,之后一律跳过,因此改写从未送达 messages-tuple 消费方:chat() 与 headless --print 返回的是改写之前的文 本,而不是终态错误或强制停止提示;TUI 从未显示停止提示或安全提示;模型节点 之后追加的 token_usage_attribution 也始终没到。values 路径现在会为每个 id 记住最后一次见到的消息对象,并在快照中持有另一个对象时重新检查它,只把新增的 文本作为又一次 delta 发出,并把新的 additional_kwargs 走既有的仅元数据后续 事件;未变化的消息仍会被跳过,不再重新抽取文本。不会扩展已发送文本的替换则不 会重新发出。(#5479)

  • mcp: 并发初始化时 MCP 会话池不再超出容量上限。MCPSessionPool 只在会话 创建开始之前检查 LRU 容量,而由于初始化需要 await,多个不同的 key 可能各自观 察到还有空余容量、各自进入 _inflight,随后在没有二次检查的情况下被提升进 _entries——让持久会话注册表突破 MAX_SESSIONS,并连带保留其所暗示的额外子 进程与连接。现在会话被提升时会原子地重新检查并强制执行容量,提升时的淘汰受害 者则经由其属主任务生命周期,在注册表锁之外关闭。(#4962)

  • mcp: 并行的同步 MCP 调用不再互相取消对方的连接。当内嵌客户端向同一个 stdio server 派发两次同步调用时,每个包装器都跑在自己的事件循环上,而池会把 兄弟循环上的存活会话与进行中的创建视为过期——把它们取消或关闭,并以 CancelledError 中止某个工具步骤,而不是返回两个结果。已建立的会话与进行中 的创建现在按 (server_name, scope_key, owning_loop) 归键,因此同一循环内的 调用方仍共享创建与状态,而不同的循环各自独立,每个属主也只回收自己的记录,包 括在正常的 asyncio.run() 关闭之后。同步调用仍使用彼此独立的子进程会话,也 不获得任何共享的服务端状态。(#5396)

  • web-fetch: 抽取出的 Markdown 里的相对目标现在会按其来源页面解析。Jina、 Browserless 与 InfoQuest 原样返回 HTML 中书写的链接,因此位于 https://example.com/docs/current 的页面会产出 [Next](../next),让智能体拿 不到任何可以继续跟进的 URL。三个 provider 现在都会把请求的 URL 传给共享抽取 器,由它把锚点与图片目标相对该 URL、或相对首个可用的文档 <base> 解析;只改 写目标属性的取值,而不是序列化另一棵解析树,因此畸形的标记、注释、脚本与属性 格式都原封不动地保留,并仍以原样到达 Readability.js。fetch 校验、4096 字符的 输出上限与线程外抽取均未改变,Python 回退路径也保持纯文本行为。(#5310)

  • community: Firecrawl 的 web_fetch 与 web_search 工具现在遵循该工具的 base_url。_get_firecrawl_client 只读取 api_key,因此 FirecrawlApp 总是 回退到 https://api.firecrawl.dev——并且由于只要目标是云端 API 而未设置 key, SDK 就会抛出 Error: No API key provided,自托管的 Firecrawl 无论怎么配置都 无法访问。base_url 现在会作为 api_url 转发,让本地的 Firecrawl 无需云端 key 即可同时服务这两个工具;未配置 base_url 的配置仍与原先完全一样地构造客 户端。(#5392)

  • community: Tavily 抽取现在从 web_fetch 条目而非搜索配置读取凭据。当 web_search 用 Serper、web_fetch 用 Tavily 时,fetch 用 Serper 的 key 构造 客户端,把独立的 Tavily fetch key 完全忽略了。现在每个工具都从自己的条目读取 api_key,并在未提供时回退到 SDK 的 TAVILY_API_KEY,与既有的 Exa 辅助函数保 持一致。只在 web_search 下放置共享 Tavily key 的配置,必须同时在 web_fetch 下也设置它,或让两者都依赖 TAVILY_API_KEY。(#5496)

  • client: DeerFlowClient.stream() 现在会把流式工具调用只发出一次,并带上 完整参数。OpenAI 风格的模型把工具名与 id 放在第一个 chunk 里,不带 id 的参数 片段放在其余 chunk 里,而客户端为每个 chunk 都发出一个 messages-tuple tool_calls 事件,每个都只依据那一个 chunk 解析——因此完整调用(它确实出现在 values 快照里)反而因为其消息 id 已在 streamed_ids 中而被跳过。工具本身仍以 正确的参数运行;错的只是事件流,在 TUI 里工具卡片显示的是 bash 而详情为空, 而不是那条命令。现在每个消息从 values 快照只发出一个事件,而非流式模型的完整 AI 消息与流式文本则不受影响。(#5408)

  • browser: 浏览器会话的拆除不再随调用方的取消而中断。 BrowserSessionManager.close_session() 会在 await 其关闭之前先把会话从注册表 中移除,而该关闭又直接 await 私有的 Playwright 循环,因此此刻的取消会穿过 asyncio.wrap_future() 传播到清理逻辑本身——留下浏览器进程与 context 成为孤 儿,会话却已不在注册表中,也没有属主再来重试。close_all_sessions() 在更大规 模上有同样的属主问题:它先清空整个注册表,因此关闭第一个会话时的取消会让之后 每个会话都彻底没有安排任何拆除。现在拆除会在任何可取消的 await 之前提交给私有 循环,并在 asyncio.shield() 之后 await,而 close-all 会在其首个 await 之前先 安排好每个会话,并对组等待加 shield。调用方仍会收到 CancelledError;只有已 经启动的清理的属主身份与它隔离了。(#5444)

  • browser: 无论工具字段顺序如何都能检测到浏览器依赖。启动期的依赖检测只 在 name 是 YAML 列表项的第一个 key 时才认得该工具名,因此把 name 移到 use 或 group 之后,工具配置仍能工作,却静默地把 browser extra 从 uv sync 中漏掉,让浏览器工具没有必需的依赖。现在直接的 name 字段在工具条 目中任何位置都能被认得,按缩进追踪可防止嵌套选项内部的名称去启用该 extra,而 检测器仍保持仅用标准库,因为它运行在依赖安装之前。(#5456)

  • setup: 同一个 pre-sync extras 检测器现在按 utf-8-sig 读取 config.yaml。PyYAML 能接受前导的 UTF-8 BOM,但检测器按纯 utf-8 读取该文 件,把 BOM 留在了第一行上,因此其锚定的章节模式无法认出合法配置的开头章节, 并漏掉了它声明的 extras——postgres、browser 与 ollama 的检测在其章节位 于最前时都以同样方式失败,而同一份文件的纯 UTF-8 副本却可以正常工作。 (#5504)

  • video: 把 --aspect-ratio 转发进 Gemini Veo 请求。该技能 CLI 接受此旗标 并传给 generate_video(),但 Gemini 分支仅用 instances 构造它的 predictLongRunning 请求体,把该值丢掉了,因此无论参数是什么,每次 Veo 请求 都按 provider 默认比例渲染。现在它作为 parameters.aspectRatio 发送。 (#5388)

  • channels: WeCom 的两条对外路径都发送无上限的文本,而 bot 协议把内容上限 压到 20480 UTF-8 字节,这个门槛一份深度研究报告轻易就能越过。超过上限时, _send_with_retry 重试三次后放弃:流式路径让回复卡在中途,从未发送过 finish=true;推送路径则把回复整个丢弃。流式回复现在在 UTF-8 字符边界上截 断,其后带一个可见的截断标记——一个流承载整条回复,因此不会在中途断开——而 没有可回复帧的主动推送(例如定时任务通知)则按换行边界拆成顺序的 markdown 消 息,让完整内容仍能送达。两条路径现在都以 UTF-8 字节而非字符来度量载荷。 (#5148)

  • channels: 入站 WeChat(iLink)与 WeCom 媒体在任何大小限制生效之前就已 被完整下载,而下载目标又是一个直接取自消息 payload、未做任何目的地校验的 URL,于是一个确实超限的附件会在遭到拒绝之前先把 Gateway 的内存顶上去。两个 频道现在都会流式传输,一旦超过 max_inbound_image_bytes / max_inbound_file_bytes 就在传输途中中止,而精确的解密后大小检查仍保留为 第二道防线。入站 full_url 被限制为 http/https 加一份按点边界的主机后缀允 许列表,默认为 qq.com 系列与已配置的 CDN 主机,可用 channels.wechat.allowed_media_hosts 与 channels.wecom.allowed_media_hosts 扩展,其中 WeCom COS 门禁固定为已验证的 Tencent Cloud APPID 形态,WeCom 管理端读取器还有 50 MB 的传输中上限。读取 器会请求 Accept-Encoding: identity 并拒绝任何残留的 Content-Encoding, 因此 httpx 无法在测量之前把压缩过的响应体膨胀到超过上限。格式正确且未超限 的媒体不受影响;超限或不在允许列表中的媒体现在会被跳过,并附上指明原因的警 告。(#5225)

  • channels: 现在单条无法解码的 WeChat 消息不会再拖累同批其余消 息。_poll_loop 在收到整批消息后立刻持久化 get_updates_buf 游标,随后逐 条迭代 data["msgs"] 且没有按消息隔离错误,于是只要有一条消息的处理抛出异 常,现实中的触发因素是损坏或无法解密的附件,例如被截断的加密图片 payload, 循环就会中断,而游标此时已经越过整批消息。失败消息之后的每条消息都会被永久 丢弃,下一次轮询也不会重新拉取它们。现在 _handle_update 在按消息包裹的 try/except 中运行,因此同批消息仍能送达 bus,失败会连同其消息 id 一起 记入日志,而游标仍会在整批消息全部尝试过之后前进。(#4231)

  • channels: 当 Discord 客户端线程死亡时,原因是 token 失效或不可恢复的 关闭,Client.start() 会返回,并留下一个已停止但未关闭的事件循环,于是 call_soon_threadsafe 排入了一个永远不会执行的回调,而 send、send_file 与 _get_channel_or_thread 中那个没有上限的 await asyncio.wrap_future(...) 会永远挂住。每次向已死频道发出的消息都会 从默认五人的 worker 池里永久占用一个 ChannelManager worker,因此区区几条 消息就能冻结所有 IM 频道的入站处理,而不仅是 Discord。这些跨循环调用现在统 一经由一个辅助函数,把等待时间限制在 DISCORD_OUTBOUND_TIMEOUT_SECONDS(30 秒),文件上传则另有 DISCORD_UPLOAD_TIMEOUT_SECONDS(120 秒),这样缓慢 的上行链路加上较长的 429 retry-after 就不会取消一次健康的传输,事件循环缺 失或已停止时则会关闭该协程并立即抛出异常。DiscordChannel.is_running 现在 也要求客户端线程存活,正如 FeishuChannel 此前的做法,因此就绪轮询会重启 频道,而不是把一个已死的频道当作健康;start() 失败时,半启动状态的实例现 在会被停止并丢弃,这样反复的就绪重试就不会在 bus 上累积陈旧的对外监听 器。(#5227)

  • channels: Discord 线程映射在线程创建之后于事件循环之外持久化,因此进 程若在创建与其后台写入之间被杀掉,仍可能丢失最新的映射,并让某个 Discord 线程在重启后无法访问。stop() 现在会把内存中的映射刷写到磁盘,并做了包 装,使关闭路径永不被阻塞,这是在硬杀窗口之上的一张尽力而为的兜底网,而不是 新的写入路径。(#5461)

  • channels: 在 Telegram 频道配置中启用 rich_messages 会让每条最终对外 消息都渲染成一行损坏的单行文本:像 /help 这样的命令菜单与普通的错误回复 都被经 sendRichMessage 以 rich_message.markdown 发送,而后者会合并单个 换行,并剥掉菜单所依赖的 <name> / <skill-name> / <task> 尖括号 token。现在只有 rich_messages 已启用 且 文本确实包含某种富文本构造,即 围栏代码块、表格行或分隔行、任务列表、[text](url) 链接、粗体、斜 体、<details> 块或 $$ 数学式时,消息才会以富文本形式发送。该检测器刻意 保守,要求表格必须让该行以 | 开头,因此 /help 中像 /goal [condition|clear] 这样的一行永远不会触发它。(#5470)

  • community: InfoQuest 的 reader、web search 与 image search 调用 requests 时没有设置传输层超时,因此即使配置了远程 crawl 的 timeout 字 段,端点停滞仍会让同步 worker 无限期等待。三处调用点现在共享一个 30 秒的连 接/读取非活动上限;crawl 超时与导航 payload 字段、成功的解析以及既有的 Error: 返回都未改变。这限制的是非活动时间,而非总墙上时钟时间。(#5315)

  • models: 请求准入限流器在两次独立的加锁中分别决定立即准入与入队,因此 一个阻塞型调用方可能观察到下一个许可尚未到期、在入队之前被挂起,随后在许可 到期而等待队列仍为空时被更新的调用方抢先:新来者拿走了许可,较早的调用方只 能再等一个间隔,或在 max_wait_seconds 较短的情况下超时,而较晚的那个请求 却成功了。立即准入与 FIFO 入队现在是一次加锁保护的单一决策。非阻塞调用仍会 快速失败且永不入队,队列化限速、取消、超时、队列容量与无突发行为均未改 变。(#5459)

  • uploads: 上传文档的大纲把任何以 # 开头的行都当作标题,因此一份在 # Real section 之前有 51 行 #tag 的文档会耗尽所有大纲槽位,把真正的章 节藏起来;缩进四个空格或用制表符的注释会变成假标题,可选的结尾井号也会留在 标题里。提取器现在按原始行匹配根层级的 ATX 语法,即一到六个井号、空格或制 表符分隔符或行尾、最多三个前导空格,并在既有的粗体清理之前剥掉合法的结尾井 号。物理行号、PDF 结构标题、围栏代码排除与标题数量上限均未改变,扫描器仍保 持有界,而不会变成一个 Markdown 解析器。(#5316)

  • uploads: 单个 200,000 字符的段落会产出 199,999 字符的上传预览,尽管有 五行的限制;过长的合法标题也会绕过 50 条的上限,而该上限实际上是上下文大小 的约束。大纲标题现在统一截断到 200 字符,回退预览文本则截断到 2,000 字符并 跨所有行,省略标记也计入这些预算。物理行号、短文本、既有的标题与预览数量, 以及原始文件字节都得到保留;这约束的只是返回的摘要文本,而不是文件扫描内 存,也不是跨上传的总预算。(#5323)

  • doctor: 在全新克隆上,先 make config 再 make doctor 会报出四个错 误,而其中只有一个是真实的:config.example.yaml 提供的 models: 键里每 个条目都被注释掉了,它解析为 None,于是 .get("models", []) 里的 [] 默认值从未生效,一个宽泛的 except 又把由此产生的 TypeError 渲染成三条 'NoneType' object is not iterable 检查结果。新用户会在 make doctor 本 应减少困惑的那一刻看到一副安装损坏的样子,而他们其实只是还没有配置任何模 型。现在当没有配置任何模型时 LLM 检查会跳过,只留下可操作的 models configured 失败及其 make setup 提示;一旦配置了模型,一切都不会 改变。(#5296)

  • scripts: detect_uv_extras.py 从 config.yaml 解析 uv extras,使 make dev 不会在每次重启时清掉可选依赖,但它没有针对 Ollama 的规则,于是 配置了 Ollama 模型时它什么都不返回,serve.sh 便运行了不带 --extra ollama 的 uv sync,langchain-ollama 就从一套能正常工作的环境里被卸载了。随后 配置的模型会以 ModuleNotFoundError: No module named 'langchain_ollama' 失败,而没有任何线索指向 make dev 才是原因,尽管 make doctor 刚刚报告 该包已安装。现在配置 Ollama 模型会让 make dev 传入 --extra ollama,与 database.backend: postgres 传入 --extra postgres 的方式相同;不含 Ollama 模型的配置不受影响,config.example.yaml 中被注释掉的 Ollama 示例也会被正 确地忽略。(#5318)

  • docker: 在 Windows Git Bash 上,make docker-start 与 make up 会无 条件中止,并报出 Docker socket not found at /var/run/docker.sock — AioSandboxProvider (DooD) will not work. MSYS2/Git Bash 在该路径下并没有 物理的 Unix socket,Docker Desktop 使用的是命名管道,尽管守护进程原生支持把 /var/run/docker.sock 挂载进 Linux 容器,因此这项检查永远不可能通过。在 MINGW*、MSYS* 与 CYGWIN* 下,预检现在改为验证 docker info 的连通 性,而不再要求存在 socket 文件;POSIX 主机在缺少 socket 时仍会快速失 败。(#5371)

  • deploy: 在 Windows Git Bash 上,make up 会在容器启动阶段失败,报出 mkdir C:\Program Files\Git\var: Access is denied. scripts/deploy.sh 导 出了 DEER_FLOW_DOCKER_SOCKET=/var/run/docker.sock,而 MSYS 在调用原生 docker compose 时把这个已导出的值转换成了 Windows 主机路径,于是 docker-compose.dood.yaml 挂载了 C:\Program Files\Git\var\run\docker.sock,守护进程便试图创建主机上并不存 在的目录。socket 现在保存在一个未导出的局部变量里,正如 scripts/docker.sh 此前的做法;在 Windows Git Bash 上,当该变量持有默认值时会将其 unset,让 Compose 展开它自己的字面量 /var/run/docker.sock;运维人员设置的自定义 socket 路径仍会照常传入。(#5402)

  • MCP: 从工作区变更排除内部 stdio 临时目录 .mcp/tmp。(#4898)

  • MCP: 持久任务提交中途取消时同时取消远端任务。(#4933)

  • 沙箱: 接受文档中的 E2B reconciliation 配置字段。(#4772)

  • 沙箱: 按文件、挂载及整个上传过程限制 E2B mount 上传的大小、文件数和时间。(#4812、#4842)

  • 沙箱: 保留 E2B 同步文件名尾部空白并容忍越界远端 mtime。(#4861)

  • 沙箱: 配置解析时拒绝 Redis 所有权中的非有限租约时间值。(#4960)

  • 沙箱: 结构化技能读取经沙箱 provider 路径映射解析,与 ls/shell 使用同一启用状态投影。(#4792)

  • 技能: moderation scanner 支持 Responses API content block,合法技能管理决策不再误判不可解析。(#4936)

  • 记忆: Honcho 与 Mem0 在配置解析时拒绝非正或非有限的 timeout/字符上限。(#4783、#4823)

  • 记忆: 自定义智能体 bootstrap 事实限定到所选智能体 bucket。(#4804)

  • Artifact: Windows 支持原子保存;读取响应提供 SHA-256 ETag,使普通 HTTP LAN 等无 crypto.subtle 环境也能预览和编辑。(#4629、#4865)

  • 前端: 长 run 前后保持会话顺序稳定,用户消息不再重复或落到自身步骤之后, mid-run 页面重载后回合步骤也不再出现在触发该 run 的用户消息之前。(#4620、 #4660、#4834)

  • 前端: HTML artifact 注入 base href 时不再把 <header> 误判为 <head>。(#4625)

  • 前端: 落地页案例通过公开只读 /showcase/ 路由打开。(#4635)

  • 前端: 聊天页按置顶状态排序。(#4643)

  • 前端: Markdown inline code 中的 <think> 保持原样,并恢复纯 reasoning 回合的复制按钮。(#4647)

  • 前端: 模型加载失败时显示工作区错误 banner 与重试操作。(#4840、#5021)

  • 前端: 后续回合流式输出时仍保留已完成助手消息的复制等操作。(#4844)

  • 前端: Browser Live 重连成功后保持新连接,不再立即拆除并再次重连。(#4951)

  • 前端: 复制 Lark 授权链接时复用 clipboard fallback。(#4767)

  • 前端: 统一使用“DeerFlow”大小写并修复落地页 “What's New” 标题。(#4970)

  • 渠道: 用固定 worker pool 与有界队列限制入站流量,关闭时等待真实跨线程任务。(#4800、#4816)

  • 渠道: 飞书、Telegram 与企业微信发送附件时把文件 IO 移到 worker 线程。(#4633)

  • 渠道: Telegram connection identity 查询回到 Gateway 事件循环执行。(#4815)

  • 飞书: 接收文件保持事件循环非阻塞,避免重名覆盖、越界写入,并让单个附件失败不阻塞其余消息。(#4627、#4903)

  • 钉钉: 命令分类前剥离前导 @bot,群聊中的 /new 等命令可被识别。(#4724)

  • Discord: 渠道停止后不再启动 typing-indicator 循环。(#4752)

  • 企业微信: 序列化 WebSocket 启停并等待 SDK 接收任务真正结束。(#4762)

  • Buzz: 用持久 seen-id store 丢弃重连后的重复事件。(#4888)

  • Lark: 沙箱内 CLI lock 目录保持可写,含凭据的 config 根目录仍为只读。(#4701)

  • 调度器: 手动触发也遵守全局 max_concurrent_runs,达到上限返回 HTTP 409。(#4769)

  • 调度器: 读取时转换序列化的任务时间戳。(#4785)

  • 调度器: 支持安全的多实例恢复,启动时不会把 peer 的活动 run 当成本地残留; 通过 scheduler.multi_instance 显式启用。(#4713)

  • 调度器: busy 的定时 occurrence 改为进入持久队列而非跳过,由 scheduler.queue_timeout_seconds 限制等待并可跨 Gateway 重启。(#4918)

  • CLI: headless --print、--json 与 --cli 新增 --recursion-limit。(#4615)

  • 开发: backend make dev 的 Uvicorn watcher 排除运行时状态,智能体写文件不再重启 Gateway。(#4759)

  • 开发: 诊断脚本按自身位置解析路径,可从任意工作目录运行根诊断命令。(#4736)

  • Docker: 加固本地与容器启动:make up 等待健康检查,允许缺失 .env,生产 环境可写 extensions 配置,运行数据不进入构建上下文,日志命令正确解析 checkout, 默认回环 origin 可完成 dev setup hydration。(#4658、#4806、#4852、#4853、#4956、#4959)

  • 网关: 为持久化消息标记服务端权威的 feed 位置,避免历史超过一页且触发上下文 压缩后,较早的用户消息消失或跳到步骤流中间。(#4696)

  • Lark: 托管凭据切换时,先清除旧应用的 OAuth 数据再写入替换项,从而保留新 app secret,后续浏览器授权不再解析到空的 client_secret。(#4820)

  • 消息: 移除旧版 <uploaded_files> 标签处理:后端将 #4174 之前的写法视为 普通内容,仅剥离 <current_uploads>;前端继续剥离旧标签,确保历史会话仍能干净 渲染。(#4826)

  • 技能: 在写入门控处拒绝空的 SKILL.md description,与 loader 的既有要求 保持一致;编辑自定义技能时,空 description 不再先写入一个随后被 loader 拒绝、 从而破坏磁盘技能的文件。(#4867)

  • 沙箱: 将 bash、ls、glob、grep、read_file、write_file、 str_replace 和 task 面向模型的 description 参数统一改为可选(默认空), provider 省略该参数时不再在执行前被拒绝。(#4878)

  • 沙箱: 限制 Windows 命令执行:host 命令在新进程组中运行,超时后通过 taskkill /T /F 终止,避免子进程使调用一直保持打开;输出继续使用现有的 10 MiB 有界捕获。(#4946)

  • 沙箱: 将 Windows MSYS 路径转换排除限定到安全的虚拟路径前缀,不再全局 禁用转换,使依赖正常路径转换的 host-native CLI launcher 恢复工作。(#5003)

  • 技能: app config 热重载后重新构建按用户的技能存储,避免其继续绑定到旧配置 实例中的路径。(#4972)

  • 技能: 解析可移植的 allowed-tools scalar 时感知括号,使 Bash(tvly *) 这类条目保持完整;未匹配括号会被拒绝而非静默拆分,参数限定条目保持字面含义, 不会扩大访问范围。(#4984)

  • 智能体: 规范化 Command 结果中返回的 ToolMessage,错误 payload 不再默认 获得成功回执,工具进度追踪也能正确识别。(#4977)

  • config: use_previous_response_id 在 config.yaml 的模型条目里是可 用的;它并非 ModelConfig 字段,只经由模型工厂的 extra="allow" 透传抵达 ChatOpenAI;但它从未被记录,读起来又像是 use_responses_api 的同义词。 事实并非如此:use_responses_api 选择的是端点,而 use_previous_response_id 则把 Responses API 从每轮重放完整历史切换为在服 务端状态上串接。config.example.yaml 中的 OpenAI Responses API 示例新增了 一行被注释掉的 use_previous_response_id: false,并注明它会原样转发给 ChatOpenAI、串接起来的上下文仍按 input token 计费、以及客户端的历史改写 只在历史被重放时才生效。仅新增注释,没有 schema 变更,没有新的 ModelConfig 字段,也没有 config_version 版本号提升。(#5359)

  • MCP: get_session 在 eviction 期间被取消时拆除 in-flight session owner, 避免调用方取消后泄漏 owner task 或超时后仍保持挂起。(#5008)

  • MCP: 普通 stdio 工具在 transport 断开后可重新连接:仅当失败的 pooled session 仍在注册时将其淘汰,原始错误照常返回且不自动重放,后续重试会启动新的 子进程。(#5018)

  • MCP: 持久 MCP 任务轮询发生协议超时时保留 pooled stdio session——408 并非断开——使任务状态得以保留,下一次轮询不再报告 task_not_found。(#5027)

  • MCP: 在配置边界拒绝无法作为 HTTP header value 传输的凭据(尾部换行或空白、 非 ASCII),避免 transport 异常回显完整值并将 secret 泄漏到模型上下文、 checkpoint 和 trace。(#5066)

  • 子智能体: poller 意外退出时清理后台任务条目,提交失败时移除 PENDING registry 条目,避免失败或崩溃的轮询泄漏条目,或让子智能体在无人管理的情况下继续运行。 (#5069)

  • 子智能体: 在提交失败路径停止僵尸 PENDING registry 条目,并直接根据 waiter 长度计算容量快照中的 queued 数量,避免遍历被其他线程并发修改的 deque。(#5086)

  • 渠道: 将 ChannelStore 读取与 mutation 同步,get_thread_id()/ list_entries() 不再触发 dictionary changed size during iteration。(#5083)

  • Discord: 强引用 ack-reaction task,并在关闭时将其 drain,避免 GC 静默丢弃 reaction 或在重启周期之间固定住 channel。(#5049)

  • Buzz: 将 seen-event 持久化移出事件循环,使用合并的原子写入;写入过程中有 新事件时保留 dirty generation,并在关闭时等待最终 flush。(#5103)

  • 流式传输: subscriber 使用过期 cursor 重连到空或已 drain 的 stream 时, MemoryStreamBridge._make_gap 不再触发 IndexError。(#5047)

  • 上传: dedupe 文件名时在 UTF-8 code point 边界截断 stem,使其保持在 255-byte 上限内;两个仅 dedupe suffix 不同的最大长度文件现在都能成功上传,不再导致整个 batch 失败。(#5059)

  • 前端: 格式化结构化上传错误详情(FastAPI validation issue、对象、数组), 不再显示 [object Object]。(#5071)

  • 前端: 无需重载即可在当前聊天 header、document title、搜索结果和 metadata cache 之间同步重命名后的会话标题。(#5045)

  • 前端: 将已选模型名称限制在 selector button 宽度内,过长名称会在 composer 和 sidecar 中显示省略号,而不再溢出。(#5050)

  • 前端: 长子任务卡片标题截断为单行并提供 tooltip;当委派模型省略 description 而回退到完整 prompt 时,不再撑破聊天布局。(#5136)

  • 开发: 所有平台的前端开发服务器默认使用 Webpack (DEER_FLOW_DEV_BUNDLER=turbo 可重新启用 Turbopack),避免 Turbopack 在 macOS 上泄漏 PostCSS worker、在 Windows 上发生 runtime panic。(#5036、#5133)

  • 脚本: 使用显式 interpreter(bash scripts/...)运行仓库 shell script, 避免 zip/tarball 下载、core.fileMode=false 或非 POSIX 文件系统导致 executable bit 丢失后,make docker-start 等命令以 Permission denied 失败。(#5031)

  • 依赖: 改为依赖重命名后的 tenki package,而非已从 PyPI 移除的 tenki-sandbox(import 仍为 tenki_sandbox),使干净 checkout 能在 make dev/uv sync 时正常解析依赖。(#5087)

  • 记忆: 配置为终止回合的记忆读取现在会抛出与后端无关的 MemoryReadError, 且 prompt 装配阶段不会再吞掉它:严格模式的 OpenViking(read: raise)、Mem0 与 Honcho 读取都会传播;OpenViking 作用域解析失败遵循配置的读取策略;5 秒注入 deadline 同样遵循该策略(fail-open 时无上下文继续,严格模式以超时为原因抛出)。 (#4726)

  • persistence: 在不启用它的前提下,先加上按线程的 incarnation token 所 需的存储基础。新增可空的 threads_meta.incarnation 与 mcp_tasks.thread_incarnation 列,即一个新的 Alembic head,使任务可以绑定 到被复用的线程 ID 所属的那个具体线程,因为已删除线程的 ID 可能被后来的线程 占用,而持久化的 MCP 工作会比这次删除活得更久。新建的线程记录会被赋予一个 随机的 32 字符 incarnation,并仅在该线程属于任务用户、或是无属主的遗留行时 才复制进新的 MCP 任务行;查询与插入在 SQLite 上通过标量子查询、在 PostgreSQL 上通过 share lock 保持原子。目前还没有任何代码读取这些字段,它们也被排除在 线程与 MCP 任务的 API 响应之外,且两列都可空、无默认值,从而让较旧的二进制 文件在滚动部署期间仍能继续写入。(#5216)

  • scripts: 在原生 Windows 上,shutil.which("pnpm") 遵循 PATH/PATHEXT 的解析规则,可能选中一个通用的 pnpm 匹配,即 .exe 或 .bat,或位于更早路径上的包装脚本,而不是 npm 安装的 pnpm.cmd 包装脚 本。共享的主机运行器现在会先检查 pnpm.cmd 与 corepack.cmd,再检查那些 通用名称,与仓库既有文档中记录的 Windows 命令选择顺序一致;POSIX 上的解析 顺序未变。(#5305)

  • tests: 在一位 Windows 贡献者的主机上,后端测试套件跨七个文件失败了 68 个 shell 脚本测试,而它们没有一个是由于被测代码本身有问题。CreateProcess 会先搜索 System32 再搜索 PATH,因此 WSL 的 bash.exe 启动器赢下了每一次 ["bash", ...] 的派发,却又无法针对 Windows 检出路径运行仓库脚本:在非英语 Windows 上,它那条本地化的 UTF-16LE 警告会击穿 harness 的管道解码,把真正 的失败埋在 TypeError 背后;sh 在 MSYS2 之外并不存在;而 dev-entrypoint.sh 的 command -v python3 探测选中的是 Microsoft Store 的别名存根,它们在执行时会以 49 退出。各套件现在通过一个共享辅助函数来解析 shell,它镜像了仓库的 Git Bash 包装脚本发现逻辑,并显式拒绝 WSL 启动器与 Store 存根;在不存在 Git Bash 的地方则干净地跳过。POSIX 与 CI 的行为未 变。(#5404)

  • tests: 在 Windows 主机上,跨三个套件的六个测试纯粹因路径分隔符的写法 而失败。provisioner 的 join_host_path 有意保留主机原生风格,甚至有一条显 式的 PureWindowsPath 分支,而 os.path.normpath 在那里会把 POSIX 形式的 输入用反斜杠重新拼写,因此它构造的 hostPath 字符串、docker --mount 的 src= 写法,以及 review CLI 的 PYTHONPATH 都与 POSIX 风格的字面量对不 上。断言现在会在比较之前先把主机原生的一侧规范化,这在 POSIX 上是空操作, 因此 CI 的期望值保持逐字节一致;生产行为未变。(#5413)

  • tests: 技能 request-scoped-secrets 套件中的五个测试在 Windows 主机上 失败,而两个负向检查则以空洞的方式通过,因为探针里嵌入了 POSIX 语 法:LocalSandbox._get_shell() 在那里解析为 PowerShell,其中 $VAR 是一 个未定义的 PowerShell 变量,会展开为空,因此正向检查永远看不到注入的值,而 "secret" not in out 断言实际上是在与空输出比较。探针现在会按生产实际解析 出的 shell 的语法来渲染,PowerShell 下为 $env:NAME,cmd.exe 下为 %NAME%,POSIX shell 下为 $NAME,因此负向检查在 Windows 上真正验证了擦 除效果,而按调用划分作用域的用例也证明了注入的值确实到达了第一个子进 程。POSIX 上的输出在形式上未变。(#5415)

  • tests: test_run_on_isolated_subagent_loop_survives_caller_loop_teardown 在 CI 负载下、在无关的 PR 上,以及 main 自身上都间歇性失败。竞态出在测试里,而 不是 run_on_isolated_subagent_loop 中:那个辅助函数就是 asyncio.run_coroutine_threadsafe,它的 concurrent.futures.Future 只有 在协程返回之后才会被标记为完成,但测试从协程内部发出信号并立刻断言 done(),于是主线程可能在 future 仍处于 pending 时就醒了过来。断言现在 会在检查 done() 之前先阻塞在 result(timeout=10) 上;没有引入 sleep, 被测行为也未改变。(#5299)

  • tests: checkpoint 保留需要一份可执行的声明,说明一次删除绝不可以破坏 什么;一个只保留最新 N 个的提案就曾在评审显示它会静默破坏分支与时间旅行的 父链语义之后被撤回。新套件把六种保留场景固定在 memory、SQLite 与 Postgres 三套 checkpointer 上:完整 schema 与 delta schema 各自按步骤的增长基线;删 除分支祖先会以 CheckpointLineageError 大声失败,而不是静默破坏分支或重新 生成;删除一个显式的恢复目标会移除恢复入口;挂起的写入是被保留的状态,而非 垃圾;末尾仅含时长的叶子与叶子的同级分支(真正的分叉路径)可以被安全删除。 一份配套草案记录了受保护的集合、可证明安全的删除形态,以及带孤儿记账的联合 表机制,供未来任何 max_checkpoints_per_thread 或 TTL 工作对照校 验。(#5255)

  • 记忆: 删除或清空自定义智能体时会取消缓冲中的记忆抽取,待处理的防抖定时器 不再复活已删除的按智能体记忆作用域,也不会用过期的待处理更新覆盖新的清空结果。 (#5123)

  • 智能体: 当上传(或其他)上下文包装被注入消息文本时,会话标题改用用户的原始 消息内容生成,标题不再引用服务端注入的 <current_uploads> 上下文;仅含附件的 消息保持 New Conversation 兜底。(#4729)

  • 智能体: 分数形式的摘要触发阈值会基于模型声明的 context_window 解析(现已 转换为 LangChain profile),无法解析的分数子句降级为永不触发的阈值并给出告警, 而不是让整个智能体构建崩溃;百分比风格与非有限的触发值会在配置加载时被拒绝。 (#4901)

  • 智能体: 仅当搜索模式无法解析主应用配置时,自定义智能体存储才回退到文件存储 ——非法配置与缺失配置路径会直接暴露,而非静默切换存储后端——异步智能体路由中的 store IO 也移出事件循环。(#4952)

  • 中间件: 循环检测的硬停止在整个工具调用批次内生效:选中软告警后不再结束检查 ,同一响应中较晚跨过运营方配置硬上限的调用会被拒绝,而不是随早前的告警一起放行 。(#5245)

  • 沙箱: 五个远程沙箱 provider(E2B、OpenSandbox、AIO、Tenki、BoxLite)的 list_dir 与 glob 原样返回文件名,不再剥离空白,名称以空格开头或结尾的文件 不会列在不存在的路径下。(#4980)

  • 沙箱: 共享同一会话沙箱的并发子智能体在进程级执行租约与任务作用域的 AIO shell 会话下运行:一个兄弟节点完成不再在他方仍在运行时释放共享沙箱,也不会损坏 隐式持久会话;发生损坏后会晋升健康的替换会话,而不是继续使用它。(#5134)

  • 沙箱: Bash 工具引导智能体用证据(uname -s、sw_vers、uname -a)检测 执行环境,而非依赖模型假设;被拒绝的 host 路径会指引它改用纯命令探测或允许的 虚拟路径,而不是重复被拦截的命令。(#5111)

  • 沙箱: Docker AIO 兼容 capability allowlist 加入 FOWNER,启动时会 chmod /run/user/1000 的 AIO 镜像(如 1.11.0)在加固后的默认 capability 下 可以再次启动,no-new-privileges 保持开启。(#5163)

  • 沙箱: 文件追加不再在预读失败时破坏已有内容:E2B 追加只把“文件不存在”类 错误视为空文件,其他错误直接抛出,不再用追加的尾部覆盖整个文件;AIO 追加改用 服务端原生 append 模式,完全不需要预读。(#5261、#5278)

  • 技能: 技能 Markdown 显式以 UTF-8 读取,默认代码页非 UTF-8 的 Windows 主机 上,本地化技能不再以 UnicodeDecodeError 校验失败。(#4995)

  • MCP: 运行时配置变更后 MCP 工具缓存可以重新初始化:此前的模块级 asyncio.Lock 绑定到已关闭的事件循环,且初始化标志无同步保护,导致更新后的 每次调用都以 Lock is bound to a different event loop 失败,或在多个 worker 线程间产生初始化竞态。(#5062)

  • MCP: 同步包装的 MCP 工具保持 LangGraph ToolRuntime 注入(无注解的同步 包装器现在对 functools.wraps 透明),按用户的作用域解析与持久任务提交不再以 runtime=None 运行——此前这会让完成通知 run 落到默认 lead 智能体而非该会话的 自定义智能体。(#5164)

  • 认证: 重复的 OAuth 身份不再误报“Email already registered”——两类完整性 冲突已区分——OAuth 身份的部分索引也声明了 postgresql_where,Postgres 会按 意图构建部分索引而非全量索引。(#5026)

  • 前端: 移动端侧边栏触发按钮在普通与自定义智能体欢迎页保持可点击;此前换行的 (较长的本地化)欢迎文案在相同 z-index 覆盖层中可能盖住 header 的可点区域。 (#5149)

  • 子智能体: SubagentResult 生命周期时间戳在所有写入点都使用带 UTC 的时区, 遵循仓库统一约定,非 UTC 主机上不再写入本地挂钟时间。(#5153)

  • 浏览器: 后台 live-frame 调度任务保持强引用,GC 不再因回收任务而丢失其 finally 中的 pending guard 清理,Browser Live 视图不再静默停止刷新。(#5155)

  • 运行时: 同一 LangChain run id 的重复 on_llm_end 回调只持久化一条 llm.ai.response 事件(重放的 usage 按生成位置合并,首个回调为准),provider 重发带回 usage 的回调时,追加式消息 API 不再返回重复响应。(#5187)

  • 运行时: completion hook 或任务停止扇出内抛出的取消会先让终态收尾完成—— 扩展 observer 得以运行、流的 END 标记得以发布——然后再重新抛出中断;收尾尾部 仍可被中断。(#5191)

  • 模型: 被取消的 LLM 调用会释放其持有的熔断器恢复探测(覆盖 provider 执行、 并发准入与退避),取消后同中间件的后续调用不再看到 CircuitBreakerOpen;探测 所有权按每次调用的 token 加围栏,取消较旧的调用不会释放其他调用的探测。(#5197)

  • 运行时: 内嵌 DeerFlowClient 的图缓存在任何授权模式下都按生效用户建立键, 并在运行时上下文中落实同一用户,顺序复用于不同用户时不再提供用其他用户的 prompt 与工作区状态装配的图。(#5206)

  • 运行时: 被取消的工作区变更快照捕获会排空已在运行的扫描并清理该 run 的文本 缓存,不再泄漏 deerflow-workspace-changes-* 目录;仅元数据的捕获则立即传播 取消,不再等待扫描完成。(#5232、#5234)

  • 持久化: Gateway 启动时容忍数据库已被迁移到经明确评审的更新版本 (0019_thread_incarnations),使部署更新后仍可回滚到本镜像;其他未知版本、 空版本表与多行版本表仍然快速失败。(#5219)

  • 社区工具: Tavily Extract 结果缺少 title 时回退到结果 URL 或请求 URL 作为 展示标题,不再因 KeyError 丢弃可用页面内容。(#5280)

  • 上传: 上传文档的大纲排除围栏代码块,代码注释与围栏内的粗体示例不再挤占 50 条标题预算中的真实章节。(#5281)

  • 子智能体: 压缩之后,委派账本会区分“执行完成”与“任务验收”,并保留已完成 子智能体未满足与未验证验收标准的有界示例,主智能体会修复剩余缺口,而不是把已完成 的结果当作全部完成。(#5287)

  • 开发: _pick_python() 通过 /usr/bin/env 校验解释器候选,与前端实际的 启动方式保持一致;在 Microsoft Store Python 桩能通过 Bash 探测却无法通过 env 的 Windows 上,make dev 不再无法启动前端。(#5181)

性能优化

  • 运行时: 为 MemoryRunStore 按 thread_id、MemoryRunEventStore 事件按 run_id 建立索引,避免 O(n) 扫描。(#3562、#3686)
  • 子智能体: 通过 seen-id 集合对流式 AI 消息去重(O(n²) -> O(n))。(#3687)
  • 沙箱: LocalSandbox 的路径改写正则与本地路径脱敏模式改为按实例缓存,不再 每次搜索命中都重新编译。(#3648、#3713)
  • 消息: 按组为工具调用结果建立索引。(#4411)
  • 前端: 流式渲染按帧预算合并,而非逐 chunk 渲染。(#4425)
  • 前端: 不再在每个流式 chunk 上重新推导消息内容。(#4441)
  • 沙箱: read_file 只从沙箱读取请求的行范围,不再先获取整个文件。(#3824)
  • 浏览器: Browser Live 进度帧改用 JPEG 编码,减小传输负载。(#4836)
  • 中间件: 通过 wrap_model_call 注入 view_image 内容,而非使用进入 checkpoint 的隐藏消息,避免每张已查看图片最多 20 MB 的 base64 数据同时存在于两个 checkpoint 中,也避免中断的 run 遗留该 payload。(#5014)
  • 前端: 在流式 chunk 之间缓存已稳定消息的 copy-data 推导结果,不再让每个 chunk 都为每条已稳定消息重新计算 toolbar/copy 文本。(#5095)
  • 运行时: 限制终态 run 结束后的 Gateway 内存,阻止 GC 后的低水位随已完成 session 持续上升。(#5112)
  • 前端: 聊天流改为请求 messages-tuple + updates + custom,不再请求完整 的 values 状态快照——重传的历史 values 消息约占 SSE 负载的 75%——在本地把 reducer 事件折叠进渲染状态,完整快照仅保留用于回放缺口恢复。(#5159)

安全

  • 技能: 修复公共技能审查门禁中文件可绕过 SkillScan 的缺口。审查分析器此前只把解码为 文本的文件交给 SkillScan,可执行二进制文件和嵌套压缩包从未被检查;豁免了任意层级 evals/fixtures/ 目录下的所有文件;重复的压缩包成员或仅大小写不同的文件名会在扫描前静默 覆盖先前的文件。现在 SkillScan 会逐字节接收每个文件,仅 eval fixture 的 SKILL.md 样本 仍被豁免,路径冲突会将审查标记为不完整。SkillScan 此前还会跳过含有 NUL 或非 UTF-8 字节 的代码文件,注释中的一个字节就能让反弹 shell 躲过审查门禁,NUL 字节也会让安装时的静态 分析被跳过。此类文件现在会报告 package-undecodable-script 并照常分析,CRITICAL 命中仍会拦截。SkillScan 的 Mach-O 检测遗漏了安装器会拦截的 32 位小端和 fat 变体;安装器、 导出校验与 SkillScan 现在共用同一份代码文件与可执行文件魔数定义。审查快照为二进制文件 新增 content_base64 字段。(#5431)

  • 提示词注入: 新增输入净化中间件防御提示词注入,输入护栏中伪造的框架标签会 被拦截,系统上下文以 SystemMessage 注入以隔离角色。(#3662、#4155、#3661)

  • 提示词注入: 对渲染进模型 prompt 的不可信内容进行 HTML 转义——记忆事实与摘 要、SOUL.md、子智能体描述、技能元数据,以及记忆更新 prompt 中的会话块——并中 和 web_capture 工具结果中的提示词注入标签。(#4028、#4119、#4137、#4157 、#4162、#4099、#4060、#4097、#4128)

  • **prompt-injection:**对 MindIE 工具响应框架进行转义。该 provider 的 _fix_messages 在把工具调用名称与参数渲染进 <tool_call> / <function=...> 标签前会对其进行转义,但把 ToolMessage 包进 <tool_response> 的姊妹路径却原样传递文本——而该输出到达时大体未经净化, 因为工具结果净化器只覆盖其允许列表中的远程内容工具(web_fetch、 web_search、image_search、web_capture),并有意不处理本地 bash / read_file 输出以及名称不同的 MCP 工具。因此 read_file 结果中一 个字面的 <tool_response> 会提前闭合框架,其后的一切都以仿佛位于工具响应之 外的方式呈现给 MindIE 模型,于是埋在不可信文件中的负载可以伪造 <system-reminder>。内容现在用 quote=False 做 HTML 转义,与工具调用路径 一致;模型仍会将实体解码回来,因此只有框架受到保护,合法结果的理解方式与之前 完全一致。(#4253)

  • 提示词注入: 修复两处输入净化绕过。hide_from_ui 与人类消息上的 name="summary" 会让 is_genuine_user_message 认定该消息由框架写入,从而完全跳 过净化;现在携带这两者的不可信 run 输入与线程状态写入会在服务端被标记并照常净化, 调用方再也无法把原样的 <system-reminder> 放到 user-input 边界标记之外——而主智能 体 prompt 正是把边界外的内容声明为可信的框架数据。标记本身会被保留,因此仅用 hide_from_ui 来不显示在对话记录中的消息——引用的会话上下文、sidecar 上下文、保存 智能体命令、HumanInputCard 回复——行为不变,受信任的内部启动路径也不受影响。净化范围也从"仅最新一轮"扩大到每一条真实用户消息:该变换只作用于单次请 求,因此仅处理最后一轮只能让载荷在一次模型调用中失效,下一轮起就会被原样回放。 (#5375)

  • 机密: 从技能环境中清除继承来的密钥环境变量(MYSQL_PWD、REDISCLI_AUTH 、缩写形式的 *_PASS 与 Postgres 的 PGPASSFILE);请求作用域的密钥对斜杠激 活与自主调用的技能都会绑定。(#4018、#4026、#3871、#3938)

  • web_fetch: 针对自托管 provider 的 SSRF 防护。(#3942)

  • 护栏: 空的 allowlist 现在会拒绝所有工具,而非放行(fail open)。(#4067)

  • 鉴权: 全局技能管理接口现在要求管理员权限;旧版 skills 挂载按用户可见性门 控;artifact 遵循受信任的 owner-user-id header;受信任的鉴权主体透传到运行时 。(#3855、#3985、#3982、#4203)

  • 认证: 在 access-token 生命周期内持久化 csrf_token cookie。(#3872)

  • 存储: 不再在 checkpoint 状态中持久化 base64 图片数据。(#4140)

  • MCP: 拒绝 run metadata 中的旧版 MCP 凭据。(#4448)

  • MCP: 在配置 API 中限制 stdio launcher 参数与环境变量,防止 allowlist 中的 npx/uvx 注册被参数或环境变量转化为任意代码执行。(#4617)

  • 认证: 加固登录后 next 路径校验。(#4587)

  • 运行时: 在智能体、上传、ThreadData、记忆与技能中遵循 LangGraph Server 的 已认证用户身份,并拒绝客户端提供的身份字段。(#4538)

  • 前端: 分离 origin 部署中,模型、工作区变更和 range artifact 请求会发送 session cookie。(#4827)

  • 前端: 恢复自定义 Streamdown rehype 链的净化,artifact Markdown 与记忆设置 摘要不再能渲染 javascript: 链接或 on* handler 等恶意 HTML。(#4987)

  • 技能: 投影技能文件改为复制而非 hardlink,沙箱写入不能再修改规范来源;所有 平台(含 Windows)遇到漂移的投影命名空间都会 fail closed。(#4825、#4830)

  • 脚本: support bundle 中任意位置的 secret-shaped key(如 db_pass、 signing_key)都会脱敏。(#4242)

  • 沙箱: MCP 来源的工具结果现在会通过与内置网络工具相同的信任边界进行净化, 恶意或被攻陷的 MCP server 无法再向模型传入伪造的 <system-reminder> 或用户输入 边界标签。(#4839)

  • 沙箱: 加固本地 Docker sandbox container:sandbox host 非 loopback 时, 发布端口默认绑定 Docker bridge gateway 而非 0.0.0.0 (DEER_FLOW_SANDBOX_BIND_HOST=0.0.0.0 可恢复广泛绑定);使用 Docker 默认 seccomp profile 替代无条件 seccomp=unconfined (DEER_FLOW_SANDBOX_SECCOMP_UNCONFINED=1 可重新启用);container 还会丢弃 所有 capability、启用 no-new-privileges 并使用有界资源。(#4986)

  • 鉴权: 在无状态 stream/wait endpoint 上强制 run-create 权限 (runs:create);创建、更新、恢复和手动触发定时任务 mutation 时,同时要求 threads:write 与 runs:create。(#5030)

  • 鉴权: 复用持久化 sandbox 前重新检查授权策略,使被撤销的 sandbox:execute grant 在下一次 sandbox-backed 回合立即生效,而不会随缓存的 sandbox 继续存活。(#5006)

  • 技能: 在 sandbox 文件系统层强制 Custom Agent 技能 allowlist:显式 skills 策略会生成签名的按用户/会话技能视图,带 shell 或文件工具的 Custom Agent 不再能 读取策略排除的技能。(#5077)

  • run: GET stream join 上的取消/回滚 action 现在返回 405 Method Not Allowed——取消后继续 stream 应使用 POST——关闭 safe method 上 被 CSRF middleware 有意豁免的状态变更;不带 action 的 GET join 保持不变。 (#5092)

  • Lark: Windows 上的 Lark CLI 凭据目录强制私有 ACL(仅 Gateway SID 的受保护 DACL、拒绝 reparse point、基于句柄的遍历),把 POSIX 0700/0600 的保密性 契约扩展到继承授权、junction 重定向、硬链接别名与 TOCTOU 替换等场景。(#5141)

  • 沙箱: 沙箱子进程环境会清除 SSH_AUTH_SOCK——继承宿主机 ssh-agent socket 会让沙箱内代码用智能体持有的所有密钥签名与认证——除非技能通过 required-secrets 显式声明。(#5145)

  • Artifact: XML 产物现在与 HTML、SVG 一样以下载附件形式返回。 GET /api/threads/{id}/artifacts/{path} 此前会在应用源内联渲染 .xml、.xsl、 .rdf 文件(以及宿主 MIME 数据库映射为 +xml 的 .rss 等类型),被 prompt 注入的智能体写出带 XHTML 命名空间 <script> 的 XML 后,用户从聊天链接打开即可 以其会话调用 API。所有 XML MIME 类型(text/xml、application/xml、text/xsl 及任意 +xml 子类型)现均视为主动内容,.skill 归档成员同样适用;Artifact 面板仍通过 Range 请求预览 XML。(#5353)

文档

  • 文档: 说明生产 Docker 下 LocalSandboxProvider 如何解析 sandbox.mounts[].host_path,并给出 Gateway bind-mount 与配置示例。(#3833)
  • 文档: 说明 Crawl4AI >= 0.9 需要 bearer token。(#4518)
  • 文档: 记录 GitHub 入站去重 TTL 语义及不会被去重的重投递,并收紧测试。(#4274)
  • 文档: 更新智能体 AGENTS.md 与 ARCHITECTURE.md 指南。(#4817)
  • 文档: 新增 Honcho 记忆后端专门指南,并在 README 加入长期记忆入口。(#4822)
  • 文档: 自定义智能体文档与 API 对齐(中英文 agents / threads / lead-agent 页面):必填的 ASCII name 请求字段、小写存储、/api/agents/check 的名称可用 性行为,以及不再声称从 display_name 自动派生 slug。(#4944)
  • 文档: 将子 Agent 文档重构为中英文各十一章的用户手册(harness/subagents/): 概念、快速上手、目录、委派用法、结果与验收、限制与容量、沙箱与隔离、可观测性、 按症状排查、开发者集成,以及附带 2026 年 6 月至 9 月变更记录的参考附录。原单页 成为该章节的索引页,指向该页面的已有链接保持有效;指向旧页面小节锚点的深链接 会落到索引页。(#5761)
  • 文档: 新增中英文扩展开发手册(harness/extensions/),覆盖 deerflow-extension-api 0.2.1 契约:何时编写扩展、快速上手、运行时模型、中间件 放置位置、生命周期与观察者、服务与路由、运行证据读取器、扩展运维、按错误信息排查, 以及列出全部公开名称和契约版本历史的参考章节。同时修正 AGENTS.md 中对贡献类型 和运行证据元数据脱敏的过时描述。(#5769)

内部改进

  • 测试: 前端单元测试迁移到 rstest,并在 DOM 环境运行 hook 级测试。(#3703、#4453)

  • 测试: live client 测试要求显式 opt-in。(#4482)

  • 测试: LLM 错误测试替身不再复用共享 FakeError。(#4744)

  • 测试: 工具输出测试用自构造失败条件替代魔法不可写绝对路径。(#4722)

  • 测试: 新增多回合消息流图集成不变量测试。(#3708)

  • 测试: 使用 Monocle Test Tools 新增 trace 行为测试,校验路由、工具调用和 token/时长成本。(#4025)

  • 测试: 覆盖 MCP 层中被动技能的工具可见性。(#4247)

  • 测试: 为感知租约的孤儿恢复增加 SQL 与并发 reconciler 覆盖。(#4427)

  • 测试: 恢复 memory updater 回归测试。(#4490)

  • 测试: 锁定 Gateway offline banner 的 POST logout 行为。(#4506)

  • 测试: 在 SkillScan 测试中记录已知 instance-client 假阴性。(#4644)

  • 重构: 抽取并测试前端 placeholder 检测工具。(#3783)

  • 重构: 合并 E2B client 生命周期 helper,并在 warm-pool 淘汰时复用 kill helper。(#4262、#4298)

  • 重构: 命名 E2B capacity ledger 的 meta-field 数量,使 admission offset 明确。(#4764)

  • 开发: blocking-IO detector 的调用图追踪 self/cls 属性链和本地别名。(#4200)

  • CI: 发布 lark-cli-init 与 lark-broker 镜像。(#4558)

  • 开发: host 侧 pnpm 调用统一经带 Corepack fallback 的 runner。(#4405)

  • 基准: 新增隔离的 checkpoint channel-mode 基准,对比 full 与 delta 的延迟、存储和回放。(#4395)

  • deps: 升级 cryptography 49.0.0 -> 50.0.0、postcss 8.4.31 -> 8.5.25、 h2 4.3.0 -> 4.4.1、langgraph-checkpoint-sqlite 和 langgraph-checkpoint-postgres 3.1.0 -> 3.1.1、nanoid 5.1.6 -> 5.1.16、 h3 1.15.6 -> 1.15.9,以及 next 16.2.11 -> 16.3.3。其中 next 的升级修复 了两条未经身份验证的远程代码执行公告,一条位于 Image Optimization API 的 AVIF 路径,一条影响部署在 Windows 上的服务器; h3 则阻止经过双重编码的 . 段越出静态路由目录。 (#4211, #4681, #4683, #4737, #4738, #4747, #4748, #5377)

  • 依赖: 升级 cryptography 49.0.0 -> 50.0.0、postcss 8.4.31 -> 8.5.25、 h2 4.3.0 -> 4.4.1、两个 langgraph-checkpoint-* 3.1.0 -> 3.1.1,以及 nanoid 5.1.6 -> 5.1.16。(#4681、#4683、#4737、#4738、#4747、#4748)

  • 基准: 在 backend/scripts/benchmark/deermem_eviction/ 下新增可复现的混合 memory eviction 评测,为 #4789 策略提供按构造实现 blind 的确定性 grader。 (#4810)

  • 基准: 在 checkpoint benchmark 中同时测量 Postgres checkpoint/blob/write 的存储增长,并与内存和 SQLite 对照。(#5051)

  • 测试: 将 tests/blocking_io/ 从 make test 中排除;专用的 make test-blocking-io suite(及其 CI workflow)仍是该目录的 owner。(#5105)

  • 重构: 在五个远程 sandbox provider 间共享 sandbox identity 推导和 acquire serialization(RFC #4741),替换五张会随进程生命周期无限增长的 provider 专用 lock table;每个 provider 的 golden vector 保证推导出的 id 逐字节一致。(#5089)

  • CI: 后端单元测试 workflow 拆分为四个并行分片——各自独占 runner 与隔离的 Postgres/Redis——使用 pytest-split 与提交的时长基线,基线缺失时直接失败; make test 仍是完整的离线套件入口。(#5137)

  • 开发: Playwright webServer 的 Next.js 改经 pnpm exec 启动,Windows 的 E2E 运行可解析平台对应的包二进制,不再因无扩展名的 POSIX shim 失败。(#5185)

  • 测试: Windows 上跳过 POSIX mode-bit 技能权限断言(chmod 契约在该平台不可 观测),使 Windows 贡献者可以获得绿色的后端套件基线。(#5244)

  • 测试: 针对空技能目录固定 composer 技能建议的行为(RFC #4063 Phase 4):skills 策略不允许任何技能的调用方——或没有任何技能的全新安装——会从 GET /api/skills 得到 [],此时 composer 必须降级为仅含内置命令的下拉框,而不是损坏或彻底消失。新增测试 驱动 matcher 并真实挂载 InputBox:输入 / 时恰好列出内置命令,输入无匹配的查询时 不渲染列表框。没有生产代码改动;已审计全部四处 useSkills() 消费方,均能优雅降级。 (#5490)

2.0.0 — 2026-06-15

DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子智能体、持久化记忆、 沙箱执行以及可扩展的技能(Skill)/工具系统。本版本与 1.x 系列没有共享代码, 原有的 Deep Research 框架仍在 main-1.x 分支上维护。

本次发布关闭了 2.0.0 里程碑, 自首个 2.0 里程碑标签以来累计合并 180 个 Pull Request。

⚠ 不兼容变更(Breaking Changes)

  • harness: 从 RunStore 重新加载历史 run,并持久化"已中断(interrupted)" 状态。run 的取消 / 多任务调度语义现在要求拥有该 run 的 worker 上具备可用的 RunStore;跨 worker 的取消请求将返回 409,不再静默"伪成功"。(#2932)

新增

智能体与运行时

  • 智能体: 自定义智能体支持自我更新,并按用户隔离 —— 智能体可在普通对话中 持久化对自身 SOUL.md / config.yaml 的修改。(#2713)
  • 循环检测: 支持配置开关,并可按工具维度覆盖触发频率。(#2586、#2711)
  • 循环检测: 警告注入延后执行,避免与工具调用生命周期错位。(#2752)
  • 运行: 将 model_name 从 gateway 请求一直透传到运行时与持久化层(SQLite 存储)。(#2775)
  • 子智能体: 通过终态任务事件,把子智能体的 token 用量流式上报到 header。 (#2882)
  • 记忆: 新增 memory.token_counting 配置,支持在受限网络环境下禁用 tiktoken。(#3465)
  • 建议: AI 追问(follow-up)建议改为可选。(#3591)

模型与集成

  • 模型: 新增 StepFun 推理模型适配器。(#3461)
  • 社区工具: 新增 Brave Search 网络检索工具。(#3528)
  • 渠道: Discord 增加"仅响应 mention"模式、话题路由(thread routing)以及 正在输入指示。(#2842)
  • IM: 新增"用户自有 IM 渠道连接"——用户可以在运营方配置的 bot 之上,绑定 自己的 Slack / Telegram / Discord / 飞书 / 钉钉 / 微信 / 企业微信 账号。 (#3487)
  • 模型: 新增 MiMo 推理内容(reasoning content)的补丁支持。(#3298)
  • 模型: 新增 MiniMax provider,用于图像 / 视频 / 播客类技能,并新增"音乐 生成"技能。(#3437)
  • 社区工具: 新增 SearXNG 与 Browserless 的网络检索 / 抓取工具。(#3451)
  • 社区工具: 为 image_search 新增 Serper Google 图片 provider。(#3575)
  • 渠道: Telegram 智能体回复改为就地编辑占位消息的方式流式输出。(#3534)

可观测性

  • 追踪: LangGraph 追踪名设置为 lead_agent(自定义智能体则使用其 agent_name),让 Langfuse / LangSmith 中的 trace 更清晰。(#3101)
  • 前端: 优化 token 用量的展示模式。(#2329)
  • 默认值: 默认开启 token 用量统计。(#2841)
  • 默认值: 提高默认的上下文摘要触发阈值。(#3174)
  • 追踪: 把子智能体的 span 归属到父线程的 Langfuse trace 上。(#3611)

技能

  • 技能: 新增 blocking-io-guard 技能,用于阻塞 IO 排查与运行时锚点。 (#3503)
  • 技能: 新增面向维护者的 issue 与 PR 工作流技能。(#3554)
  • 技能: 增强维护者编排(orchestrator)的评审工作流。(#3606)

性能优化

  • harness: 把 thread 元数据过滤下推到 SQL,不再在 Python 侧后过滤。 (#2865)
  • 运行时: 为 run 增加 thread_id 索引,避免 RunManager 中的 O(n) 扫描。 (#3499)
  • 运行时: 为 MemoryRunEventStore 中的消息建立索引,避免 O(n) 扫描。 (#3531)
  • 持久化: 按类缓存 Base.to_dict 的列反射结果。(#3654)
  • 沙箱: 加快 glob/grep 遍历中的 should_ignore_name 判断。(#3657)

安全

  • 上传: 拒绝指向符号链接的上传目标。(#2623)
  • 上传: 在 Windows 上支持基于符号链接保护的安全上传。(#2794)
  • MCP: 在 MCP 配置接口的响应中对敏感字段进行脱敏。(#2667)
  • MCP: 加固 MCP 配置接口对异常输入的处理。(#3425)
  • 认证: 拒绝跨站点(cross-site)的认证 POST 请求。(#2740)
  • 网关: 限制 skill artifact 预览的解压上限,避免被 zip-bomb 类构造滥用。 (#2963)
  • 沙箱: 仅在 aio(DooD)沙箱模式下挂载宿主机的 Docker socket。(#3517)
  • 沙箱: 默认不再 bind-mount 宿主机的 CLI 认证目录。(#3521)

修复

运行时、网关与持久化

  • 运行时: rollback 恢复的 checkpoint 现在能够覆盖更新的 checkpoint。 (#2582)
  • 运行时: 持久化 run 的消息摘要。(#2850)
  • 运行时: 限制 write_file 执行失败时上报的观测信息长度,避免失败 trace 撑爆上下文。(#3133)
  • 运行时: 加锁保护同步单例的初始化与 reset 路径。(#3413)
  • 运行时: 为 run events 移除 PostgreSQL 上不必要的聚合 FOR UPDATE。(#2962)
  • 运行: gateway 重启后从持久化存储中恢复历史 run。(#2989)
  • 网关: threads 接口返回 ISO 8601 格式的时间戳。(#2599)
  • 网关: 对已经处于 interrupted 状态的 run,cancel 接口幂等返回。 (#3058)
  • 网关: 将 stream_existing_run 拆分为按 HTTP 方法区分的多个路由,确保 OpenAPI operationId 唯一。(#3228)
  • 事件: 序列化结构化的 DB event 内容。(#2762)
  • 持久化: SQLite 后端的存储统一返回带时区的时间戳。(#3130)
  • 持久化: 复用 token 用量按模型分组的 SQL 表达式。(#2910)
  • 运行: 忽略已过期的 run reconnect 冲突。(#3284)
  • nginx: 把 CORS 策略下放到 gateway 的 allowlist,避免双重应用。 (#2861)
  • 持久化: 修复运行时 journal 中 run 生命周期事件的记录。(#3470)
  • 网关: 在无状态(stateless)run 接口上强制校验 thread 归属。(#3473)
  • 运行时: 通过 SSE values 事件把 interrupt 透传给 LangGraph SDK。(#3605)
  • 序列化: 从流式 values 事件中剥离 base64 图片数据。(#3631)
  • 历史: 从 REST 接口响应中剥离 base64 图片数据。(#3535)
  • 网关: token 用量归因到实际使用的模型。(#3658)

智能体、子智能体与中间件

  • 子智能体: 让"子智能体超时"成为原子化的终态。(#2583)
  • 子智能体: 工具与中间件按 model 覆盖(model override)来构造。(#2641)
  • 子智能体: 把 system_prompt 与 skills 合并到单条 SystemMessage。 (#2701)
  • 子智能体: 子智能体与父 run 的 checkpointer 隔离。(#3559)
  • 智能体: update_agent 与 setup_agent 一致,遵循 runtime.context 的 user_id。(#2867)
  • 智能体: 解决 TodoMiddleware 中 todos 通道的类型冲突。(#3200)
  • 智能体: 把自定义智能体路由中的阻塞文件 IO 移出事件循环。(#3457)
  • 智能体: 新智能体的 bootstrap 流程保持在用户作用域内。(#2784)
  • 循环检测: 注入 warn 时仍保持 tool-call 配对。(#2725)
  • 中间件: 同步原始 tool-call 元数据。(#2757)
  • 中间件: dangling 配对中间件正确处理非法 tool call。(#2891)
  • 中间件: 防止 todo 完成提醒消息泄漏到 IM 渠道。(#2907)
  • 中间件: 调用模型前先把 tool result 的相邻关系规范化。(#2939)
  • 智能体: resolve_agent_dir 要求存在 config.yaml,从而跳过仅含记忆的 目录。(#3481)
  • 智能体: 在 context / configurable 间同步 agent_name,并拒绝空的 soul。(#3553)
  • 中间件: 把 UploadsMiddleware 中的上传扫描移出事件循环。(#3311)
  • 中间件: 把记忆注入移出事件循环,避免 tiktoken 造成阻塞。(#3411)
  • 中间件: 针对非挂载型沙箱,把超限的工具输出外置到沙箱中。(#3417)
  • 中间件: 在中间件 state 中保留 sandbox reducer。(#3629)
  • 子智能体: general-purpose 的 max_turns 提升到 150,默认超时提升到 30 分钟。(#3610)

记忆与追踪

  • 记忆: 用常驻事件循环替换短生命周期的 asyncio.run()。(#2627)
  • 记忆: 队列化的 memory 更新按智能体维度隔离。(#2941)
  • 记忆: 解析被外层包裹的 memory 更新 JSON 响应。(#3252)
  • 追踪: 把 session_id 与 user_id 透传到 Langfuse trace。(#2944)
  • 追踪: 修复中文 memory trace 信息显示为 unicode 转义序列的问题。 (#3104)

工具、沙箱与 MCP

  • MCP: 修复 MCP 配置中列表型变量的环境变量解析。(#2556)
  • 模型: Codex 的 token 用量记录到 usage_metadata。(#2585)
  • 沙箱: 在 RemoteSandboxBackend 中补上 list_running。(#2716)
  • 沙箱: Windows / Git Bash 下关闭 MSYS 路径转换。(#2766)
  • 沙箱: 沙箱就绪轮询不再阻塞事件循环。(#2822)
  • 沙箱: Sandbox API 边界统一遵守 /mnt/user-data 契约。(#2881)
  • 沙箱: Provisioner 的 PVC 数据按用户隔离。(#2973)
  • 沙箱: 合并幂等的沙箱状态更新。(#3518)
  • 工具: 引入 Runtime 类型别名,消除 Pydantic 序列化告警。(#2774)
  • 工具: 在重入式 get_available_tools 调用之间保留 tool_search 的提升 状态。(#2885)
  • harness: 为同步客户端封装仅异步可用的 config 工具。(#2878)
  • harness: 同步客户端可用所有原本仅异步的工具(统一封装)。(#2935)
  • 工具检索: 通过移除 ContextVar,可靠地隐藏延迟加载的 MCP schema。 (#3342)
  • 检索: 修复 DDGS 的维基百科区域处理。(#3423)
  • web_fetch: 在受限网络环境下为 Jina reader 支持代理。(#3430)
  • 沙箱: 通过 Command 持久化懒加载获取到的沙箱状态。(#3464)
  • 沙箱: 修复 AIO 沙箱缓存被陈旧复用的问题。(#3494)
  • 沙箱: 在使用全新 id 重试前先创建 shell session。(#3577)
  • 沙箱: 不再把字符串字面量路径片段误判为不安全的绝对路径。(#3623)
  • 沙箱: read_file 命中二进制文件时返回可操作的提示信息。(#3624)
  • MCP: 让 stdio MCP 产出的文件可通过虚拟沙箱路径解析。(#3600)
  • MCP: 在设置页的工具列表上展示"需要管理员权限"的状态。(#3533)
  • MCP: 新增工具缓存重置接口。(#3602)
  • 上传: 修复上传文件大小的接口契约。(#3408)

技能与渠道

  • 技能: 强制校验 allowed-tools 元数据。(#2626)
  • 技能: 在各聊天渠道下加固 /skill 斜杠激活。(#3466)
  • 技能: 修复自定义 skill 安装时的权限问题。(#3241)
  • 渠道: Gateway 的命令请求需要鉴权。(#2742)
  • 技能: SKILL.md 出现 YAML 错误时,展示出错行号与引号提示。(#3335)
  • 技能: 把技能压缩包的安装移出事件循环。(#3505)
  • 渠道: 提取回复时忽略隐藏的控制消息。(#3270)
  • 渠道: 渠道重启时重新加载配置。(#3514)
  • 渠道: 暴露企业微信(WeCom)WebSocket 连接失败的信息。(#3526)
  • 渠道: Discord 上传完成后关闭文件句柄。(#3561)
  • 渠道: 用户自有 IM 消息要求绑定身份。(#3578)
  • 渠道: IM 文件与辅助命令限定在 owner 作用域内。(#3579)
  • 渠道: 以运行时的 provider 状态为权威来源。(#3580)
  • 渠道: 加固运行时凭证管理接口。(#3581)
  • 渠道: 让渠道连接流程可确定(deterministic)。(#3582)
  • 渠道: 集中各渠道共享的重试辅助逻辑。(#3583)
  • 渠道: 增加运行态的防护约束(operational guardrails)。(#3584)
  • 渠道: 按相等性(equality)退订渠道监听器。(#3608)

认证

  • 认证: 用缓存响应替换 setup-status 接口的 429 限流。(#2915)
  • 认证: 自动生成的 JWT secret 持久化保存,重启后仍可用。(#2933)
  • 认证: 对齐"认证禁用(auth-disabled)"模式与 mock 历史加载行为。 (#3471)

前端

  • 前端: 在 prod 模式下恢复 getGatewayConfig 的 localhost 兜底。 (#2718)
  • 聊天: 修复新会话第一条用户消息被吞掉的问题。(#2731)
  • 前端: header 总计 token 数采用后端线程级 token 用量。(#2800)
  • 前端: 异步 chat submit 完成后再清空输入框。(#2940)
  • 前端: 修复登录页闪烁与 ResizeObserver 死循环。(#2954)
  • 前端: 对恢复出的会话消息去重。(#2958)
  • 前端: 避免乐观渲染产生重复的用户消息。(#3002)
  • 前端: 流式中的 assistant 消息不再展示复制按钮。(#3176)
  • 前端: 新建 thread 后立即在侧边栏显示。(#3283)
  • 前端: 新建会话的 thread 消息相互隔离。(#3508)
  • 前端: 限制深层嵌套列表的缩进,避免渲染崩溃。(#3393、#3570)
  • token 用量: token 用量按 message id 去重聚合。(#2770)
  • 前端: 剪贴板复制回退到 Streamdown。(#3397)
  • 前端: 移除用 Backspace 删除 prompt 附件的快捷键。(#3410)
  • 前端: 把记忆设置的工具栏重排为两行。(#3433)
  • 建议: 解析追问问题前先剥离内联的 <think> 推理内容。(#3435)
  • 前端: 追问建议被禁用时不再发起请求。(#3599)
  • 前端: 工作区会话列表在超过 50 个 thread 后分页加载。(#3485)
  • 前端: 避免长串不可断行文本导致用户消息气泡溢出。(#3488)
  • 前端: SSR 鉴权探测无法连通 gateway 时仍保持工作区可交互。(#3495)
  • 前端: 用户消息按纯文本渲染,并限制引用(blockquote)嵌套层级。 (#3502)
  • 前端: 删除后重置当前激活的会话。(#3519)
  • 前端: 优化移动端工作区布局。(#3646)
  • 前端: 多段(multi-part)AI 消息渲染完整内容。(#3649)

构建、部署、脚本与配置

  • 打包: 新增 postgres extra 以支持 store / checkpointer,并完善安装 说明。(#2584)
  • harness: 运行时路径以项目根目录为基准解析。(#2642)
  • Docker: 让 nginx 在每次请求时再解析 upstream 名称。(#2717)
  • Docker: 把 Gateway 默认改为单 worker,避免多 worker 模式下出现异常。 (#3475)
  • 脚本: make dev 重启时保留 uv extras。(#2767、#2754)
  • 脚本: 停止时清理本地 nginx。(#3005)
  • 部署: 没有 python3 时,secret 生成回退到 python / openssl。 (#3074)
  • 配置: 让 reload boundary 在代码层面可发现。(#3144、#3153)
  • replay-e2e: 重放 fixture 按调用方与会话作为 key。(#3453)
  • 安装向导: 更新 LLM provider 向导的默认值。(#3421)
  • 配置: 把 config.yaml 中为 null 的列表字段归一为空列表。(#3434)
  • 脚本: gateway reload 时排除运行时状态目录。(#3426)
  • 脚本: 在 uvicorn 的 reload-exclude 生效前先创建 backend/sandbox 目录。 (#3460)
  • 脚本: 修复 make start-daemon 之后无法用 make stop 正确停止 next-server 的问题。(#3498)
  • Makefile: 修复 per-commit hooks 的安装。(#3569)
  • replay-e2e: 重放匹配改为按会话,而非使用当前系统 prompt。(#3436)

变更

  • provider(重构): 各 provider 间共享 assistant payload 的回放匹配逻辑。 (#3307)
  • lead-agent(重构): 把 build_middlewares 改为 public,去掉最后一个跨 模块的私有导入。(#3458)
  • todo(重构): 移除未使用的完成提醒计数器。(#3530)

文档

  • 补充 blocking-IO 检测的使用与维护说明。(#3233)
  • 清理文档中残留的"独立 LangGraph 服务器"相关内容。(#3301)
  • 在 PR 模板与 CONTRIBUTING 中补充 AI 辅助声明。(#3398)
  • 补充自定义 AIO 沙箱镜像的文档。(#3548)

内部改进

  • 开发: 新增 async / thread 边界检测器。(#2936)
  • 运行时: 增加 lifecycle 端到端测试覆盖。(#2946)
  • Windows: 后端 Makefile 各 target 加入 PYTHONIOENCODING 与 PYTHONUTF8。(#3069)
  • blocking-io: 检测器以"显式失败(fail-loud)"方式解析仓库根目录,并提供 共享 CLI 入口。(#3512)
  • 运行时: 为 JsonlRunEventStore 的异步 IO 增加 Blockbuster 运行时锚点。 (#3313)
  • CI: 统一 PR / issue 打标签逻辑,修复 reviewing 任务的崩溃与标签抖动。 (#3455)