链路

一条消息从进来到发出去

消息先落进持久化队列,再经过确定性门禁和接话评分决定回不回;要回时组装上下文、交给模型或 Agent 生成,过完发送前审核才分条发回来源通道。每一步拦下都会留下结果代码和原因。

总览

五段链路

步骤判断终点 拦下 / 沉默转到别处继续回到前面某一步
  1. 平台消息

    OneBot / Telegram / QQ / 钉钉 / 飞书 / 企业微信,归一化为 MessageEvent。

  2. 持久化入站队列

    断线补拉、缺口检测、失败重试,进程重启也不丢。

  3. 回复触发

    确定性门禁 → 直接触发 → 接话评分。

    不回记录原因
  4. 组装上下文

    SOUL.md、历史、记忆、媒体,稳定部分在前以命中前缀缓存。

  5. 生成

    开了 Agent 走工具循环(可能用到浏览器),否则一次模型调用。

  6. 发送前审核

    沉默、去重、审核模型、群叫停。

    拦下记录原因
  7. 分条发送

    发送时守卫、幂等、退避重试。

  8. 记录结果

    inbound_events 的结果代码与原因,WebUI 事件中心据此展示。

01 回复触发

消息进来之后,决定回不回

HandleEvent 不直接生成回复,而是先把消息写进 SQLite 入站队列再唤醒 worker。进程崩溃、重启或者模型卡住时消息还在库里:worker 认领时拿的是 10 分钟租约,过期后会被重新认领。

  1. HandleEvent

    自己发的消息只记历史;戳一戳、进群、加好友、撤回这类通知各走各的开关与冷却,不进队列。

  2. 私聊准入

    全部 / 仅主人 / 白名单。

    不通过ignored_private_admission
  3. 登记直接呼叫

    noteDirectedInbound:入队前就登记,后面用来打断同一人的旧回复。

  4. 写入入站队列

    优先级:直接触发 100、引用回复 80、可解析链接 60、其余 0;同优先级按时间。

    写入失败写重试日志,2–30 秒退避重放,并安排一次补拉
  5. worker 认领

    每会话默认群 3 条、私聊 2 条同时处理,另受全局回复并发限制。

  6. 补拉来的旧消息?
    只补历史backfill_history_only早于重放窗口ignored_stale
  7. 语音转写 → 路由

    routeMessageEvent。转写暂时失败会让这一条稍后重试。

  8. 确定性门禁

    12 项不调用模型的检查,见下方表格。

    拦下ignored_*
  9. 直接触发?

    私聊、@、引用机器人、称呼、主人命令、可解析链接、插件命令;先要通过回复准入(主人直通 → 屏蔽 → 白名单 → 豁免 → 活跃时段 → 群等级)。

    否主动接话评分
  10. 同一人正在生成回复?

    2 分钟内追加的消息由模型判断是重复、补充、纠正还是独立问题。

    补充或纠正,置信度 ≥ 阈值Agent 规划中接进本轮,否则旧回复作废重新生成 merged_into_reply
  11. 进入回复流程

处理出错按 5 秒起、翻倍到 5 分钟重试,最多 5 次,用尽记 dropped_retries_exhausted;平台明确拒收记 dropped_send_rejected,不再重试。

01 回复触发

断线补拉与缺口检测

掉线期间平台上的消息收不到推送,只能事后向平台拉历史。协调器 runInboundCoordinator 负责这件事。

  1. 进程启动

    释放过期租约,读取上次在线的检查点。在线时每 30 秒保存一次检查点。

  2. 心跳发现断线或账号离线

    停止认领,保存检查点。

  3. 重新连上

    重放截止时间 = 断线时刻 − 30 分钟,最多回看 24 小时。

  4. 补拉三次

    1 秒后补一次,+1 分钟、+5 分钟再各补一次。遍历群列表和最近联系人,每页 100 条、每会话上限 200 条,逐个会话串行拉。

  5. 去重

    和已入队、已入库的消息比对,同一条不会处理两次。

  6. 这条要不要参与回复?

    每个会话只有最新 N 条(history_backfill_message_limit,默认 3)、且本来就会直接触发的消息正常入队。

    其余只补进历史,不接话
  7. 正常入队
