@zhin.js/core 1.0.57 → 1.1.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 (126) hide show
  1. package/lib/adapter.d.ts +1 -26
  2. package/lib/adapter.d.ts.map +1 -1
  3. package/lib/adapter.js +20 -117
  4. package/lib/adapter.js.map +1 -1
  5. package/lib/ai/index.d.ts +2 -0
  6. package/lib/ai/index.d.ts.map +1 -1
  7. package/lib/ai/index.js +1 -0
  8. package/lib/ai/index.js.map +1 -1
  9. package/lib/built/adapter-process.d.ts +0 -4
  10. package/lib/built/adapter-process.d.ts.map +1 -1
  11. package/lib/built/adapter-process.js +0 -95
  12. package/lib/built/adapter-process.js.map +1 -1
  13. package/lib/built/agent-preset.d.ts +2 -0
  14. package/lib/built/agent-preset.d.ts.map +1 -1
  15. package/lib/built/agent-preset.js +4 -0
  16. package/lib/built/agent-preset.js.map +1 -1
  17. package/lib/built/command.d.ts +4 -0
  18. package/lib/built/command.d.ts.map +1 -1
  19. package/lib/built/command.js +6 -0
  20. package/lib/built/command.js.map +1 -1
  21. package/lib/built/component.d.ts.map +1 -1
  22. package/lib/built/component.js +1 -0
  23. package/lib/built/component.js.map +1 -1
  24. package/lib/built/dispatcher.d.ts.map +1 -1
  25. package/lib/built/dispatcher.js +0 -13
  26. package/lib/built/dispatcher.js.map +1 -1
  27. package/lib/built/message-filter.d.ts +2 -0
  28. package/lib/built/message-filter.d.ts.map +1 -1
  29. package/lib/built/message-filter.js +5 -0
  30. package/lib/built/message-filter.js.map +1 -1
  31. package/lib/built/skill.d.ts +11 -0
  32. package/lib/built/skill.d.ts.map +1 -1
  33. package/lib/built/skill.js +14 -0
  34. package/lib/built/skill.js.map +1 -1
  35. package/lib/built/tool.d.ts +11 -44
  36. package/lib/built/tool.d.ts.map +1 -1
  37. package/lib/built/tool.js +14 -353
  38. package/lib/built/tool.js.map +1 -1
  39. package/lib/plugin.d.ts +1 -25
  40. package/lib/plugin.d.ts.map +1 -1
  41. package/lib/plugin.js +1 -77
  42. package/lib/plugin.js.map +1 -1
  43. package/lib/types.d.ts +0 -25
  44. package/lib/types.d.ts.map +1 -1
  45. package/package.json +10 -7
  46. package/CHANGELOG.md +0 -538
  47. package/REFACTORING_COMPLETE.md +0 -178
  48. package/REFACTORING_STATUS.md +0 -263
  49. package/src/adapter.ts +0 -275
  50. package/src/ai/index.ts +0 -52
  51. package/src/ai/providers/anthropic.ts +0 -379
  52. package/src/ai/providers/base.ts +0 -175
  53. package/src/ai/providers/index.ts +0 -13
  54. package/src/ai/providers/ollama.ts +0 -302
  55. package/src/ai/providers/openai.ts +0 -174
  56. package/src/ai/types.ts +0 -348
  57. package/src/bot.ts +0 -37
  58. package/src/built/adapter-process.ts +0 -177
  59. package/src/built/agent-preset.ts +0 -136
  60. package/src/built/ai-trigger.ts +0 -259
  61. package/src/built/command.ts +0 -108
  62. package/src/built/common-adapter-tools.ts +0 -242
  63. package/src/built/component.ts +0 -130
  64. package/src/built/config.ts +0 -335
  65. package/src/built/cron.ts +0 -156
  66. package/src/built/database.ts +0 -134
  67. package/src/built/dispatcher.ts +0 -496
  68. package/src/built/login-assist.ts +0 -131
  69. package/src/built/message-filter.ts +0 -390
  70. package/src/built/permission.ts +0 -151
  71. package/src/built/schema-feature.ts +0 -190
  72. package/src/built/skill.ts +0 -221
  73. package/src/built/tool.ts +0 -948
  74. package/src/command.ts +0 -87
  75. package/src/component.ts +0 -565
  76. package/src/cron.ts +0 -4
  77. package/src/errors.ts +0 -46
  78. package/src/feature.ts +0 -7
  79. package/src/index.ts +0 -53
  80. package/src/jsx-dev-runtime.ts +0 -2
  81. package/src/jsx-runtime.ts +0 -12
  82. package/src/jsx.ts +0 -135
  83. package/src/message.ts +0 -48
  84. package/src/models/system-log.ts +0 -20
  85. package/src/models/user.ts +0 -15
  86. package/src/notice.ts +0 -98
  87. package/src/plugin.ts +0 -896
  88. package/src/prompt.ts +0 -293
  89. package/src/request.ts +0 -95
  90. package/src/scheduler/index.ts +0 -19
  91. package/src/scheduler/scheduler.ts +0 -372
  92. package/src/scheduler/types.ts +0 -74
  93. package/src/tool-zod.ts +0 -115
  94. package/src/types-generator.ts +0 -78
  95. package/src/types.ts +0 -505
  96. package/src/utils.ts +0 -227
  97. package/tests/adapter.test.ts +0 -638
  98. package/tests/ai/ai-trigger.test.ts +0 -368
  99. package/tests/ai/providers.integration.test.ts +0 -227
  100. package/tests/ai/setup.ts +0 -308
  101. package/tests/ai/tool.test.ts +0 -800
  102. package/tests/bot.test.ts +0 -151
  103. package/tests/command.test.ts +0 -737
  104. package/tests/component-new.test.ts +0 -361
  105. package/tests/config.test.ts +0 -372
  106. package/tests/cron.test.ts +0 -82
  107. package/tests/dispatcher.test.ts +0 -293
  108. package/tests/errors.test.ts +0 -21
  109. package/tests/expression-evaluation.test.ts +0 -258
  110. package/tests/features-builtin.test.ts +0 -191
  111. package/tests/jsx-runtime.test.ts +0 -45
  112. package/tests/jsx.test.ts +0 -319
  113. package/tests/message-filter.test.ts +0 -566
  114. package/tests/message.test.ts +0 -402
  115. package/tests/notice.test.ts +0 -198
  116. package/tests/plugin.test.ts +0 -779
  117. package/tests/prompt.test.ts +0 -78
  118. package/tests/redos-protection.test.ts +0 -198
  119. package/tests/request.test.ts +0 -221
  120. package/tests/schema.test.ts +0 -248
  121. package/tests/skill-feature.test.ts +0 -179
  122. package/tests/test-utils.ts +0 -59
  123. package/tests/tool-feature.test.ts +0 -254
  124. package/tests/types.test.ts +0 -162
  125. package/tests/utils.test.ts +0 -135
  126. package/tsconfig.json +0 -24
