Skip to content

Releases

v0.3.25 — 补审批漏接防护(0.3.24 收尾)

Choose a tag to compare

@CCYellowStar2 CCYellowStar2 released this 08 Nov 10:33
e8a3d23

v0.3.25 — 补上「审批漏接」防护(0.3.24 的收尾)

背景

0.3.24 按市场审核意见把 _umo_is_bound() 从「同步阻塞 HTTP」改成「纯缓存读 + 异步刷新」,
审阅点已修。但 TTL 缓存带来一个自己引入的漏接风险:

/dsh <任务> 刚在网关侧建立绑定、本地缓存还是 False 时,紧跟的审批回复
(纯数字 / 批准 / 取消)会被判成「没绑定」而不捕获 → 落到人设手里。
而这条链路很常见:任务要跑工具 → 弹审批 → 用户回 1。

改法(三处强制刷新,全部异步)

时机 原因
审批类消息捕获之前 接不接取决于此刻绑定状态,等 TTL 会判错
一轮任务/命令结束后 /dsh <任务>、
ew、use、end 都走 _forward,它们会改绑定
/dsh follow 结束后 follow 在未绑定时可能自动绑定

验证(容器内探针,import 实机那份 main.py)

断言 实测
过期缓存(False) + 审批文本 → 捕获 False(复现漏接)
force 刷新后 → 绑定 True → 再捕获 True ✅
_should_capture 耗时(空/命中) 0.117 ms / 0.014 ms(仍无网络 I/O)
_refresh_binding 真请求 → 再命中 212.8 ms → 0.0133 ms

安装

  • AstrBot 侧:装 0.3.25 后重载插件(市场审核请以本版重新提交)。
  • DSH 侧(ingress):本版未改 lib/,无需重载。

v0.3.24 — 修并发模型问题(事件循环内不再有阻塞 HTTP)

Choose a tag to compare

@CCYellowStar2 CCYellowStar2 released this 08 Nov 10:29
f4a50d4

v0.3.24 — 修 AstrBot 市场审核打回项:事件循环里的阻塞 HTTP

审核意见(原文摘要)

main.py:597 的 _umo_is_bound() 在同步函数中直接调用 httpx.Client(timeout=3.0)发起阻塞式
网络请求,而它被同步的 _should_capture() 调用,后者运行在异步事件处理函数 on_message() 内。

属实,而且是 3 秒超时 × 4 个调用分支。

改法(I/O 移出事件循环,捕获语义不变)

函数 改动
_umo_is_bound() 改成纯内存缓存读,函数体内零网络调用
_refresh_binding()(新增,�sync) httpx.AsyncClient 请求 GET /binding;命中缓存直接返回;成功缓存 30s / 失败缓存 5s
_dsh_side_cwd() 同样改为纯缓存读(cwd 由刷新时顺带写入)
on_message() 开头 �wait self._refresh_binding(...) 刷一次,随后同步的 _should_capture() 只读内存

验证(容器内探针,import 实机那份 main.py)

断言 实测
_refresh_binding 是协程 ✅
_umo_is_bound(空缓存/命中) 0.0067 ms / 0.0012 ms
_should_capture(空缓存/命中) 0.108 ms / 0.0133 ms(全程无 I/O)
真请求(异步)后再命中 121.7 ms → 0.0172 ms
缓存语义 空=False、命中=True、"1" 正确走批准分支

唯一行为差异

绑定状态探测最多 30 秒延迟(探测失败 5 秒内重试)。所有改绑定的命令本身走 HTTP,不受影响。

安装

  • AstrBot 侧:装 0.3.24 后重载插件。
  • DSH 侧(ingress):本版未改 lib/,无需重载;npm 版本号同步到 0.3.24。

v0.3.23 — 主动消息拉黑不再跨群连累

Choose a tag to compare

@CCYellowStar2 CCYellowStar2 released this 27 Sep 18:11
65020fb

v0.3.23 — 主动消息「拉黑」不再跨群连累

现象

/dsh follow 看起来不优先尝试主动发送 —— 一直在被动回复,5 条一到就发不出去了。

真因(日志铁证)

{'message': '主动消息失败, 无权限', 'code': 40034105}
→ 10 次,全部来自群 23552507E8E4D0A0428643533EF44CB3
→ 用户的群 C121A06D… 在整个日志窗口里出现 0 次