为什么只回最新几条

掉线一小时回来,对攒下的上百条群聊逐条接话只会刷屏。补回来的历史照样进上下文,只是不再逐条触发回复。

在线时还有一道缺口检测:重连后每个群的第一条实时消息、以及任何 seq 跳号,都会触发 verifyGroupSeqGap——等补拉结束后定向重拉,并在 15 秒、1 分钟、3 分钟复查。日志动作是 backfill_gap_detected / resolved / unresolved。

01 回复触发

确定性门禁

按顺序检查,任何一步拦下都带着具体原因结束。这些检查不调用模型(第 12、13 项除外),结果稳定可复现。

#检查拦下时的结果
1机器人已退群或群不可用ignored_unavailable_group
2补全引用、转发、图片,插件观察,写入历史—
3群未启用 / 不在准入范围(主人的抑制命令例外)。停用分两档:彻底休眠(默认)只落本地历史,不跑长期记忆提取、会话摘要、语义索引和语音转写;静默旁观照常做这些,只是不回复ignored_policy;被屏蔽的用户记 ignored_user_blocked
4机器人在该群被禁言ignored_bot_muted;开了「禁言期间仍判断」时跑完判断再记 ignored_bot_muted_judged
5群模型配额用尽ignored_model_quota
6事件触发器(关键词 / 正则任务)不拦截,任务在后台照常跑
7主人的清空上下文命令立即处理
8积压交接(见「并发与合并」)merged_into_backlog_turn
9群成员等级门槛(OneBot;@、主人、管理员豁免)ignored_member_level
10响应抑制期内ignored_response_suppression
11只有视频没有文字ignored_video
12发送者是被标记的机器人或 Telegram bot,且没有明确 @ 或引用本机器人:用模型判断是不是在跟自己说话ignored_bot_message
13回复疲劳攒满(对同一个人回得多,且对方长句、没新意、没目的;分快慢两档消退,时间常数 10 分钟和 6 小时,总疲劳 = 快 + 0.2 × 慢):已决定要回之后先看攒着的判断:最近几轮审核打的目的分按权重攒着、30 分钟消退,这个人一直在追问、纠正、请它做事就照回;攒得不够再用模型看这句(和意图识别同一份按时间排的对话)有没有新东西、是不是在提问或请求,两样都没有才不回,@ 和引用也一样;疲劳没攒满或攒着的目的够高时不调用ignored_reply_fatigue

01 回复触发

主动接话评分

没有直接触发的普通群消息,先过群的随机采样比例(主人豁免),再交给接话模型。模型对每条候选只回答两件事:relevance.directed(是不是明确在跟机器人说话,只有是或否)和 chat_in.score(0–1 的闲聊适合度),各附一句理由。

  1. 两个开关都关,或群里 10 分钟内有人叫停?
    是沉默,不调用模型
  2. 组装评分输入

    寻址信息、按时间排的对话稿、本群补充判据。

  3. 调用接话模型

    用途 proactive_reply_router,可绑对话模型或判断模型 Jev,最多等 60 秒。

    超时 / 出错沉默解析失败带提醒重问一次,仍失败则沉默
  4. directed = false 且 chat_in = 0?

    表示对方在叫停或正在机械循环。

    是沉默,「完全不限制」档也不越过
  5. 回应提问开着且 directed = true?
    是放行 replied_proactive
  6. 闲聊分达到档位门槛,且冷却已过?

    冷却默认 30 秒,闲聊回复真正发出去后才开始计时。

    否ignored
  7. 放行

    replied_proactive,进入回复流程。

档位界面名称闲聊分门槛
off从不闲聊不闲聊
minimal很少插话≥ 0.90(最严)
low偶尔接话≥ 0.70
medium适度参与≥ 0.50
high积极参与≥ 0.30
always完全不限制不看分数,仍受冷却限制

「是不是在跟机器人说话」不打分:给 0–1 的分数,模型会在 0.45 和 0.55 之间摇摆,同一类消息这次回下次不回。模型出错时一律沉默——以前超时会退回关键词规则(扫到问号就当公开提问),判错的方向是「本来不该说话却开口」。评分细节写在 proactive_reply_route 日志里;档位、打分口径和补充判据见仓库的 发言偏好,判断模型的接入见 判断模型(Jev)。

