mycohive-claw 4.0.0

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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +222 -0
  3. package/dist/circuit-breaker.d.ts +82 -0
  4. package/dist/circuit-breaker.d.ts.map +1 -0
  5. package/dist/circuit-breaker.js +214 -0
  6. package/dist/circuit-breaker.js.map +1 -0
  7. package/dist/context-tree.d.ts +94 -0
  8. package/dist/context-tree.d.ts.map +1 -0
  9. package/dist/context-tree.js +624 -0
  10. package/dist/context-tree.js.map +1 -0
  11. package/dist/event-bus.d.ts +174 -0
  12. package/dist/event-bus.d.ts.map +1 -0
  13. package/dist/event-bus.js +750 -0
  14. package/dist/event-bus.js.map +1 -0
  15. package/dist/i18n.d.ts +91 -0
  16. package/dist/i18n.d.ts.map +1 -0
  17. package/dist/i18n.js +150 -0
  18. package/dist/i18n.js.map +1 -0
  19. package/dist/index.d.ts +38 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +301 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/intent-analyzer.d.ts +119 -0
  24. package/dist/intent-analyzer.d.ts.map +1 -0
  25. package/dist/intent-analyzer.js +563 -0
  26. package/dist/intent-analyzer.js.map +1 -0
  27. package/dist/logger.d.ts +46 -0
  28. package/dist/logger.d.ts.map +1 -0
  29. package/dist/logger.js +126 -0
  30. package/dist/logger.js.map +1 -0
  31. package/dist/metrics.d.ts +98 -0
  32. package/dist/metrics.d.ts.map +1 -0
  33. package/dist/metrics.js +192 -0
  34. package/dist/metrics.js.map +1 -0
  35. package/dist/openclaw-api.d.ts +122 -0
  36. package/dist/openclaw-api.d.ts.map +1 -0
  37. package/dist/openclaw-api.js +8 -0
  38. package/dist/openclaw-api.js.map +1 -0
  39. package/dist/rate-limiter.d.ts +46 -0
  40. package/dist/rate-limiter.d.ts.map +1 -0
  41. package/dist/rate-limiter.js +134 -0
  42. package/dist/rate-limiter.js.map +1 -0
  43. package/dist/result-cache.d.ts +76 -0
  44. package/dist/result-cache.d.ts.map +1 -0
  45. package/dist/result-cache.js +158 -0
  46. package/dist/result-cache.js.map +1 -0
  47. package/dist/router.d.ts +90 -0
  48. package/dist/router.d.ts.map +1 -0
  49. package/dist/router.js +447 -0
  50. package/dist/router.js.map +1 -0
  51. package/dist/setup-entry.d.ts +18 -0
  52. package/dist/setup-entry.d.ts.map +1 -0
  53. package/dist/setup-entry.js +64 -0
  54. package/dist/setup-entry.js.map +1 -0
  55. package/dist/token-counter.d.ts +57 -0
  56. package/dist/token-counter.d.ts.map +1 -0
  57. package/dist/token-counter.js +125 -0
  58. package/dist/token-counter.js.map +1 -0
  59. package/dist/tool-handlers.d.ts +52 -0
  60. package/dist/tool-handlers.d.ts.map +1 -0
  61. package/dist/tool-handlers.js +317 -0
  62. package/dist/tool-handlers.js.map +1 -0
  63. package/dist/tools/mycohive-tools.d.ts +11 -0
  64. package/dist/tools/mycohive-tools.d.ts.map +1 -0
  65. package/dist/tools/mycohive-tools.js +160 -0
  66. package/dist/tools/mycohive-tools.js.map +1 -0
  67. package/dist/types.d.ts +58 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +57 -0
  70. package/dist/types.js.map +1 -0
  71. package/dist/workspace-loader.d.ts +67 -0
  72. package/dist/workspace-loader.d.ts.map +1 -0
  73. package/dist/workspace-loader.js +175 -0
  74. package/dist/workspace-loader.js.map +1 -0
  75. package/package.json +52 -0
  76. package/workspaces/api_dev/CONFIG.md +31 -0
  77. package/workspaces/api_dev/RULES.md +8 -0
  78. package/workspaces/api_dev/SKILL.md +69 -0
  79. package/workspaces/api_dev/SOUL.md +19 -0
  80. package/workspaces/architect/CONFIG.md +32 -0
  81. package/workspaces/architect/RULES.md +8 -0
  82. package/workspaces/architect/SKILL.md +63 -0
  83. package/workspaces/architect/SOUL.md +19 -0
  84. package/workspaces/backend_dev/CONFIG.md +42 -0
  85. package/workspaces/backend_dev/RULES.md +8 -0
  86. package/workspaces/backend_dev/SKILL.md +41 -0
  87. package/workspaces/backend_dev/SOUL.md +19 -0
  88. package/workspaces/browser/CONFIG.md +42 -0
  89. package/workspaces/browser/RULES.md +8 -0
  90. package/workspaces/browser/SKILL.md +57 -0
  91. package/workspaces/browser/SOUL.md +19 -0
  92. package/workspaces/builder/CONFIG.md +31 -0
  93. package/workspaces/builder/RULES.md +8 -0
  94. package/workspaces/builder/SKILL.md +39 -0
  95. package/workspaces/builder/SOUL.md +18 -0
  96. package/workspaces/coder/CONFIG.md +44 -0
  97. package/workspaces/coder/RULES.md +9 -0
  98. package/workspaces/coder/SKILL.md +53 -0
  99. package/workspaces/coder/SOUL.md +19 -0
  100. package/workspaces/coordinator/CONFIG.md +62 -0
  101. package/workspaces/coordinator/RULES.md +8 -0
  102. package/workspaces/coordinator/SKILL.md +77 -0
  103. package/workspaces/coordinator/SOUL.md +20 -0
  104. package/workspaces/data_engineer/CONFIG.md +32 -0
  105. package/workspaces/data_engineer/RULES.md +8 -0
  106. package/workspaces/data_engineer/SKILL.md +44 -0
  107. package/workspaces/data_engineer/SOUL.md +19 -0
  108. package/workspaces/deployer/CONFIG.md +45 -0
  109. package/workspaces/deployer/RULES.md +8 -0
  110. package/workspaces/deployer/SKILL.md +74 -0
  111. package/workspaces/deployer/SOUL.md +19 -0
  112. package/workspaces/dreamer/CONFIG.md +34 -0
  113. package/workspaces/dreamer/RULES.md +8 -0
  114. package/workspaces/dreamer/SKILL.md +48 -0
  115. package/workspaces/dreamer/SOUL.md +19 -0
  116. package/workspaces/evaluator/CONFIG.md +40 -0
  117. package/workspaces/evaluator/RULES.md +21 -0
  118. package/workspaces/evaluator/SKILL.md +65 -0
  119. package/workspaces/evaluator/SOUL.md +20 -0
  120. package/workspaces/front_director/CONFIG.md +54 -0
  121. package/workspaces/front_director/RULES.md +8 -0
  122. package/workspaces/front_director/SKILL.md +52 -0
  123. package/workspaces/front_director/SOUL.md +20 -0
  124. package/workspaces/frontend_dev/CONFIG.md +33 -0
  125. package/workspaces/frontend_dev/RULES.md +8 -0
  126. package/workspaces/frontend_dev/SKILL.md +42 -0
  127. package/workspaces/frontend_dev/SOUL.md +19 -0
  128. package/workspaces/observer/CONFIG.md +60 -0
  129. package/workspaces/observer/RULES.md +7 -0
  130. package/workspaces/observer/SKILL.md +77 -0
  131. package/workspaces/observer/SOUL.md +19 -0
  132. package/workspaces/planner/CONFIG.md +43 -0
  133. package/workspaces/planner/RULES.md +8 -0
  134. package/workspaces/planner/SKILL.md +65 -0
  135. package/workspaces/planner/SOUL.md +20 -0
  136. package/workspaces/qa/CONFIG.md +32 -0
  137. package/workspaces/qa/RULES.md +8 -0
  138. package/workspaces/qa/SKILL.md +52 -0
  139. package/workspaces/qa/SOUL.md +19 -0
  140. package/workspaces/researcher/CONFIG.md +32 -0
  141. package/workspaces/researcher/RULES.md +8 -0
  142. package/workspaces/researcher/SKILL.md +58 -0
  143. package/workspaces/researcher/SOUL.md +20 -0
  144. package/workspaces/reviewer/CONFIG.md +41 -0
  145. package/workspaces/reviewer/RULES.md +17 -0
  146. package/workspaces/reviewer/SKILL.md +56 -0
  147. package/workspaces/reviewer/SOUL.md +20 -0
  148. package/workspaces/router/CONFIG.md +58 -0
  149. package/workspaces/router/RULES.md +8 -0
  150. package/workspaces/router/SKILL.md +97 -0
  151. package/workspaces/router/SOUL.md +20 -0
  152. package/workspaces/scraper/CONFIG.md +41 -0
  153. package/workspaces/scraper/RULES.md +8 -0
  154. package/workspaces/scraper/SKILL.md +51 -0
  155. package/workspaces/scraper/SOUL.md +19 -0
  156. package/workspaces/scripter/CONFIG.md +42 -0
  157. package/workspaces/scripter/RULES.md +8 -0
  158. package/workspaces/scripter/SKILL.md +54 -0
  159. package/workspaces/scripter/SOUL.md +18 -0
  160. package/workspaces/security/CONFIG.md +47 -0
  161. package/workspaces/security/RULES.md +8 -0
  162. package/workspaces/security/SKILL.md +50 -0
  163. package/workspaces/security/SOUL.md +19 -0
  164. package/workspaces/translator/CONFIG.md +43 -0
  165. package/workspaces/translator/RULES.md +8 -0
  166. package/workspaces/translator/SKILL.md +35 -0
  167. package/workspaces/translator/SOUL.md +19 -0
  168. package/workspaces/writer/CONFIG.md +31 -0
  169. package/workspaces/writer/RULES.md +8 -0
  170. package/workspaces/writer/SKILL.md +40 -0
  171. package/workspaces/writer/SOUL.md +19 -0