@@ -1,134 +0,0 @@
1
- /**
2
- * DatabaseFeature
3
- * 数据库服务,管理数据模型定义,继承自 Feature 抽象类
4
- */
5
- import { Registry, Definition, Databases, Database } from "@zhin.js/database";
6
- import { DatabaseConfig, Models } from "../types.js";
7
- import { Feature, FeatureJSON } from "../feature.js";
8
- import { Plugin, getPlugin } from "../plugin.js";
9
- import type { PluginLike } from '@zhin.js/kernel';
10
- import { SystemLogDefinition } from "../models/system-log.js";
11
- import { UserDefinition } from "../models/user.js";
12
-
13
- /**
14
- * 模型定义记录
15
- */
16
- export interface ModelRecord {
17
- name: string;
18
- definition: Definition<any>;
19
- }
20
-
21
- declare module "../plugin.js" {
22
- namespace Plugin {
23
- interface Extensions {
24
- defineModel<K extends keyof Models>(name: K, definition: Definition<Models[K]>): void;
25
- }
26
- interface Contexts {
27
- database: DatabaseFeature;
28
- }
29
- }
30
- }
31
-
32
- export class DatabaseFeature extends Feature<ModelRecord> {
33
- readonly name = 'database' as const;
34
- readonly icon = 'Database';
35
- readonly desc = '数据模型';
36
-
37
- /** 内部数据库实例 */
38
- readonly db: Database<any, Models, any>;
39
-
40
- /** 按模型名索引 */
41
- readonly byName = new Map<string, ModelRecord>();
42
-
43
- constructor(config: DatabaseConfig) {
44
- super();
45
- this.db = Registry.create<Models, keyof Databases>(config.dialect, config, {
46
- SystemLog: SystemLogDefinition,
47
- User: UserDefinition,
48
- });
49
- }
50
-
51
- // ====================================================================
52
- // 向后兼容代理:旧代码 inject('database').xxx 可直接使用
53
- // ====================================================================
54
-
55
- get models() {
56
- return this.db.models;
57
- }
58
-
59
- define<K extends keyof Models>(name: K, definition: Definition<Models[K]>) {
60
- return this.db.define(name, definition);
61
- }
62
-
63
- start() {
64
- return this.db.start();
65
- }
66
-
67
- stop() {
68
- return this.db.stop();
69
- }
70
-
71
- /**
72
- * 添加模型定义
73
- */
74
- add(record: ModelRecord, pluginName: string): () => void {
75
- this.db.define(record.name as keyof Models, record.definition);
76
- this.byName.set(record.name, record);
77
- return super.add(record, pluginName);
78
- }
79
-
80
- /**
81
- * 移除模型定义
82
- */
83
- remove(record: ModelRecord): boolean {
84
- this.byName.delete(record.name);
85
- return super.remove(record);
86
- }
87
-
88
- /**
89
- * 生命周期: 启动数据库
90
- */
91
- async mounted(plugin: PluginLike): Promise<void> {
92
- plugin.logger.info('Database service started');
93
- await this.db.start();
94
- }
95
-
96
- /**
97
- * 生命周期: 停止数据库
98
- */
99
- async dispose(): Promise<void> {
100
- await this.db.stop();
101
- }
102
-
103
- /**
104
- * 序列化为 JSON
105
- */
106
- toJSON(pluginName?: string): FeatureJSON {
107
- const list = pluginName ? this.getByPlugin(pluginName) : this.items;
108
- return {
109
- name: this.name,
110
- icon: this.icon,
111
- desc: this.desc,
112
- count: list.length,
113
- items: list.map(r => ({
114
- name: r.name,
115
- })),
116
- };
117
- }
118
-
119
- /**
120
- * 提供给 Plugin.prototype 的扩展方法
121
- */
122
- get extensions() {
123
- const feature = this;
124
- return {
125
- defineModel<K extends keyof Models>(name: K, definition: Definition<Models[K]>) {
126
- const plugin = getPlugin();
127
- const record: ModelRecord = { name: name as string, definition };
128
- const dispose = feature.add(record, plugin.name);
129
- plugin.recordFeatureContribution(feature.name, name as string);
130
- plugin.onDispose(dispose);
131
- },
132
- };
133
- }
134
- }
@@ -1,496 +0,0 @@
1
- /**
2
- * MessageDispatcher — 消息调度器
3
- *
4
- * 取代原先的"大杂烩中间件链",将消息处理分为三个清晰阶段:
5
- *
6
- * ┌────────────────────────────────────────┐
7
- * │ Stage 1: Guardrail(护栏) │
8
- * │ 鉴权、限流、安全过滤、日志记录 │
9
- * │ 始终执行,不可被 AI 跳过 │
10
- * └──────────────┬─────────────────────────┘
11
- * ▼
12
- * ┌────────────────────────────────────────┐
13
- * │ Stage 2: Route(路径判定) │
14
- * │ exclusive:命令与 AI 互斥(旧行为) │
15
- * │ dual:命令与 AI 独立判定,可同时命中 │
16
- * └──────────────┬─────────────────────────┘
17
- * ▼
18
- * ┌────────────────────────────────────────┐
19
- * │ Stage 3: Handle(处理) │
20
- * │ Command: commandService.handle() │
21
- * │ AI: aiHandler (由 AI 模块注册) │
22
- * │ 出站:replyWithPolish → $reply → Adapter.sendMessage → before.sendMessage │
23
- * └────────────────────────────────────────┘
24
- *
25
- * 注意:Context key 为 'dispatcher',避免与 HTTP 模块的 'router' 冲突。
26
- *
27
- * 默认路由为 exclusive(命令与 AI 互斥);需双轨时请显式 dualRoute.mode: 'dual'。
28
- */
29
-
30
- import { AsyncLocalStorage } from 'node:async_hooks';
31
- import { Message } from '../message.js';
32
- import { Plugin, getPlugin } from '../plugin.js';
33
- import type {
34
- MessageMiddleware,
35
- RegisteredAdapter,
36
- MaybePromise,
37
- SendContent,
38
- OutboundReplySource,
39
- OutboundPolishContext,
40
- OutboundPolishMiddleware,
41
- BeforeSendHandler,
42
- } from '../types.js';
43
- import type { Context } from '../plugin.js';
44
-
45
- /** Dispatcher 管理的「会话回复」异步上下文,供 `before.sendMessage` 内读取(与 Adapter.renderSendMessage 同链) */
46
- const outboundReplyAls = new AsyncLocalStorage<{ message: Message<any>; source: OutboundReplySource }>();
47
-
48
- export function getOutboundReplyStore(): { message: Message<any>; source: OutboundReplySource } | undefined {
49
- return outboundReplyAls.getStore();
50
- }
51
-
52
- // ============================================================================
53
- // 类型定义
54
- // ============================================================================
55
-
56
- /**
57
- * 路由判定结果(互斥模式 legacy)
58
- */
59
- export type RouteResult =
60
- | { type: 'command' }
61
- | { type: 'ai'; content: string }
62
- | { type: 'skip' };
63
-
64
- /**
65
- * 双轨分流配置
66
- */
67
- export interface DualRouteConfig {
68
- /**
69
- * exclusive:与旧版一致,命中命令则不再走 AI;
70
- * dual:命令与 AI 独立判定,可同时执行(顺序由 order 决定)
71
- */
72
- mode?: 'exclusive' | 'dual';
73
- /** 同时命中时的执行顺序,默认先指令后 AI */
74
- order?: 'command-first' | 'ai-first';
75
- /**
76
- * 是否允许在双命中时各回复一次;为 false 时仅执行 order 中的第一个分支
77
- */
78
- allowDualReply?: boolean;
79
- }
80
-
81
- export type ResolvedDualRouteConfig = Required<DualRouteConfig>;
82
-
83
- const DUAL_ROUTE_DEFAULTS: ResolvedDualRouteConfig = {
84
- mode: 'exclusive',
85
- order: 'command-first',
86
- allowDualReply: false,
87
- };
88
-
89
- function resolveDualRouteConfig(partial?: Partial<DualRouteConfig>): ResolvedDualRouteConfig {
90
- return {
91
- mode: partial?.mode ?? DUAL_ROUTE_DEFAULTS.mode,
92
- order: partial?.order ?? DUAL_ROUTE_DEFAULTS.order,
93
- allowDualReply: partial?.allowDualReply ?? DUAL_ROUTE_DEFAULTS.allowDualReply,
94
- };
95
- }
96
-
97
- /**
98
- * AI 处理函数签名
99
- * 由 AI 模块通过 dispatcher.setAIHandler() 注册
100
- */
101
- export type AIHandler = (
102
- message: Message<any>,
103
- content: string,
104
- ) => MaybePromise<void>;
105
-
106
- /**
107
- * 命令前缀判定函数
108
- */
109
- export type CommandMatcher = (text: string, message: Message<any>) => boolean;
110
-
111
- /**
112
- * AI 触发判定函数
113
- */
114
- export type AITriggerMatcher = (message: Message<any>) => { triggered: boolean; content: string };
115
-
116
- export type GuardrailMiddleware = MessageMiddleware<RegisteredAdapter>;
117
-
118
- /** @alias OutboundReplySource:出站回复来源(指令 / AI) */
119
- export type ReplySource = OutboundReplySource;
120
-
121
- export interface CreateMessageDispatcherOptions {
122
- dualRoute?: Partial<DualRouteConfig>;
123
- }
124
-
125
- // ============================================================================
126
- // MessageDispatcher 服务
127
- // ============================================================================
128
-
129
- export interface MessageDispatcherService {
130
- dispatch(message: Message<any>): Promise<void>;
131
-
132
- addGuardrail(guardrail: GuardrailMiddleware): () => void;
133
-
134
- setCommandMatcher(matcher: CommandMatcher): void;
135
-
136
- setAITriggerMatcher(matcher: AITriggerMatcher): void;
137
-
138
- setAIHandler(handler: AIHandler): void;
139
-
140
- hasAIHandler(): boolean;
141
-
142
- /** 合并更新双轨配置 */
143
- setDualRouteConfig(config: Partial<DualRouteConfig>): void;
144
-
145
- getDualRouteConfig(): Readonly<ResolvedDualRouteConfig>;
146
-
147
- /** 注册出站润色:挂到根插件 `before.sendMessage`;仅在 `replyWithPolish` 触发的发送中生效(见 getOutboundReplyStore) */
148
- addOutboundPolish(handler: OutboundPolishMiddleware): () => void;
149
-
150
- /**
151
- * 在 `before.sendMessage` 管道内调用 `message.$reply`(与 Adapter#sendMessage 同一出站链)
152
- */
153
- replyWithPolish(
154
- message: Message<any>,
155
- source: ReplySource,
156
- content: SendContent,
157
- ): Promise<unknown>;
158
-
159
- /**
160
- * 是否匹配为指令路径(与 dispatch 内判定一致)
161
- */
162
- matchCommand(message: Message<any>): boolean;
163
-
164
- /**
165
- * AI 触发判定结果
166
- */
167
- matchAI(message: Message<any>): { triggered: boolean; content: string };
168
- }
169
-
170
- // ============================================================================
171
- // 扩展 Plugin 接口
172
- // ============================================================================
173
-
174
- export interface DispatcherContextExtensions {
175
- addGuardrail(guardrail: GuardrailMiddleware): () => void;
176
- addOutboundPolish(handler: OutboundPolishMiddleware): () => void;
177
- }
178
-
179
- declare module '../plugin.js' {
180
- namespace Plugin {
181
- interface Extensions extends DispatcherContextExtensions {}
182
- interface Contexts {
183
- dispatcher: MessageDispatcherService;
184
- }
185
- }
186
- }
187
-
188
- // ============================================================================
189
- // 实现
190
- // ============================================================================
191
-
192
- export function createMessageDispatcher(
193
- options?: CreateMessageDispatcherOptions,
194
- ): Context<'dispatcher', DispatcherContextExtensions> {
195
- const guardrails: GuardrailMiddleware[] = [];
196
- /** mounted 前注册的润色,在 mounted 时挂到 root.before.sendMessage */
197
- const pendingOutboundPolish: OutboundPolishMiddleware[] = [];
198
- let aiHandler: AIHandler | null = null;
199
- let aiTriggerMatcher: AITriggerMatcher | null = null;
200
- let commandMatcher: CommandMatcher | null = null;
201
- let rootPlugin: Plugin | null = null;
202
- let dualRoute = resolveDualRouteConfig(options?.dualRoute);
203
-
204
- let commandPrefixIndex: Map<string, boolean> | null = null;
205
- let lastCommandCount = -1;
206
-
207
- function rebuildCommandIndex(): Map<string, boolean> {
208
- const index = new Map<string, boolean>();
209
- if (rootPlugin) {
210
- const commandService = rootPlugin.inject('command');
211
- if (commandService?.items) {
212
- for (const cmd of commandService.items) {
213
- const prefix = cmd.pattern?.split(/\s/)[0];
214
- if (prefix) index.set(prefix, true);
215
- }
216
- }
217
- }
218
- return index;
219
- }
220
-
221
- function getCommandIndex(): Map<string, boolean> {
222
- const commandService = rootPlugin?.inject('command');
223
- const currentCount = commandService?.items?.length ?? 0;
224
- if (!commandPrefixIndex || currentCount !== lastCommandCount) {
225
- commandPrefixIndex = rebuildCommandIndex();
226
- lastCommandCount = currentCount;
227
- }
228
- return commandPrefixIndex;
229
- }
230
-
231
- async function runGuardrails(message: Message<any>): Promise<boolean> {
232
- if (guardrails.length === 0) return true;
233
-
234
- for (const guardrail of guardrails) {
235
- let nextCalled = false;
236
- try {
237
- await guardrail(message, async () => {
238
- nextCalled = true;
239
- });
240
- } catch {
241
- return false;
242
- }
243
- if (!nextCalled) return false;
244
- }
245
- return true;
246
- }
247
-
248
- function extractText(message: Message<any>): string {
249
- if (!message.$content) return '';
250
- return message.$content
251
- .map((seg: any) => {
252
- if (typeof seg === 'string') return seg;
253
- if (seg.type === 'text') return seg.data?.text || '';
254
- return '';
255
- })
256
- .join('')
257
- .trim();
258
- }
259
-
260
- function matchCommandInternal(message: Message<any>): boolean {
261
- const text = extractText(message);
262
- if (commandMatcher && commandMatcher(text, message)) return true;
263
- const index = getCommandIndex();
264
- for (const [prefix] of index) {
265
- if (text.startsWith(prefix)) return true;
266
- }
267
- return false;
268
- }
269
-
270
- function matchAIInternal(message: Message<any>): { triggered: boolean; content: string } {
271
- if (!aiTriggerMatcher) return { triggered: false, content: '' };
272
- return aiTriggerMatcher(message);
273
- }
274
-
275
- /** 互斥路由(与旧版 route 一致) */
276
- function routeExclusive(message: Message<any>): RouteResult {
277
- const text = extractText(message);
278
-
279
- if (commandMatcher && commandMatcher(text, message)) {
280
- return { type: 'command' };
281
- }
282
-
283
- const index = getCommandIndex();
284
- for (const [prefix] of index) {
285
- if (text.startsWith(prefix)) {
286
- return { type: 'command' };
287
- }
288
- }
289
-
290
- if (aiTriggerMatcher) {
291
- const { triggered, content } = aiTriggerMatcher(message);
292
- if (triggered) {
293
- return { type: 'ai', content };
294
- }
295
- }
296
-
297
- return { type: 'skip' };
298
- }
299
-
300
- function wrapPolishAsBeforeSend(handler: OutboundPolishMiddleware): BeforeSendHandler {
301
- return async (options) => {
302
- const store = outboundReplyAls.getStore();
303
- if (!store) return;
304
- const ctx: OutboundPolishContext = {
305
- message: store.message,
306
- content: options.content,
307
- source: store.source,
308
- };
309
- const next = await handler(ctx);
310
- if (next !== undefined) return { ...options, content: next };
311
- };
312
- }
313
-
314
- function flushPendingOutboundPolish(): void {
315
- if (!rootPlugin) return;
316
- const root = rootPlugin.root;
317
- for (const mw of pendingOutboundPolish) {
318
- const fn = wrapPolishAsBeforeSend(mw);
319
- root.on('before.sendMessage', fn);
320
- }
321
- pendingOutboundPolish.length = 0;
322
- }
323
-
324
- async function replyWithPolishInternal(
325
- message: Message<any>,
326
- source: ReplySource,
327
- content: SendContent,
328
- ): Promise<unknown> {
329
- if (!rootPlugin) {
330
- return message.$reply(content);
331
- }
332
- return outboundReplyAls.run({ message, source }, () => message.$reply(content));
333
- }
334
-
335
- async function runCommandBranch(message: Message<any>): Promise<void> {
336
- if (!rootPlugin) return;
337
- const commandService = rootPlugin.inject('command');
338
- if (!commandService) return;
339
- const response = await commandService.handle(message, rootPlugin);
340
- if (response) {
341
- await replyWithPolishInternal(message, 'command', response);
342
- }
343
- }
344
-
345
- async function runCustomMiddlewares(message: Message<any>): Promise<void> {
346
- if (!rootPlugin) return;
347
- const customMiddlewares = (rootPlugin as any)._getCustomMiddlewares?.() as
348
- | MessageMiddleware<RegisteredAdapter>[]
349
- | undefined;
350
- if (customMiddlewares && customMiddlewares.length > 0) {
351
- const { compose } = await import('../utils.js');
352
- const composed = compose(customMiddlewares);
353
- await composed(message, async () => {});
354
- }
355
- }
356
-
357
- const service: MessageDispatcherService = {
358
- async dispatch(message: Message<any>) {
359
- const passed = await runGuardrails(message);
360
- if (!passed) return;
361
-
362
- const cfg = dualRoute;
363
-
364
- if (cfg.mode === 'exclusive') {
365
- const result = routeExclusive(message);
366
- switch (result.type) {
367
- case 'command':
368
- await runCommandBranch(message);
369
- break;
370
- case 'ai':
371
- if (aiHandler) await aiHandler(message, result.content);
372
- break;
373
- default:
374
- break;
375
- }
376
- await runCustomMiddlewares(message);
377
- return;
378
- }
379
-
380
- // dual 模式
381
- let wantCmd = matchCommandInternal(message);
382
- const aiRes = matchAIInternal(message);
383
- let wantAi = aiRes.triggered;
384
-
385
- if (!wantCmd && !wantAi) {
386
- await runCustomMiddlewares(message);
387
- return;
388
- }
389
-
390
- if (!cfg.allowDualReply && wantCmd && wantAi) {
391
- if (cfg.order === 'command-first') wantAi = false;
392
- else wantCmd = false;
393
- }
394
-
395
- const runCmd = async () => {
396
- if (wantCmd) await runCommandBranch(message);
397
- };
398
- const runAi = async () => {
399
- if (wantAi && aiHandler) await aiHandler(message, aiRes.content);
400
- };
401
-
402
- if (cfg.order === 'ai-first') {
403
- await runAi();
404
- await runCmd();
405
- } else {
406
- await runCmd();
407
- await runAi();
408
- }
409
-
410
- await runCustomMiddlewares(message);
411
- },
412
-
413
- addGuardrail(guardrail: GuardrailMiddleware) {
414
- guardrails.push(guardrail);
415
- return () => {
416
- const index = guardrails.indexOf(guardrail);
417
- if (index !== -1) guardrails.splice(index, 1);
418
- };
419
- },
420
-
421
- setCommandMatcher(matcher: CommandMatcher) {
422
- commandMatcher = matcher;
423
- },
424
-
425
- setAITriggerMatcher(matcher: AITriggerMatcher) {
426
- aiTriggerMatcher = matcher;
427
- },
428
-
429
- setAIHandler(handler: AIHandler) {
430
- aiHandler = handler;
431
- },
432
-
433
- hasAIHandler() {
434
- return aiHandler !== null;
435
- },
436
-
437
- setDualRouteConfig(config: Partial<DualRouteConfig>) {
438
- dualRoute = resolveDualRouteConfig({ ...dualRoute, ...config });
439
- },
440
-
441
- getDualRouteConfig() {
442
- return { ...dualRoute };
443
- },
444
-
445
- addOutboundPolish(handler: OutboundPolishMiddleware) {
446
- const fn = wrapPolishAsBeforeSend(handler);
447
- if (rootPlugin) {
448
- const root = rootPlugin.root;
449
- root.on('before.sendMessage', fn);
450
- return () => root.off('before.sendMessage', fn);
451
- }
452
- pendingOutboundPolish.push(handler);
453
- return () => {
454
- const i = pendingOutboundPolish.indexOf(handler);
455
- if (i !== -1) pendingOutboundPolish.splice(i, 1);
456
- };
457
- },
458
-
459
- replyWithPolish(message, source, content) {
460
- return replyWithPolishInternal(message, source, content);
461
- },
462
-
463
- matchCommand(message) {
464
- return matchCommandInternal(message);
465
- },
466
-
467
- matchAI(message) {
468
- return matchAIInternal(message);
469
- },
470
- };
471
-
472
- return {
473
- name: 'dispatcher',
474
- description: '消息调度器 — 统一消息路由分流 (Command / AI)',
475
- value: service,
476
- mounted(plugin: Plugin) {
477
- rootPlugin = plugin.root;
478
- flushPendingOutboundPolish();
479
- return service;
480
- },
481
- extensions: {
482
- addGuardrail(guardrail: GuardrailMiddleware) {
483
- const plugin = getPlugin();
484
- const dispose = service.addGuardrail(guardrail);
485
- plugin.onDispose(dispose);
486
- return dispose;
487
- },
488
- addOutboundPolish(handler: OutboundPolishMiddleware) {
489
- const plugin = getPlugin();
490
- const dispose = service.addOutboundPolish(handler);
491
- plugin.onDispose(dispose);
492
- return dispose;
493
- },
494
- },
495
- };
496
- }