01 回复触发

并发、合并与叫停

同一会话里消息来得快,三种机制避免「一条一回」或者答非所问:

积压交接

  1. 排队 ≥ 30 秒

    且后面还有同会话消息;只看第一次处理,主人的消息不交接。

  2. 存进积压包

    最多 20 条,保留 10 分钟。

  3. 下一条一并取走

    最新的直接触发当回复目标;没有直接触发时,能接话的候选一起交给接话评分。

生成中合并

  1. 同一机器人、会话和用户在 2 分钟内追加

    仅在确定走可重生成的对话路径后开放;解析、固定插件结果和本地命令不接走新问题。

  2. 重复 / 补充 / 纠正 / 独立?

    置信度 ≥ 阈值(默认 75%)才合并。

  3. 重复共用答案,补充和纠正接进本轮或重新生成

    Agent 还在规划时,不带媒体的补充在下一步规划前接进上下文,已有工具结果保留,正在跑的那步规划作废重做;其余情况立刻中断这一版重新生成(工具运行中等它结束,已写外部系统则不中断)。最多生成 3 次;最后一次生成前、开始发送或产生外部副作用时关闭窗口。回答之后再问,走新的回复。

打断

  1. 群里同一人又直接叫了机器人
  2. 旧回复发送前丢弃

    superseded_follow_up;撤回只去掉引用装饰,不取消回复。

合并后的请求仍需结算

合并先记为持久化待定交接。包含这些请求的回答完整送达后才落定;沉默、生成失败、取消或仅发出部分答案时,未完成的请求重新排队。进程重启会恢复无人负责的待定交接。已经尝试发送、却无法确认结果的答案不自动重发;未包含在该答案里的补充仍恢复处理。插件事实投递保留独立交付,可选的模型跟评遵守追问发送闸门。

群里叫停

在群里 @、引用或点名机器人说「闭嘴」「别说了」,发送前审核会顺带判出叫停,给本群记 10 分钟窗口:窗口内接话评分直接跳过,在路上的主动接话回复发送前丢掉;@、引用和点名照常回答——叫停说的是别插话,不是不理人。

02 回复流程

决定要回之后

replyAndRecord 最多跑 3 次 replyTo:同一个人在生成期间补了一句、被判为补充或纠正时,Agent 还在规划就把补充接进这一轮继续跑;接不进去(非 Agent、带媒体、Agent 已收尾)时旧的这版作废,带上新消息重新生成;第 3 次之前封口,不再接受合并。

  1. 准备

    当前消息图片、历史、身份脱敏、请求超时、「正在输入」(Telegram 与 OneBot 私聊)。

  2. 主人命令 / 状态命令?只是可解析链接?
    命令直接发送,不过审核链接只解析链接,按资源去重后发送
  3. 插件处理

    可能启动后台任务,也可能直接给出回复。

    插件直接回复审核后发送,不调用对话模型
  4. 组装上下文与工具

    见下方提示词顺序。回复规则可以指定本轮的模型配置,或把回复换成语音。

  5. 开了 Agent 且关系允许用工具?
    是Agent 运行否一次模型调用,有图或音频走视觉分组
  6. 整理格式

    取出控制标记;拦下写成文字的工具调用;平台不支持富文本时转纯文本;超长先在自然边界拆、再单独拆代码块,还不行就用模型压缩(30 秒、每条最多 2 次)。

  7. 发送前审核
    拦下对应的 ignored_*
  8. 分条发送

    见下一节。

    发送时发现补充消息回到准备,重新生成
  9. 记录

    历史、消息 ID、投递阶段、事件结果;闲聊冷却开始计时;排入关系评估。

提示词顺序

系统头(SOUL.md、固定规则、工具规则)→ 摘要检查点 → 稳定历史 → 缓存断点 → 可变块(插件上下文、会话状态、记忆、笔记本、世界书、自我笔记、媒体索引、最近引用来源、最近工具调用)→ 同轮候选消息 → 依赖图 → 系统尾(说话人)→ 时钟 → 装饰规则 → 当前消息。断点之前的内容在同一个群里轮与轮之间保持不变,让上游的前缀缓存命中。发给模型的请求里真实的用户 ID、群号换成别名,回复和工具调用里再换回来。