@@ -0,0 +1,750 @@
1
+ /**
2
+ * Event Bus 模块
3
+ * 版本: v4.0.0
4
+ *
5
+ * 发布-订阅事件驱动通信机制。
6
+ * 支持 OpenClaw 原生事件和自定义事件。
7
+ * 优化:使用前缀树(Trie)加速订阅匹配,DLQ 重试+告警机制
8
+ */
9
+ import * as fs from "fs";
10
+ import * as path from "path";
11
+ export var EventType;
12
+ (function (EventType) {
13
+ // 任务生命周期
14
+ EventType["TASK_CREATED"] = "task.created";
15
+ EventType["TASK_STARTED"] = "task.started";
16
+ EventType["TASK_COMPLETED"] = "task.completed";
17
+ EventType["TASK_FAILED"] = "task.failed";
18
+ EventType["TASK_CANCELLED"] = "task.cancelled";
19
+ // 步骤生命周期
20
+ EventType["STEP_COMPLETED"] = "step.completed";
21
+ EventType["STEP_FAILED"] = "step.failed";
22
+ // 话题生命周期
23
+ EventType["TOPIC_CREATED"] = "topic.created";
24
+ EventType["TOPIC_CLOSED"] = "topic.closed";
25
+ // Agent 通信
26
+ EventType["AGENT_MESSAGE"] = "agent.message";
27
+ EventType["AGENT_DISPATCH"] = "agent.dispatch";
28
+ // 路由事件
29
+ EventType["ROUTER_ANALYSIS"] = "router.analysis";
30
+ EventType["ROUTER_DISPATCH"] = "router.dispatch";
31
+ // 迭代事件
32
+ EventType["ITERATE_ROUND_DONE"] = "iterate.round_done";
33
+ // 系统事件
34
+ EventType["SYSTEM_READY"] = "system.ready";
35
+ EventType["SYSTEM_ERROR"] = "system.error";
36
+ // Session 生命周期
37
+ EventType["SESSION_CREATED"] = "session.created";
38
+ EventType["SESSION_ACTIVATED"] = "session.activated";
39
+ EventType["SESSION_IDLE"] = "session.idle";
40
+ EventType["SESSION_ENDED"] = "session.ended";
41
+ EventType["SESSION_ERROR"] = "session.error";
42
+ // Message 生命周期
43
+ EventType["FRONT_MESSAGE_RECEIVED"] = "front.message_received";
44
+ EventType["MESSAGE_RECEIVED"] = "message.received";
45
+ EventType["MESSAGE_SENT"] = "message.sent";
46
+ EventType["MESSAGE_FAILED"] = "message.failed";
47
+ EventType["MESSAGE_ROUTED"] = "message.routed";
48
+ // Routing 生命周期
49
+ EventType["ROUTING_STARTED"] = "routing.started";
50
+ EventType["ROUTING_COMPLETED"] = "routing.completed";
51
+ EventType["ROUTING_FAILED"] = "routing.failed";
52
+ })(EventType || (EventType = {}));
53
+ /**
54
+ * 前缀树节点 - 用于加速事件类型匹配
55
+ */
56
+ class TrieNode {
57
+ handlers = [];
58
+ children = new Map();
59
+ }
60
+ export class EventBus {
61
+ subscriptions = new Map();
62
+ eventHistory = [];
63
+ maxHistory = 1000;
64
+ maxAge = 24 * 60 * 60 * 1000; // 24小时
65
+ storagePath = null;
66
+ dlqPath = null;
67
+ cleanupTimer = null;
68
+ // DLQ 重试相关
69
+ dlqRetryTimer = null;
70
+ dlqAlertThreshold = 10; // DLQ 超过此数量时触发告警
71
+ dlqRetryDelayMs = 5000; // 5 秒后重试
72
+ maxDlqRetries = 3;
73
+ // 前缀树(加速订阅匹配)
74
+ subscriptionTrie = new TrieNode();
75
+ hasWildcard = false;
76
+ hasPatternSubs = false;
77
+ constructor(options) {
78
+ this.storagePath = options?.storagePath || null;
79
+ this.dlqPath = options?.dlqPath || null;
80
+ this.maxHistory = options?.maxHistory || 1000;
81
+ this.maxAge = options?.maxAge || 24 * 60 * 60 * 1000; // 24小时
82
+ if (options?.dlqAlertThreshold)
83
+ this.dlqAlertThreshold = options.dlqAlertThreshold;
84
+ if (options?.dlqRetryDelayMs)
85
+ this.dlqRetryDelayMs = options.dlqRetryDelayMs;
86
+ if (options?.maxDlqRetries)
87
+ this.maxDlqRetries = options.maxDlqRetries;
88
+ this.loadHistory();
89
+ this.startCleanupTimer();
90
+ this.startDlqRetryTimer();
91
+ }
92
+ /**
93
+ * 启动定期清理定时器
94
+ */
95
+ startCleanupTimer() {
96
+ // 每小时清理一次
97
+ this.cleanupTimer = setInterval(() => {
98
+ this.cleanup();
99
+ }, 60 * 60 * 1000);
100
+ }
101
+ /**
102
+ * 启动 DLQ 重试定时器
103
+ */
104
+ startDlqRetryTimer() {
105
+ this.dlqRetryTimer = setInterval(() => {
106
+ this.processDlqRetries();
107
+ }, this.dlqRetryDelayMs);
108
+ }
109
+ /**
110
+ * 计算指数退避延迟
111
+ */
112
+ calculateDlqBackoff(retries) {
113
+ return Math.min(this.dlqRetryDelayMs * Math.pow(2, retries), 60000);
114
+ }
115
+ /**
116
+ * 处理 DLQ 重试
117
+ */
118
+ async processDlqRetries() {
119
+ if (!this.dlqPath)
120
+ return;
121
+ try {
122
+ if (!fs.existsSync(this.dlqPath))
123
+ return;
124
+ const dlq = JSON.parse(fs.readFileSync(this.dlqPath, "utf-8"));
125
+ const now = Date.now();
126
+ const remaining = [];
127
+ for (const item of dlq) {
128
+ // 检查是否在退避中
129
+ const backoffDelay = this.calculateDlqBackoff(item.retries);
130
+ if (now - item.failed_at < backoffDelay) {
131
+ remaining.push(item);
132
+ continue;
133
+ }
134
+ if (item.retries < this.maxDlqRetries) {
135
+ // 重新尝试处理
136
+ let hadError = false;
137
+ try {
138
+ const handlers = this.getMatchingHandlers(item.event.type);
139
+ for (const { handler, async } of handlers) {
140
+ if (async) {
141
+ Promise.resolve(handler(item.event)).catch((e) => {
142
+ hadError = true;
143
+ console.error(`[EventBus] Async DLQ handler error: ${e instanceof Error ? e.message : String(e)}`);
144
+ });
145
+ }
146
+ else {
147
+ try {
148
+ handler(item.event);
149
+ }
150
+ catch (syncError) {
151
+ hadError = true;
152
+ // 同步 handler 错误不再被吞掉
153
+ console.error(`[EventBus] Sync DLQ handler error: ${syncError instanceof Error ? syncError.message : String(syncError)}, event: ${item.event.type}`);
154
+ }
155
+ }
156
+ }
157
+ // 成功处理,移除(无错误才移除)
158
+ if (!hadError) {
159
+ continue;
160
+ }
161
+ }
162
+ catch {
163
+ hadError = true;
164
+ }
165
+ if (hadError) {
166
+ item.retries++;
167
+ remaining.push(item);
168
+ }
169
+ }
170
+ else {
171
+ // 超过最大重试次数,保留但不重试,发布 exhausted 事件
172
+ remaining.push(item);
173
+ this.publish({
174
+ id: "",
175
+ type: "system.dlq_exhausted",
176
+ source: "event-bus",
177
+ data: { event: item.event, reason: item.reason, retries: item.retries },
178
+ timestamp: Date.now(),
179
+ }, false);
180
+ }
181
+ }
182
+ // 检查告警阈值
183
+ if (remaining.length >= this.dlqAlertThreshold) {
184
+ console.warn(`[EventBus] DLQ alert: ${remaining.length} events in DLQ, threshold: ${this.dlqAlertThreshold}`);
185
+ this.emitDlqAlert(remaining.length);
186
+ }
187
+ // 保存剩余 DLQ
188
+ if (remaining.length > 0) {
189
+ fs.writeFileSync(this.dlqPath, JSON.stringify(remaining, null, 2), "utf-8");
190
+ }
191
+ else if (fs.existsSync(this.dlqPath)) {
192
+ fs.unlinkSync(this.dlqPath);
193
+ }
194
+ }
195
+ catch {
196
+ // ignore
197
+ }
198
+ }
199
+ /**
200
+ * DLQ 告警(可被外部覆盖)
201
+ */
202
+ emitDlqAlert(count) {
203
+ // 发布告警事件
204
+ this.publish({
205
+ id: "",
206
+ type: "system.dlq_alert",
207
+ source: "event-bus",
208
+ data: { dlq_count: count, threshold: this.dlqAlertThreshold },
209
+ timestamp: Date.now(),
210
+ }, false);
211
+ console.error(`[EventBus] DLQ ALERT: ${count} dead letters accumulated`);
212
+ }
213
+ /**
214
+ * 清理过期事件,防止内存泄漏
215
+ */
216
+ cleanup() {
217
+ const now = Date.now();
218
+ const beforeCount = this.eventHistory.length;
219
+ // 按时间和数量双重限制
220
+ this.eventHistory = this.eventHistory.filter((e) => {
221
+ const ageOk = now - e.timestamp < this.maxAge;
222
+ return ageOk;
223
+ });
224
+ // 额外裁剪到 maxHistory
225
+ if (this.eventHistory.length > this.maxHistory) {
226
+ this.eventHistory = this.eventHistory.slice(-this.maxHistory);
227
+ }
228
+ const removed = beforeCount - this.eventHistory.length;
229
+ if (removed > 0) {
230
+ console.log(`[EventBus] Cleaned up ${removed} events`);
231
+ }
232
+ return removed;
233
+ }
234
+ /**
235
+ * 停止清理定时器和 DLQ 重试
236
+ */
237
+ dispose() {
238
+ if (this.cleanupTimer) {
239
+ clearInterval(this.cleanupTimer);
240
+ this.cleanupTimer = null;
241
+ }
242
+ if (this.dlqRetryTimer) {
243
+ clearInterval(this.dlqRetryTimer);
244
+ this.dlqRetryTimer = null;
245
+ }
246
+ this.subscriptions.clear();
247
+ this.eventHistory = [];
248
+ }
249
+ loadHistory() {
250
+ if (!this.storagePath)
251
+ return;
252
+ try {
253
+ if (fs.existsSync(this.storagePath)) {
254
+ const data = JSON.parse(fs.readFileSync(this.storagePath, "utf-8"));
255
+ this.eventHistory = (data.events || []).map((e) => ({
256
+ ...e,
257
+ timestamp: e.timestamp || Date.now(),
258
+ }));
259
+ }
260
+ }
261
+ catch {
262
+ // ignore
263
+ }
264
+ }
265
+ saveHistory() {
266
+ if (!this.storagePath)
267
+ return;
268
+ try {
269
+ const dir = path.dirname(this.storagePath);
270
+ if (!fs.existsSync(dir)) {
271
+ fs.mkdirSync(dir, { recursive: true });
272
+ }
273
+ const recent = this.eventHistory.slice(-this.maxHistory);
274
+ const data = { events: recent };
275
+ fs.writeFileSync(this.storagePath, JSON.stringify(data, null, 2), "utf-8");
276
+ }
277
+ catch {
278
+ // ignore
279
+ }
280
+ }
281
+ generateId() {
282
+ const now = new Date();
283
+ const ts = now.toISOString().replace(/[-:T.Z]/g, "").slice(0, 14);
284
+ return `EVT${ts}-${String(this.eventHistory.length).padStart(4, "0")}`;
285
+ }
286
+ publishDLQ(event, reason, retries = 0) {
287
+ if (!this.dlqPath)
288
+ return;
289
+ try {
290
+ let dlq = [];
291
+ if (fs.existsSync(this.dlqPath)) {
292
+ dlq = JSON.parse(fs.readFileSync(this.dlqPath, "utf-8"));
293
+ }
294
+ dlq.push({ event, reason, failed_at: Date.now(), retries });
295
+ dlq = dlq.slice(-100); // keep last 100
296
+ const dir = path.dirname(this.dlqPath);
297
+ if (!fs.existsSync(dir)) {
298
+ fs.mkdirSync(dir, { recursive: true });
299
+ }
300
+ fs.writeFileSync(this.dlqPath, JSON.stringify(dlq, null, 2), "utf-8");
301
+ }
302
+ catch {
303
+ // ignore
304
+ }
305
+ }
306
+ // === 订阅管理 ===
307
+ /**
308
+ * 将事件类型插入前缀树
309
+ */
310
+ insertIntoTrie(eventType, sub) {
311
+ // 检查是否有通配符或模式
312
+ if (eventType.includes("*")) {
313
+ this.hasPatternSubs = true;
314
+ // 模式订阅仍保留在 Map 中(匹配时单独处理)
315
+ return;
316
+ }
317
+ const parts = eventType.split(".");
318
+ let node = this.subscriptionTrie;
319
+ for (const part of parts) {
320
+ if (!node.children.has(part)) {
321
+ node.children.set(part, new TrieNode());
322
+ }
323
+ node = node.children.get(part);
324
+ }
325
+ node.handlers.push(sub);
326
+ }
327
+ /**
328
+ * 从前缀树移除订阅
329
+ */
330
+ removeFromTrie(eventType, handler) {
331
+ if (eventType.includes("*"))
332
+ return;
333
+ const parts = eventType.split(".");
334
+ let node = this.subscriptionTrie;
335
+ for (const part of parts) {
336
+ if (!node.children.has(part))
337
+ return;
338
+ node = node.children.get(part);
339
+ }
340
+ const idx = node.handlers.findIndex(s => s.handler === handler);
341
+ if (idx !== -1) {
342
+ node.handlers.splice(idx, 1);
343
+ }
344
+ }
345
+ subscribe(eventType, handler) {
346
+ if (!this.subscriptions.has(eventType)) {
347
+ this.subscriptions.set(eventType, []);
348
+ }
349
+ const sub = { handler, async: false };
350
+ this.subscriptions.get(eventType).push(sub);
351
+ // 插入前缀树
352
+ if (!eventType.includes("*")) {
353
+ this.insertIntoTrie(eventType, sub);
354
+ }
355
+ if (eventType === "*") {
356
+ this.hasWildcard = true;
357
+ }
358
+ // 返回取消订阅函数
359
+ return () => this.unsubscribe(eventType, handler);
360
+ }
361
+ subscribeAsync(eventType, handler) {
362
+ if (!this.subscriptions.has(eventType)) {
363
+ this.subscriptions.set(eventType, []);
364
+ }
365
+ const sub = { handler: handler, async: true };
366
+ this.subscriptions.get(eventType).push(sub);
367
+ // 插入前缀树
368
+ if (!eventType.includes("*")) {
369
+ this.insertIntoTrie(eventType, sub);
370
+ }
371
+ if (eventType === "*") {
372
+ this.hasWildcard = true;
373
+ }
374
+ return () => this.unsubscribe(eventType, handler);
375
+ }
376
+ unsubscribe(eventType, handler) {
377
+ const subs = this.subscriptions.get(eventType);
378
+ if (!subs)
379
+ return;
380
+ const idx = subs.findIndex((s) => s.handler === handler);
381
+ if (idx !== -1) {
382
+ subs.splice(idx, 1);
383
+ }
384
+ // 从前缀树移除
385
+ this.removeFromTrie(eventType, handler);
386
+ if (subs.length === 0) {
387
+ this.subscriptions.delete(eventType);
388
+ }
389
+ // 检查是否还有通配符订阅
390
+ this.hasWildcard = this.subscriptions.has("*");
391
+ this.hasPatternSubs = Array.from(this.subscriptions.keys()).some(k => k.includes("*"));
392
+ }
393
+ // === 事件发布 ===
394
+ publish(event, persist = true) {
395
+ event.id = event.id || this.generateId();
396
+ // 自动从 data 中提取 session_key 用于隔离
397
+ if (!event.session_key && event.data?.session_key) {
398
+ event.session_key = event.data.session_key;
399
+ }
400
+ if (persist) {
401
+ this.eventHistory.push(event);
402
+ this.saveHistory();
403
+ }
404
+ // 获取所有匹配的处理者(使用优化的匹配算法)
405
+ const handlers = this.getMatchingHandlers(event.type);
406
+ for (const { handler, async } of handlers) {
407
+ try {
408
+ // Session 隔离验证:检查 handler 是否需要 session_key 过滤
409
+ if (event.session_key && !this.handlerAcceptsSession(event.session_key, handler)) {
410
+ continue; // 跳过不匹配 session 的 handler
411
+ }
412
+ if (async) {
413
+ // 异步处理,不 await
414
+ Promise.resolve(handler(event)).catch((e) => this.publishDLQ(event, `Async handler error: ${e instanceof Error ? e.message : String(e)}`, 0));
415
+ }
416
+ else {
417
+ handler(event);
418
+ }
419
+ }
420
+ catch (e) {
421
+ const errorMsg = e instanceof Error ? e.message : String(e);
422
+ this.publishDLQ(event, `Handler error: ${errorMsg}`, 0);
423
+ }
424
+ }
425
+ }
426
+ /**
427
+ * 检查 handler 是否应该接受某个 session 的事件
428
+ * 通过检查订阅时的 eventType 是否包含 session 信息来简单判断
429
+ * 实际生产中建议在订阅时显式注册 session-scoped handlers
430
+ */
431
+ handlerAcceptsSession(sessionKey, handler) {
432
+ // 默认接受所有。如果需要严格 session 隔离,
433
+ // 可以在 subscribe 时记录 session_key 并在此验证
434
+ // 这里做宽松处理:只记录但不阻止
435
+ return true;
436
+ }
437
+ async publishAsync(event, persist = true) {
438
+ event.id = event.id || this.generateId();
439
+ if (persist) {
440
+ this.eventHistory.push(event);
441
+ this.saveHistory();
442
+ }
443
+ const handlers = this.getMatchingHandlers(event.type);
444
+ for (const { handler, async } of handlers) {
445
+ try {
446
+ await handler(event);
447
+ }
448
+ catch (e) {
449
+ this.publishDLQ(event, `Async handler error: ${e}`, 0);
450
+ }
451
+ }
452
+ }
453
+ /**
454
+ * 优化的订阅匹配(使用前缀树 + 缓存)
455
+ */
456
+ getMatchingHandlers(eventType) {
457
+ const handlers = [];
458
+ // 1. 前缀树精确匹配(O(k),k=事件类型分段数)
459
+ if (!this.hasPatternSubs) {
460
+ // 快速路径:无模式订阅,只用前缀树
461
+ const parts = eventType.split(".");
462
+ let node = this.subscriptionTrie;
463
+ for (const part of parts) {
464
+ // 收集当前节点的处理者
465
+ if (node.handlers.length > 0) {
466
+ handlers.push(...node.handlers);
467
+ }
468
+ // 回溯检查通配符 "*"
469
+ if (node.children.has("*")) {
470
+ handlers.push(...node.children.get("*").handlers);
471
+ }
472
+ if (!node.children.has(part))
473
+ break;
474
+ node = node.children.get(part);
475
+ }
476
+ }
477
+ else {
478
+ // 慢速路径:需要检查模式匹配
479
+ const parts = eventType.split(".");
480
+ this.collectMatchingHandlersFromTrie(parts, 0, this.subscriptionTrie, handlers);
481
+ }
482
+ // 2. 通配符 "*"
483
+ if (this.hasWildcard) {
484
+ handlers.push(...this.subscriptions.get("*"));
485
+ }
486
+ // 3. 模式匹配(如 "task.*")
487
+ if (this.hasPatternSubs) {
488
+ for (const [pattern, subs] of this.subscriptions.entries()) {
489
+ if (!pattern.includes("*"))
490
+ continue;
491
+ if (this.matchPattern(pattern, eventType)) {
492
+ handlers.push(...subs);
493
+ }
494
+ }
495
+ }
496
+ return handlers;
497
+ }
498
+ /**
499
+ * 从前缀树收集匹配的处理者
500
+ * 支持通配符 "*" 子节点回溯匹配
501
+ */
502
+ collectMatchingHandlersFromTrie(parts, idx, node, handlers) {
503
+ if (idx >= parts.length) {
504
+ handlers.push(...node.handlers);
505
+ return;
506
+ }
507
+ const currentPart = parts[idx];
508
+ // 优先收集当前节点的处理者(父级匹配)
509
+ if (node.handlers.length > 0) {
510
+ handlers.push(...node.handlers);
511
+ }
512
+ // 精确匹配子节点
513
+ if (node.children.has(currentPart)) {
514
+ this.collectMatchingHandlersFromTrie(parts, idx + 1, node.children.get(currentPart), handlers);
515
+ }
516
+ // 回溯检查通配符 "*" 子节点
517
+ if (node.children.has("*")) {
518
+ this.collectMatchingHandlersFromTrie(parts, idx + 1, node.children.get("*"), handlers);
519
+ }
520
+ }
521
+ /**
522
+ * 检查事件类型是否匹配模式(如 "task.*" 匹配 "task.created")
523
+ */
524
+ matchPattern(pattern, eventType) {
525
+ const patternParts = pattern.split(".");
526
+ const eventParts = eventType.split(".");
527
+ if (patternParts.length !== eventParts.length) {
528
+ return false;
529
+ }
530
+ for (let i = 0; i < patternParts.length; i++) {
531
+ if (patternParts[i] !== "*" && patternParts[i] !== eventParts[i]) {
532
+ return false;
533
+ }
534
+ }
535
+ return true;
536
+ }
537
+ // === 历史查询 ===
538
+ /**
539
+ * 统一提取 session_key 的方法
540
+ */
541
+ extractSessionKey(event) {
542
+ return event.session_key || event.data?.session_key;
543
+ }
544
+ getHistory(eventType, limit = 100) {
545
+ let history = this.eventHistory.slice(-limit);
546
+ if (eventType) {
547
+ history = history.filter((e) => e.type === eventType);
548
+ }
549
+ return history;
550
+ }
551
+ /**
552
+ * 获取特定 session 的事件历史
553
+ */
554
+ getHistoryBySession(sessionKey, limit = 100) {
555
+ const sessionEvents = this.eventHistory.filter((e) => this.extractSessionKey(e) === sessionKey);
556
+ return sessionEvents.slice(-limit);
557
+ }
558
+ /**
559
+ * 获取所有活跃 session(最近有事件的 session)
560
+ */
561
+ getActiveSessions() {
562
+ const sessions = new Set();
563
+ const cutoff = Date.now() - this.maxAge;
564
+ for (const event of this.eventHistory) {
565
+ if (event.timestamp < cutoff)
566
+ continue;
567
+ const sk = this.extractSessionKey(event);
568
+ if (sk) {
569
+ sessions.add(sk);
570
+ }
571
+ }
572
+ return Array.from(sessions);
573
+ }
574
+ clearHistory() {
575
+ this.eventHistory = [];
576
+ this.saveHistory();
577
+ }
578
+ // === 便捷发布方法 ===
579
+ emitTaskCreated(taskId, agent, extra = {}) {
580
+ this.publish({
581
+ id: "",
582
+ type: EventType.TASK_CREATED,
583
+ source: "event-bus",
584
+ data: { task_id: taskId, agent, ...extra },
585
+ timestamp: Date.now(),
586
+ });
587
+ }
588
+ emitTaskCompleted(taskId, result, extra = {}) {
589
+ this.publish({
590
+ id: "",
591
+ type: EventType.TASK_COMPLETED,
592
+ source: "event-bus",
593
+ data: { task_id: taskId, result, ...extra },
594
+ timestamp: Date.now(),
595
+ });
596
+ }
597
+ emitTaskFailed(taskId, error, extra = {}) {
598
+ this.publish({
599
+ id: "",
600
+ type: EventType.TASK_FAILED,
601
+ source: "event-bus",
602
+ data: { task_id: taskId, error, ...extra },
603
+ timestamp: Date.now(),
604
+ });
605
+ }
606
+ emitAgentDispatch(from, to, taskId, extra = {}) {
607
+ this.publish({
608
+ id: "",
609
+ type: EventType.AGENT_DISPATCH,
610
+ source: from,
611
+ data: { from, to, task_id: taskId, ...extra },
612
+ timestamp: Date.now(),
613
+ });
614
+ }
615
+ emitRouterAnalysis(userMessage, intent, confidence, extra = {}) {
616
+ this.publish({
617
+ id: "",
618
+ type: EventType.ROUTER_ANALYSIS,
619
+ source: "router",
620
+ data: { user_message: userMessage, intent, confidence, ...extra },
621
+ timestamp: Date.now(),
622
+ });
623
+ }
624
+ // === 迭代事件 ===
625
+ emitIterateRoundDone(taskId, verdict, extra = {}) {
626
+ this.publish({
627
+ id: "",
628
+ type: EventType.ITERATE_ROUND_DONE,
629
+ source: "evaluator",
630
+ data: { task_id: taskId, verdict, ...extra },
631
+ timestamp: Date.now(),
632
+ });
633
+ }
634
+ // === Session 事件 ===
635
+ emitSessionActivated(sessionKey, extra = {}) {
636
+ this.publish({
637
+ id: "",
638
+ type: EventType.SESSION_ACTIVATED,
639
+ source: "event-bus",
640
+ data: { session_key: sessionKey, ...extra },
641
+ timestamp: Date.now(),
642
+ });
643
+ }
644
+ emitSessionIdle(sessionKey, extra = {}) {
645
+ this.publish({
646
+ id: "",
647
+ type: EventType.SESSION_IDLE,
648
+ source: "event-bus",
649
+ data: { session_key: sessionKey, ...extra },
650
+ timestamp: Date.now(),
651
+ });
652
+ }
653
+ emitSessionError(sessionKey, error, extra = {}) {
654
+ this.publish({
655
+ id: "",
656
+ type: EventType.SESSION_ERROR,
657
+ source: "event-bus",
658
+ data: { session_key: sessionKey, error, ...extra },
659
+ timestamp: Date.now(),
660
+ });
661
+ }
662
+ // === Message 事件 ===
663
+ // 入口层消息接收事件(载荷格式: {session_id, content})
664
+ emitFrontMessageReceived(sessionId, content, extra = {}) {
665
+ this.publish({
666
+ id: "",
667
+ type: EventType.FRONT_MESSAGE_RECEIVED,
668
+ source: "front_director",
669
+ data: { session_id: sessionId, content, ...extra },
670
+ timestamp: Date.now(),
671
+ });
672
+ }
673
+ emitMessageReceived(sessionKey, message, extra = {}) {
674
+ this.publish({
675
+ id: "",
676
+ type: EventType.MESSAGE_RECEIVED,
677
+ source: "event-bus",
678
+ data: { session_key: sessionKey, message, ...extra },
679
+ timestamp: Date.now(),
680
+ });
681
+ }
682
+ emitMessageSent(sessionKey, message, extra = {}) {
683
+ this.publish({
684
+ id: "",
685
+ type: EventType.MESSAGE_SENT,
686
+ source: "event-bus",
687
+ data: { session_key: sessionKey, message, ...extra },
688
+ timestamp: Date.now(),
689
+ });
690
+ }
691
+ emitMessageRouted(sessionKey, routedTo, extra = {}) {
692
+ this.publish({
693
+ id: "",
694
+ type: EventType.MESSAGE_ROUTED,
695
+ source: "router",
696
+ data: { session_key: sessionKey, routed_to: routedTo, ...extra },
697
+ timestamp: Date.now(),
698
+ });
699
+ }
700
+ // === Routing 事件 ===
701
+ emitRoutingStarted(sessionKey, message, extra = {}) {
702
+ this.publish({
703
+ id: "",
704
+ type: EventType.ROUTING_STARTED,
705
+ source: "router",
706
+ data: { session_key: sessionKey, message, ...extra },
707
+ timestamp: Date.now(),
708
+ });
709
+ }
710
+ emitRoutingCompleted(sessionKey, intent, extra = {}) {
711
+ this.publish({
712
+ id: "",
713
+ type: EventType.ROUTING_COMPLETED,
714
+ source: "router",
715
+ data: { session_key: sessionKey, intent, ...extra },
716
+ timestamp: Date.now(),
717
+ });
718
+ }
719
+ emitRoutingFailed(sessionKey, error, extra = {}) {
720
+ this.publish({
721
+ id: "",
722
+ type: EventType.ROUTING_FAILED,
723
+ source: "router",
724
+ data: { session_key: sessionKey, error, ...extra },
725
+ timestamp: Date.now(),
726
+ });
727
+ }
728
+ // router.dispatched 事件(载荷: {session_id, plan})
729
+ emitRouterDispatched(sessionId, plan, extra = {}) {
730
+ this.publish({
731
+ id: "",
732
+ type: EventType.ROUTER_DISPATCH,
733
+ source: "router",
734
+ data: { session_id: sessionId, plan, ...extra },
735
+ timestamp: Date.now(),
736
+ });
737
+ }
738
+ }
739
+ // 全局单例
740
+ let globalBus = null;
741
+ export function getEventBus() {
742
+ if (!globalBus) {
743
+ globalBus = new EventBus({ storagePath: "data/events.json", dlqPath: "data/events.dlq.json" });
744
+ }
745
+ return globalBus;
746
+ }
747
+ export function setEventBus(bus) {
748
+ globalBus = bus;
749
+ }
750
+ //# sourceMappingURL=event-bus.js.map