_proactive_denied 原是个全局布尔:任何一个会话撞一次 40034105,就把所有会话一起
拉黑成被动。而主动消息权限是按群授予的(要群主在开放平台开)—— 那个没权限的群把用户的群
也钉死在被动,passive-first 的「首条之后转主动」永远走不到。

修复

改成按会话记(set[str],键用 _convo_key):只有真正被拒的会话才退避;
其它群若已开权限仍会尝试主动。

验证

容器内探针(import 实机那份 main.py,两个真实群号):

群A 撞过 40034105 之后:群A 敢走主动=False(对)  群B 敢走主动=True(对,不再连累)

回滚成全局布尔后立刻复现「群B 也被拉黑」。

安装

  • AstrBot 侧:装 0.3.23 后重载插件 —— 本次改动全在插件侧,DSH 侧不用重启。
  • 之后每个群第一次走主动时各撞一次 40034105 是正常的(权限逐群授予),
    撞过就只对该群退避。

v0.3.22 — 修 runningSessions 用了不存在的会话字段

Choose a tag to compare

@CCYellowStar2 CCYellowStar2 released this 26 Sep 16:20
179912f

v0.3.22 — 修「解绑后网页在跑,follow 却说没有在跑的会话」

原因(我 0.3.21 写的 bug)

0.3.21 的
unningSessions()\ 里我用了不存在的字段:

\\js
for (const s of ctx.sessions.list())
if (s.agent?.status === 'running') … // ← Session 根本没有 agent 字段
\\

\Session\ 只有 \id / cwd / header\ 那些,没有 \�gent* —— 这个循环一个都匹配不上,
函数实际只靠 \state.turns\ 兜底,而
解绑后它是空的*,于是「明明在跑却说没有在跑的会话」。

正确的权威来源是 **\ctx.agents.list()**(活 agent 注册表),会话 id 取 \�gent.session.id\。

回滚验证:改回旧写法,harness 立刻复现该现象(\400 当前没有绑定会话,也没有正在跑的会话\)。

测试基建也修了

\ ollow_adopt_harness.mjs\ 的桩把 \Session\ 桩成了带 \�gent\ 字段、并给 \ctx.agents
漏了 \list()\ —— 这正是那个错法能混过测试的原因。现在桩按真实形状来:
Session 只有 \id/\header\,判断在跑必须走 \�gents.list()\。

安装

  • DSH 侧:\dsh plugin --profile web add dsh-astrbot-ingress@0.3.22\ 然后重启。
  • AstrBot 侧:装 0.3.22 后重载插件。

v0.3.21 — 未绑定也能 follow(唯一的在跑会话自动接上)

Choose a tag to compare

@CCYellowStar2 CCYellowStar2 released this 26 Sep 15:35
acd2abc

v0.3.21 — 未绑定时 /dsh follow 会自动接上正在跑的会话

新增

以前群里没绑定会话时 /dsh follow\ 直接报「当前没有绑定会话」。现在分三种情况:

情况 行为
未绑定 + 恰好一个会话在跑 用它,并顺手把这个 QQ 会话绑上(等同 /dsh use\ 过了)
未绑定 + 多个在跑 不猜,列出候选(标题 + 短 id),先 /dsh use <序号>\
未绑定 + 没有在跑的 说清「先 /dsh <任务> 起一个」

为什么多个时坚决不猜:可能同时跑着好几个会话,而「你要的是哪个」桥这边判断不了
(网页当前打开的那个只是浏览器内存状态,读不到)。猜错会把别人的输出接到这个群,比报错更糟。

顺带

  • 插件侧不再写死 400/409 的文案,改为照读 ingress 给的 \error\。
  • 修掉一个自己写出来的判据错误:把「有回合记录」当成「在跑」会让候选数虚高
    (表现为自动绑定成功一次后,下一次被误判成「有 3 个在跑」)。现收紧为
    有活着的 sink,或 agent 状态确实是 running。

验证

\ ollow_adopt_harness.mjs\ 三分支断言全过;连同此前四个 harness 共 5 个全绿。

安装

  • DSH 侧:\dsh plugin --profile web add dsh-astrbot-ingress@0.3.21\ 然后重启(ingress 有改动)。
  • AstrBot 侧:装 0.3.21 后重载插件。