生成结果为空时按标记兜底:暂停响应就发一句人设内的提示后沉默;拒答发固定文本;有出图任务在排队就把「开始生成」当回复;这一轮已经用 say 说过话就沉默;都不是时发「我这边没有生成有效回复。」

02 回复流程

分条发送

  1. 分条

    按 [diana-msg] / [diana-line] 分条,去句尾句号;同会话的回复排队,不交错。

  2. OneBot 且超过合并转发阈值?
    是发合并转发卡片(卡片文字另过账号安全审核);被审核拦下就不发,其他失败且未超时则退回逐条发送
  3. 逐条发

    只有第一条带引用和 @;触发消息被撤回时不引用,回复针对另一位参与者时不引用当前消息。

  4. 节奏

    第二条起等 max(分条间隔 300 毫秒, 每字 100 毫秒),最多 6 秒;等待时保持「正在输入」。

  5. 单条出口 sendOutgoingWithResult

    凭据打码 → 本地图片与语音准备 → 解析 [diana-reply] / [diana-at] → 发送时守卫。

  6. 这一步已经发过?

    按指纹记在本轮的投递账本里。

    是跳过,复用原消息 ID
  7. 租用媒体分享链接

    每次重试前续期到至少十分钟以后。

  8. executeOutboundCall
    被踢 / 被禁言放弃连接离线快速失败,入站队列稍后整轮重试群发送失败文字、媒体两条通道各自指数退避,窗口用尽后丢弃冷却
  9. 成功

    记 Acknowledged、写入聊天历史、记下消息 ID,暂停「正在输入」。

聊天回复在单次调用内只发一次;平台因媒体校验拒收时会短暂重试,连同第一次最多 3 次。send_retry_attempts 用于定时推送等非回复发送。入站队列整轮重试时,已经发出去的分条靠投递账本跳过,不会重复发。

03 Agent 运行

一步只做一件事

机器人开了 Agent 且对方的关系允许用工具时,这一轮由 Runner.Run 生成:模型每一步要么调用一个工具,要么用 agent_finalize 收尾。

  1. 组装配置

    不设工具调用次数上限;主人单次工具输出 20000 字,其他人 8000 字。单工具 60 秒,收尾预留 20 秒,协议修复 3 次。

  2. 组装工具

    核心工具每轮直接可用;其余只在目录里列一行,先 tools_load 再经 tools_execute 调用。按权限白名单与机器人 / 群级扩展开关裁剪,无权工具回答「没有权限」而不是「不存在」。群聊加载过的工具随群会话快照跨轮恢复。

  3. Runner.Run 启动

    协议提示词与延迟工具目录;按触发词注入 Skill;可信时钟;上一轮被迫收尾时附上续跑档案。

  4. 还有步数,离截止还够收尾?
    否强制收尾
  5. 调用模型

    规划截止 = 请求截止 − 收尾预留(配置值与剩余时间三分之一取小)。

    规划截止到了强制收尾其他错误交给回复流程的错误处理纯文本 / agent_finalize收尾校验
  6. 只执行一个工具调用

    同批其他调用回「未执行」,让每一步都能看到上一步的结果。

  7. 执行前检查

    延迟工具须先加载并过 schema 校验 → 未知或无权 → 参数纠正 → 扩展变更须带确认码 → 与上一步完全相同则跳过 → 配额(web_search 3 次,自省 6 次、tools_load 4 次不占步数)。

    不通过计一次协议修复,回到步数判断;修复用完则强制收尾
  8. 执行工具

    单工具超时与输出预算;run_command 先过命令白名单,再按配置放进沙箱;MCP 另有 60 秒超时。

  9. 整理结果

    凭据打码、超长截断;搜索和渲染结果记入证据账本;附上剩余预算。工具报错不致命,变成观察结果交回模型。

    下一步回到步数判断

