@zaofan/dsh-qqbot 1.8.8 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +113 -26
  2. package/README_EN.md +122 -8
  3. package/dist/features/ext-capabilities.d.ts +85 -0
  4. package/dist/features/ext-capabilities.d.ts.map +1 -1
  5. package/dist/features/ext-capabilities.js +4 -0
  6. package/dist/features/ext-capabilities.js.map +1 -1
  7. package/dist/features/ext-hooks.d.ts +84 -0
  8. package/dist/features/ext-hooks.d.ts.map +1 -0
  9. package/dist/features/ext-hooks.js +299 -0
  10. package/dist/features/ext-hooks.js.map +1 -0
  11. package/dist/features/ext-inbound-context.d.ts +202 -0
  12. package/dist/features/ext-inbound-context.d.ts.map +1 -0
  13. package/dist/features/ext-inbound-context.js +724 -0
  14. package/dist/features/ext-inbound-context.js.map +1 -0
  15. package/dist/features/ext-inbound-state.d.ts +153 -0
  16. package/dist/features/ext-inbound-state.d.ts.map +1 -0
  17. package/dist/features/ext-inbound-state.js +419 -0
  18. package/dist/features/ext-inbound-state.js.map +1 -0
  19. package/dist/features/ext-inbound.d.ts +117 -0
  20. package/dist/features/ext-inbound.d.ts.map +1 -0
  21. package/dist/features/ext-inbound.js +518 -0
  22. package/dist/features/ext-inbound.js.map +1 -0
  23. package/dist/features/ext-reload.d.ts +22 -0
  24. package/dist/features/ext-reload.d.ts.map +1 -1
  25. package/dist/features/ext-reload.js +32 -0
  26. package/dist/features/ext-reload.js.map +1 -1
  27. package/dist/features/extension-store.d.ts +13 -0
  28. package/dist/features/extension-store.d.ts.map +1 -1
  29. package/dist/features/extension-store.js +32 -4
  30. package/dist/features/extension-store.js.map +1 -1
  31. package/dist/gateway/bootstrap.d.ts.map +1 -1
  32. package/dist/gateway/bootstrap.js +44 -0
  33. package/dist/gateway/bootstrap.js.map +1 -1
  34. package/dist/gateway/debounce.d.ts +43 -1
  35. package/dist/gateway/debounce.d.ts.map +1 -1
  36. package/dist/gateway/debounce.js +71 -35
  37. package/dist/gateway/debounce.js.map +1 -1
  38. package/dist/gateway/middleware-setup.d.ts.map +1 -1
  39. package/dist/gateway/middleware-setup.js +88 -1
  40. package/dist/gateway/middleware-setup.js.map +1 -1
  41. package/dist/session/session-manager.d.ts.map +1 -1
  42. package/dist/session/session-manager.js +7 -3
  43. package/dist/session/session-manager.js.map +1 -1
  44. package/dist/shared/utils.d.ts +6 -0
  45. package/dist/shared/utils.d.ts.map +1 -1
  46. package/dist/shared/utils.js +6 -2
  47. package/dist/shared/utils.js.map +1 -1
  48. package/dist/transport/inbound.d.ts.map +1 -1
  49. package/dist/transport/inbound.js +23 -3
  50. package/dist/transport/inbound.js.map +1 -1
  51. package/docs/USER-GUIDE.md +92 -35
  52. package/docs/botplay-contract.md +2 -0
  53. package/examples/ext-hooks/README.md +95 -0
  54. package/examples/ext-hooks/hello-hook.mjs +54 -0
  55. package/examples/ext-hooks/session-watch-hook.mjs +119 -0
  56. package/examples/ext-inbound/README.md +230 -0
  57. package/examples/ext-inbound/class-advisor.mjs +306 -0
  58. package/examples/ext-inbound/no-wake-on-child-quote.mjs +192 -0
  59. package/package.json +1 -1
