@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.
- package/README.md +113 -26
- package/README_EN.md +122 -8
- package/dist/features/ext-capabilities.d.ts +85 -0
- package/dist/features/ext-capabilities.d.ts.map +1 -1
- package/dist/features/ext-capabilities.js +4 -0
- package/dist/features/ext-capabilities.js.map +1 -1
- package/dist/features/ext-hooks.d.ts +84 -0
- package/dist/features/ext-hooks.d.ts.map +1 -0
- package/dist/features/ext-hooks.js +299 -0
- package/dist/features/ext-hooks.js.map +1 -0
- package/dist/features/ext-inbound-context.d.ts +202 -0
- package/dist/features/ext-inbound-context.d.ts.map +1 -0
- package/dist/features/ext-inbound-context.js +724 -0
- package/dist/features/ext-inbound-context.js.map +1 -0
- package/dist/features/ext-inbound-state.d.ts +153 -0
- package/dist/features/ext-inbound-state.d.ts.map +1 -0
- package/dist/features/ext-inbound-state.js +419 -0
- package/dist/features/ext-inbound-state.js.map +1 -0
- package/dist/features/ext-inbound.d.ts +117 -0
- package/dist/features/ext-inbound.d.ts.map +1 -0
- package/dist/features/ext-inbound.js +518 -0
- package/dist/features/ext-inbound.js.map +1 -0
- package/dist/features/ext-reload.d.ts +22 -0
- package/dist/features/ext-reload.d.ts.map +1 -1
- package/dist/features/ext-reload.js +32 -0
- package/dist/features/ext-reload.js.map +1 -1
- package/dist/features/extension-store.d.ts +13 -0
- package/dist/features/extension-store.d.ts.map +1 -1
- package/dist/features/extension-store.js +32 -4
- package/dist/features/extension-store.js.map +1 -1
- package/dist/gateway/bootstrap.d.ts.map +1 -1
- package/dist/gateway/bootstrap.js +44 -0
- package/dist/gateway/bootstrap.js.map +1 -1
- package/dist/gateway/debounce.d.ts +43 -1
- package/dist/gateway/debounce.d.ts.map +1 -1
- package/dist/gateway/debounce.js +71 -35
- package/dist/gateway/debounce.js.map +1 -1
- package/dist/gateway/middleware-setup.d.ts.map +1 -1
- package/dist/gateway/middleware-setup.js +88 -1
- package/dist/gateway/middleware-setup.js.map +1 -1
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +7 -3
- package/dist/session/session-manager.js.map +1 -1
- package/dist/shared/utils.d.ts +6 -0
- package/dist/shared/utils.d.ts.map +1 -1
- package/dist/shared/utils.js +6 -2
- package/dist/shared/utils.js.map +1 -1
- package/dist/transport/inbound.d.ts.map +1 -1
- package/dist/transport/inbound.js +23 -3
- package/dist/transport/inbound.js.map +1 -1
- package/docs/USER-GUIDE.md +92 -35
- package/docs/botplay-contract.md +2 -0
- package/examples/ext-hooks/README.md +95 -0
- package/examples/ext-hooks/hello-hook.mjs +54 -0
- package/examples/ext-hooks/session-watch-hook.mjs +119 -0
- package/examples/ext-inbound/README.md +230 -0
- package/examples/ext-inbound/class-advisor.mjs +306 -0
- package/examples/ext-inbound/no-wake-on-child-quote.mjs +192 -0
- package/package.json +1 -1
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ext-hooks.ts — 启动钩子扩展点(2026-10-12,第四套用户扩展)
|
|
3
|
+
*
|
|
4
|
+
* ── 它是什么 ───────────────────────────────────────────────────────────────
|
|
5
|
+
* 前三套扩展都要"有人来触发"才跑:
|
|
6
|
+
* · `commands/` 要用户敲 `/命令`;· `tools/` 要 LLM 主动调用;· `botplay/` 要有人点卡片。
|
|
7
|
+
* 有些事**没人触发也得跑**:恢复上次没跑完的长期任务、注册监听、起定时器、预取数据。
|
|
8
|
+
* 于是有了这一套 —— 放在 `{dataRoot}/.qqbot-extensions/hooks/*.mjs`,
|
|
9
|
+
* **dsh 启动(插件 apply 链路)时自动执行一次**,不需要任何调用方。
|
|
10
|
+
*
|
|
11
|
+
* ── 契约(与 docs / README / examples/ext-hooks 三处保持一致)─────────────
|
|
12
|
+
* ```js
|
|
13
|
+
* // {dataRoot}/.qqbot-extensions/hooks/hello.mjs
|
|
14
|
+
* export async function run(env) { // 也接受 `export default { run }` / `export default async (env) => {}`
|
|
15
|
+
* env.logger.info('[hello-hook] 起来了');
|
|
16
|
+
* return 'ok'; // 返回值只记日志,不参与控制流
|
|
17
|
+
* }
|
|
18
|
+
* export async function dispose(env) { // 可选:插件卸载 / 宿主退出时调用
|
|
19
|
+
* env.logger.info('[hello-hook] 再见');
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
* `env` = `{ ctx, logger, dataRoot, config, registerDispose, pluginVersion, hookFile, caps, store }`:
|
|
23
|
+
* · `ctx` —— 插件自己的 cordis Context("遥控器",同 `env.kernel` 语义;能 `ctx.on()` / `ctx.get()`)
|
|
24
|
+
* · `logger` —— 插件 logger(带 `[ext-hook]` 前缀建议)
|
|
25
|
+
* · `dataRoot` / `config` —— 数据根与当前实例配置(只读用;要改设置请走设置页)
|
|
26
|
+
* · `registerDispose(fn)` —— 注册一个清理函数(返回 Promise 也行);与导出的 `dispose()` 一样,
|
|
27
|
+
* 都在插件卸载时被调用,**逆序**执行
|
|
28
|
+
* · `pluginVersion` —— 插件包版本号(`1.8.9` 这种),可按需做版本分支
|
|
29
|
+
* · `store.load()/save()` —— 该钩子自己的持久化(落 `{dataRoot}/.qqbot-extensions/hooks/data/<文件名>.json`)
|
|
30
|
+
*
|
|
31
|
+
* ── 铁律(都是"绝不能影响启动"派生出来的)─────────────────────────────────
|
|
32
|
+
* ① **单钩子隔离**:每个钩子独立 try/catch,失败/超时只 log 警告并继续下一个;
|
|
33
|
+
* ② **超时保护**:默认单钩子 30s(`timeoutMs`),超时只告警、不中断启动;
|
|
34
|
+
* 超时的钩子**不会被强杀**(Node 里没法安全 kill 一个 promise),只是从启动路径上放行;
|
|
35
|
+
* ③ **目录缺失/为空完全安静**:不报错、不打日志(连 info 都不打);
|
|
36
|
+
* ④ **每个钩子的名字与结果都记账**(成功/失败/超时),日志可读、返回值可诊断。
|
|
37
|
+
*
|
|
38
|
+
* ── 与 extension-store 的分工 ─────────────────────────────────────────────
|
|
39
|
+
* 扫描约定(目录名、允许的扩展名、`?v=<mtime>` 做 cache-bust)复用 extension-store 的常量,
|
|
40
|
+
* 但"加载成 SlashCommand / ExtensionToolDef"的归一化完全不是一回事 —— 钩子是"启动就跑的模块",
|
|
41
|
+
* 归一到 `run()/dispose()` 就够了,所以单独一个文件,不往 extension-store 里塞第四种形状。
|
|
42
|
+
*/
|
|
43
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
44
|
+
import { extname, join } from 'node:path';
|
|
45
|
+
import { pathToFileURL } from 'node:url';
|
|
46
|
+
import { EXT_HOOKS_SUBDIR } from './extension-store.js';
|
|
47
|
+
import { makeExtCaps } from './ext-capabilities.js';
|
|
48
|
+
import { PLUGIN_VERSION } from '../shared/utils.js';
|
|
49
|
+
/** 单钩子默认超时(ms):超过就只告警放行,绝不拖住插件启动 */
|
|
50
|
+
export const DEFAULT_HOOK_TIMEOUT_MS = 30_000;
|
|
51
|
+
/** 扩展文件允许的扩展名(与 extension-store 的 ALLOWED_EXT 同款;这里按需再声明一次避免反向导出) */
|
|
52
|
+
const ALLOWED_EXT = new Set(['.js', '.mjs', '.cjs']);
|
|
53
|
+
/** 已注册的清理函数(模块级:dispose 可能在加载还没跑完时就被调用,所以放模块级而非闭包) */
|
|
54
|
+
let disposers = [];
|
|
55
|
+
/** 卸载是否已经发生过:发生过之后新注册的清理函数**立即执行**(防"启动竞态漏清理") */
|
|
56
|
+
let disposed = false;
|
|
57
|
+
/** 扩展钩子目录(相对数据根) */
|
|
58
|
+
export function extHooksDir(dataRoot) {
|
|
59
|
+
return join(dataRoot, EXT_HOOKS_SUBDIR);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 登记一个清理函数。
|
|
63
|
+
* ⚠️ 卸载**已经发生过**之后再注册的,说明它是在"启动钩子还没 await 完、插件就被卸载"这条竞态路上
|
|
64
|
+
* 登记的 —— 那种情况下没人会再来跑清理,所以这里**立即执行**,杜绝"注册了却永远不清理"。
|
|
65
|
+
*/
|
|
66
|
+
function pushDisposer(file, fn, logger) {
|
|
67
|
+
if (!disposed) {
|
|
68
|
+
disposers.push({ file, fn });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
void (async () => {
|
|
72
|
+
try {
|
|
73
|
+
await fn();
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
logger.warn?.(`[ext-hook] ${file} 卸载后登记的清理失败(已忽略): ${err instanceof Error ? err.message : String(err)}`);
|
|
77
|
+
}
|
|
78
|
+
})();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 把一个动态 import 的结果归一化成钩子。
|
|
82
|
+
* 接受三种写法(都在文档里写了,别只支持一种):
|
|
83
|
+
* ① `export function run(env) {}` (具名导出,最直白)
|
|
84
|
+
* ② `export default { name, run, dispose }` (对象默认导出,与 tools 同风格)
|
|
85
|
+
* ③ `export default async (env) => {}` (裸函数默认导出,最短)
|
|
86
|
+
* 返回 null = 形状不合法(缺 run),由调用方按"跳过"记账。
|
|
87
|
+
*/
|
|
88
|
+
function normalizeHook(mod, file, logger) {
|
|
89
|
+
try {
|
|
90
|
+
const raw = mod;
|
|
91
|
+
const dflt = raw?.default;
|
|
92
|
+
const name = String((typeof dflt === 'object' && dflt !== null ? dflt.name : undefined) ?? raw?.name ?? '').trim() || file.replace(/\.(mjs?|cjs)$/i, '');
|
|
93
|
+
// ③ 裸函数默认导出
|
|
94
|
+
if (typeof dflt === 'function') {
|
|
95
|
+
const obj = dflt;
|
|
96
|
+
return { name, run: dflt, ...(typeof obj.dispose === 'function' ? { dispose: obj.dispose } : {}) };
|
|
97
|
+
}
|
|
98
|
+
// ② 对象默认导出(优先)与 ① 具名导出
|
|
99
|
+
const src = (typeof dflt === 'object' && dflt !== null ? dflt : raw);
|
|
100
|
+
if (typeof src?.run !== 'function') {
|
|
101
|
+
logger.warn(`[ext-hook] ${file}: 没有可执行的 run —— 需 \`export function run(env)\` 或 \`export default { run }\`,已跳过`);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
name,
|
|
106
|
+
run: src.run,
|
|
107
|
+
...(typeof src.dispose === 'function' ? { dispose: src.dispose } : {}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
logger.warn(`[ext-hook] ${file}: 归一化失败 ${err instanceof Error ? err.message : String(err)}`);
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 带超时的 await:超时**不 kill** 原 promise(Node 做不到安全强杀),只让启动路径放行。
|
|
117
|
+
* ⚠️ 超时窗口关闭后原 promise 才 reject(钩子自己抛了异步错)也不能变成 unhandledRejection
|
|
118
|
+
* —— 先给它挂一个空 catch 观察者(`.catch()` 的返回值我们不 await,但它自己永不 reject)。
|
|
119
|
+
*/
|
|
120
|
+
async function withTimeout(p, ms) {
|
|
121
|
+
let timer;
|
|
122
|
+
// 空观察者:只负责"接住"迟到的 rejection,不改变下面 race 的结果
|
|
123
|
+
void p.catch(() => undefined);
|
|
124
|
+
try {
|
|
125
|
+
return await Promise.race([
|
|
126
|
+
p.then((value) => ({ ok: true, value })),
|
|
127
|
+
new Promise((resolve) => {
|
|
128
|
+
timer = setTimeout(() => resolve({ ok: false, reason: 'timeout' }), Math.max(1, ms));
|
|
129
|
+
}),
|
|
130
|
+
]);
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
if (timer)
|
|
134
|
+
clearTimeout(timer);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 扫描 `{dataRoot}/.qqbot-extensions/hooks/*.mjs` 并**逐个执行**。
|
|
139
|
+
*
|
|
140
|
+
* 目录不存在 / 目录为空 → 直接返回空汇总,**不打任何日志**(这是最常见的正常状态)。
|
|
141
|
+
* 单个钩子失败或超时 → 只记 `warn`,继续下一个;本函数**永不抛出**。
|
|
142
|
+
*/
|
|
143
|
+
export async function runStartupHooks(opts) {
|
|
144
|
+
const { dataRoot, logger } = opts;
|
|
145
|
+
// ★ 2026-10-12 修(真 bug):**新一轮生命周期开始,必须把 disposed 复位**。
|
|
146
|
+
// 不重置的话,下面这条链会把刚起来的钩子当场拆掉:
|
|
147
|
+
// 启动 → runStartupHooks(此时 disposed=false,正常)→ 保存连接设置触发网关重建
|
|
148
|
+
// → dispose 里调 disposeStartupHooks() ⇒ disposed=true → 重新 bootstrap
|
|
149
|
+
// → 再次 runStartupHooks,钩子调 registerDispose() 时看到 disposed===true
|
|
150
|
+
// ⇒ pushDisposer 走"卸载后登记 ⇒ 立即执行"分支 ⇒ 注册即被清理(刚订阅就退订)。
|
|
151
|
+
// `disposeStartupHooks` 已经把 disposers 清空,所以这里只补复位标志即可。
|
|
152
|
+
disposed = false;
|
|
153
|
+
const timeoutMs = Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0
|
|
154
|
+
? Number(opts.timeoutMs)
|
|
155
|
+
: DEFAULT_HOOK_TIMEOUT_MS;
|
|
156
|
+
const empty = { total: 0, ok: 0, failed: 0, timedOut: 0, skipped: 0, results: [] };
|
|
157
|
+
const dir = extHooksDir(dataRoot);
|
|
158
|
+
let files;
|
|
159
|
+
try {
|
|
160
|
+
// 只取**直接子文件**:子目录不扫描(与 tools/ 的既有约定一致;lib/ 之类可安全存放私有模块)
|
|
161
|
+
files = readdirSync(dir, { withFileTypes: true })
|
|
162
|
+
.filter((e) => e.isFile() && ALLOWED_EXT.has(extname(e.name).toLowerCase()))
|
|
163
|
+
.map((e) => e.name)
|
|
164
|
+
.sort();
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return empty; // 目录不存在 → 完全安静
|
|
168
|
+
}
|
|
169
|
+
if (files.length === 0)
|
|
170
|
+
return empty; // 空目录 → 同样安静
|
|
171
|
+
logger.info(`[ext-hook] 发现 ${files.length} 个启动钩子,开始执行(单钩子超时 ${timeoutMs}ms)`);
|
|
172
|
+
const summary = { total: 0, ok: 0, failed: 0, timedOut: 0, skipped: 0, results: [] };
|
|
173
|
+
for (const f of files) {
|
|
174
|
+
const abs = join(dir, f);
|
|
175
|
+
const started = Date.now();
|
|
176
|
+
try {
|
|
177
|
+
// Windows 绝对路径必须转 file:// URL;cache-bust 用 mtime(与 extension-store 同款,别改回时间戳)
|
|
178
|
+
const url = pathToFileURL(abs).href + `?v=${fileVersion(abs)}`;
|
|
179
|
+
const mod = await import(url);
|
|
180
|
+
const hook = normalizeHook(mod, f, logger);
|
|
181
|
+
if (!hook) {
|
|
182
|
+
summary.skipped += 1;
|
|
183
|
+
summary.results.push({ file: f, path: abs, name: f, status: 'skipped', ms: 0 });
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
summary.total += 1;
|
|
187
|
+
// 本钩子的清理收集器:registerDispose(fn) 与导出 dispose() 都进这里
|
|
188
|
+
const registered = [];
|
|
189
|
+
const caps = makeExtCaps({
|
|
190
|
+
dataRoot,
|
|
191
|
+
logger,
|
|
192
|
+
kernel: opts.ctx,
|
|
193
|
+
selfName: f,
|
|
194
|
+
kind: 'hooks',
|
|
195
|
+
});
|
|
196
|
+
const env = {
|
|
197
|
+
ctx: opts.ctx,
|
|
198
|
+
logger,
|
|
199
|
+
dataRoot,
|
|
200
|
+
config: opts.config,
|
|
201
|
+
registerDispose: (fn) => {
|
|
202
|
+
if (typeof fn !== 'function')
|
|
203
|
+
return;
|
|
204
|
+
registered.push(fn);
|
|
205
|
+
},
|
|
206
|
+
pluginVersion: PLUGIN_VERSION,
|
|
207
|
+
hookFile: abs,
|
|
208
|
+
caps,
|
|
209
|
+
store: caps.store,
|
|
210
|
+
};
|
|
211
|
+
// 清理登记**先做**:即使后面 run 超时/失败,它已经注册的清理函数也照样要跑
|
|
212
|
+
pushDisposer(f, async () => {
|
|
213
|
+
// registerDispose 逆序 → 后注册的先清理(与嵌套资源的习惯一致)
|
|
214
|
+
for (const fn of [...registered].reverse()) {
|
|
215
|
+
try {
|
|
216
|
+
await fn();
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
logger.warn?.(`[ext-hook] ${f} registerDispose 清理失败(已忽略): ${err instanceof Error ? err.message : String(err)}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (hook.dispose) {
|
|
223
|
+
try {
|
|
224
|
+
await hook.dispose(env);
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
logger.warn?.(`[ext-hook] ${f} dispose() 失败(已忽略): ${err instanceof Error ? err.message : String(err)}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}, logger);
|
|
231
|
+
const raced = await withTimeout(Promise.resolve(hook.run(env)), timeoutMs);
|
|
232
|
+
const ms = Date.now() - started;
|
|
233
|
+
if (!raced.ok) {
|
|
234
|
+
summary.timedOut += 1;
|
|
235
|
+
summary.results.push({ file: f, path: abs, name: hook.name, status: 'timeout', ms, error: `超过 ${timeoutMs}ms 未返回` });
|
|
236
|
+
logger.warn(`[ext-hook] ${hook.name} (${f}) 超时 ${timeoutMs}ms —— 已放行继续启动(它仍在后台跑)`);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
summary.ok += 1;
|
|
240
|
+
summary.results.push({ file: f, path: abs, name: hook.name, status: 'ok', ms });
|
|
241
|
+
logger.info(`[ext-hook] ✓ ${hook.name} (${f}) ${ms}ms`);
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
const ms = Date.now() - started;
|
|
245
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
246
|
+
summary.failed += 1;
|
|
247
|
+
summary.results.push({ file: f, path: abs, name: f, status: 'error', ms, error: msg });
|
|
248
|
+
logger.warn(`[ext-hook] ✗ ${f} 执行失败(已跳过, 不影响启动与其它钩子): ${msg}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (summary.results.length > 0) {
|
|
252
|
+
logger.info(`[ext-hook] 启动钩子执行完毕: 成功 ${summary.ok} / 失败 ${summary.failed} / 超时 ${summary.timedOut}` +
|
|
253
|
+
(summary.skipped > 0 ? ` / 跳过 ${summary.skipped}` : ''));
|
|
254
|
+
}
|
|
255
|
+
return summary;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* 调用所有钩子登记的清理函数(导出 `dispose()` + `registerDispose(fn)`)。
|
|
259
|
+
*
|
|
260
|
+
* 由 `bootstrapGateway` 的 dispose 链路调用(插件卸载 / 宿主退出 / 保存连接设置后重建网关)。
|
|
261
|
+
* 纪律:逆序执行(后启动的先清理)、**单个失败只告警**、整体永不抛出、幂等。
|
|
262
|
+
*/
|
|
263
|
+
export async function disposeStartupHooks(logger) {
|
|
264
|
+
disposed = true;
|
|
265
|
+
const list = disposers;
|
|
266
|
+
disposers = [];
|
|
267
|
+
let n = 0;
|
|
268
|
+
for (const item of [...list].reverse()) {
|
|
269
|
+
n += 1;
|
|
270
|
+
try {
|
|
271
|
+
await item.fn();
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
logger.warn?.(`[ext-hook] ${item.file} 清理异常(已忽略): ${err instanceof Error ? err.message : String(err)}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (n > 0)
|
|
278
|
+
logger.info?.(`[ext-hook] 已清理 ${n} 个启动钩子的注册项`);
|
|
279
|
+
return n;
|
|
280
|
+
}
|
|
281
|
+
/** 重置模块级状态(仅供单元测试;生产不要调 —— 会把正在跑的钩子的清理登记丢掉) */
|
|
282
|
+
export function __resetStartupHooksForTest() {
|
|
283
|
+
disposers = [];
|
|
284
|
+
disposed = false;
|
|
285
|
+
}
|
|
286
|
+
/** 当前已登记清理函数的钩子数(诊断用) */
|
|
287
|
+
export function startupHooksDisposeCount() {
|
|
288
|
+
return disposers.length;
|
|
289
|
+
}
|
|
290
|
+
/** 文件版本号(mtime,秒级取整)—— 与 extension-store.fileVersion 同款纪律,别改回时间戳 */
|
|
291
|
+
function fileVersion(p) {
|
|
292
|
+
try {
|
|
293
|
+
return String(Math.floor(statSync(p).mtimeMs));
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return String(Date.now());
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=ext-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ext-hooks.js","sourceRoot":"","sources":["../../src/features/ext-hooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ext-inbound-context.ts — 挂到中间件 `ctx` 上的「动作 API」(2026-10-12)
|
|
3
|
+
*
|
|
4
|
+
* 用户看到的全部能力就是这些(`examples/ext-inbound/README.md` 里的表与此一一对应):
|
|
5
|
+
*
|
|
6
|
+
* ```js
|
|
7
|
+
* // ── 消息维(早挂点 onInbound 用)──
|
|
8
|
+
* ctx.annihilate({ reason }) // 消灭这条:不进历史 / 不进上下文 / 不占冷却(内部=不 next)
|
|
9
|
+
* ctx.rewrite({ text, reason }) // 替换文本(必须在记群历史之前生效 ⇒ 早挂点)
|
|
10
|
+
* ctx.forceWake({ reason }) // 强制入站:跳过价值评分 / @门控 / 冷却等"值不值得回"的判断
|
|
11
|
+
* // ── 消息维(晚挂点 onDecide 用)──
|
|
12
|
+
* ctx.decide({ mention: 'ignore' | 'force', reason })
|
|
13
|
+
* // ── 消息维(两个挂点都能用)──
|
|
14
|
+
* ctx.silentInto(text?) // ★静默进上下文:内容进当前会话的 AI 上下文,**不唤醒**
|
|
15
|
+
* // ── 用户维(两个挂点都能用)──
|
|
16
|
+
* ctx.muteUser({ id?, minutes, reason }) // id 缺省 = 当前发送者;写进持久名单
|
|
17
|
+
* ctx.unmuteUser({ id? })
|
|
18
|
+
* ctx.isMuted(id?) / ctx.mutedUsers() // 读名单(带剩余时间)
|
|
19
|
+
* // ── 通用 ──
|
|
20
|
+
* ctx.text / ctx.scope / ctx.sender / ctx.senderName / ctx.isOwner / ctx.groupId / ctx.mentions
|
|
21
|
+
* ctx.quoted({ text, fromSelf }) / ctx.platform({ mentioned })
|
|
22
|
+
* ctx.logger / ctx.dataRoot
|
|
23
|
+
* ctx.kv.get(key) / ctx.kv.set(key, value)
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* ── `silentInto` 为什么必须存在(与 `annihilate` 的分工)────────────────────
|
|
27
|
+
* `annihilate` 的语义是"**不调 next()**"⇒ 这条消息在链上被彻底拦掉,**后面组装上下文的那一步
|
|
28
|
+
* 根本看不到它** —— 内容是"消失",不是"安静地留下"。可有些场景要的恰恰是后者:
|
|
29
|
+
* "这条别叫醒她,但内容得让她下次跑的时候看得到"(例:群里被引用的子会话转发、只报数的日志流)。
|
|
30
|
+
* 于是补一个**注入**动作:`silentInto` 把正文塞进当前会话的 AI 上下文,不唤醒、不进群历史、
|
|
31
|
+
* 不占冷却;她空闲时它就**静静躺在收件箱里**,等下一次真有回合跑起来时自然出现。
|
|
32
|
+
*
|
|
33
|
+
* ── 它走哪条通路(**硬要求,别改**)────────────────────────────────────────
|
|
34
|
+
* 只走宿主的 `agent.inject(...)`(dsh 底层 `send(msg,'next-step',wakeup:false)`):
|
|
35
|
+
* 排队到下个 step 组包、**不拆 `tool_calls ↔ tool/result` 配对**、忙闲皆安全。
|
|
36
|
+
* ⚠️ **绝不用 `session.append` / `caps.appendSilent` 硬写会话** —— 那条路绕过回合边界,
|
|
37
|
+
* 落在"LLM 已回 tool_calls、工具还在执行"的中途就会把配对拆散 ⇒ 下一轮请求非法 ⇒
|
|
38
|
+
* QQ 侧「本轮异常结束」。这道雷 `mc_chat` 的 daemon 早先踩过(它源码注释里写着
|
|
39
|
+
* "tick 一律只走宿主的 agent.inject"),别再踩第二次。
|
|
40
|
+
* 组消息这件事本身复用插件既有入口:`createUserMessage`(与 `features/group-hub.ts` 的
|
|
41
|
+
* `safeAppendUserMessage`、`gateway/debounce.ts` 的插话、`commands/inject.ts` 同款形状),
|
|
42
|
+
* 而不是自己拼一个 `{ role: 'user', … }` 裸对象;正文缺省值也复用 `shared/mention-clean.ts`
|
|
43
|
+
* 的 `replaceBotMention`(把 `@她` 的 32 位 id 换成短标记 `@bot`,与正常入站的清洗同款)。
|
|
44
|
+
*
|
|
45
|
+
* 拿不到 agent(该会话还没建立 / 嵌入方没给解析器)或注入抛错 ⇒ **只告警 + 返回 false**,
|
|
46
|
+
* 绝不抛到链上;审计里记 `failed-no-agent` / `failed-inject`,一眼能看出"没注进去"。
|
|
47
|
+
*
|
|
48
|
+
* ── 四条保命纪律(主人点名)───────────────────────────────────────────────
|
|
49
|
+
* ① **白名单最高优先**:主人/owner(`config.groupAdmin.owners`)**永不可被消灭/禁言** ——
|
|
50
|
+
* 扩展对 owner 调 `annihilate` 会被**拒绝执行**并记一条 warn(消息照常放行);`muteUser(owner)` 同理。
|
|
51
|
+
* ⚠️ **`silentInto` 不受这条限制**(有意为之,别"顺手"加上):白名单挡的是"主人的消息被吞掉",
|
|
52
|
+
* 而静默注入**内容是进上下文的**,不存在"被吞"的风险 —— 只是换了个不叫醒她的入口。
|
|
53
|
+
* 它仍然要**落审计**(可回看"谁在什么时候塞了什么")并**计入熔断**(见 ③),
|
|
54
|
+
* 所以"主人消息被扩展大规模改道"这件事依然看得见、也依然刹得住。
|
|
55
|
+
* ② **强唤醒优先于静默**:本模块只保证 `forceWake` **不被别的动作抵消**(`annihilate` 除外——
|
|
56
|
+
* 那是"整条不进站",两者本就互斥,谁先发生谁生效);**顺序由扩展自己排**,
|
|
57
|
+
* 文档里写明"求助关键词判断要写在黑名单判断之前"。
|
|
58
|
+
* ③ **熔断**:破坏性动作(消灭/静默)密度异常 ⇒ 停用该扩展的破坏性动作(消息照常放行)+ 告警。
|
|
59
|
+
* 统计与阈值在 `ext-inbound-state.ts`;这里只负责"问一句、记账、拒绝"。
|
|
60
|
+
* ④ **审计 + 可撤销**:每个动作落一行 `audit.jsonl`(**文本摘要要截断**);`muteUser` 能用
|
|
61
|
+
* `unmuteUser` 解掉。
|
|
62
|
+
*
|
|
63
|
+
* ── 铁律:**任何动作都绝不抛到链上** ────────────────────────────────────────
|
|
64
|
+
* 每个动作整体包在 try/catch 里,出错只 `warn` 并返回 false;ctx 上的属性一律
|
|
65
|
+
* `Object.defineProperty(..., { enumerable: false })` —— 不参与 `{...ctx}` 展开、
|
|
66
|
+
* 不会被 JSON.stringify 抓走(内部命令层就是靠展开 ctx 组 handler 参数的,别去污染它)。
|
|
67
|
+
*/
|
|
68
|
+
import type { MiddlewareContext } from '@tencent-connect/qqbot-nodejs';
|
|
69
|
+
import type { Logger } from '../types.js';
|
|
70
|
+
import { type ExtInboundCaps } from './ext-capabilities.js';
|
|
71
|
+
/** 运行环境(由挂载点/运行器传进来) */
|
|
72
|
+
export interface ExtRunEnv {
|
|
73
|
+
/** 数据根:状态文件、审计都落它下面 */
|
|
74
|
+
dataRoot: string;
|
|
75
|
+
/** 主人 openid 白名单(`config.groupAdmin.owners`) */
|
|
76
|
+
owners: string[];
|
|
77
|
+
/** 插件 logger(动作告警用) */
|
|
78
|
+
logger: Logger;
|
|
79
|
+
/** 当前 bot 的 appId(`ctx.platform.mentioned` 的内容兜底扫描要用,与 SDK mentionGate 同款判据) */
|
|
80
|
+
appId?: string;
|
|
81
|
+
/**
|
|
82
|
+
* ★ `silentInto` 用:把"**这条消息所属会话**的宿主 agent"解析出来。
|
|
83
|
+
*
|
|
84
|
+
* 为什么不在这里自己找:agent 在 `SessionManager` 里(挂载点持有它),本模块只拿到 `ctx`。
|
|
85
|
+
* 挂载点(`gateway/middleware-setup.ts`)用 `manager.findByPeer(scope, peerId)` 实现它 ——
|
|
86
|
+
* 与 `commands/inject.ts` 取"当前会话 agent"是同一口径(会话 id 由 `scope + peerId` 确定性派生,
|
|
87
|
+
* 所以"这条消息将去哪"与"解析到的 agent"必然是同一个)。
|
|
88
|
+
*
|
|
89
|
+
* 拿不到(会话还没建立 / 嵌入方没传)⇒ 返回 undefined,`silentInto` 只告警、不注入。
|
|
90
|
+
*/
|
|
91
|
+
resolveAgent?: (ctx: MiddlewareContext) => unknown;
|
|
92
|
+
/** 当前会话的发送器(发消息 / 发富媒体用)。没有 = 发送类能力不可用 */
|
|
93
|
+
sender?: unknown;
|
|
94
|
+
/** 当前这条消息的回复目标(被动回复优先,缺了退主动发送) */
|
|
95
|
+
replyTarget?: unknown;
|
|
96
|
+
/** bot 密钥(`api()` 自建 token 兜底用;给了 apiCall 就优先用 apiCall) */
|
|
97
|
+
appSecret?: string;
|
|
98
|
+
/** 带 token 的官方 API 调用(挂载点从 `manager.groupAdmin.apiCall` 取) */
|
|
99
|
+
apiCall?: unknown;
|
|
100
|
+
/** 插件自己的 cordis Context —— 统一能力包里的 `kernel`("遥控器") */
|
|
101
|
+
kernel?: unknown;
|
|
102
|
+
}
|
|
103
|
+
/** `silentInto` 的结果码(审计的 `result` 与 `ctx.extState.silentInto` 共用这一套词) */
|
|
104
|
+
export type SilentIntoResult =
|
|
105
|
+
/** 本次真的注入成功 */
|
|
106
|
+
'ok'
|
|
107
|
+
/** 之前已经注入过(幂等命中,本次没重复塞) */
|
|
108
|
+
| 'ok-duplicate'
|
|
109
|
+
/** 文本空/全空白 ⇒ 不注入(别往上下文里塞空消息) */
|
|
110
|
+
| 'refused-empty'
|
|
111
|
+
/** 参数不合法(非字符串、也非 `{ text }`)⇒ 不注入(绝不 `String(未知对象)` 塞进去) */
|
|
112
|
+
| 'refused-invalid'
|
|
113
|
+
/** 熔断中:该扩展的破坏性动作被停用(`silentInto` 也算在内) */
|
|
114
|
+
| 'refused-breaker'
|
|
115
|
+
/** 拿不到宿主 agent(会话还没建立 / 环境没给解析器) */
|
|
116
|
+
| 'failed-no-agent'
|
|
117
|
+
/** `agent.inject` 抛错 */
|
|
118
|
+
| 'failed-inject';
|
|
119
|
+
/** 一条消息、一个中间件 ctx 对应的一份扩展会话(早/晚两个挂点共享同一份) */
|
|
120
|
+
export interface ExtCtxSession {
|
|
121
|
+
env: ExtRunEnv;
|
|
122
|
+
/** 当前在跑哪个挂点:早 `inbound` / 晚 `decide` */
|
|
123
|
+
phase: 'inbound' | 'decide';
|
|
124
|
+
/** 当前正在执行的扩展(动作归属、审计署名、熔断统计都认它) */
|
|
125
|
+
current?: {
|
|
126
|
+
file: string;
|
|
127
|
+
name: string;
|
|
128
|
+
};
|
|
129
|
+
/** 已请求消灭(读它的地方是运行器的 next 包装:置位后再调 next() 也不会往下走) */
|
|
130
|
+
annihilate?: {
|
|
131
|
+
reason?: string;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* 请求过消灭但**被拒**(白名单 / 熔断 / 挂点不允许)。
|
|
135
|
+
*
|
|
136
|
+
* 为什么必须记下来:扩展最常见的写法是
|
|
137
|
+
* ```js
|
|
138
|
+
* if (该消灭) { ctx.annihilate({ reason }); return; } // 只调动作,不调 next
|
|
139
|
+
* ```
|
|
140
|
+
* 一旦 annihilate 被拒,`return` 仍然会让链停在这一层 —— 等于"插件拒绝了,消息还是被吞了"。
|
|
141
|
+
* 所以运行器看到这个标记时会**替扩展放行**(白名单最高优先 / 熔断后只放行)。
|
|
142
|
+
*/
|
|
143
|
+
annihilateRefused?: {
|
|
144
|
+
reason: string;
|
|
145
|
+
};
|
|
146
|
+
/** 已请求强制入站 */
|
|
147
|
+
forceWake?: {
|
|
148
|
+
reason?: string;
|
|
149
|
+
};
|
|
150
|
+
/** 已覆盖 @ 判定 */
|
|
151
|
+
decide?: {
|
|
152
|
+
mention: 'ignore' | 'force';
|
|
153
|
+
reason?: string;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* ★ 本消息的静默注入状态(`silentInto`)。
|
|
157
|
+
*
|
|
158
|
+
* **幂等**:`injected === true` 之后,本 ctx(= 这**一条**消息)再调多少次 `silentInto` 都不会
|
|
159
|
+
* 重复塞(早/晚两个挂点共用同一份会话 ⇒ 跨挂点也只塞一次)。
|
|
160
|
+
* 只有**成功**才置 `injected`:失败(拿不到 agent / 熔断 / 空文本)不留痕 ——
|
|
161
|
+
* 晚挂点若那时才拿到 agent,仍然可以再试一次。
|
|
162
|
+
*/
|
|
163
|
+
silentInto?: {
|
|
164
|
+
injected: boolean;
|
|
165
|
+
result: SilentIntoResult;
|
|
166
|
+
chars: number;
|
|
167
|
+
};
|
|
168
|
+
/** 本消息里 rewrite 过几次(诊断) */
|
|
169
|
+
rewriteCount: number;
|
|
170
|
+
/** 本消息里破坏性动作成功过几次(熔断统计看增量) */
|
|
171
|
+
destructiveCount: number;
|
|
172
|
+
/** 参与记账去重:同一个扩展在同一条消息上(早+晚)只记一次参与 */
|
|
173
|
+
noted: Set<string>;
|
|
174
|
+
/** 已告警过的提示(一次性,防刷屏) */
|
|
175
|
+
warned: Set<string>;
|
|
176
|
+
}
|
|
177
|
+
/** 取(必要时建)某个 ctx 的扩展会话,并把动作 API 挂上去(幂等) */
|
|
178
|
+
export declare function ensureExtSession(ctx: MiddlewareContext, env: ExtRunEnv): ExtCtxSession;
|
|
179
|
+
/** 只读会话(没挂过则 undefined) */
|
|
180
|
+
export declare function sessionOf(ctx: MiddlewareContext): ExtCtxSession | undefined;
|
|
181
|
+
/**
|
|
182
|
+
* 进入某个扩展的执行:设置归属 + 挂点,返回会话。
|
|
183
|
+
* 调用方(运行器)负责在用完之后吞掉异常 —— 本模块的动作自己不会抛。
|
|
184
|
+
*/
|
|
185
|
+
export declare function enterExtension(ctx: MiddlewareContext, env: ExtRunEnv, ext: {
|
|
186
|
+
file: string;
|
|
187
|
+
name: string;
|
|
188
|
+
}, phase: 'inbound' | 'decide'): ExtCtxSession;
|
|
189
|
+
/**
|
|
190
|
+
* 把动作 API 挂到 ctx 上(每个 ctx 只挂一次)。
|
|
191
|
+
* ⚠️ 一律**不可枚举**:middleware-setup 的命令层会 `{...ctx}` 组 handler 参数,
|
|
192
|
+
* 可枚举的话会把这些动作/取值器一起复制过去(还可能覆盖扩展命令自己的 caps)。
|
|
193
|
+
*/
|
|
194
|
+
/**
|
|
195
|
+
* ★ 把「入站操控」这一组**打包**(2026-10-12 主人定:它也要进统一能力包)。
|
|
196
|
+
*
|
|
197
|
+
* 和上面那些 def 在 ctx 上的是**同一份实现**(不是两套):这里只是换个入口交出去,
|
|
198
|
+
* 好让契约写成「一套包、五套扩展」——包里的 `inbound` 组"有值"就等于"你现在正握着
|
|
199
|
+
* 一条消息的处置权"。别的四套拿它会是 undefined,那是语义,不是缺失。
|
|
200
|
+
*/
|
|
201
|
+
export declare function inboundCapsOf(ctx: MiddlewareContext, s: ExtCtxSession): ExtInboundCaps;
|
|
202
|
+
//# sourceMappingURL=ext-inbound-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ext-inbound-context.d.ts","sourceRoot":"","sources":["../../src/features/ext-inbound-context.ts"],"names":[],"mappings":"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"}
|