收尾校验

  1. silent = true?
    是,没有出图任务排队这轮不回有出图任务排队拒绝静默,计一次修复
  2. 正文里有真实换行?

    分条必须用 [diana-msg] / [diana-line]。

    有计一次修复
  3. 要求证据却没搜?出现本轮没取回、对话里也没出现过的链接?
    是计一次修复
  4. 泄露内部协议词?正文为空?
    是计一次修复
  5. 返回回复

强制收尾发生在步数用完、修复用完、收尾预留时间到了、证据要求没满足时:追加收尾指令和证据账本摘要,只开放 agent_finalize 并强制调用。结束后,被迫收尾时存下最多 12 条步骤摘要作续跑档案(10 分钟有效);最近 8 次工具调用保留 30 分钟注入后续轮次。

中途说一句 say

一轮最多 3 次、每次 80 字以内、不能换行,直接发出去并重新点亮「正在输入」。说过之后这一轮不再被合并重来,也不过发送前审核。

子任务 subtask

同步的、没有工具也没有历史的一次模型调用,每条回复最多 4 次、材料不超过 6000 字、90 秒超时。

上下文超限

依次把最长的文本消息摘要(最多 2 次)、把图片换成描述、缩小图片、最后丢掉图片。

模型报错

Runner 不重试。provider 栈里瞬时错误 700 毫秒后重试一次,上下文超长减半重试最多 4 次,再按配置顺序切到下一个模型配置,已完成的工具步骤不丢。

04 浏览器机制

这一轮用哪个浏览器

一次性无头渲染 browser_render 所有人可用、始终注册;带登录态的交互式浏览器只有主人能驱动,每轮只注册一套,模型不会同时拿到两套再自己去猜。

  1. 对方是主人?
    否只有 browser_render
  2. 按优先级选来源

    browsersource.Pick,默认内置在前、扩展在后,取第一个用得上的。

  3. 内置浏览器:已勾选且找得到 Chrome

    1. 这台机器人关掉了内置浏览器?

      agent_browser_box_disabled

      是browser_* 改接外部 CDP 地址
    2. 接这台机器人的内置浏览器

      注册 browser_*。

    扩展:总开关开、有连接、没人接管

    1. 这台机器人开了扩展控制?

      agent_browser_control_enabled,默认关。

      否同「都用不上」,不会退回内置
    2. 接你的 Chrome

      注册 browser_ext_*。

    都用不上

    1. 这台机器人改过外部 CDP 地址?

      不是默认的 127.0.0.1:9222。

      否没有交互式浏览器
    2. 接那个 CDP 地址

      注册 browser_*。

内置浏览器被接管时不会切到扩展

内置浏览器的可用性只看「开着且找得到 Chrome」,接管期间 browser_* 直接报「用户正在人工接管内置浏览器」。扩展正好相反:所有连接都在接管时算作用不上,会轮到下一个来源。

04 浏览器机制

一次性无头渲染

  1. browser_render 或网页渲染插件

    插件每条消息最多渲染 2 个链接,正文以「不可信内容」块注入上下文。

  2. 公网地址校验

    只允许 http/https,拒绝内网、回环、链路本地、localhost、.local 等地址。

    不通过拒绝GitHub /releases 链接先走官方 API
  3. 领名额

    默认同时 3 个,最多排队 20 秒,排队时间不计入超时。

    排不上同时运行的一次性浏览器已达上限
  4. 起 Chrome

    临时 profile(0700),HOME / XDG 指到临时目录,屏蔽本机域名解析。

  5. 导航

    每个主文档请求都重新校验地址;触发下载就中止。

  6. 等页面稳定

    DOM、网络、定时器都安静下来;默认 25 秒,最多 60 秒。

    超时取最后一次非空快照,一点内容都没有才报错
  7. 返回网址、标题、正文

    结束进程,删掉临时目录。

  • 只有主文档请求被拦截重验,页面里的 XHR、图片等子资源不逐个校验地址。
  • HTML 转图片:简单页面走 Chrome --screenshot(产物上限 8 MiB,占名额);带脚本或动画的走 html_capture,页面放在假域名 diana-render.invalid 下,请求要么本地应答要么被拦,另有严格 CSP 和虚拟时钟,最多 600 帧、4096 像素,目前不占名额。
  • 找浏览器:配置路径 → DIANA_HEADLESS_BROWSER_EXECUTABLE → DIANA_AGENT_BROWSER_EXECUTABLE → 常见安装位置(含下载来的 Chrome for Testing)→ PATH。安装先用系统包管理器,glibc Linux 上装不了时下载 Chrome for Testing 到 DIANA_BROWSER_DIR。