@@ -0,0 +1,518 @@
1
+ /**
2
+ * ext-inbound.ts — 入站中间件扩展点(2026-10-12,第五套用户扩展)
3
+ *
4
+ * ── 它是什么 ───────────────────────────────────────────────────────────────
5
+ * 前四套扩展里,没有一套能**影响入站消息本身**:
6
+ * · `commands/`(用户敲命令)、`tools/`(LLM 调工具)、`botplay/`(有人点卡片)都要"有人触发";
7
+ * · `hooks/` 只在**启动时跑一次**,那时消息还没来。
8
+ * 可有些规则必须**每条入站消息都过一遍**,而且要能左右这条消息的命运:
9
+ * "这个人在禁言名单里 → 整条别进来"、"这条先过一层敏感词改写"、"求助关键词来了必须叫醒她"、
10
+ * "这条虽然 @ 了我,但它是引用子会话转发 → 别当 @ 处理"。
11
+ * 于是有了这一套 —— 放在 `{dataRoot}/.qqbot-extensions/inbound/*.mjs`。
12
+ *
13
+ * ── 两个挂点(2026-10-12 扩成两个;位置是硬要求,别挪)─────────────────────
14
+ *
15
+ * ```
16
+ * … → quoteRef → ★onInbound(早)★ → 引用图预取 → 群历史缓冲 → 表情/台账
17
+ * → mentionGate(@ 门控)→ ★默认导出 / onDecide(晚,原有)★
18
+ * → contentSanitizer → debounce(延迟聚合)→ 命令 → 冷却 → 并发合并 → envelope → AI
19
+ * ```
20
+ *
21
+ * | | **早:`onInbound`** | **晚:`onDecide` / `export default`** |
22
+ * |---|---|---|
23
+ * | 链上位置 | quoteRef 之后、**记群历史之前** | mentionGate 之后、contentSanitizer 之前 |
24
+ * | 读得到 | `ctx.message` / `ctx.state.quote` / `ctx.platform.mentioned` | 上面这些 **+ `ctx.state.mention`**(门控判定) |
25
+ * | 做得了 | **消灭**(不进历史/不进上下文/不占冷却)、**改写**(历史里就是改后文本)、**强制入站**、查/写禁言名单 | **覆盖 @ 判定**(`ctx.decide`)、改写、强制入站、查/写禁言名单 |
26
+ * | 做不了 | `ctx.decide`(`ctx.state.mention` 还没写出来);除"不调 next()"外没有别的拦截手段 | **消灭**(群历史已经记了;`ctx.annihilate` 会明确拒绝并告警) |
27
+ *
28
+ * 一个文件可以**同时导出两个挂点**(互不影响);只导出默认导出 = 旧契约,语义等同 `onDecide`。
29
+ *
30
+ * ```js
31
+ * // {dataRoot}/.qqbot-extensions/inbound/my-rule.mjs
32
+ * export async function onInbound(ctx, next) { // 早:消灭/改写/强制入站/查禁言
33
+ * if (isSpam(ctx.text)) { ctx.annihilate({ reason: '广告' }); return; } // 不调 next = 整条消失
34
+ * ctx.rewrite({ text: ctx.text.replace(/敏感词/g, '***') }); // 记历史之前生效
35
+ * return next();
36
+ * }
37
+ * export default async function onDecide(ctx, next) { // 晚:@ 覆盖(原有契约,语义一个字没变)
38
+ * if (ctx.state?.mention?.wasMentioned === true && isChildSessionQuote(ctx.state.quote?.text)) {
39
+ * ctx.decide({ mention: 'ignore', reason: '引用子会话转发' });
40
+ * }
41
+ * return next();
42
+ * }
43
+ * export function dispose() { … } // 可选:插件卸载时清理(本模块起了定时器/监听才需要)
44
+ * ```
45
+ * 也接受 `export const onInbound = …` / `export const middleware = …` / `export async function middleware(ctx, next) {}` /
46
+ * `export default { name, onInbound, onDecide, middleware, dispose }`;`name` 只用于日志展示。
47
+ *
48
+ * ── 与 `hooks/` 的区别(最容易混的一处)──────────────────────────────────
49
+ * · `hooks/` = **启动时跑一次**(没人触发也跑),给它的是 `env`,管"起定时器/注册监听/恢复任务";
50
+ * · `inbound/` = **每条入站消息都过一遍**,给它的是 `(ctx, next)`,管"这条消息怎么处理"。
51
+ * 两者不通用:入站扩展写成 `run(env)` 不会被装载,钩子写成 `(ctx, next)` 也不会被装载。
52
+ *
53
+ * ── 动作 API(用户看到的就是这些;实现在 features/ext-inbound-context.ts)──
54
+ * ```js
55
+ * // 消息维(早挂点):消灭 / 改写 / 强制入站
56
+ * ctx.annihilate({ reason }) // 消灭:不进历史、不进上下文、不占冷却(内部=不 next)
57
+ * ctx.rewrite({ text, reason })// 替换文本(早挂点在记历史之前 ⇒ 群历史里也是改后文本)
58
+ * ctx.forceWake({ reason }) // 强制入站:跳过价值评分/@门控/冷却等一切"值不值得回"的判断
59
+ * // 消息维(晚挂点):ctx.decide({ mention: 'ignore' | 'force', reason }) —— 覆盖 @ 判定
60
+ * // 用户维(两个挂点都能用):ctx.muteUser({ id?, minutes, reason }) / ctx.unmuteUser({ id? })
61
+ * // ctx.isMuted(id?) / ctx.mutedUsers()(带剩余时间;minutes<=0 = 永久)
62
+ * // 通用:ctx.text / ctx.scope / ctx.sender / ctx.senderName / ctx.isOwner / ctx.groupId / ctx.mentions
63
+ * // ctx.quoted({text,fromSelf}) / ctx.platform({mentioned}) / ctx.logger / ctx.dataRoot
64
+ * // ctx.kv.get(key) / ctx.kv.set(key, value) ← 每个扩展自己的持久化
65
+ * ```
66
+ * 每个动作**内部 try/catch,任何情况下都不抛到链上**;出错只 warn 并返回 false。
67
+ *
68
+ * ── 铁律(都是"绝不能因为用户脚本坏了就吞消息"派生出来的)──────────────────
69
+ * ① **单扩展隔离**:每个扩展独立 try/catch,抛错只 `warn` 并**继续往下走**(该消息照常派发给 AI);
70
+ * ② **next() 一定被 await**:扩展忘了 await,引擎替它 await —— 否则下游没跑完,中间件链的顺序保证就断了;
71
+ * ③ **next() 幂等**:扩展误调两次只算第一次(SDK 的 `next() called multiple times` 会直接炸整条链);
72
+ * ④ **消灭 = 不调 next()**:早挂点里置位 `annihilate` 后,**即便又调了 next() 也不再往下走**
73
+ * (链停在这里 ⇒ 连群历史都不会记);
74
+ * ⑤ **目录缺失/为空完全安静**:不报错、不打日志(这是最常见的正常状态);
75
+ * ⑥ 只扫**根下直接子文件**、扩展名 `.js/.mjs/.cjs`、按文件名排序、`?v=<mtime>` 做 cache-bust
76
+ * —— 全部与 `hooks/`、`tools/` 的既有约定一致(`.state/` 这类子目录不会被当扩展装载)。
77
+ *
78
+ * ── 四条保命件(实现在 ext-inbound-context.ts + ext-inbound-state.ts)──────
79
+ * · **白名单最高优先**:主人(`config.groupAdmin.owners`)永不可被消灭/静默 ——
80
+ * 拒执行 + warn + 消息照常放行(`annihilate`/`muteUser` 都拦)。
81
+ * ⚠️ 光"拒绝动作"不够:扩展最常见的写法是 `if (该消灭) { ctx.annihilate(); return; }`,
82
+ * 那个 `return`(=不调 next)仍然会把链停住 ⇒ **引擎会替它放行**(见下"兜底放行")。
83
+ * · **强唤醒优先于静默**:`forceWake` 不被 `annihilate` 以外的东西抵消(顺序由扩展自己排,
84
+ * 文档里写明"求助关键词判断要写在黑名单判断之前");
85
+ * · **熔断**:按扩展统计消灭/静默密度(比例 >50% 或 10s 内 >20 次),异常就**停用该扩展的破坏性动作**
86
+ * 并 logger.warn(带扩展名与原因)—— 消息照常放行(同样靠"兜底放行"落地);
87
+ * · **审计 + 可撤销**:每个动作一行 `{dataRoot}/.qqbot-extensions/inbound/.state/audit.jsonl`
88
+ * (时间/扩展名/动作/目标/原因/**截断后的**文本摘要,512KB 轮转),`muteUser` 能 `unmuteUser`。
89
+ *
90
+ * ── 兜底放行(让上面两条承诺真的成立)──────────────────────────────────────
91
+ * 扩展**既没调 next()、也没有一次成功的消灭**时,引擎分两种情形处理:
92
+ * ① 它请求过消灭但**被拒**(发送者是主人 / 已熔断 / 挂在晚挂点)⇒ **替它放行** + warn;
93
+ * ② 发送者是**主人**且这次没有成功的消灭 ⇒ **替它放行** + warn(白名单最高优先)。
94
+ * 其余情况(普通群友、扩展自己有意 `return`)保持老契约:**链停在这里**(这条消息不进后续任何环节)。
95
+ *
96
+ * ── 装载时机 / 热重载 ─────────────────────────────────────────────────────
97
+ * 启动(网关 bootstrap → setupMiddlewares)时扫描装载,**至少重启生效**;
98
+ * 另外 `SessionManager.reloadAllChannelTools()`(= `/tools-reload`、`tools_reload` 工具)
99
+ * 会经 `features/ext-reload.ts` 顺带重扫一次 → **不重启也能生效**(挂载点是个读可变列表的代理,
100
+ * 换代只是换内容,不用重挂中间件)。每次装载都**复位该扩展的熔断记录**(熔断的恢复通道)。
101
+ */
102
+ import { readdirSync, statSync } from 'node:fs';
103
+ import { extname, join } from 'node:path';
104
+ import { pathToFileURL } from 'node:url';
105
+ import { EXT_INBOUND_SUBDIR } from './extension-store.js';
106
+ import { registerExtInboundReloader } from './ext-reload.js';
107
+ import { enterExtension, ensureExtSession, sessionOf, } from './ext-inbound-context.js';
108
+ import { noteParticipation, resetBreaker, upgradeLastParticipation } from './ext-inbound-state.js';
109
+ /** 扩展文件允许的扩展名(与 extension-store 的 ALLOWED_EXT 同款) */
110
+ const ALLOWED_EXT = new Set(['.js', '.mjs', '.cjs']);
111
+ /** 入站扩展目录(相对数据根) */
112
+ export function inboundExtensionsDir(dataRoot) {
113
+ return join(dataRoot, EXT_INBOUND_SUBDIR);
114
+ }
115
+ /**
116
+ * 当前装载的那一批(热重载时整体替换)。
117
+ * ⚠️ 挂载点(middleware-setup)持有的是**自己的**可变数组,换代时内容被换掉;
118
+ * 这里这份只是给诊断/dispose 用。
119
+ */
120
+ let loaded = [];
121
+ /**
122
+ * 卸载时要清理的扩展,**按中间件函数身份去重**(Map key = middleware)。
123
+ * 为什么要去重、且要留住旧版:热重载后同一个文件会产生**新的模块对象**(`?v=<mtime>`),
124
+ * 旧版模块若起过定时器/监听,也得有人来收 —— 但同一个模块被重复装载(多实例/重复重载)
125
+ * 不该 dispose 两次,所以用模块内稳定的中间件函数引用当身份。
126
+ */
127
+ const known = new Map();
128
+ /** 装载结果为空时的返回值(每次新建,避免调用方互相污染) */
129
+ function emptyResult(files = []) {
130
+ return { total: 0, skipped: 0, middleware: [], early: [], extensions: [], files };
131
+ }
132
+ /** 挑第一个是函数的候选当 dispose(模块具名导出优先于 default 上的属性) */
133
+ function pickDispose(...cands) {
134
+ for (const c of cands) {
135
+ if (typeof c === 'function')
136
+ return c;
137
+ }
138
+ return undefined;
139
+ }
140
+ /** 挑第一个是函数的候选当挂点(用于"具名导出优先,其次对象属性"这类归一化) */
141
+ function pickFn(...cands) {
142
+ for (const c of cands) {
143
+ if (typeof c === 'function')
144
+ return c;
145
+ }
146
+ return undefined;
147
+ }
148
+ /** 只导出早挂点时的占位晚挂点(直接放行;也保证 `middleware` 字段永远是函数) */
149
+ const passThrough = async (_ctx, next) => { await next(); };
150
+ /**
151
+ * 把一个动态 import 的结果归一化成入站扩展。
152
+ *
153
+ * 早挂点(可选):具名 `onInbound` / 对象默认导出的 `onInbound`
154
+ * 晚挂点(二者必有其一):
155
+ * 具名 `onDecide` > **裸函数默认导出**(旧契约) > 对象默认导出的 `onDecide` > 对象默认导出的 `middleware` > 具名 `middleware`
156
+ * ⚠️ `export default async (ctx,next)=>{}` **语义等同 `onDecide`**(既有的示例与用户契约一个字没变)。
157
+ *
158
+ * 返回 null = 形状不合法(两个挂点一个都没有),由调用方按"跳过"记账(只告警)。
159
+ */
160
+ function normalizeInbound(mod, file, abs, logger) {
161
+ try {
162
+ const raw = mod;
163
+ const dflt = raw?.default;
164
+ const dfltObj = typeof dflt === 'object' && dflt !== null ? dflt : undefined;
165
+ // ⚠️ 名字只从**对象** default 上取:`export default function () {}` 的函数名会被规范设成 "default",
166
+ // 直接拿 dflt.name 会得到字面量 "default"(ext-hooks 也是这么绕开的)。
167
+ const name = String(dfltObj?.name ?? raw?.name ?? '').trim() || file.replace(/\.(mjs?|cjs)$/i, '');
168
+ // 早挂点:具名导出优先,其次对象默认导出上的属性
169
+ const onInbound = pickFn(raw?.onInbound, dfltObj?.onInbound);
170
+ // 晚挂点
171
+ const middleware = pickFn(raw?.onDecide, typeof dflt === 'function' ? dflt : undefined, dfltObj?.onDecide, dfltObj?.middleware, raw?.middleware);
172
+ if (!middleware && !onInbound) {
173
+ logger.warn(`[ext-inbound] ${file}: 没找到中间件 —— 需 \`export default async (ctx, next) => {…}\`、` +
174
+ '`export async function onDecide(…)` 或 `export async function onInbound(…)`,已跳过');
175
+ return null;
176
+ }
177
+ // 只导出早挂点时补一个直通的晚挂点(引擎两处都跑,行为等价"没有晚挂点")
178
+ const dispose = pickDispose(dfltObj?.dispose, raw?.dispose);
179
+ return {
180
+ file, path: abs, name,
181
+ middleware: middleware ?? passThrough,
182
+ ...(onInbound ? { onInbound } : {}),
183
+ ...(dispose ? { dispose } : {}),
184
+ };
185
+ }
186
+ catch (err) {
187
+ logger.warn(`[ext-inbound] ${file}: 归一化失败 ${err instanceof Error ? err.message : String(err)}`);
188
+ return null;
189
+ }
190
+ }
191
+ /** 文件版本号(mtime,秒级取整)—— 与 extension-store / ext-hooks 同款纪律,别改回时间戳 */
192
+ function fileVersion(p) {
193
+ try {
194
+ return String(Math.floor(statSync(p).mtimeMs));
195
+ }
196
+ catch {
197
+ return String(Date.now());
198
+ }
199
+ }
200
+ /**
201
+ * 扫描 `{dataRoot}/.qqbot-extensions/inbound/*.mjs` 并装载。
202
+ *
203
+ * 目录不存在 / 为空 → 返回空结果,**不打任何日志**。
204
+ * 单个文件 import 失败或形状不合法 → 只记 `warn`,继续下一个;本函数**永不抛出**。
205
+ * 重新调用 = 热重载:`loaded` 被整体替换成新一批(旧版模块留给 dispose 收尾),
206
+ * 并**复位每个文件的熔断记录**(这是文档写明的熔断恢复通道)。
207
+ */
208
+ export async function loadInboundExtensions(opts) {
209
+ const { dataRoot, logger } = opts;
210
+ const dir = inboundExtensionsDir(dataRoot);
211
+ let files;
212
+ try {
213
+ // 只取**直接子文件**:子目录不扫描(与 tools/hooks 的既有约定一致;lib/、.state/ 之类可安全存放私有内容)
214
+ files = readdirSync(dir, { withFileTypes: true })
215
+ .filter((e) => e.isFile() && ALLOWED_EXT.has(extname(e.name).toLowerCase()))
216
+ .map((e) => e.name)
217
+ .sort();
218
+ }
219
+ catch {
220
+ loaded = [];
221
+ return emptyResult(); // 目录不存在 → 完全安静
222
+ }
223
+ if (files.length === 0) {
224
+ loaded = [];
225
+ return emptyResult(); // 空目录 → 同样安静
226
+ }
227
+ const result = emptyResult(files);
228
+ for (const f of files) {
229
+ const abs = join(dir, f);
230
+ try {
231
+ // Windows 绝对路径必须转 file:// URL;cache-bust 用 mtime(改了才重新 import,别改回时间戳)
232
+ const url = pathToFileURL(abs).href + `?v=${fileVersion(abs)}`;
233
+ const mod = await import(url);
234
+ const ext = normalizeInbound(mod, f, abs, logger);
235
+ if (!ext) {
236
+ result.skipped += 1;
237
+ continue;
238
+ }
239
+ result.total += 1;
240
+ result.extensions.push(ext);
241
+ result.middleware.push(ext.middleware);
242
+ if (ext.onInbound)
243
+ result.early.push(ext.onInbound);
244
+ // 装载 = 熔断复位(改文件后 /tools-reload 就是文档里的"恢复通道")
245
+ resetBreaker(f);
246
+ // 身份 = 中间件函数引用(同一模块重复装载时是同一个引用 → 不会重复 dispose)
247
+ if (!known.has(ext.middleware))
248
+ known.set(ext.middleware, ext);
249
+ }
250
+ catch (err) {
251
+ result.skipped += 1;
252
+ logger.warn(`[ext-inbound] ✗ ${f} 装载失败(已跳过, 不影响其它入站扩展): ${err instanceof Error ? err.message : String(err)}`);
253
+ }
254
+ }
255
+ loaded = result.extensions;
256
+ if (result.total > 0) {
257
+ const earlyCount = result.extensions.filter((e) => e.onInbound).length;
258
+ logger.info(`[ext-inbound] 已装载 ${result.total} 个入站中间件: ${result.extensions.map((e) => e.name).join(', ')}` +
259
+ (earlyCount > 0 ? `(其中 ${earlyCount} 个含早挂点 onInbound)` : '') +
260
+ (result.skipped > 0 ? `(跳过 ${result.skipped} 个)` : ''));
261
+ }
262
+ return result;
263
+ }
264
+ /** 运行器环境(缺 dataRoot 时动作 API 的名单/kv/审计一律降级为空操作,绝不往相对路径写盘) */
265
+ function runEnvOf(opts, logger) {
266
+ return {
267
+ dataRoot: String(opts?.dataRoot ?? ''),
268
+ owners: Array.isArray(opts?.owners) ? opts.owners.map(String) : [],
269
+ logger,
270
+ ...(opts?.appId ? { appId: String(opts.appId) } : {}),
271
+ // ★ 2026-10-12 能力面对齐:统一能力包(与 silentInto)要的输入**原样透传**。
272
+ // 用 `!== undefined` 判据(不是真值判据):sender 之类可能是对象/函数,别被布尔化吃掉。
273
+ ...(opts?.sender !== undefined ? { sender: opts.sender } : {}),
274
+ ...(opts?.replyTarget !== undefined ? { replyTarget: opts.replyTarget } : {}),
275
+ ...(opts?.appSecret ? { appSecret: String(opts.appSecret) } : {}),
276
+ ...(opts?.apiCall !== undefined ? { apiCall: opts.apiCall } : {}),
277
+ ...(opts?.kernel !== undefined ? { kernel: opts.kernel } : {}),
278
+ ...(opts?.resolveAgent ? { resolveAgent: opts.resolveAgent } : {}),
279
+ };
280
+ }
281
+ /** 记一次"该扩展参与了这条消息",返回熔断原因(有的话由调用方告警) */
282
+ function noteHook(s, ext, destructiveBefore, logger) {
283
+ const destructive = s.destructiveCount > destructiveBefore;
284
+ if (s.noted.has(ext.file)) {
285
+ // 早+晚两个挂点:同一条消息只记一次参与;但晚挂点又做了破坏性动作 → 把那条记录升级成 true
286
+ if (destructive) {
287
+ try {
288
+ upgradeLastParticipation(ext.file);
289
+ }
290
+ catch { /* ignore */ }
291
+ }
292
+ return;
293
+ }
294
+ s.noted.add(ext.file);
295
+ const reason = noteParticipation(ext.file, destructive);
296
+ if (reason) {
297
+ logger.warn?.(`[ext-inbound] ⚠ 熔断「${ext.name}」(${ext.file}): ${reason} —— ` +
298
+ '该扩展的消灭/静默动作已停用(消息照常放行);改文件后 /tools-reload 或重启插件可复位');
299
+ }
300
+ }
301
+ /** 主人 openid 白名单判定(只读 ctx.message.senderId) */
302
+ function isOwnerSender(ctx, env) {
303
+ try {
304
+ const id = String(ctx.message?.senderId ?? '');
305
+ return id !== '' && env.owners.includes(id);
306
+ }
307
+ catch {
308
+ return false;
309
+ }
310
+ }
311
+ /**
312
+ * 扩展**既没调 next()、也没有一次成功的消灭**时,引擎要不要替它放行?
313
+ *
314
+ * 这条兜底是"白名单最高优先 / 熔断后只放行"能不能真正成立的**关键一半**:
315
+ * 扩展最常见的写法是 `if (该消灭) { ctx.annihilate(); return; }` —— 只调动作、不调 next。
316
+ * 一旦 `annihilate` 被拒(发送者是主人 / 已熔断 / 挂点不允许),那个 `return` 仍然会把链停住,
317
+ * 结果就是"插件说拒绝了,消息还是被吞了"。所以:
318
+ * · 请求过消灭但**被拒** ⇒ 引擎替它放行(记一条 warn);
319
+ * · 发送者是**主人**且这次没有成功的消灭 ⇒ 同样替它放行(白名单最高优先)。
320
+ * 两种情况之外(普通群友、扩展自己有意 `return`)⇒ 保持老契约:链停在这里。
321
+ */
322
+ function rescueReason(ctx, env, s) {
323
+ if (s?.annihilateRefused)
324
+ return s.annihilateRefused.reason;
325
+ if (isOwnerSender(ctx, env))
326
+ return '发送者是主人(白名单最高优先:主人的消息永远不被入站扩展吞掉)';
327
+ return undefined;
328
+ }
329
+ /**
330
+ * 跑一遍**早**挂点(`onInbound`):挂载点在 quoteRef 之后、**记群历史之前**。
331
+ *
332
+ * 与晚挂点共用同一套兜底(单扩展 try/catch、next() 幂等且一定被 await、下游异常原样上抛),
333
+ * 外加一条:`ctx.annihilate()` 置位后,**即便扩展又调了 next() 也不再往下走**
334
+ * (消灭的语义是"不进历史、不进上下文、不占冷却",只能靠"链停在这里"实现)。
335
+ * 没导出 `onInbound` 的扩展自动跳过(不占一层 next)。
336
+ */
337
+ export async function runEarlyInboundExtensions(exts, ctx, finalNext, logger, opts) {
338
+ const env = runEnvOf(opts, logger);
339
+ const step = async (i) => {
340
+ const ext = exts[i];
341
+ if (!ext) {
342
+ await finalNext();
343
+ return;
344
+ }
345
+ const hook = ext.onInbound;
346
+ if (!hook) {
347
+ await step(i + 1);
348
+ return;
349
+ }
350
+ /** 该扩展是否已经往下走过(next() 幂等:误调两次只算第一次) */
351
+ let onward;
352
+ const innerNext = () => {
353
+ if (onward) {
354
+ logger.debug?.(`[ext-inbound] ${ext.file} 调了多次 next()(只算第一次)`);
355
+ return onward;
356
+ }
357
+ // ★ 消灭:已请求消灭 ⇒ 这里直接"吃掉" next,链到此为止(连群历史都不会记)
358
+ if (sessionOf(ctx)?.annihilate) {
359
+ onward = Promise.resolve();
360
+ return onward;
361
+ }
362
+ onward = step(i + 1);
363
+ return onward;
364
+ };
365
+ const s = enterExtension(ctx, env, ext, 'inbound');
366
+ const before = s.destructiveCount;
367
+ try {
368
+ await hook(ctx, innerNext);
369
+ }
370
+ catch (err) {
371
+ const msg = err instanceof Error ? err.message : String(err);
372
+ if (onward) {
373
+ // 已经往下走过:先等下游(它自己抛的错要原样上抛),再判断这条异常属于谁
374
+ await onward;
375
+ logger.warn?.(`[ext-inbound] ${ext.file} 在 next() 之后抛异常(已忽略): ${msg}`);
376
+ }
377
+ else {
378
+ logger.warn?.(`[ext-inbound] ✗ ${ext.file} 执行异常(已忽略, 本条消息继续往下走): ${msg}`);
379
+ onward = step(i + 1);
380
+ }
381
+ }
382
+ // 扩展忘了 await next() 也由引擎兜住;短路(压根没调 next / 已消灭)时这里为空操作
383
+ if (!onward) {
384
+ // ★ 兜底放行(见 rescueReason 注释):消灭被拒 / 发送者是主人 ⇒ 主人的消息与"被拒的消灭"
385
+ // 都不允许真的把链停住,否则白名单与熔断只是"纸面承诺"。
386
+ const rescue = rescueReason(ctx, env, sessionOf(ctx));
387
+ if (rescue) {
388
+ logger.warn?.(`[ext-inbound] ${ext.file} 没有放行这条消息(${rescue})→ 引擎替它放行:` +
389
+ '白名单最高优先 / 熔断后只放行,都不会因为"忘了调 next()"而失效');
390
+ onward = step(i + 1);
391
+ }
392
+ }
393
+ if (onward)
394
+ await onward;
395
+ noteHook(s, ext, before, logger);
396
+ };
397
+ await step(0);
398
+ }
399
+ /**
400
+ * 跑一遍**晚**挂点(`onDecide` / 默认导出):挂载点在 mentionGate 之后、contentSanitizer 之前。
401
+ *
402
+ * 这是本扩展点自 2026-10-12 起的原有契约(洋葱模型:ext0 → ext1 → … → finalNext),
403
+ * 挂载点与单元测试都直接调它(不依赖 SDK 的 `runMiddlewareChain`)。
404
+ *
405
+ * 三条兜底(见文件头"铁律"):
406
+ * · 扩展抛错且**还没往下走** ⇒ 告警 + 替它 `next()`(用户脚本坏了,消息也绝不能被吞掉);
407
+ * · 扩展抛错但**已经往下走** ⇒ 原样把下游的异常上抛(那是下游的错,不能记成"扩展坏了");
408
+ * · 扩展忘了 await `next()` ⇒ 引擎替它 await(保证中间件链的顺序语义)。
409
+ * 扩展**不调** `next()` ⇒ 整条链到此为止(这是合法用法:这条消息被拦掉;但群历史**已经**记过)。
410
+ */
411
+ export async function runInboundExtensions(exts, ctx, finalNext, logger, opts) {
412
+ const env = runEnvOf(opts, logger);
413
+ const step = async (i) => {
414
+ const ext = exts[i];
415
+ if (!ext) {
416
+ await finalNext();
417
+ return;
418
+ }
419
+ /** 该扩展是否已经往下走过(next() 幂等:误调两次只算第一次) */
420
+ let onward;
421
+ const innerNext = () => {
422
+ if (!onward)
423
+ onward = step(i + 1);
424
+ else
425
+ logger.debug?.(`[ext-inbound] ${ext.file} 调了多次 next()(只算第一次)`);
426
+ return onward;
427
+ };
428
+ const s = enterExtension(ctx, env, ext, 'decide');
429
+ const before = s.destructiveCount;
430
+ try {
431
+ await ext.middleware(ctx, innerNext);
432
+ }
433
+ catch (err) {
434
+ const msg = err instanceof Error ? err.message : String(err);
435
+ if (onward) {
436
+ // 已经往下走过:先等下游(它自己抛的错要原样上抛),再判断这条异常属于谁
437
+ await onward;
438
+ logger.warn?.(`[ext-inbound] ${ext.file} 在 next() 之后抛异常(已忽略): ${msg}`);
439
+ }
440
+ else {
441
+ logger.warn?.(`[ext-inbound] ✗ ${ext.file} 执行异常(已忽略, 本条消息继续往下走): ${msg}`);
442
+ onward = step(i + 1);
443
+ }
444
+ }
445
+ // 扩展忘了 await next() 也由引擎兜住;短路(压根没调 next)时这里为空操作
446
+ if (!onward) {
447
+ // ★ 与早挂点同款兜底:晚挂点调 annihilate 会被拒(历史已记),若扩展就此 return,
448
+ // 那条"拒绝"就变成空话 ⇒ 这里替它放行(主人消息同理)。
449
+ const rescue = rescueReason(ctx, env, sessionOf(ctx));
450
+ if (rescue) {
451
+ logger.warn?.(`[ext-inbound] ${ext.file} 没有放行这条消息(${rescue})→ 引擎替它放行:` +
452
+ '白名单最高优先 / "消灭只在早挂点有效",都不会因为"忘了调 next()"而失效');
453
+ onward = step(i + 1);
454
+ }
455
+ }
456
+ if (onward)
457
+ await onward;
458
+ noteHook(s, ext, before, logger);
459
+ };
460
+ await step(0);
461
+ }
462
+ /**
463
+ * 把已装载的扩展组合成一个中间件(等价于 {@link runInboundExtensions} 的中间件写法)。
464
+ * 挂载点用的是"可变列表 + runInboundExtensions"(为了热重载),这个组合版给需要固定列表的场景用。
465
+ */
466
+ export function composeInboundExtensions(exts, logger, opts) {
467
+ return async (ctx, next) => { await runInboundExtensions(exts, ctx, next, logger, opts); };
468
+ }
469
+ /**
470
+ * 只把 ctx 的动作 API 提前挂上(不跑任何扩展)。
471
+ * 正常使用不必调 —— 两个运行器都会自动挂;需要它的场景是调用方自己写了
472
+ * "早挂点之后、晚挂点之前"的中间件,想读 `ctx.state.qqInboundForceWake` 之类由动作设置的标记。
473
+ */
474
+ export function attachExtContext(ctx, logger, opts) {
475
+ ensureExtSession(ctx, runEnvOf(opts, logger));
476
+ }
477
+ /**
478
+ * 调用所有入站扩展的 `dispose()`(插件卸载 / 宿主退出 / 保存连接设置后重建网关)。
479
+ *
480
+ * 纪律:**逆序**执行(后装载的先清理)、单个失败只告警、整体永不抛出、幂等、清空登记。
481
+ * 返回本次真正调用成功的 dispose 数量(没有导出的 dispose 就不算)。
482
+ * 同时把 ext-reload 里的热重载入口摘掉 —— 否则它会一直指向这个已经死掉的实例。
483
+ */
484
+ export async function disposeInboundExtensions(logger) {
485
+ const list = [...known.values()].reverse();
486
+ known.clear();
487
+ loaded = [];
488
+ registerExtInboundReloader(undefined);
489
+ let n = 0;
490
+ for (const ext of list) {
491
+ if (typeof ext.dispose !== 'function')
492
+ continue;
493
+ n += 1;
494
+ try {
495
+ await ext.dispose();
496
+ }
497
+ catch (err) {
498
+ logger.warn?.(`[ext-inbound] ${ext.file} dispose() 失败(已忽略): ${err instanceof Error ? err.message : String(err)}`);
499
+ }
500
+ }
501
+ if (n > 0)
502
+ logger.info?.(`[ext-inbound] 已清理 ${n} 个入站扩展`);
503
+ return n;
504
+ }
505
+ /** 重置模块级状态(仅供单元测试;生产不要调 —— 会把正在生效的扩展登记丢掉) */
506
+ export function __resetInboundExtensionsForTest() {
507
+ loaded = [];
508
+ known.clear();
509
+ }
510
+ /** 当前登记在册的扩展数(诊断用) */
511
+ export function inboundExtensionsCount() {
512
+ return known.size;
513
+ }
514
+ /** 当前生效的那一批(诊断用;返回副本,防外部改动) */
515
+ export function currentInboundExtensions() {
516
+ return [...loaded];
517
+ }
518
+ //# sourceMappingURL=ext-inbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ext-inbound.js","sourceRoot":"","sources":["../../src/features/ext-inbound.ts"],"names":[],"mappings":"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"}
@@ -35,4 +35,26 @@ export declare function reloadExtCommands(): Promise<ExtCommandReloadResult | {
35
35
  export declare function describeExtCommandReload(r: ExtCommandReloadResult | {
36
36
  error: string;
37
37
  }): string;
38
+ /** 一次入站扩展重载的结果(回执/日志用) */
39
+ export interface ExtInboundReloadResult {
40
+ /** 本次装载到的扩展数 */
41
+ count: number;
42
+ /** 扩展展示名(去重前,便于回执里展示) */
43
+ names: string[];
44
+ }
45
+ /** 注册/注销入站扩展重载器(注销传 undefined;插件重建时调用方会覆盖为新的) */
46
+ export declare function registerExtInboundReloader(fn: (() => Promise<ExtInboundReloadResult>) | undefined): void;
47
+ /**
48
+ * 热重载入站扩展。
49
+ * 返回 `{ error }` 表示"没装成"(比如插件还没启动完)—— 不抛错,理由同 {@link reloadExtCommands}。
50
+ */
51
+ export declare function reloadExtInbound(): Promise<ExtInboundReloadResult | {
52
+ error: string;
53
+ }>;
54
+ /** 给回执文本用:把入站扩展重载结果拼成一句人话 */
55
+ export declare function describeExtInboundReload(r: ExtInboundReloadResult | {
56
+ error: string;
57
+ }): string;
58
+ /** 当前是否已注册入站扩展重载器(诊断用) */
59
+ export declare function hasExtInboundReloader(): boolean;
38
60
  //# sourceMappingURL=ext-reload.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ext-reload.d.ts","sourceRoot":"","sources":["../../src/features/ext-reload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,sBAAsB;IACrC,+BAA+B;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAKD,6CAA6C;AAC7C,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,CAAC,MAAM,OAAO,CAAC,sBAAsB,CAAC,CAAC,GAAG,SAAS,GAAG,IAAI,CAExG;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,sBAAsB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAO7F;AAED,6CAA6C;AAC7C,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,sBAAsB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAI9F"}
1
+ {"version":3,"file":"ext-reload.d.ts","sourceRoot":"","sources":["../../src/features/ext-reload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,sBAAsB;IACrC,+BAA+B;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAKD,6CAA6C;AAC7C,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,CAAC,MAAM,OAAO,CAAC,sBAAsB,CAAC,CAAC,GAAG,SAAS,GAAG,IAAI,CAExG;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,sBAAsB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAO7F;AAED,6CAA6C;AAC7C,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,sBAAsB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAI9F;AAWD,0BAA0B;AAC1B,MAAM,WAAW,sBAAsB;IACrC,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAKD,iDAAiD;AACjD,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,CAAC,MAAM,OAAO,CAAC,sBAAsB,CAAC,CAAC,GAAG,SAAS,GAAG,IAAI,CAExG;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,sBAAsB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAO5F;AAED,6BAA6B;AAC7B,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,sBAAsB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAI9F;AAED,0BAA0B;AAC1B,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C"}
@@ -27,4 +27,36 @@ export function describeExtCommandReload(r) {
27
27
  return '扩展命令: 0 个(目录里没有 commands/*.js?)';
28
28
  return `扩展命令: ${r.names.length} 个已重载 → ${r.names.map((n) => '/' + n).join(', ')}`;
29
29
  }
30
+ /** 由 middleware-setup 注册:重扫入站扩展目录并原地换掉生效列表 */
31
+ let inboundReloader;
32
+ /** 注册/注销入站扩展重载器(注销传 undefined;插件重建时调用方会覆盖为新的) */
33
+ export function registerExtInboundReloader(fn) {
34
+ inboundReloader = fn;
35
+ }
36
+ /**
37
+ * 热重载入站扩展。
38
+ * 返回 `{ error }` 表示"没装成"(比如插件还没启动完)—— 不抛错,理由同 {@link reloadExtCommands}。
39
+ */
40
+ export async function reloadExtInbound() {
41
+ if (!inboundReloader)
42
+ return { error: '入站扩展表尚未注册(插件还没完成启动?)' };
43
+ try {
44
+ return await inboundReloader();
45
+ }
46
+ catch (err) {
47
+ return { error: `入站扩展重载失败: ${err instanceof Error ? err.message : String(err)}` };
48
+ }
49
+ }
50
+ /** 给回执文本用:把入站扩展重载结果拼成一句人话 */
51
+ export function describeExtInboundReload(r) {
52
+ if ('error' in r)
53
+ return `入站扩展: ${r.error}`;
54
+ if (r.count === 0)
55
+ return '入站扩展: 0 个(目录里没有 inbound/*.mjs?)';
56
+ return `入站扩展: ${r.count} 个已重载 → ${r.names.join(', ')}`;
57
+ }
58
+ /** 当前是否已注册入站扩展重载器(诊断用) */
59
+ export function hasExtInboundReloader() {
60
+ return typeof inboundReloader === 'function';
61
+ }
30
62
  //# sourceMappingURL=ext-reload.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ext-reload.js","sourceRoot":"","sources":["../../src/features/ext-reload.ts"],"names":[],"mappings":"AAwBA,gDAAgD;AAChD,IAAI,QAA6D,CAAC;AAElE,6CAA6C;AAC7C,MAAM,UAAU,0BAA0B,CAAC,EAAuD;IAChG,QAAQ,GAAG,EAAE,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC;IACxD,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,EAAE,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,KAAK,EAAE,aAAa,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;IACpF,CAAC;AACH,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,wBAAwB,CAAC,CAA6C;IACpF,IAAI,OAAO,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC,CAAC,KAAK,EAAE,CAAC;IAC5C,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,iCAAiC,CAAC;IACnE,OAAO,SAAS,CAAC,CAAC,KAAK,CAAC,MAAM,WAAW,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACpF,CAAC"}
1
+ {"version":3,"file":"ext-reload.js","sourceRoot":"","sources":["../../src/features/ext-reload.ts"],"names":[],"mappings":"AAwBA,gDAAgD;AAChD,IAAI,QAA6D,CAAC;AAElE,6CAA6C;AAC7C,MAAM,UAAU,0BAA0B,CAAC,EAAuD;IAChG,QAAQ,GAAG,EAAE,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC;IACxD,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,EAAE,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,KAAK,EAAE,aAAa,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;IACpF,CAAC;AACH,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,wBAAwB,CAAC,CAA6C;IACpF,IAAI,OAAO,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC,CAAC,KAAK,EAAE,CAAC;IAC5C,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,iCAAiC,CAAC;IACnE,OAAO,SAAS,CAAC,CAAC,KAAK,CAAC,MAAM,WAAW,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACpF,CAAC;AAmBD,8CAA8C;AAC9C,IAAI,eAAoE,CAAC;AAEzE,iDAAiD;AACjD,MAAM,UAAU,0BAA0B,CAAC,EAAuD;IAChG,eAAe,GAAG,EAAE,CAAC;AACvB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC,eAAe;QAAE,OAAO,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC;IAC/D,IAAI,CAAC;QACH,OAAO,MAAM,eAAe,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,KAAK,EAAE,aAAa,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;IACpF,CAAC;AACH,CAAC;AAED,6BAA6B;AAC7B,MAAM,UAAU,wBAAwB,CAAC,CAA6C;IACpF,IAAI,OAAO,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC,CAAC,KAAK,EAAE,CAAC;IAC5C,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC;QAAE,OAAO,iCAAiC,CAAC;IAC5D,OAAO,SAAS,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACzD,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,qBAAqB;IACnC,OAAO,OAAO,eAAe,KAAK,UAAU,CAAC;AAC/C,CAAC"}
@@ -6,6 +6,19 @@ export declare const EXT_COMMANDS_SUBDIR: string;
6
6
  export declare const EXT_TOOLS_SUBDIR: string;
7
7
  /** botplay 自定义事件目录名(相对数据根; 2026-10-05 第三套扩展) */
8
8
  export declare const EXT_BOTPLAY_SUBDIR: string;
9
+ /**
10
+ * 启动钩子目录名(相对数据根; 2026-10-12 第四套扩展)。
11
+ * 与前几套的区别: 钩子**不需要任何人触发** —— 插件启动(apply)时自动跑一次。
12
+ * 引擎在 features/ext-hooks.ts(本文件只提供目录常量, 免得两边各写一份字符串写歪)。
13
+ */
14
+ export declare const EXT_HOOKS_SUBDIR: string;
15
+ /**
16
+ * 入站中间件目录名(相对数据根; 2026-10-12 第五套扩展)。
17
+ * 与前几套的区别: 入站扩展**每条消息都跑一遍**, 且能读到/改写 ctx.state.mention、
18
+ * 甚至不调 next() 把这条整条拦掉 —— 它是"影响入站"的扩展点。
19
+ * 引擎在 features/ext-inbound.ts(本文件只提供目录常量, 免得两边各写一份字符串写歪)。
20
+ */
21
+ export declare const EXT_INBOUND_SUBDIR: string;
9
22
  /**
10
23
  * 扫描并加载扩展命令目录下的全部命令。
11
24
  * fail-soft: 单个文件坏/抛错只记日志, 不影响其它扩展与插件本体。