v0.3.20 — 紧急修复 dsh web 崩溃

Choose a tag to compare

@CCYellowStar2 CCYellowStar2 released this 26 Sep 15:06
9d00ba5

v0.3.20 — 紧急修复:0.3.19 引入的 dsh web 崩溃

崩溃

\
dsh: fatal uncaught exception: TypeError: Cannot read properties of null (reading 'write')
at sseWrite (.../dsh-astrbot-ingress/lib/index.js:255:7)
at Timeout._onTimeout (.../lib/index.js:509:9)
\\

0.3.19 让「流被掐断的回合」降级成无源流记录(\ urn.res = null\),但只要有跟随者,
\ urnHasSink()\ 就为 true —— 于是 digest 定时器走到 \sseWrite(live.res, …)\ 去读

ull.write\,异常从 \setInterval\ 逃出去、直接杀掉宿主进程。这是我的 bug。

修复

  1. 那个写出点改用 \ urnWrite(live, …)\(写向所有 sink,null 安全);minimal 档的保活同理。
  2. \sseWrite()\ 加兜底守卫:
    es\ 为空 / 不可写 / 已结束 时直接返回。
    宿主插件里「少发一条状态」和「进程崩掉」不对等,这类地方一律 fail-soft。

验证

\ ollow_crash_harness.mjs\:复刻崩溃前状态并把 \progress_interval_sec\ 压到 10 秒,
等 digest 定时器真的触发 —— 带修复发给跟随者且进程存活;回滚修复则逐字复现崩溃。

安装

  • DSH 侧:更新 profile 并重启(本版 ingress 有改动):
    \dsh plugin --profile web add dsh-astrbot-ingress@0.3.20\
  • AstrBot 侧:装 0.3.20 后重载插件。

v0.3.19 — 修「5 条额度用完后就 follow 不到」

Choose a tag to compare

@CCYellowStar2 CCYellowStar2 released this 26 Sep 14:06
fd17afd

v0.3.19 — 修「QQ 回合 5 条额度用完后就 follow 不到」

现象

QQ 里发起的回合,被动回复额度用尽(每条消息 5 次)之后,群里再 /dsh follow\ 会说
「这个会话当前没有在跑的回合」—— 可那个回合在 DSH 那边还在正常跑。

真因(有日志证据)

\
botpy.errors.ServerError: 回复消息失败,被动回复时间或者次数超过限制
在调用插件 astrbot_plugin_dsh 的处理函数 on_message 时出现异常:…
\\

额度用尽 → 插件 \event.send()\ 抛异常 → 从 \on_message\ 冒出去 →
SSE 读取循环中断、那条流关闭 → ingress 的
es.on('close')\ 把整条回合记录删掉
→ 群里再 follow 就找不到回合了。

两处修

  • 插件侧:发送失败不再抛(_try_deliver\),额度用尽时安静收场并记日志
    (额度用完就是发不出去,再发还是撞墙)。回合照旧跑完,发一条新消息 + /dsh follow\ 接着看。
  • ingress 侧:
    es.on('close')\ 不再删记录,而是降级成无源流记录,follow 随时能接上。

验证

\ ollow_detach_strict_harness.mjs\:掐断 QQ 那条流、并故意让 agent 状态保持 idle
(不给「follow 现场建记录」兜底)→ 带修复 200 并收到断开后的正文;
改回旧行为则 409「没有在跑的回合」,与服务端日志现象一致。

安装

  • DSH 侧:需要重载 \dsh-astrbot-ingress\(或重启 \dsh web\)—— 本版两侧都有改动。
  • AstrBot 侧:装 0.3.19 后重载插件。

v0.3.18 — /dsh follow 也能接住网页发起的回合

Choose a tag to compare

@CCYellowStar2 CCYellowStar2 released this 26 Sep 13:28
f7cbef2

v0.3.18 — /dsh follow 现在也能接住「网页发起的回合」

修的是什么

0.3.17 的 /dsh follow 只对从 QQ 发起的回合有效。网页上跑起来的回合去 follow 会得到
「这个会话当前没有在跑的回合」—— 因为 state.turns 只由 /inbound\ 创建,
网页发起的回合在桥里根本不存在,而它恰恰是这个功能最主要的目标场景。