04 浏览器机制

交互式浏览器

  1. browser_open

    只检查 http/https 或 about:blank,加上内置浏览器的 denied_hosts。

  2. 本会话有当前标签页,且没要 new_tab?
    有沿用当前页
  3. 机器人自己开的标签页已到 8 个?

    标签页被用过后 10 分钟内算作那个会话占用。

    有空闲页关掉最久没用的一页全被占着拒绝,让模型改用当前页或先关一页
  4. 导航

    按 agent_browser_timeout_ms(默认 15 秒)计时,超时 stopLoading 并报错;正文迟迟传不完的等 8 秒后停止加载。

  5. 动作

    click / type / key / wait / eval / screenshot…;点击和按键用真实输入事件,被遮挡时退回 el.click();browser_eval 返回值里的 Cookie 打码。

    页面卡死或崩溃终止脚本 → 重置为 about:blank → 关闭标签页
  6. 返回页面状态

    截图存在工作目录 .agent-browser/(每会话一个文件,7 天清理),同时作为图片附给模型;每次调用写一条 browser_action 日志。

内置浏览器外部 CDP扩展(你的 Chrome)
工具browser_*browser_*browser_ext_*
登录态在这个浏览器里自己登的,按机器人隔离那个 Chrome 自己的你日常浏览器的,Diana 不碰 Cookie
网络边界scheme 检查 + denied_hosts,能访问局域网scheme 检查站点白名单 / 黑名单、读写档位、限速
接管点画面自动接管,5 分钟无输入自动交还—扩展或 WebUI 里切换
截图有有没有(避免扩大扩展权限)

进程细节(按需启动、崩溃 3 秒后重启、Xvfb、实时画面)见仓库的 内置浏览器,扩展的授权模型见 浏览器控制扩展,Docker 与 seccomp 见 浏览器依赖。

05 发送前审核

发出去之前的检查

回复生成出来之后、真正发出去之前,还有一串检查可以丢弃、改写或推迟它。其中只有一次是专门的审核模型调用,其余是程序检查。

  1. 带了依赖图,模型却说看不到图?
    是,且还没产生外部副作用改用图片描述重新生成一次
  2. 模型选择沉默?
    可以沉默ignored_model_silent已有副作用 / 插件事实结果 / 出图排队不许沉默,按空回复兜底
  3. 闲聊回复为空或被标记拒答?
    是ignored_no_natural_reply
  4. 暂停响应 / 拒答标记?

    [[DIANA_IGNORE_CURRENT_USER_30M]] / [[DIANA_REFUSE_CURRENT]]

    暂停且正文为空发一句人设内提示后沉默拒答且正文为空换成固定拒答文本
  5. 和本轮 say 说过的某句相同?
    是不发第二次
  6. 审核模型

    对最终候选回复执行一次审核,不再另行调用语义去重模型丢弃或改写正文。

  7. 应用审核结论

    见下一节。

    拦下对应的 ignored_* 或公开提示
  8. 群里 10 分钟内有人叫停,且这是主动回复?
    是ignored_stop_requested
  9. 进入发送

    每条消息再过发送时守卫。

05 发送前审核

审核模型

