@aalis/plugin-flow-control 0.4.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ace Nyan <ace@acenyan.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # @aalis/plugin-flow-control
2
+
3
+ 平台无关流控:禁言/冷却/限速/闲置触发
4
+ ## 角色
5
+
6
+ 运行时插件
7
+
8
+ ## 安装
9
+
10
+ ```bash
11
+ pnpm add @aalis/plugin-flow-control
12
+ ```
13
+
14
+ ## 文档
15
+
16
+ 详见 [docs/plugins/plugin-flow-control.md](../../docs/plugins/plugin-flow-control.md)。
17
+
18
+ ## 许可
19
+
20
+ 见仓库根目录 LICENSE。
@@ -0,0 +1,77 @@
1
+ export interface FlowControlConfig {
2
+ /**
3
+ * 统一作用域名单(multiselect),元素格式 "platform:sessionType[:targetId]",
4
+ * 支持 "*" 通配:onebot:group / onebot:* / *:group / * / onebot:private:10001 。
5
+ * 默认 ['*:group'] 与历史 OneBot ChatFlow 行为一致。
6
+ * 空数组 = 不生效(等于禁用流控);但若存在任一 overrides[].scope 命中也视为启用。
7
+ */
8
+ scopes: string[];
9
+ /**
10
+ * 分作用域覆盖:每条覆盖针对一个 scope 字符串(语法同 scopes,3 段),
11
+ * 仅在该 scope 命中时把列出的字段覆盖到顶层默认之上;未列字段穿透到顶层。
12
+ * 命中时按"最具体优先"挑选(targetId > sessionType > platform > 通配)。
13
+ * 写一条 override 即自动启用该 scope,无需重复在 scopes 中列出。
14
+ * 例:private 单独 10 秒冷却而群聊不变 →
15
+ * overrides: [{ scope: '*:private', cooldownSeconds: 10 }]
16
+ */
17
+ overrides: ScopeOverride[];
18
+ /** 固定间隔:每 N 条消息累计一次触发 */
19
+ fixedInterval: number;
20
+ /** 动态阈值上下限(用于回复后高、长时间无回复后低) */
21
+ activityScoreLower: number;
22
+ activityScoreUpper: number;
23
+ /** 阈值衰减分钟数:上次回复到当前的时间差越大,阈值越低 */
24
+ activityDecayMinutes: number;
25
+ /** 评分本身的衰减分钟数(0 表示评分不主动衰减) */
26
+ scoreDecayMinutes: number;
27
+ /** 回复后冷却(秒) */
28
+ cooldownSeconds: number;
29
+ /** mute 关键词命中时的禁言时长(秒) */
30
+ muteTimeSeconds: number;
31
+ /** 限速窗口(秒,0 关闭) */
32
+ rateLimitWindow: number;
33
+ /** 窗口内最大回复次数 */
34
+ rateLimitMaxReplies: number;
35
+ /** 闲置触发范围 */
36
+ idleTriggerScope: 'off' | 'session' | 'platform';
37
+ idleTriggerStrategy: 'all-quiet' | 'fixed';
38
+ idleTriggerMinutes: number;
39
+ idleTriggerStyle: 'exponential' | 'fixed';
40
+ idleTriggerMaxMinutes: number;
41
+ idleTriggerJitter: boolean;
42
+ /** 闲置触发注入的 system 提示文本 */
43
+ idleTriggerPrompt: string;
44
+ }
45
+ /**
46
+ * 分作用域覆盖项:含 scope 字符串 + 任意字段覆盖。
47
+ * 未指定的字段会从顶层 FlowControlConfig 默认中穿透。
48
+ */
49
+ export interface ScopeOverride {
50
+ scope: string;
51
+ fixedInterval?: number;
52
+ activityScoreLower?: number;
53
+ activityScoreUpper?: number;
54
+ activityDecayMinutes?: number;
55
+ scoreDecayMinutes?: number;
56
+ cooldownSeconds?: number;
57
+ muteTimeSeconds?: number;
58
+ rateLimitWindow?: number;
59
+ rateLimitMaxReplies?: number;
60
+ idleTriggerScope?: 'off' | 'session' | 'platform';
61
+ idleTriggerStrategy?: 'all-quiet' | 'fixed';
62
+ idleTriggerMinutes?: number;
63
+ idleTriggerStyle?: 'exponential' | 'fixed';
64
+ idleTriggerMaxMinutes?: number;
65
+ idleTriggerJitter?: boolean;
66
+ idleTriggerPrompt?: string;
67
+ }
68
+ export declare const defaultFlowControlConfig: FlowControlConfig;
69
+ export declare function resolveFlowControlConfig(raw: Record<string, unknown>): FlowControlConfig;
70
+ /** 统一作用域匹配:`(platform, sessionType[, targetId])` 是否命中 cfg.scopes 或任一 overrides[].scope。 */
71
+ export declare function isScopeEnabled(cfg: FlowControlConfig, platform: string | undefined, sessionType: string | undefined, targetId?: string | undefined): boolean;
72
+ /**
73
+ * 在 cfg.overrides 中找出最具体匹配项,把其字段叠加到 base cfg 之上;无匹配时原样返回。
74
+ * 具体度:精确 targetId > 精确 sessionType > 精确 platform > 通配。
75
+ */
76
+ export declare function resolveEffectiveConfig(cfg: FlowControlConfig, platform: string | undefined, sessionType: string | undefined, targetId?: string | undefined): FlowControlConfig;
77
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IAEjB;;;;;;;OAOG;IACH,SAAS,EAAE,aAAa,EAAE,CAAC;IAE3B,yBAAyB;IACzB,aAAa,EAAE,MAAM,CAAC;IAEtB,+BAA+B;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iCAAiC;IACjC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,8BAA8B;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAE1B,eAAe;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,0BAA0B;IAC1B,eAAe,EAAE,MAAM,CAAC;IAExB,mBAAmB;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB;IAChB,mBAAmB,EAAE,MAAM,CAAC;IAE5B,aAAa;IACb,gBAAgB,EAAE,KAAK,GAAG,SAAS,GAAG,UAAU,CAAC;IACjD,mBAAmB,EAAE,WAAW,GAAG,OAAO,CAAC;IAC3C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,aAAa,GAAG,OAAO,CAAC;IAC1C,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,0BAA0B;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gBAAgB,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,UAAU,CAAC;IAClD,mBAAmB,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAC3C,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,wBAAwB,EAAE,iBAmBtC,CAAC;AAaF,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,CA4BxF;AAED,2FAA2F;AAC3F,wBAAgB,cAAc,CAC5B,GAAG,EAAE,iBAAiB,EACtB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAWT;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,iBAAiB,EACtB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,GAC5B,iBAAiB,CAwBnB"}
package/dist/config.js ADDED
@@ -0,0 +1,178 @@
1
+ // ----- 流控配置类型与默认值 -----
2
+ export const defaultFlowControlConfig = {
3
+ scopes: ['*:group'],
4
+ overrides: [],
5
+ fixedInterval: 5,
6
+ activityScoreLower: 0.3,
7
+ activityScoreUpper: 0.85,
8
+ activityDecayMinutes: 10,
9
+ scoreDecayMinutes: 0,
10
+ cooldownSeconds: 10,
11
+ muteTimeSeconds: 60,
12
+ rateLimitWindow: 0,
13
+ rateLimitMaxReplies: 10,
14
+ idleTriggerScope: 'off',
15
+ idleTriggerStrategy: 'all-quiet',
16
+ idleTriggerMinutes: 180,
17
+ idleTriggerStyle: 'exponential',
18
+ idleTriggerMaxMinutes: 1440,
19
+ idleTriggerJitter: true,
20
+ idleTriggerPrompt: '',
21
+ };
22
+ function parseStringList(val) {
23
+ if (Array.isArray(val))
24
+ return val.filter(Boolean).map(String);
25
+ if (typeof val === 'string' && val.trim()) {
26
+ return val
27
+ .split(',')
28
+ .map(s => s.trim())
29
+ .filter(Boolean);
30
+ }
31
+ return [];
32
+ }
33
+ export function resolveFlowControlConfig(raw) {
34
+ const d = defaultFlowControlConfig;
35
+ return {
36
+ scopes: raw.scopes === undefined ? d.scopes : parseStringList(raw.scopes),
37
+ overrides: parseOverrides(raw.overrides),
38
+ fixedInterval: raw.fixedInterval ?? d.fixedInterval,
39
+ activityScoreLower: raw.activityScoreLower ?? d.activityScoreLower,
40
+ activityScoreUpper: raw.activityScoreUpper ?? d.activityScoreUpper,
41
+ activityDecayMinutes: raw.activityDecayMinutes ?? d.activityDecayMinutes,
42
+ scoreDecayMinutes: raw.scoreDecayMinutes ?? d.scoreDecayMinutes,
43
+ cooldownSeconds: raw.cooldownSeconds ?? d.cooldownSeconds,
44
+ muteTimeSeconds: raw.muteTimeSeconds ?? d.muteTimeSeconds,
45
+ rateLimitWindow: raw.rateLimitWindow ?? d.rateLimitWindow,
46
+ rateLimitMaxReplies: raw.rateLimitMaxReplies ?? d.rateLimitMaxReplies,
47
+ idleTriggerScope: (() => {
48
+ const v = raw.idleTriggerScope;
49
+ return v === 'off' || v === 'session' || v === 'platform' ? v : d.idleTriggerScope;
50
+ })(),
51
+ idleTriggerStrategy: (() => {
52
+ const v = raw.idleTriggerStrategy;
53
+ return v === 'fixed' ? 'fixed' : 'all-quiet';
54
+ })(),
55
+ idleTriggerMinutes: raw.idleTriggerMinutes ?? d.idleTriggerMinutes,
56
+ idleTriggerStyle: raw.idleTriggerStyle ?? d.idleTriggerStyle,
57
+ idleTriggerMaxMinutes: raw.idleTriggerMaxMinutes ?? d.idleTriggerMaxMinutes,
58
+ idleTriggerJitter: raw.idleTriggerJitter ?? d.idleTriggerJitter,
59
+ idleTriggerPrompt: raw.idleTriggerPrompt || d.idleTriggerPrompt,
60
+ };
61
+ }
62
+ /** 统一作用域匹配:`(platform, sessionType[, targetId])` 是否命中 cfg.scopes 或任一 overrides[].scope。 */
63
+ export function isScopeEnabled(cfg, platform, sessionType, targetId) {
64
+ const p = platform ?? '';
65
+ const t = sessionType ?? '';
66
+ const tid = targetId ?? '';
67
+ for (const raw of cfg.scopes) {
68
+ if (matchScopeString(raw, p, t, tid))
69
+ return true;
70
+ }
71
+ for (const o of cfg.overrides) {
72
+ if (matchScopeString(o.scope, p, t, tid))
73
+ return true;
74
+ }
75
+ return false;
76
+ }
77
+ /**
78
+ * 在 cfg.overrides 中找出最具体匹配项,把其字段叠加到 base cfg 之上;无匹配时原样返回。
79
+ * 具体度:精确 targetId > 精确 sessionType > 精确 platform > 通配。
80
+ */
81
+ export function resolveEffectiveConfig(cfg, platform, sessionType, targetId) {
82
+ if (cfg.overrides.length === 0)
83
+ return cfg;
84
+ const p = platform ?? '';
85
+ const t = sessionType ?? '';
86
+ const tid = targetId ?? '';
87
+ let best = null;
88
+ let bestSpec = -1;
89
+ for (const o of cfg.overrides) {
90
+ const parts = parseScopePattern(o.scope);
91
+ if (!matchScopeParts(parts, p, t, tid))
92
+ continue;
93
+ const spec = scopeSpecificity(parts);
94
+ if (spec > bestSpec) {
95
+ best = o;
96
+ bestSpec = spec;
97
+ }
98
+ }
99
+ if (!best)
100
+ return cfg;
101
+ const merged = { ...cfg };
102
+ for (const [k, v] of Object.entries(best)) {
103
+ if (k === 'scope')
104
+ continue;
105
+ if (v === undefined)
106
+ continue;
107
+ merged[k] = v;
108
+ }
109
+ return merged;
110
+ }
111
+ function parseScopePattern(s) {
112
+ const parts = (s || '').split(':');
113
+ return {
114
+ platform: parts[0] || '*',
115
+ sessionType: parts[1] || '*',
116
+ targetId: parts[2] || '*',
117
+ };
118
+ }
119
+ function matchScopeParts(pat, p, t, tid) {
120
+ return ((pat.platform === '*' || pat.platform === '' || pat.platform === p) &&
121
+ (pat.sessionType === '*' || pat.sessionType === '' || pat.sessionType === t) &&
122
+ (pat.targetId === '*' || pat.targetId === '' || pat.targetId === tid));
123
+ }
124
+ function matchScopeString(raw, p, t, tid) {
125
+ return matchScopeParts(parseScopePattern(raw), p, t, tid);
126
+ }
127
+ function scopeSpecificity(pat) {
128
+ return ((pat.platform !== '*' && pat.platform !== '' ? 4 : 0) +
129
+ (pat.sessionType !== '*' && pat.sessionType !== '' ? 2 : 0) +
130
+ (pat.targetId !== '*' && pat.targetId !== '' ? 1 : 0));
131
+ }
132
+ function parseOverrides(raw) {
133
+ if (!Array.isArray(raw))
134
+ return [];
135
+ const out = [];
136
+ for (const item of raw) {
137
+ if (!item || typeof item !== 'object')
138
+ continue;
139
+ const obj = item;
140
+ if (typeof obj.scope !== 'string' || !obj.scope.trim())
141
+ continue;
142
+ const o = { scope: obj.scope.trim() };
143
+ for (const k of [
144
+ 'fixedInterval',
145
+ 'activityScoreLower',
146
+ 'activityScoreUpper',
147
+ 'activityDecayMinutes',
148
+ 'scoreDecayMinutes',
149
+ 'cooldownSeconds',
150
+ 'muteTimeSeconds',
151
+ 'rateLimitWindow',
152
+ 'rateLimitMaxReplies',
153
+ 'idleTriggerMinutes',
154
+ 'idleTriggerMaxMinutes',
155
+ ]) {
156
+ if (typeof obj[k] === 'number')
157
+ o[k] = obj[k];
158
+ }
159
+ if (typeof obj.idleTriggerJitter === 'boolean')
160
+ o.idleTriggerJitter = obj.idleTriggerJitter;
161
+ // 字符串字段:仅在非空时视为覆盖;空串/未填 → 穿透到顶层默认
162
+ if (typeof obj.idleTriggerPrompt === 'string' && obj.idleTriggerPrompt !== '') {
163
+ o.idleTriggerPrompt = obj.idleTriggerPrompt;
164
+ }
165
+ const sScope = obj.idleTriggerScope;
166
+ if (sScope === 'off' || sScope === 'session' || sScope === 'platform')
167
+ o.idleTriggerScope = sScope;
168
+ const sStrat = obj.idleTriggerStrategy;
169
+ if (sStrat === 'all-quiet' || sStrat === 'fixed')
170
+ o.idleTriggerStrategy = sStrat;
171
+ const sStyle = obj.idleTriggerStyle;
172
+ if (sStyle === 'exponential' || sStyle === 'fixed')
173
+ o.idleTriggerStyle = sStyle;
174
+ out.push(o);
175
+ }
176
+ return out;
177
+ }
178
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { I18nResources } from '@aalis/core';
2
+ export declare const DEFAULT_IDLE_PROMPT_ZH = "\u5F53\u524D\u4F1A\u8BDD\u5DF2\u957F\u65F6\u95F4\u65E0\u6D88\u606F\uFF0C\u8BF7\u6839\u636E\u4EBA\u8BBE\u4E3B\u52A8\u5F00\u542F\u4E00\u4E2A\u8F7B\u677E\u7684\u8BDD\u9898\u6216\u95EE\u5019\u3002\u4E0D\u8981\u63D0\u53CA\"\u7CFB\u7EDF\u63D0\u793A\"\u6216\u8868\u660E\u4F60\u662F\u88AB\u89E6\u53D1\u53D1\u8A00\u7684\u3002";
3
+ export declare const i18n: I18nResources;
4
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,eAAO,MAAM,sBAAsB,iUAA2D,CAAC;AAE/F,eAAO,MAAM,IAAI,EAAE,aAelB,CAAC"}
package/dist/i18n.js ADDED
@@ -0,0 +1,18 @@
1
+ export const DEFAULT_IDLE_PROMPT_ZH = '当前会话已长时间无消息,请根据人设主动开启一个轻松的话题或问候。不要提及"系统提示"或表明你是被触发发言的。';
2
+ export const i18n = {
3
+ 'zh-CN': {
4
+ 'plugin.flowControl.displayName': '消息流控',
5
+ 'config.flowControl.promptLocale.label': 'Prompt 语言',
6
+ 'config.flowControl.promptLocale.description': '默认闲置触发提示词使用的语言。自定义闲置触发提示词不受此项影响。',
7
+ 'config.flowControl.promptLocale.auto': '跟随全局设置',
8
+ 'prompt.flowControl.idle': DEFAULT_IDLE_PROMPT_ZH,
9
+ },
10
+ 'en-US': {
11
+ 'plugin.flowControl.displayName': 'Flow Control',
12
+ 'config.flowControl.promptLocale.label': 'Prompt Language',
13
+ 'config.flowControl.promptLocale.description': 'Language used by the default idle-trigger prompt. Custom idle-trigger prompts are left unchanged.',
14
+ 'config.flowControl.promptLocale.auto': 'Follow Global Setting',
15
+ 'prompt.flowControl.idle': 'This conversation has been idle for a long time. Based on the persona, proactively start a light topic or greeting. Do not mention a "system prompt" or reveal that this message was triggered automatically.',
16
+ },
17
+ };
18
+ //# sourceMappingURL=i18n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,sBAAsB,GAAG,wDAAwD,CAAC;AAE/F,MAAM,CAAC,MAAM,IAAI,GAAkB;IACjC,OAAO,EAAE;QACP,gCAAgC,EAAE,MAAM;QACxC,uCAAuC,EAAE,WAAW;QACpD,6CAA6C,EAAE,kCAAkC;QACjF,sCAAsC,EAAE,QAAQ;QAChD,yBAAyB,EAAE,sBAAsB;KAClD;IACD,OAAO,EAAE;QACP,gCAAgC,EAAE,cAAc;QAChD,uCAAuC,EAAE,iBAAiB;QAC1D,6CAA6C,EAAE,mGAAmG;QAClJ,sCAAsC,EAAE,uBAAuB;QAC/D,yBAAyB,EAAE,+MAA+M;KAC3O;CACF,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { Context } from '@aalis/core';
2
+ import { type FlowControlConfig } from './config.js';
3
+ import type { MutableFlowSessionState } from './state.js';
4
+ export declare function scheduleSessionIdle(ctx: Context, cfg: FlowControlConfig, state: MutableFlowSessionState, sessionId: string, platform: string, reschedule: () => void): void;
5
+ export declare function clearSessionIdle(state: MutableFlowSessionState): void;
6
+ export declare class PlatformIdleScheduler {
7
+ private readonly ctx;
8
+ private readonly cfg;
9
+ private readonly states;
10
+ private timer;
11
+ private running;
12
+ constructor(ctx: Context, cfg: FlowControlConfig, states: Map<string, MutableFlowSessionState>);
13
+ start(): void;
14
+ stop(): void;
15
+ /** 距离全部会话静默达标还需多少 ms(返回 0 = 已达标 / 无活跃会话) */
16
+ private timeUntilAllQuiet;
17
+ /** 选一个最适合主动开聊的 sessionId */
18
+ private pickTarget;
19
+ private runOnce;
20
+ private schedule;
21
+ }
22
+ //# sourceMappingURL=idle-scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idle-scheduler.d.ts","sourceRoot":"","sources":["../src/idle-scheduler.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,KAAK,iBAAiB,EAA0B,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AA0B1D,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,uBAAuB,EAC9B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,IAAI,GACrB,IAAI,CA8BN;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAKrE;AAID,qBAAa,qBAAqB;IAK9B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IANzB,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,OAAO,CAAS;gBAGL,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,iBAAiB,EACtB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC;IAG/D,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI;IAOZ,4CAA4C;IAC5C,OAAO,CAAC,iBAAiB;IAWzB,4BAA4B;IAC5B,OAAO,CAAC,UAAU;YAoBJ,OAAO;IAqBrB,OAAO,CAAC,QAAQ;CA6BjB"}
@@ -0,0 +1,174 @@
1
+ // ----- 闲置触发调度器 -----
2
+ //
3
+ // session 范围:每会话一个 setTimeout,触发时合成 system 提示注入 gateway。
4
+ // platform 范围:跨平台一个 tick,挑"最久未联系"的 session 主动开聊。
5
+ import { resolveEffectiveConfig } from './config.js';
6
+ const DEFAULT_PROMPT = '当前会话已长时间无消息,请根据人设主动开启一个轻松的话题或问候。不要提及"系统提示"或表明你是被触发发言的。';
7
+ function buildIdleMessage(sessionId, platform, prompt) {
8
+ return {
9
+ content: prompt?.trim() ? prompt : DEFAULT_PROMPT,
10
+ sessionId,
11
+ platform,
12
+ source: 'idle-trigger',
13
+ triggerType: 'idle',
14
+ };
15
+ }
16
+ async function injectIdle(ctx, msg) {
17
+ const gateway = ctx.getService('gateway');
18
+ if (gateway) {
19
+ await gateway.ingressMessage(msg);
20
+ }
21
+ else {
22
+ await ctx.emit('inbound:message', msg);
23
+ }
24
+ }
25
+ // ===== session 范围调度 =====
26
+ export function scheduleSessionIdle(ctx, cfg, state, sessionId, platform, reschedule) {
27
+ clearSessionIdle(state);
28
+ if (cfg.idleTriggerScope !== 'session')
29
+ return;
30
+ if (cfg.idleTriggerMinutes <= 0)
31
+ return;
32
+ let delayMs;
33
+ if (cfg.idleTriggerStyle === 'exponential') {
34
+ delayMs = Math.min(cfg.idleTriggerMinutes * state.idleBackoff * 60 * 1000, cfg.idleTriggerMaxMinutes * 60 * 1000);
35
+ }
36
+ else {
37
+ delayMs = cfg.idleTriggerMinutes * 60 * 1000;
38
+ }
39
+ if (cfg.idleTriggerJitter) {
40
+ const jitter = delayMs * (0.1 * (Math.random() * 2 - 1));
41
+ delayMs = Math.max(60_000, delayMs + jitter);
42
+ }
43
+ state.idleTimer = setTimeout(async () => {
44
+ try {
45
+ ctx.logger.info(`[flow] 空闲触发: session=${sessionId} (退避 x${state.idleBackoff})`);
46
+ if (cfg.idleTriggerStyle === 'exponential') {
47
+ state.idleBackoff = Math.min(state.idleBackoff * 2, 64);
48
+ }
49
+ await injectIdle(ctx, buildIdleMessage(sessionId, platform, cfg.idleTriggerPrompt));
50
+ reschedule();
51
+ }
52
+ catch (err) {
53
+ ctx.logger.warn(`空闲触发执行失败: ${err}`);
54
+ }
55
+ }, delayMs);
56
+ ctx.logger.debug(`[flow] 空闲触发已调度: session=${sessionId}, ${Math.round(delayMs / 60_000)}分钟后`);
57
+ }
58
+ export function clearSessionIdle(state) {
59
+ if (state.idleTimer) {
60
+ clearTimeout(state.idleTimer);
61
+ state.idleTimer = null;
62
+ }
63
+ }
64
+ // ===== platform 范围调度 =====
65
+ export class PlatformIdleScheduler {
66
+ ctx;
67
+ cfg;
68
+ states;
69
+ timer = null;
70
+ running = false;
71
+ constructor(ctx, cfg, states) {
72
+ this.ctx = ctx;
73
+ this.cfg = cfg;
74
+ this.states = states;
75
+ }
76
+ start() {
77
+ this.schedule();
78
+ }
79
+ stop() {
80
+ if (this.timer) {
81
+ clearTimeout(this.timer);
82
+ this.timer = null;
83
+ }
84
+ }
85
+ /** 距离全部会话静默达标还需多少 ms(返回 0 = 已达标 / 无活跃会话) */
86
+ timeUntilAllQuiet(thresholdMs) {
87
+ let maxLast = 0;
88
+ for (const s of this.states.values()) {
89
+ const last = Math.max(s.lastMessageTime, s.lastReplyTime);
90
+ if (last > maxLast)
91
+ maxLast = last;
92
+ }
93
+ if (maxLast === 0)
94
+ return 0;
95
+ const elapsed = Date.now() - maxLast;
96
+ return Math.max(0, thresholdMs - elapsed);
97
+ }
98
+ /** 选一个最适合主动开聊的 sessionId */
99
+ pickTarget() {
100
+ const now = Date.now();
101
+ let best = null;
102
+ for (const [sid, s] of this.states) {
103
+ if (s.mutedUntil > now)
104
+ continue;
105
+ if (s.cooldownUntil > now)
106
+ continue;
107
+ const e = resolveEffectiveConfig(this.cfg, s.platform, s.sessionType, s.targetId);
108
+ if (e.rateLimitWindow > 0 && e.rateLimitMaxReplies > 0) {
109
+ const windowStart = now - e.rateLimitWindow * 1000;
110
+ const used = s.replyTimestamps.filter(t => t > windowStart).length;
111
+ if (used >= e.rateLimitMaxReplies)
112
+ continue;
113
+ }
114
+ const lastActivity = Math.max(s.lastMessageTime, s.lastReplyTime);
115
+ if (!best || lastActivity < best.lastActivity) {
116
+ best = { sessionId: sid, lastActivity, platform: s.platform };
117
+ }
118
+ }
119
+ return best;
120
+ }
121
+ async runOnce() {
122
+ if (this.running)
123
+ return;
124
+ this.running = true;
125
+ try {
126
+ const target = this.pickTarget();
127
+ if (!target) {
128
+ this.ctx.logger.debug('[flow] platform idle tick: 无可发送候选,跳过');
129
+ return;
130
+ }
131
+ this.ctx.logger.info(`[flow] platform idle tick: 主动开聊 → ${target.sessionId} ` +
132
+ `(idle=${Math.round((Date.now() - target.lastActivity) / 60_000)}min)`);
133
+ await injectIdle(this.ctx, buildIdleMessage(target.sessionId, target.platform, this.cfg.idleTriggerPrompt));
134
+ }
135
+ catch (err) {
136
+ this.ctx.logger.warn(`[flow] platform idle tick 失败: ${err}`);
137
+ }
138
+ finally {
139
+ this.running = false;
140
+ }
141
+ }
142
+ schedule() {
143
+ if (this.timer) {
144
+ clearTimeout(this.timer);
145
+ this.timer = null;
146
+ }
147
+ if (this.cfg.idleTriggerScope !== 'platform')
148
+ return;
149
+ if (this.cfg.idleTriggerMinutes <= 0)
150
+ return;
151
+ const baseMs = this.cfg.idleTriggerMinutes * 60_000;
152
+ let delay;
153
+ if (this.cfg.idleTriggerStrategy === 'fixed') {
154
+ delay = baseMs;
155
+ }
156
+ else {
157
+ delay = this.timeUntilAllQuiet(baseMs);
158
+ if (delay === 0)
159
+ delay = 1000;
160
+ }
161
+ this.timer = setTimeout(async () => {
162
+ if (this.cfg.idleTriggerStrategy === 'all-quiet') {
163
+ const remaining = this.timeUntilAllQuiet(baseMs);
164
+ if (remaining > 0) {
165
+ this.schedule();
166
+ return;
167
+ }
168
+ }
169
+ await this.runOnce();
170
+ this.schedule();
171
+ }, delay);
172
+ }
173
+ }
174
+ //# sourceMappingURL=idle-scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idle-scheduler.js","sourceRoot":"","sources":["../src/idle-scheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { ConfigSchema, Context } from '@aalis/core';
2
+ import '@aalis/plugin-gateway-api';
3
+ export type { FlowControlService, FlowSessionStateSnapshot } from './types.js';
4
+ import { type FlowControlConfig } from './config.js';
5
+ export declare const name = "@aalis/plugin-flow-control";
6
+ export declare const displayName = "\u6D88\u606F\u6D41\u63A7";
7
+ export declare const subsystem = "core";
8
+ export declare const provides: string[];
9
+ export declare const inject: {
10
+ required: string[];
11
+ optional: string[];
12
+ };
13
+ export declare const configSchema: ConfigSchema;
14
+ export declare const defaultConfig: FlowControlConfig;
15
+ export declare function apply(ctx: Context, raw: Record<string, unknown>): Promise<void>;
16
+ export type { FlowControlConfig };
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAOzD,OAAO,2BAA2B,CAAC;AAEnC,YAAY,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE/E,OAAO,EAEL,KAAK,iBAAiB,EAIvB,MAAM,aAAa,CAAC;AAcrB,eAAO,MAAM,IAAI,+BAA+B,CAAC;AACjD,eAAO,MAAM,WAAW,6BAAS,CAAC;AAClC,eAAO,MAAM,SAAS,SAAS,CAAC;AAChC,eAAO,MAAM,QAAQ,UAAmB,CAAC;AACzC,eAAO,MAAM,MAAM;;;CAGlB,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,YA4G1B,CAAC;AAEF,eAAO,MAAM,aAAa,mBAA2B,CAAC;AAItD,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAkRrF;AAGD,YAAY,EAAE,iBAAiB,EAAE,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,404 @@
1
+ import { INBOUND_PHASE } from '@aalis/plugin-gateway-api';
2
+ import { createStorageGateway } from '@aalis/plugin-storage-api';
3
+ import '@aalis/plugin-gateway-api';
4
+ import { defaultFlowControlConfig, isScopeEnabled, resolveEffectiveConfig, resolveFlowControlConfig, } from './config.js';
5
+ import { clearSessionIdle, PlatformIdleScheduler, scheduleSessionIdle } from './idle-scheduler.js';
6
+ import { applyScoreDecay, calculateScoreIncrement, createState, getCurrentThreshold, rateLimitUsedNow, snapshot, } from './state.js';
7
+ // ----- 元数据 -----
8
+ export const name = '@aalis/plugin-flow-control';
9
+ export const displayName = '消息流控';
10
+ export const subsystem = 'core';
11
+ export const provides = ['flow-control'];
12
+ export const inject = {
13
+ required: ['gateway'],
14
+ optional: ['message-archive'],
15
+ };
16
+ export const configSchema = {
17
+ scopes: {
18
+ type: 'multiselect',
19
+ label: '生效作用域',
20
+ default: defaultFlowControlConfig.scopes,
21
+ dynamicOptions: 'gateway-scopes',
22
+ allowCustom: true,
23
+ description: '格式 platform:sessionType,支持通配 *;onebot:group / onebot:* / *:group / *。默认 *:group 与历史 OneBot 行为一致。',
24
+ },
25
+ fixedInterval: { type: 'number', label: '固定间隔(每 N 条触发)', default: defaultFlowControlConfig.fixedInterval },
26
+ activityScoreLower: { type: 'number', label: '活跃指数下限', default: defaultFlowControlConfig.activityScoreLower },
27
+ activityScoreUpper: { type: 'number', label: '活跃指数上限', default: defaultFlowControlConfig.activityScoreUpper },
28
+ activityDecayMinutes: {
29
+ type: 'number',
30
+ label: '阈值衰减分钟',
31
+ default: defaultFlowControlConfig.activityDecayMinutes,
32
+ },
33
+ scoreDecayMinutes: {
34
+ type: 'number',
35
+ label: '评分衰减分钟(0=不衰减)',
36
+ default: defaultFlowControlConfig.scoreDecayMinutes,
37
+ },
38
+ cooldownSeconds: { type: 'number', label: '回复后冷却(秒)', default: defaultFlowControlConfig.cooldownSeconds },
39
+ muteTimeSeconds: { type: 'number', label: '禁言关键词时长(秒)', default: defaultFlowControlConfig.muteTimeSeconds },
40
+ rateLimitWindow: {
41
+ type: 'number',
42
+ label: '限速窗口(秒,0=关闭)',
43
+ default: defaultFlowControlConfig.rateLimitWindow,
44
+ },
45
+ rateLimitMaxReplies: {
46
+ type: 'number',
47
+ label: '窗口内最大回复数',
48
+ default: defaultFlowControlConfig.rateLimitMaxReplies,
49
+ },
50
+ idleTriggerScope: {
51
+ type: 'select',
52
+ label: '闲置触发范围',
53
+ default: defaultFlowControlConfig.idleTriggerScope,
54
+ options: [
55
+ { label: 'off (关闭)', value: 'off' },
56
+ { label: 'session (每会话独立定时)', value: 'session' },
57
+ { label: 'platform (跨会话选举)', value: 'platform' },
58
+ ],
59
+ },
60
+ idleTriggerStrategy: {
61
+ type: 'select',
62
+ label: '闲置触发策略',
63
+ default: defaultFlowControlConfig.idleTriggerStrategy,
64
+ options: [
65
+ { label: 'all-quiet (所有会话都静默时)', value: 'all-quiet' },
66
+ { label: 'fixed (固定间隔)', value: 'fixed' },
67
+ ],
68
+ },
69
+ idleTriggerMinutes: { type: 'number', label: '闲置触发分钟', default: defaultFlowControlConfig.idleTriggerMinutes },
70
+ idleTriggerStyle: {
71
+ type: 'select',
72
+ label: '闲置触发风格',
73
+ default: defaultFlowControlConfig.idleTriggerStyle,
74
+ options: [
75
+ { label: 'exponential (指数退避)', value: 'exponential' },
76
+ { label: 'fixed (固定)', value: 'fixed' },
77
+ ],
78
+ },
79
+ idleTriggerMaxMinutes: {
80
+ type: 'number',
81
+ label: '闲置触发上限分钟',
82
+ default: defaultFlowControlConfig.idleTriggerMaxMinutes,
83
+ },
84
+ idleTriggerJitter: { type: 'boolean', label: '闲置触发抖动', default: defaultFlowControlConfig.idleTriggerJitter },
85
+ idleTriggerPrompt: { type: 'string', label: '闲置触发系统提示', default: defaultFlowControlConfig.idleTriggerPrompt },
86
+ overrides: {
87
+ type: 'array',
88
+ label: '分作用域覆盖',
89
+ description: '每项 {scope: "platform:sessionType[:targetId]", ...} 仅在该 scope 命中时覆盖列出的字段;字段留空(或不填)= 沿用上方默认,不会被覆盖为 0/空。最具体匹配优先(targetId > sessionType > platform > 通配)。例:scope="*:private", cooldownSeconds=10 让所有平台私聊单独 10s 冷却,其他字段继续走默认。',
90
+ default: [],
91
+ items: {
92
+ scope: {
93
+ type: 'string',
94
+ label: '作用域',
95
+ description: '格式 platform:sessionType[:targetId],支持 *',
96
+ required: true,
97
+ },
98
+ fixedInterval: { type: 'number', label: '固定间隔(每 N 条触发)' },
99
+ activityScoreLower: { type: 'number', label: '活跃指数下限' },
100
+ activityScoreUpper: { type: 'number', label: '活跃指数上限' },
101
+ activityDecayMinutes: { type: 'number', label: '阈值衰减分钟' },
102
+ scoreDecayMinutes: { type: 'number', label: '评分衰减分钟' },
103
+ cooldownSeconds: { type: 'number', label: '回复后冷却(秒)' },
104
+ muteTimeSeconds: { type: 'number', label: '禁言关键词时长(秒)' },
105
+ rateLimitWindow: { type: 'number', label: '限速窗口(秒)' },
106
+ rateLimitMaxReplies: { type: 'number', label: '窗口内最大回复数' },
107
+ idleTriggerScope: {
108
+ type: 'select',
109
+ label: '闲置触发范围',
110
+ options: [
111
+ { label: 'off', value: 'off' },
112
+ { label: 'session', value: 'session' },
113
+ { label: 'platform', value: 'platform' },
114
+ ],
115
+ },
116
+ idleTriggerMinutes: { type: 'number', label: '闲置触发分钟' },
117
+ idleTriggerMaxMinutes: { type: 'number', label: '闲置触发上限分钟' },
118
+ idleTriggerJitter: { type: 'boolean', label: '闲置触发抖动' },
119
+ idleTriggerPrompt: { type: 'string', label: '闲置触发系统提示' },
120
+ },
121
+ },
122
+ };
123
+ export const defaultConfig = defaultFlowControlConfig;
124
+ // ----- 入口 -----
125
+ export async function apply(ctx, raw) {
126
+ const cfg = resolveFlowControlConfig(raw);
127
+ const states = new Map();
128
+ const platformIdle = new PlatformIdleScheduler(ctx, cfg, states);
129
+ // ===== mutedUntil 持久化(仅此字段) =====
130
+ // 其他运行时态(cooldownUntil/replyTimestamps/activityScore等)都是秒级短期,
131
+ // 重启后重建无危;但 mutedUntil 可能是小时级的「用户意图」,丢失会导致重启后静默解除。
132
+ const storage = createStorageGateway(ctx);
133
+ const muteStateUri = 'data:/flow-control-mutes.json';
134
+ async function loadMuteState() {
135
+ try {
136
+ let raw;
137
+ try {
138
+ raw = (await storage.readFile(muteStateUri, 'utf-8'));
139
+ }
140
+ catch {
141
+ return;
142
+ }
143
+ const data = JSON.parse(raw);
144
+ if (!data || typeof data !== 'object')
145
+ return;
146
+ const now = Date.now();
147
+ let restored = 0;
148
+ for (const [sessionId, entry] of Object.entries(data)) {
149
+ const mutedUntil = Number(entry?.mutedUntil ?? 0);
150
+ if (!mutedUntil || mutedUntil <= now)
151
+ continue;
152
+ const platform = String(entry?.platform ?? '');
153
+ const s = createState(platform);
154
+ s.mutedUntil = mutedUntil;
155
+ states.set(sessionId, s);
156
+ restored++;
157
+ }
158
+ if (restored > 0)
159
+ ctx.logger.info(`[flow] 已恢复 ${restored} 个未过期的禁言状态`);
160
+ }
161
+ catch (err) {
162
+ ctx.logger.warn(`[flow] 加载禁言状态失败: ${err}`);
163
+ }
164
+ }
165
+ let saveChain = Promise.resolve();
166
+ function saveMuteState() {
167
+ const now = Date.now();
168
+ const out = {};
169
+ for (const [sessionId, s] of states.entries()) {
170
+ if (s.mutedUntil > now)
171
+ out[sessionId] = { platform: s.platform ?? '', mutedUntil: s.mutedUntil };
172
+ }
173
+ const payload = JSON.stringify(out, null, 2);
174
+ saveChain = saveChain
175
+ .then(() => storage.writeFile(muteStateUri, payload))
176
+ .catch(err => {
177
+ ctx.logger.warn(`[flow] 持久化禁言状态失败: ${err}`);
178
+ });
179
+ }
180
+ await loadMuteState();
181
+ /** 从 IncomingMessage 派生 per-scope override 用的 targetId(群=groupId / 私=userId / 其他=空) */
182
+ function extractTargetId(message) {
183
+ if (message.sessionType === 'group')
184
+ return message.groupId ?? '';
185
+ if (message.sessionType === 'private')
186
+ return message.userId ?? '';
187
+ return '';
188
+ }
189
+ function getOrCreate(sessionId, platform, sessionType = '', targetId = '') {
190
+ let s = states.get(sessionId);
191
+ if (!s) {
192
+ s = createState(platform, sessionType, targetId);
193
+ states.set(sessionId, s);
194
+ }
195
+ else {
196
+ if (!s.platform && platform)
197
+ s.platform = platform;
198
+ if (!s.sessionType && sessionType)
199
+ s.sessionType = sessionType;
200
+ if (!s.targetId && targetId)
201
+ s.targetId = targetId;
202
+ }
203
+ return s;
204
+ }
205
+ /** 按 state 上下文解析生效 cfg(应用 overrides) */
206
+ function eff(s) {
207
+ if (!s)
208
+ return cfg;
209
+ return resolveEffectiveConfig(cfg, s.platform, s.sessionType, s.targetId);
210
+ }
211
+ function logStatus(sessionId, s, label) {
212
+ const e = eff(s);
213
+ const threshold = getCurrentThreshold(s, e);
214
+ ctx.logger.debug(`[flow] ${label} | session=${sessionId} | ` +
215
+ `计数=${s.messageCount}/${e.fixedInterval} | ` +
216
+ `指数=${s.activityScore.toFixed(3)} (阈值=${threshold.toFixed(3)})`);
217
+ }
218
+ /** 把"被流控吞掉"的入站消息归档到 message-archive,下次触发时作为上下文 */
219
+ async function shadowArchive(message) {
220
+ const archive = ctx.getService('message-archive');
221
+ if (!archive)
222
+ return;
223
+ try {
224
+ await archive.archiveIncoming(message);
225
+ }
226
+ catch (err) {
227
+ ctx.logger.warn(`[flow] shadow 归档失败: ${err}`);
228
+ }
229
+ }
230
+ // ===== Service 实现 =====
231
+ const service = {
232
+ ensureState(sessionId, platform, sessionType, targetId) {
233
+ getOrCreate(sessionId, platform, sessionType, targetId);
234
+ },
235
+ getStateSnapshot(sessionId) {
236
+ const s = states.get(sessionId);
237
+ return s ? snapshot(s, eff(s)) : undefined;
238
+ },
239
+ recordIncoming(sessionId, platform, userId, sessionType, targetId) {
240
+ const s = getOrCreate(sessionId, platform, sessionType, targetId);
241
+ const e = eff(s);
242
+ const now = Date.now();
243
+ applyScoreDecay(s, e);
244
+ if (userId) {
245
+ const prev = s.userInteractions.get(userId) ?? { count: 0, lastTime: 0 };
246
+ s.userInteractions.set(userId, { count: prev.count + 1, lastTime: now });
247
+ }
248
+ s.lastMessageTime = now;
249
+ s.messageCount++;
250
+ s.activityScore += calculateScoreIncrement(s, e, userId);
251
+ },
252
+ recordTriggered(sessionId) {
253
+ const s = states.get(sessionId);
254
+ if (!s)
255
+ return;
256
+ s.messageCount = 0;
257
+ s.activityScore = 0;
258
+ s.lastReplyTime = Date.now();
259
+ s.idleBackoff = 1;
260
+ },
261
+ recordReply(sessionId, platform) {
262
+ const s = getOrCreate(sessionId, platform);
263
+ const e = eff(s);
264
+ if (e.cooldownSeconds > 0) {
265
+ s.cooldownUntil = Date.now() + e.cooldownSeconds * 1000;
266
+ }
267
+ s.idleBackoff = 1;
268
+ s.replyTimestamps.push(Date.now());
269
+ this.rescheduleIdle(sessionId, platform);
270
+ },
271
+ isCoolingDown(sessionId) {
272
+ const s = states.get(sessionId);
273
+ return !!s && Date.now() < s.cooldownUntil;
274
+ },
275
+ isMuted(sessionId) {
276
+ const s = states.get(sessionId);
277
+ return !!s && Date.now() < s.mutedUntil;
278
+ },
279
+ isRateLimited(sessionId) {
280
+ const s = states.get(sessionId);
281
+ if (!s)
282
+ return false;
283
+ const e = eff(s);
284
+ if (e.rateLimitWindow <= 0 || e.rateLimitMaxReplies <= 0)
285
+ return false;
286
+ return rateLimitUsedNow(s, e) >= e.rateLimitMaxReplies;
287
+ },
288
+ setMuted(sessionId, durationSec, platform) {
289
+ let s = states.get(sessionId);
290
+ if (!s && platform && durationSec > 0) {
291
+ s = getOrCreate(sessionId, platform);
292
+ }
293
+ if (!s)
294
+ return;
295
+ if (durationSec <= 0) {
296
+ s.mutedUntil = 0;
297
+ saveMuteState();
298
+ ctx.logger.info(`[flow] 已解除自禁言: session=${sessionId}`);
299
+ return;
300
+ }
301
+ s.mutedUntil = Date.now() + durationSec * 1000;
302
+ s.messageCount = 0;
303
+ s.activityScore = 0;
304
+ clearSessionIdle(s);
305
+ saveMuteState();
306
+ ctx.logger.info(`[flow] 已设置自禁言: session=${sessionId}, ${durationSec}s`);
307
+ },
308
+ getThreshold(sessionId) {
309
+ const s = states.get(sessionId);
310
+ if (!s)
311
+ return cfg.activityScoreLower;
312
+ return getCurrentThreshold(s, eff(s));
313
+ },
314
+ rescheduleIdle(sessionId, platform) {
315
+ const s = states.get(sessionId);
316
+ if (!s)
317
+ return;
318
+ scheduleSessionIdle(ctx, eff(s), s, sessionId, platform, () => this.rescheduleIdle(sessionId, platform));
319
+ },
320
+ };
321
+ ctx.provide('flow-control', service);
322
+ ctx.logger.info(`[flow] 已启用 (固定间隔=${cfg.fixedInterval}, 阈值=${cfg.activityScoreLower}~${cfg.activityScoreUpper}, ` +
323
+ `冷却=${cfg.cooldownSeconds}s, 限速=${cfg.rateLimitWindow}s/${cfg.rateLimitMaxReplies}次, ` +
324
+ `idle=${cfg.idleTriggerScope}/${cfg.idleTriggerStrategy}, scopes=${cfg.scopes.join('|') || '<空>'}, ` +
325
+ `overrides=${cfg.overrides.length})`);
326
+ // ===== inbound:flow 相位:流控前置闸门 =====
327
+ // 由 plugin-gateway 在 inbound:command 之后、inbound:trigger 之前触发。
328
+ // 默认 scopes=['*:group'] 与历史 OneBot ChatFlow 行为一致;
329
+ // overrides 中任一 scope 命中也视为启用(用于 *:private 等单独覆盖场景)。
330
+ ctx.middleware(INBOUND_PHASE.FLOW, async (data, next) => {
331
+ const { message } = data;
332
+ if (!isScopeEnabled(cfg, message.platform, message.sessionType, extractTargetId(message)))
333
+ return next();
334
+ if (message.source === 'idle-trigger')
335
+ return next(); // 内部注入不再过流控
336
+ const targetId = extractTargetId(message);
337
+ service.ensureState(message.sessionId, message.platform, message.sessionType, targetId);
338
+ service.recordIncoming(message.sessionId, message.platform, message.userId, message.sessionType, targetId);
339
+ const s = states.get(message.sessionId);
340
+ if (service.isMuted(message.sessionId)) {
341
+ // 禁言期不重置 idle timer:避免在禁言结束后被立即唤醒重复触发。
342
+ logStatus(message.sessionId, s, '禁言中 → 吞噬');
343
+ await shadowArchive(message);
344
+ return; // swallow
345
+ }
346
+ if (service.isCoolingDown(message.sessionId)) {
347
+ logStatus(message.sessionId, s, '冷却中 → 吞噬');
348
+ await shadowArchive(message);
349
+ service.rescheduleIdle(message.sessionId, message.platform);
350
+ return; // swallow
351
+ }
352
+ if (service.isRateLimited(message.sessionId)) {
353
+ logStatus(message.sessionId, s, '限速 → 吞噬');
354
+ await shadowArchive(message);
355
+ service.rescheduleIdle(message.sessionId, message.platform);
356
+ return; // swallow
357
+ }
358
+ // 通过流控前置闸门:交给下一相位(inbound:trigger → inbound:dispatch)
359
+ await next();
360
+ });
361
+ // 出站消息后记录冷却 / 重置退避(同样仅对群会话计入流控)
362
+ ctx.on('outbound:message', (msg) => {
363
+ if (!msg.sessionId)
364
+ return;
365
+ if (msg.source !== 'agent')
366
+ return; // 命令/系统回复不算"对话回复"
367
+ const s = states.get(msg.sessionId);
368
+ if (!s)
369
+ return; // 没有 state 说明该 session 从未走过群流控(私聊/CLI 等),跳过
370
+ service.recordReply(msg.sessionId, s.platform);
371
+ });
372
+ // 平台级 idle 启动
373
+ ctx.on('ready', () => {
374
+ platformIdle.start();
375
+ });
376
+ // 长寿进程下避免 states 无限增长:每天扫描一次,清理 30 天未活动且无禁言/冷却挂起的会话
377
+ const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000;
378
+ const SWEEP_INTERVAL_MS = 24 * 60 * 60 * 1000;
379
+ const sweepTimer = setInterval(() => {
380
+ const now = Date.now();
381
+ let cleaned = 0;
382
+ for (const [sid, s] of states) {
383
+ const lastActive = Math.max(s.lastMessageTime, s.lastReplyTime);
384
+ const hasPending = s.mutedUntil > now || s.cooldownUntil > now || !!s.idleTimer;
385
+ if (!hasPending && lastActive > 0 && now - lastActive > SESSION_TTL_MS) {
386
+ clearSessionIdle(s);
387
+ states.delete(sid);
388
+ cleaned++;
389
+ }
390
+ }
391
+ if (cleaned > 0)
392
+ ctx.logger.debug(`[flow] TTL 清理已淘汰 ${cleaned} 个长期非活跃会话状态`);
393
+ }, SWEEP_INTERVAL_MS);
394
+ if (typeof sweepTimer.unref === 'function')
395
+ sweepTimer.unref();
396
+ ctx.onDispose(() => {
397
+ clearInterval(sweepTimer);
398
+ platformIdle.stop();
399
+ for (const s of states.values())
400
+ clearSessionIdle(s);
401
+ states.clear();
402
+ });
403
+ }
404
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { FlowControlConfig } from './config.js';
2
+ import type { FlowSessionStateSnapshot } from './types.js';
3
+ export interface MutableFlowSessionState {
4
+ messageCount: number;
5
+ lastReplyTime: number;
6
+ lastMessageTime: number;
7
+ activityScore: number;
8
+ cooldownUntil: number;
9
+ mutedUntil: number;
10
+ idleTimer: ReturnType<typeof setTimeout> | null;
11
+ idleBackoff: number;
12
+ userInteractions: Map<string, {
13
+ count: number;
14
+ lastTime: number;
15
+ }>;
16
+ /** 滑动窗口内的回复时间戳(用于防 DDoS 限速) */
17
+ replyTimestamps: number[];
18
+ /** 该 session 所属 platform(首次记录时设置;用于 idle 调度找回) */
19
+ platform: string;
20
+ /** 该 session 的 sessionType(如 group/private/channel);用于 per-scope 覆盖匹配 */
21
+ sessionType: string;
22
+ /** 该 session 的目标 id(群号/用户号/频道号);用于 per-scope 覆盖匹配 */
23
+ targetId: string;
24
+ }
25
+ export declare function createState(platform: string, sessionType?: string, targetId?: string): MutableFlowSessionState;
26
+ /** 当前阈值(动态衰减) */
27
+ export declare function getCurrentThreshold(state: MutableFlowSessionState, cfg: FlowControlConfig): number;
28
+ /** 评分按距离上次消息的时间线性衰减(原地修改) */
29
+ export declare function applyScoreDecay(state: MutableFlowSessionState, cfg: FlowControlConfig): void;
30
+ /** 计算单条入站对评分的增量(受用户交互权重影响) */
31
+ export declare function calculateScoreIncrement(state: MutableFlowSessionState, cfg: FlowControlConfig, userId?: string): number;
32
+ export declare function rateLimitUsedNow(state: MutableFlowSessionState, cfg: FlowControlConfig): number;
33
+ export declare function snapshot(state: MutableFlowSessionState, cfg: FlowControlConfig): FlowSessionStateSnapshot;
34
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAC;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,+BAA+B;IAC/B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,SAAK,EAAE,QAAQ,SAAK,GAAG,uBAAuB,CAgBtG;AAED,iBAAiB;AACjB,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,uBAAuB,EAAE,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAMlG;AAED,6BAA6B;AAC7B,wBAAgB,eAAe,CAAC,KAAK,EAAE,uBAAuB,EAAE,GAAG,EAAE,iBAAiB,GAAG,IAAI,CAO5F;AAED,8BAA8B;AAC9B,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,uBAAuB,EAC9B,GAAG,EAAE,iBAAiB,EACtB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAUR;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,uBAAuB,EAAE,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAI/F;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,uBAAuB,EAAE,GAAG,EAAE,iBAAiB,GAAG,wBAAwB,CAczG"}
package/dist/state.js ADDED
@@ -0,0 +1,72 @@
1
+ // ----- FlowSessionState 内部实现 + 衰减/计数算法 -----
2
+ export function createState(platform, sessionType = '', targetId = '') {
3
+ return {
4
+ messageCount: 0,
5
+ lastReplyTime: 0,
6
+ lastMessageTime: 0,
7
+ activityScore: 0,
8
+ cooldownUntil: 0,
9
+ mutedUntil: 0,
10
+ idleTimer: null,
11
+ idleBackoff: 1,
12
+ userInteractions: new Map(),
13
+ replyTimestamps: [],
14
+ platform,
15
+ sessionType,
16
+ targetId,
17
+ };
18
+ }
19
+ /** 当前阈值(动态衰减) */
20
+ export function getCurrentThreshold(state, cfg) {
21
+ if (state.lastReplyTime === 0)
22
+ return cfg.activityScoreLower;
23
+ const elapsed = Date.now() - state.lastReplyTime;
24
+ const decayMs = cfg.activityDecayMinutes * 60 * 1000;
25
+ const factor = Math.max(0, 1 - elapsed / decayMs);
26
+ return cfg.activityScoreLower + (cfg.activityScoreUpper - cfg.activityScoreLower) * factor;
27
+ }
28
+ /** 评分按距离上次消息的时间线性衰减(原地修改) */
29
+ export function applyScoreDecay(state, cfg) {
30
+ if (cfg.scoreDecayMinutes <= 0 || state.activityScore <= 0 || state.lastMessageTime === 0)
31
+ return;
32
+ const elapsed = Date.now() - state.lastMessageTime;
33
+ const decayMs = cfg.scoreDecayMinutes * 60 * 1000;
34
+ const factor = Math.max(0, 1 - elapsed / decayMs);
35
+ state.activityScore *= factor;
36
+ if (state.activityScore < 0.001)
37
+ state.activityScore = 0;
38
+ }
39
+ /** 计算单条入站对评分的增量(受用户交互权重影响) */
40
+ export function calculateScoreIncrement(state, cfg, userId) {
41
+ const base = 1.0 / Math.max(1, cfg.fixedInterval);
42
+ let userWeight = 1.0;
43
+ if (userId) {
44
+ const interaction = state.userInteractions.get(userId);
45
+ if (interaction) {
46
+ userWeight = 1.0 + 0.5 * Math.min(interaction.count / 10, 1.0);
47
+ }
48
+ }
49
+ return base * userWeight;
50
+ }
51
+ export function rateLimitUsedNow(state, cfg) {
52
+ if (cfg.rateLimitWindow <= 0)
53
+ return 0;
54
+ const windowStart = Date.now() - cfg.rateLimitWindow * 1000;
55
+ return state.replyTimestamps.filter(t => t > windowStart).length;
56
+ }
57
+ export function snapshot(state, cfg) {
58
+ return {
59
+ messageCount: state.messageCount,
60
+ activityScore: state.activityScore,
61
+ lastReplyTime: state.lastReplyTime,
62
+ lastMessageTime: state.lastMessageTime,
63
+ cooldownUntil: state.cooldownUntil,
64
+ mutedUntil: state.mutedUntil,
65
+ idleBackoff: state.idleBackoff,
66
+ rateLimitUsed: rateLimitUsedNow(state, cfg),
67
+ rateLimitMax: cfg.rateLimitMaxReplies,
68
+ fixedInterval: cfg.fixedInterval,
69
+ userInteractions: state.userInteractions,
70
+ };
71
+ }
72
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export type { FlowControlService, FlowSessionStateSnapshot } from '@aalis/plugin-flow-control-api';
2
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAKA,YAAY,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@aalis/plugin-flow-control",
3
+ "version": "0.4.1",
4
+ "license": "MIT",
5
+ "description": "平台无关流控:禁言/冷却/限速/闲置触发",
6
+ "type": "module",
7
+ "keywords": [
8
+ "aalis",
9
+ "aalis-plugin"
10
+ ],
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "dev": "tsc --watch"
19
+ },
20
+ "dependencies": {
21
+ "@aalis/plugin-flow-control-api": "workspace:^",
22
+ "@aalis/plugin-gateway-api": "workspace:^",
23
+ "@aalis/plugin-message-api": "workspace:^",
24
+ "@aalis/plugin-storage-api": "workspace:^"
25
+ },
26
+ "devDependencies": {
27
+ "@aalis/core": "workspace:*",
28
+ "@aalis/plugin-message-archive-api": "workspace:*",
29
+ "@aalis/plugin-webui-api": "workspace:*",
30
+ "@types/node": "^22.0.0",
31
+ "typescript": "^5.7.0"
32
+ },
33
+ "peerDependencies": {
34
+ "@aalis/core": ">=0.2.0 <1.0.0"
35
+ },
36
+ "aalis": {
37
+ "service": {
38
+ "required": [
39
+ "gateway"
40
+ ],
41
+ "optional": [
42
+ "message-archive"
43
+ ],
44
+ "provides": [
45
+ "flow-control"
46
+ ]
47
+ }
48
+ }
49
+ }