现在新增无源头流的回合记录:来自已绑定会话、但桥里没有记录的事件,会为它建一条记录
(没有「发起者那条流」),于是跟随者可以随时挂上来,事件照常认领、回合结束照常收尾;
没人跟随时它什么都不写,只是一条内存记录。

顺带修掉一个 0.3.18 自己引入的回归

无源头流的记录会让审批/提问有机会命中网页发起的回合 —— 那会把审批卡/提问框从网页抢走、
然后在群里没人应答。现在只接管「QQ 那边真的能答」的回合:有源流,或此刻有人跟随。

用法没变

\
/dsh follow # 或 /dsh 跟随 / /dsh 续上
\\

网页那边跑起来 → 群里发 /dsh follow\ → 输出接到这条新消息上,回合结束自动断开。
被动回复 5 条用完时,再发一条新消息 + /dsh follow\ 就换到新额度。

安装

  • DSH 侧:需要重载 \dsh-astrbot-ingress\(或重启 \dsh web\)——本版 ingress 有改动。
  • AstrBot 侧:装 0.3.18 后重载插件。

v0.3.17 — /dsh follow:把正在跑的回合接到群里

Choose a tag to compare

@CCYellowStar2 CCYellowStar2 released this 26 Sep 12:47
7a0c45a

v0.3.17 — /dsh follow:把正在跑的回合接到群里

解决什么

以前只有在群里发起的会话才会持续输出到群里 —— 那条 QQ 消息自己开着一条 SSE。
网页(或其它地方)发起的回合没有这条流,群里永远看不到结果。

现在:群里发一次 /dsh follow\,就能把正在跑的那个回合的正文、工具行、结束标记
实时接到这条新消息上,回合结束自动断开(临时跟随,不是长期订阅)。

顺带解决官方 QQ 被动回复 5 条上限

官方规则:被动回复的额度是按「被回复的那条消息」算的(每条消息 5 分钟 / 5 次)。
所以每发一条新消息就换来一份新的 5 条额度 —— 5 条用完时,在群里再发一条消息 +
/dsh follow\,就接着输出。

日志里 \40034128\(被动回复时间或次数超限)现在会被单独识别并提示换新额度,
不再与 \40034105\(没有主动消息权限)混为一谈。

用法

\
/dsh follow # 或 /dsh 跟随 / /dsh 续上
\\

  • 当前没有在跑的回合 → 明确告诉你「跟随只在回合进行中有意义」
  • 会话还没绑定 → 提示先 /dsh <任务>\ 或 /dsh use\

安装

  • DSH 侧(ingress):需要重载 \dsh-astrbot-ingress\,或重启 \dsh web\。
  • AstrBot 侧:装 0.3.17 后重载插件。

v0.3.16 — @机器人+直接发图不再被接管

Choose a tag to compare

@CCYellowStar2 CCYellowStar2 released this 26 Sep 05:57
c16fc84

v0.3.16 — 「@机器人 + 自己直接发一张图」不再被当成续聊接管

现象

群里只要 @机器人 + 带一张图(自己发的图,不是引用),消息就会被交给 DSH —— 哪怕
�ound_media_passthrough 和 official_at_continue 都关着。

根因

官方通道有一条特例:「引用一张图/文件 + @机器人」也要接住(AstrBot 不建 Reply,
被引附件只能从原始 payload 的 msg_elements[].attachments[] 里捡)。但那条判断用的是
_has_inbound_media() —— 它把本条消息自带的附件和被引消息里的附件混在一起,
于是「自己发的那张图」也满足了条件。而且那条分支完全不看 �ound_media_passthrough,
所以关掉开关也没用。

修法

新增 _has_quoted_media(),只看被引消息(aiocqhttp 看 Reply 段里的组件,官方通道看
msg_elements[].attachments[]),那条特例改用它:

场景 之前 现在
@机器人 + 本条直接带一张图 接住 ❌ 不接住 ✅(�ound_media_passthrough 打开时才会接住)
@机器人 + 引用一条带图的消息 接住 接住(特例保留)
@机器人 + 只有文字 不接住 不接住

容器内探针验证(D:\dswk\.dsh-inbox\capture_media_probe.py),上表四行(含开关打开那行)全部符合预期。

安装

全在 AstrBot 插件侧,装完重载「DSH 桥」;DSH 侧不用动。