审核只有开和关两档(默认开,群配置可单独覆盖)。一次调用同时判断下面几项,哪一项都不需要时整个跳过。结论按这个顺序落地:

  1. 审核调用失败?
    主动回复沉默:拿不准就别插话直接回复放行:模型不可用时集体哑火更糟
  2. 私聊收尾 / 叫停

    私聊里机器人上一条发出 3 分钟内才判;只认置信度 ≥ 0.90 的结论。

    叫停ignored_stop_requested,非主人暂停响应 30 分钟道别超过宽限次数ignored_conversation_closed
  3. 群里叫停

    群里 @、引用、点名机器人的消息顺带判;判出来给本群记 10 分钟窗口,这一条照常发。

  4. 空转

    机器人互聊检测默认开。没内容时记一次空转;对方被标记为机器人时,回得密又没目的也记一次。未标记的账号斗嘴不计。同一次审核还给这一轮的新意和目的打分,发出去后按对方这句的篇幅累加回复疲劳。

    30 分钟内判中 3 次(次数和暂停时长可在设置里改,也可整体关掉临时响应屏蔽),对方不是主人暂停并提示 ignored_ai_reply_loop
  5. 账号安全分 < 0.10?

    刻意压低:疑似风险仍放行,只有接近确定的不安全结论才拦。

    是改发中性公开提示(可按人设改写)
  6. 表达质量(只对主动回复)

    自相矛盾、答非所问、被截断、有害建议,或 send_confidence 低于 proactive_reply_threshold。

    不过关ignored_proactive_reply_quality
  7. 放行

    判为拒答(置信度 ≥ 0.90)的照常发,30 分钟内累计 4 次拒答后暂停响应。

顺序是有意的:对方都说「别回了」,再纠结这条够不够准确没有意义,所以收尾排最前。质量门槛只给主动回复——被点名在说话却因为一个打分沉默,对方看到的就是装死。插件直接给出的回复也过同一次审核。

发送时守卫

守卫条件结果
凭据打码文本里出现已登记的凭据替换成打码后的文本
提及与引用标记[diana-at] / [diana-reply] 格式不对或指向不存在的消息改写或去掉
群不可用已确认退群或群被禁用ignored_unavailable_group
暂停响应对这个人的暂停期内ignored_response_suppression
补充消息同一轮里对方补了一句,被合并进来Agent 规划中接进本轮;否则这一版作废,重新生成
被新消息取代群里同一人又直接叫了机器人;消息已被合并进别的轮次;主动接话批次已变superseded_follow_up / superseded_reply_turn / superseded_proactive
幂等这一步已经发过跳过,复用原消息 ID
禁言 / 离线 / 退避被禁言;连接离线;群发送退避中禁言丢弃;离线交给队列重试;退避等待,窗口用尽后丢弃

打断和取代只在还没开始分条发送、也没产生外部副作用时生效;已经发出第一条的回复会发完。身份脱敏不是发送时检查:真实 ID 在发给模型时就换成了别名。

排查

结果代码在哪一段

每条消息无论回没回,最后都会写一条结果:decision 是结果代码,decision_reason 是给人看的原因。排查时先看它停在哪一段,再回到对应章节。

阶段结果代码
入站队列ignored_stale backfill_history_only ignored_private_admission dropped_retries_exhausted
确定性门禁ignored_policy ignored_user_blocked ignored_bot_muted ignored_model_quota ignored_member_level ignored_response_suppression ignored_video ignored_bot_message ignored_reply_fatigue
接话评分ignored replied_proactive
并发与合并merged_into_reply merged_into_backlog_turn superseded_follow_up superseded_reply_turn superseded_proactive
发送前审核ignored_model_silent ignored_no_natural_reply ignored_duplicate_reply ignored_proactive_reply_quality ignored_stop_requested ignored_conversation_closed ignored_ai_reply_loop
发送ignored_unavailable_group dropped_send_rejected

代码位置

环节文件
入站与补拉model/assistant/runtime_inbound.go inbound_queue.go inbound_gap.go inbound_retry.go inbound_backlog_merge.go
门禁与接话model/assistant/runtime.go(routeMessageEvent、routeProactiveReplyBatch)participation_single_score.go direct_reply_merge.go reply_interrupt.go group_stop.go
回复与发送model/assistant/runtime.go(replyTo、sendDecorated)reply_compression.go runtime_outbound.go group_send_guard.go outbound_idempotency.go
Agentmodel/agent/runner.go finalize.go deferred_tools.go claim_evidence.go;model/assistant/agent_scope.go
浏览器model/browsersource/ model/browserbox/ model/browserctl/ model/agent/headless_browser*.go browser_tools.go html_capture.go
发送前审核model/assistant/proactive_reply_quality.go reply_semantic_gate.go private_closing.go reply_suppression.go outbound_forward_gate.go
结果记录model/storage/inbound_queue.go event_audit.go