@ai-setting/roy-agent-core 1.5.15-test → 1.5.16-test
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/dist/config/index.d.ts +1250 -0
- package/dist/config/index.js +32 -0
- package/dist/env/agent/index.d.ts +2279 -0
- package/dist/env/agent/index.js +24 -0
- package/dist/env/commands/index.d.ts +1131 -0
- package/dist/env/commands/index.js +14 -0
- package/dist/env/debug/formatters/index.d.ts +236 -0
- package/dist/env/debug/formatters/index.js +11 -0
- package/dist/env/debug/index.d.ts +1652 -0
- package/dist/env/debug/index.js +26 -0
- package/dist/env/hook/index.d.ts +279 -0
- package/dist/env/hook/index.js +29 -0
- package/dist/env/index.d.ts +3481 -0
- package/dist/env/index.js +82 -0
- package/dist/env/llm/index.d.ts +1760 -0
- package/dist/env/llm/index.js +40 -0
- package/dist/env/log-trace/index.d.ts +1574 -0
- package/dist/env/log-trace/index.js +83 -0
- package/dist/env/mcp/index.d.ts +1331 -0
- package/dist/env/mcp/index.js +39 -0
- package/dist/env/mcp/tool/index.d.ts +183 -0
- package/dist/env/mcp/tool/index.js +14 -0
- package/dist/env/memory/built-in/index.d.ts +232 -0
- package/dist/env/memory/built-in/index.js +11 -0
- package/dist/env/memory/index.d.ts +1799 -0
- package/dist/env/memory/index.js +56 -0
- package/dist/env/memory/plugin/index.d.ts +747 -0
- package/dist/env/memory/plugin/index.js +36 -0
- package/dist/env/prompt/index.d.ts +1164 -0
- package/dist/env/prompt/index.js +20 -0
- package/dist/env/session/index.d.ts +1908 -0
- package/dist/env/session/index.js +25 -0
- package/dist/env/session/storage/index.d.ts +564 -0
- package/dist/env/session/storage/index.js +18 -0
- package/dist/env/skill/index.d.ts +1266 -0
- package/dist/env/skill/index.js +34 -0
- package/dist/env/skill/tool/index.d.ts +193 -0
- package/dist/env/skill/tool/index.js +9 -0
- package/dist/env/task/delegate/index.d.ts +1612 -0
- package/dist/env/task/delegate/index.js +18 -0
- package/dist/env/task/events/index.d.ts +171 -0
- package/dist/env/task/events/index.js +7 -0
- package/dist/env/task/hooks/index.d.ts +624 -0
- package/dist/env/task/hooks/index.js +7 -0
- package/dist/env/task/index.d.ts +1553 -0
- package/dist/env/task/index.js +34 -0
- package/dist/env/task/plugins/index.d.ts +466 -0
- package/dist/env/task/plugins/index.js +23 -0
- package/dist/env/task/storage/index.d.ts +241 -0
- package/dist/env/task/storage/index.js +14 -0
- package/dist/env/task/tools/index.d.ts +1485 -0
- package/dist/env/task/tools/index.js +17 -0
- package/dist/env/task/tools/operation/index.d.ts +1484 -0
- package/dist/env/task/tools/operation/index.js +15 -0
- package/dist/env/tool/built-in/index.d.ts +218 -0
- package/dist/env/tool/built-in/index.js +25 -0
- package/dist/env/tool/index.d.ts +1396 -0
- package/dist/env/tool/index.js +39 -0
- package/dist/env/workflow/decorators/index.d.ts +2161 -0
- package/dist/env/workflow/decorators/index.js +27 -0
- package/dist/env/workflow/engine/index.d.ts +3453 -0
- package/dist/env/workflow/engine/index.js +28 -0
- package/dist/env/workflow/index.d.ts +3546 -0
- package/dist/env/workflow/index.js +136 -0
- package/dist/env/workflow/nodes/index.d.ts +2092 -0
- package/dist/env/workflow/nodes/index.js +19 -0
- package/dist/env/workflow/service/index.d.ts +227 -0
- package/dist/env/workflow/service/index.js +13 -0
- package/dist/env/workflow/storage/index.d.ts +165 -0
- package/dist/env/workflow/storage/index.js +27 -0
- package/dist/env/workflow/tools/index.d.ts +416 -0
- package/dist/env/workflow/tools/index.js +159 -0
- package/dist/env/workflow/types/index.d.ts +2255 -0
- package/dist/env/workflow/types/index.js +98 -0
- package/dist/env/workflow/utils/index.d.ts +2031 -0
- package/dist/env/workflow/utils/index.js +637 -0
- package/dist/index.d.ts +7858 -0
- package/dist/index.js +399 -0
- package/dist/shared/@ai-setting/roy-agent-core-0rtxwr28.js +258 -0
- package/dist/shared/@ai-setting/roy-agent-core-0vbdz0x7.js +36 -0
- package/dist/shared/@ai-setting/roy-agent-core-1akcqxj9.js +349 -0
- package/dist/shared/@ai-setting/roy-agent-core-1ce3fqrk.js +117 -0
- package/dist/shared/@ai-setting/roy-agent-core-2dhd60aw.js +11 -0
- package/dist/shared/@ai-setting/roy-agent-core-3jywqmdd.js +393 -0
- package/dist/shared/@ai-setting/roy-agent-core-3rr5k71j.js +200 -0
- package/dist/shared/@ai-setting/roy-agent-core-44hnfb02.js +299 -0
- package/dist/shared/@ai-setting/roy-agent-core-4t40mkpv.js +206 -0
- package/dist/shared/@ai-setting/roy-agent-core-4txzpsbt.js +393 -0
- package/dist/shared/@ai-setting/roy-agent-core-5x94xmt6.js +350 -0
- package/dist/shared/@ai-setting/roy-agent-core-69jskqjg.js +180 -0
- package/dist/shared/@ai-setting/roy-agent-core-6kvtahqv.js +408 -0
- package/dist/shared/@ai-setting/roy-agent-core-7fgf85wc.js +284 -0
- package/dist/shared/@ai-setting/roy-agent-core-81w1963m.js +762 -0
- package/dist/shared/@ai-setting/roy-agent-core-8gxth0eh.js +10 -0
- package/dist/shared/@ai-setting/roy-agent-core-92z6t4he.js +14 -0
- package/dist/shared/@ai-setting/roy-agent-core-93zfb3r1.js +922 -0
- package/dist/shared/@ai-setting/roy-agent-core-9yxb3ty9.js +15 -0
- package/dist/shared/@ai-setting/roy-agent-core-b0x5dda6.js +1130 -0
- package/dist/shared/@ai-setting/roy-agent-core-bcbqy27c.js +14 -0
- package/dist/shared/@ai-setting/roy-agent-core-bvr1761x.js +653 -0
- package/dist/shared/@ai-setting/roy-agent-core-ctdhjv68.js +93 -0
- package/dist/shared/@ai-setting/roy-agent-core-d7cyjkf7.js +872 -0
- package/dist/shared/@ai-setting/roy-agent-core-dh9d7a3m.js +11 -0
- package/dist/shared/@ai-setting/roy-agent-core-e25xkv53.js +64 -0
- package/dist/shared/@ai-setting/roy-agent-core-eajcvp4e.js +378 -0
- package/dist/shared/@ai-setting/roy-agent-core-f7q2x5z6.js +492 -0
- package/dist/shared/@ai-setting/roy-agent-core-fs0mn2jk.js +52 -0
- package/dist/shared/@ai-setting/roy-agent-core-g1s2h0e5.js +171 -0
- package/dist/shared/@ai-setting/roy-agent-core-g99pxzn5.js +862 -0
- package/dist/shared/@ai-setting/roy-agent-core-gbqcyegm.js +1387 -0
- package/dist/shared/@ai-setting/roy-agent-core-gjq1yk68.js +208 -0
- package/dist/shared/@ai-setting/roy-agent-core-gq20wsgv.js +139 -0
- package/dist/shared/@ai-setting/roy-agent-core-gwc4h96n.js +534 -0
- package/dist/shared/@ai-setting/roy-agent-core-jfh9q2qh.js +204 -0
- package/dist/shared/@ai-setting/roy-agent-core-jvatggbb.js +603 -0
- package/dist/shared/@ai-setting/roy-agent-core-kkbwepqb.js +97 -0
- package/dist/shared/@ai-setting/roy-agent-core-pjr12nnd.js +587 -0
- package/dist/shared/@ai-setting/roy-agent-core-psv4v63c.js +176 -0
- package/dist/shared/@ai-setting/roy-agent-core-psvxt4c9.js +60 -0
- package/dist/shared/@ai-setting/roy-agent-core-qqceba6k.js +442 -0
- package/dist/shared/@ai-setting/roy-agent-core-qxhq8ven.js +57 -0
- package/dist/shared/@ai-setting/roy-agent-core-qxnbvgwe.js +66 -0
- package/dist/shared/@ai-setting/roy-agent-core-r9ezzemr.js +10 -0
- package/dist/shared/@ai-setting/roy-agent-core-rhmtwnw1.js +267 -0
- package/dist/shared/@ai-setting/roy-agent-core-rvv6ydff.js +584 -0
- package/dist/shared/@ai-setting/roy-agent-core-rvxg1wps.js +102 -0
- package/dist/shared/@ai-setting/roy-agent-core-satmq6sh.js +549 -0
- package/dist/shared/@ai-setting/roy-agent-core-sx7wsvnn.js +15 -0
- package/dist/shared/@ai-setting/roy-agent-core-t94ktchq.js +213 -0
- package/dist/shared/@ai-setting/roy-agent-core-vf215qfv.js +812 -0
- package/dist/shared/@ai-setting/roy-agent-core-vkz81f7v.js +1316 -0
- package/dist/shared/@ai-setting/roy-agent-core-vn2bc59q.js +1205 -0
- package/dist/shared/@ai-setting/roy-agent-core-wa1kzqky.js +328 -0
- package/dist/shared/@ai-setting/roy-agent-core-wft9ra24.js +20 -0
- package/dist/shared/@ai-setting/roy-agent-core-wrcy0h6z.js +2098 -0
- package/dist/shared/@ai-setting/roy-agent-core-xq8hhqb8.js +419 -0
- package/dist/shared/@ai-setting/roy-agent-core-xs5rsgat.js +368 -0
- package/dist/shared/@ai-setting/roy-agent-core-zbkpc41z.js +377 -0
- package/dist/shared/@ai-setting/roy-agent-core-zgypchmt.js +172 -0
- package/dist/shared/@ai-setting/roy-agent-core-zpn0bqa8.js +103 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1387 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ContextError,
|
|
3
|
+
ErrorCodes
|
|
4
|
+
} from "./roy-agent-core-ctdhjv68.js";
|
|
5
|
+
import {
|
|
6
|
+
envKeyToConfigKey
|
|
7
|
+
} from "./roy-agent-core-qxhq8ven.js";
|
|
8
|
+
import {
|
|
9
|
+
BaseComponent
|
|
10
|
+
} from "./roy-agent-core-kkbwepqb.js";
|
|
11
|
+
import {
|
|
12
|
+
TracedAs,
|
|
13
|
+
init_decorator
|
|
14
|
+
} from "./roy-agent-core-zgypchmt.js";
|
|
15
|
+
import {
|
|
16
|
+
createLogger,
|
|
17
|
+
init_logger
|
|
18
|
+
} from "./roy-agent-core-44hnfb02.js";
|
|
19
|
+
import {
|
|
20
|
+
__legacyDecorateClassTS,
|
|
21
|
+
__require
|
|
22
|
+
} from "./roy-agent-core-fs0mn2jk.js";
|
|
23
|
+
|
|
24
|
+
// src/env/environment.ts
|
|
25
|
+
init_logger();
|
|
26
|
+
|
|
27
|
+
// src/utils/id.ts
|
|
28
|
+
function generateId(prefix = "id") {
|
|
29
|
+
const timestamp = Date.now().toString(36);
|
|
30
|
+
const random = Math.random().toString(36).substring(2, 11);
|
|
31
|
+
return `${prefix}_${timestamp}_${random}`;
|
|
32
|
+
}
|
|
33
|
+
function generateDescendingId(prefix = "id") {
|
|
34
|
+
const timestamp = (Number.MAX_SAFE_INTEGER - Date.now()).toString(36);
|
|
35
|
+
const random = Math.random().toString(36).substring(2, 11);
|
|
36
|
+
return `${prefix}_${timestamp}_${random}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/env/environment.ts
|
|
40
|
+
init_decorator();
|
|
41
|
+
import * as fsSync from "fs";
|
|
42
|
+
import * as path from "path";
|
|
43
|
+
var logger = createLogger("environment");
|
|
44
|
+
|
|
45
|
+
class BaseEnvironment extends BaseComponent {
|
|
46
|
+
name;
|
|
47
|
+
version;
|
|
48
|
+
components = new Map;
|
|
49
|
+
constructor(config) {
|
|
50
|
+
super();
|
|
51
|
+
this.name = config?.name ?? "base-environment";
|
|
52
|
+
this.version = config?.version ?? "1.0.0";
|
|
53
|
+
this.registerDefaultComponents();
|
|
54
|
+
}
|
|
55
|
+
registerDefaultComponents() {}
|
|
56
|
+
getConfig() {
|
|
57
|
+
return {
|
|
58
|
+
name: this.name,
|
|
59
|
+
version: this.version,
|
|
60
|
+
enabled: true,
|
|
61
|
+
env: this
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
registerComponent(component) {
|
|
65
|
+
this.components.set(component.name, component);
|
|
66
|
+
logger.debug(`Component registered: ${component.name}`);
|
|
67
|
+
}
|
|
68
|
+
unregisterComponent(name) {
|
|
69
|
+
const component = this.components.get(name);
|
|
70
|
+
if (component) {
|
|
71
|
+
this.components.delete(name);
|
|
72
|
+
logger.debug(`Component unregistered: ${name}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
getComponent(name) {
|
|
76
|
+
return this.components.get(name);
|
|
77
|
+
}
|
|
78
|
+
listComponents() {
|
|
79
|
+
return Array.from(this.components.values());
|
|
80
|
+
}
|
|
81
|
+
async handle_query(query, context) {
|
|
82
|
+
const agentComponent = this.getComponent("agent");
|
|
83
|
+
if (!agentComponent) {
|
|
84
|
+
throw new Error("AgentComponent not found. Please register AgentComponent before calling handle_query.");
|
|
85
|
+
}
|
|
86
|
+
let systemPrompt = "You are a helpful assistant.";
|
|
87
|
+
let promptSource = "fallback";
|
|
88
|
+
try {
|
|
89
|
+
const promptComponent = this.getComponent("prompt");
|
|
90
|
+
if (promptComponent) {
|
|
91
|
+
const loadedPrompt = await promptComponent.getPrompt("default");
|
|
92
|
+
if (loadedPrompt) {
|
|
93
|
+
systemPrompt = loadedPrompt;
|
|
94
|
+
promptSource = "PromptComponent";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch (err) {
|
|
98
|
+
logger.warn(`[handle_query] Failed to get prompt from PromptComponent: ${err}`);
|
|
99
|
+
}
|
|
100
|
+
logger.info(`[handle_query] Using system prompt from ${promptSource}, length: ${systemPrompt.length}`);
|
|
101
|
+
let finalSystemPrompt = systemPrompt;
|
|
102
|
+
if (systemPrompt.includes("{{memory}}")) {
|
|
103
|
+
try {
|
|
104
|
+
const memoryComponent = this.getComponent("memory");
|
|
105
|
+
if (memoryComponent) {
|
|
106
|
+
const memoryContent = await memoryComponent.recallMemory();
|
|
107
|
+
finalSystemPrompt = systemPrompt.replace("{{memory}}", memoryContent || "(No memory)");
|
|
108
|
+
logger.info(`[handle_query] Injected memory content, length: ${memoryContent.length}`);
|
|
109
|
+
} else {
|
|
110
|
+
finalSystemPrompt = systemPrompt.replace("{{memory}}", "(Memory component not available)");
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
logger.warn(`[handle_query] Failed to load memory content: ${err}`);
|
|
114
|
+
finalSystemPrompt = systemPrompt.replace("{{memory}}", "(Failed to load memory)");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (finalSystemPrompt.includes("{{workspace_dir}}")) {
|
|
118
|
+
const workspaceDir = process.cwd();
|
|
119
|
+
finalSystemPrompt = finalSystemPrompt.replace("{{workspace_dir}}", workspaceDir);
|
|
120
|
+
logger.debug(`[handle_query] Injected workspace_dir: ${workspaceDir}`);
|
|
121
|
+
}
|
|
122
|
+
const agentName = context?.agentType && context.agentType !== "default" ? context.agentType : "default";
|
|
123
|
+
let agent = agentComponent.getAgent(agentName);
|
|
124
|
+
if (!agent) {
|
|
125
|
+
if (agentName !== "default") {
|
|
126
|
+
logger.warn(`[handle_query] Agent "${agentName}" not found, falling back to "default"`);
|
|
127
|
+
}
|
|
128
|
+
agent = agentComponent.getAgent("default");
|
|
129
|
+
}
|
|
130
|
+
if (!agent) {
|
|
131
|
+
agent = agentComponent.registerAgent("default", {
|
|
132
|
+
type: "primary",
|
|
133
|
+
systemPrompt: finalSystemPrompt
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const result = await agentComponent.run(agent.name, query, context);
|
|
137
|
+
if (result.error) {
|
|
138
|
+
const errorMsg = result.error.toLowerCase();
|
|
139
|
+
if (errorMsg.includes("context") || errorMsg.includes("threshold") || errorMsg.includes("token") || result.error.includes("CTX_001")) {
|
|
140
|
+
const usageMatch = result.error.match(/(\d+)\/(\d+)\s*\(([\d.]+)%\)/);
|
|
141
|
+
let usage;
|
|
142
|
+
let contextWindow;
|
|
143
|
+
if (usageMatch) {
|
|
144
|
+
const totalTokens = parseInt(usageMatch[1], 10);
|
|
145
|
+
usage = {
|
|
146
|
+
promptTokens: totalTokens,
|
|
147
|
+
completionTokens: 0,
|
|
148
|
+
totalTokens
|
|
149
|
+
};
|
|
150
|
+
contextWindow = parseInt(usageMatch[2], 10);
|
|
151
|
+
}
|
|
152
|
+
const ctxError = new ContextError(result.error, ErrorCodes.CONTEXT_THRESHOLD_EXCEEDED, context?.sessionId, usage, contextWindow);
|
|
153
|
+
throw ctxError;
|
|
154
|
+
}
|
|
155
|
+
throw new Error(result.error);
|
|
156
|
+
}
|
|
157
|
+
return result.finalText || "";
|
|
158
|
+
}
|
|
159
|
+
async handle_action(action, context) {
|
|
160
|
+
throw new Error("handle_action not implemented. Override in subclass.");
|
|
161
|
+
}
|
|
162
|
+
eventHandlers = new Map;
|
|
163
|
+
wildcardHandlers = new Set;
|
|
164
|
+
subscribe(handler) {
|
|
165
|
+
this.wildcardHandlers.add(handler);
|
|
166
|
+
logger.debug(`EnvEvent handler subscribed (wildcard)`);
|
|
167
|
+
return () => {
|
|
168
|
+
this.wildcardHandlers.delete(handler);
|
|
169
|
+
logger.debug(`EnvEvent handler unsubscribed (wildcard)`);
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
subscribeTo(eventType, handler) {
|
|
173
|
+
const types = Array.isArray(eventType) ? eventType : [eventType];
|
|
174
|
+
for (const type of types) {
|
|
175
|
+
if (!this.eventHandlers.has(type)) {
|
|
176
|
+
this.eventHandlers.set(type, new Set);
|
|
177
|
+
}
|
|
178
|
+
this.eventHandlers.get(type).add(handler);
|
|
179
|
+
}
|
|
180
|
+
logger.debug(`EnvEvent handler subscribed for types: ${types.join(", ")}`);
|
|
181
|
+
return () => {
|
|
182
|
+
for (const type of types) {
|
|
183
|
+
this.eventHandlers.get(type)?.delete(handler);
|
|
184
|
+
}
|
|
185
|
+
logger.debug(`EnvEvent handler unsubscribed for types: ${types.join(", ")}`);
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
subscribeAll(handler) {
|
|
189
|
+
return this.subscribe(handler);
|
|
190
|
+
}
|
|
191
|
+
pushEnvEvent(event) {
|
|
192
|
+
const fullEvent = "id" in event && event.id ? event : {
|
|
193
|
+
id: event.id ?? generateId(),
|
|
194
|
+
type: event.type,
|
|
195
|
+
timestamp: event.timestamp ?? Date.now(),
|
|
196
|
+
metadata: {
|
|
197
|
+
...event.metadata,
|
|
198
|
+
env_name: event.metadata?.env_name ?? this.name
|
|
199
|
+
},
|
|
200
|
+
payload: event.payload ?? {}
|
|
201
|
+
};
|
|
202
|
+
for (const handler of this.wildcardHandlers) {
|
|
203
|
+
try {
|
|
204
|
+
handler(fullEvent);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
logger.error(`Error in EnvEvent handler: ${error}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const handlers = this.eventHandlers.get(fullEvent.type);
|
|
210
|
+
if (handlers) {
|
|
211
|
+
for (const handler of handlers) {
|
|
212
|
+
try {
|
|
213
|
+
handler(fullEvent);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
logger.error(`Error in EnvEvent handler for ${fullEvent.type}: ${error}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
emit(type, payload, metadata) {
|
|
221
|
+
const event = {
|
|
222
|
+
id: generateId(),
|
|
223
|
+
type,
|
|
224
|
+
timestamp: Date.now(),
|
|
225
|
+
metadata: {
|
|
226
|
+
env_name: this.name,
|
|
227
|
+
...metadata
|
|
228
|
+
},
|
|
229
|
+
payload
|
|
230
|
+
};
|
|
231
|
+
this.pushEnvEvent(event);
|
|
232
|
+
}
|
|
233
|
+
async initializeComponents() {
|
|
234
|
+
logger.debug(`Starting component initialization, total: ${this.components.size}`);
|
|
235
|
+
const configComp = this.components.get("config");
|
|
236
|
+
if (configComp && configComp.getStatus() === "created") {
|
|
237
|
+
logger.debug(`Initializing ConfigComponent first...`);
|
|
238
|
+
await configComp.init({
|
|
239
|
+
name: "config",
|
|
240
|
+
version: configComp.version,
|
|
241
|
+
enabled: true,
|
|
242
|
+
env: this
|
|
243
|
+
});
|
|
244
|
+
logger.debug(`ConfigComponent initialized`);
|
|
245
|
+
} else {
|
|
246
|
+
logger.debug(`No ConfigComponent found, skipping first stage`);
|
|
247
|
+
}
|
|
248
|
+
const otherComponents = Array.from(this.components.values()).filter((c) => c.getStatus() === "created");
|
|
249
|
+
if (otherComponents.length > 0) {
|
|
250
|
+
logger.debug(`Initializing ${otherComponents.length} other components...`);
|
|
251
|
+
for (const component of otherComponents) {
|
|
252
|
+
logger.debug(`Initializing component: ${component.name}`);
|
|
253
|
+
await component.init({
|
|
254
|
+
name: component.name,
|
|
255
|
+
version: component.version,
|
|
256
|
+
enabled: true,
|
|
257
|
+
env: this
|
|
258
|
+
});
|
|
259
|
+
logger.debug(`Component initialized: ${component.name}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
logger.debug(`All components initialized`);
|
|
263
|
+
}
|
|
264
|
+
async startComponents() {
|
|
265
|
+
const allComponents = Array.from(this.components.values());
|
|
266
|
+
if (allComponents.length > 0) {
|
|
267
|
+
logger.debug(`Starting ${allComponents.length} components...`);
|
|
268
|
+
for (const component of allComponents) {
|
|
269
|
+
logger.debug(`Starting component: ${component.name} (current status: ${component.getStatus()})`);
|
|
270
|
+
await component.start();
|
|
271
|
+
logger.debug(`Component started: ${component.name}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async stopComponents() {
|
|
276
|
+
const componentsToStop = Array.from(this.components.values()).filter((c) => c.getStatus() !== "stopped");
|
|
277
|
+
if (componentsToStop.length > 0) {
|
|
278
|
+
logger.debug(`Stopping ${componentsToStop.length} components...`);
|
|
279
|
+
for (const component of componentsToStop) {
|
|
280
|
+
logger.debug(`Stopping component: ${component.name}`);
|
|
281
|
+
await component.stop();
|
|
282
|
+
logger.debug(`Component stopped: ${component.name}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async onInit() {
|
|
287
|
+
await this.initializeComponents();
|
|
288
|
+
logger.info(`Environment "${this.name}" initialized`);
|
|
289
|
+
}
|
|
290
|
+
async onStart() {
|
|
291
|
+
await this.startComponents();
|
|
292
|
+
logger.info(`Environment "${this.name}" started`);
|
|
293
|
+
}
|
|
294
|
+
async onStop() {
|
|
295
|
+
await this.stopComponents();
|
|
296
|
+
this.eventHandlers.clear();
|
|
297
|
+
this.wildcardHandlers.clear();
|
|
298
|
+
logger.info(`Environment "${this.name}" stopped`);
|
|
299
|
+
}
|
|
300
|
+
async init() {
|
|
301
|
+
Object.defineProperty(this, "env", {
|
|
302
|
+
value: this,
|
|
303
|
+
writable: true,
|
|
304
|
+
enumerable: false,
|
|
305
|
+
configurable: true
|
|
306
|
+
});
|
|
307
|
+
this.setStatus("initializing");
|
|
308
|
+
await this.onInit();
|
|
309
|
+
this.setStatus("running");
|
|
310
|
+
}
|
|
311
|
+
async start() {
|
|
312
|
+
if (this._started)
|
|
313
|
+
return;
|
|
314
|
+
this._started = true;
|
|
315
|
+
await this.onStart();
|
|
316
|
+
this.setStatus("running");
|
|
317
|
+
logger.info(`Environment "${this.name}" started`);
|
|
318
|
+
}
|
|
319
|
+
async stop() {
|
|
320
|
+
this.setStatus("stopping");
|
|
321
|
+
await this.onStop();
|
|
322
|
+
this.setStatus("stopped");
|
|
323
|
+
}
|
|
324
|
+
async loadServiceConfig(configPath) {
|
|
325
|
+
const configComponent = this.getComponent("config");
|
|
326
|
+
if (!configComponent) {
|
|
327
|
+
throw new Error("ConfigComponent not found. Please register ConfigComponent before loading service config.");
|
|
328
|
+
}
|
|
329
|
+
const xdgDataHome = configComponent.getXdgDataHome();
|
|
330
|
+
const fullPath = path.resolve(xdgDataHome, configPath);
|
|
331
|
+
if (!fsSync.existsSync(fullPath)) {
|
|
332
|
+
throw new Error(`Service config file not found: ${fullPath}`);
|
|
333
|
+
}
|
|
334
|
+
const content = fsSync.readFileSync(fullPath, "utf-8");
|
|
335
|
+
let config;
|
|
336
|
+
try {
|
|
337
|
+
config = JSON.parse(content);
|
|
338
|
+
} catch (e) {
|
|
339
|
+
throw new Error(`Failed to parse service config: ${e}`);
|
|
340
|
+
}
|
|
341
|
+
logger.debug(`Service config loaded from: ${fullPath}`);
|
|
342
|
+
return config;
|
|
343
|
+
}
|
|
344
|
+
generateComponentOptions(componentName, configEntry) {
|
|
345
|
+
const configComponent = this.getComponent("config");
|
|
346
|
+
if (!configComponent) {
|
|
347
|
+
throw new Error("ConfigComponent not found. Please register ConfigComponent before generating component options.");
|
|
348
|
+
}
|
|
349
|
+
const options = {
|
|
350
|
+
configComponent,
|
|
351
|
+
configPath: configEntry.configPath,
|
|
352
|
+
envPrefix: configEntry.envPrefix,
|
|
353
|
+
config: configEntry.config
|
|
354
|
+
};
|
|
355
|
+
logger.debug(`Generated options for component: ${componentName}`);
|
|
356
|
+
return options;
|
|
357
|
+
}
|
|
358
|
+
async registerComponentWithConfig(component, configEntry) {
|
|
359
|
+
if (!this.components.has(component.name)) {
|
|
360
|
+
this.registerComponent(component);
|
|
361
|
+
}
|
|
362
|
+
const options = this.generateComponentOptions(component.name, configEntry);
|
|
363
|
+
if (component.getStatus() === "running") {
|
|
364
|
+
logger.debug(`Component ${component.name} already initialized, skipping`);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
await component.init({
|
|
368
|
+
name: component.name,
|
|
369
|
+
version: component.version,
|
|
370
|
+
enabled: configEntry.enabled ?? true,
|
|
371
|
+
env: this,
|
|
372
|
+
options
|
|
373
|
+
});
|
|
374
|
+
logger.debug(`Component ${component.name} registered and initialized with config`);
|
|
375
|
+
}
|
|
376
|
+
async initFromConfig(configPath) {
|
|
377
|
+
if (!this.env) {
|
|
378
|
+
Object.defineProperty(this, "env", {
|
|
379
|
+
value: this,
|
|
380
|
+
writable: true,
|
|
381
|
+
enumerable: false,
|
|
382
|
+
configurable: true
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
const serviceConfig = await this.loadServiceConfig(configPath);
|
|
386
|
+
if (serviceConfig.environment) {
|
|
387
|
+
if (serviceConfig.environment.name) {
|
|
388
|
+
this.name = serviceConfig.environment.name;
|
|
389
|
+
}
|
|
390
|
+
if (serviceConfig.environment.version) {
|
|
391
|
+
this.version = serviceConfig.environment.version;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const configComponent = this.getComponent("config");
|
|
395
|
+
if (!configComponent) {
|
|
396
|
+
throw new Error("ConfigComponent not found. Please register ConfigComponent before initFromConfig.");
|
|
397
|
+
}
|
|
398
|
+
if (configComponent.getStatus() === "created") {
|
|
399
|
+
await configComponent.init({
|
|
400
|
+
name: "config",
|
|
401
|
+
version: configComponent.version,
|
|
402
|
+
enabled: true,
|
|
403
|
+
env: this
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
const components = serviceConfig.components || {};
|
|
407
|
+
for (const [componentName, configEntry] of Object.entries(components)) {
|
|
408
|
+
if (configEntry.enabled === false) {
|
|
409
|
+
logger.debug(`Component ${componentName} is disabled, skipping`);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const component = this.getComponent(componentName);
|
|
413
|
+
if (!component) {
|
|
414
|
+
logger.warn(`Component ${componentName} not registered, skipping`);
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (component.name === "config")
|
|
418
|
+
continue;
|
|
419
|
+
await this.registerComponentWithConfig(component, configEntry);
|
|
420
|
+
}
|
|
421
|
+
this.setStatus("running");
|
|
422
|
+
logger.info(`Environment "${this.name}" initialized from config: ${configPath}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
__legacyDecorateClassTS([
|
|
426
|
+
TracedAs("env.handle_query", { recordParams: true, recordResult: true, log: true })
|
|
427
|
+
], BaseEnvironment.prototype, "handle_query", null);
|
|
428
|
+
// src/env/event-source/event-source-component.ts
|
|
429
|
+
import { spawn, exec } from "child_process";
|
|
430
|
+
import { promisify } from "util";
|
|
431
|
+
import { join } from "path";
|
|
432
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
433
|
+
init_logger();
|
|
434
|
+
|
|
435
|
+
// src/env/event-source/event-source-config-registration.ts
|
|
436
|
+
var EVENT_SOURCE_DEFAULTS = {
|
|
437
|
+
"event-source.enabled": true,
|
|
438
|
+
"event-source.persistenceEnabled": true
|
|
439
|
+
};
|
|
440
|
+
var EVENT_SOURCE_CONFIG_REGISTRATION = {
|
|
441
|
+
name: "event-source",
|
|
442
|
+
sources: [
|
|
443
|
+
{ type: "env", envPrefix: "EVENT_SOURCE", priority: 20, watch: false }
|
|
444
|
+
],
|
|
445
|
+
keys: [
|
|
446
|
+
{ key: "event-source.enabled", sources: ["env", "file"] },
|
|
447
|
+
{ key: "event-source.persistenceEnabled", sources: ["env", "file"] },
|
|
448
|
+
{ key: "event-source.configPath", sources: ["env", "file"] }
|
|
449
|
+
]
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// src/env/event-source/event-source-component.ts
|
|
453
|
+
var execAsyncImpl = promisify(exec);
|
|
454
|
+
function getExecAsync() {
|
|
455
|
+
return execAsyncImpl;
|
|
456
|
+
}
|
|
457
|
+
var logger2 = createLogger("event-source");
|
|
458
|
+
|
|
459
|
+
class EventSourceComponent extends BaseComponent {
|
|
460
|
+
name = "event-source";
|
|
461
|
+
version = "1.0.0";
|
|
462
|
+
sources = new Map;
|
|
463
|
+
statuses = new Map;
|
|
464
|
+
processes = new Map;
|
|
465
|
+
handlers = new Map;
|
|
466
|
+
timers = new Map;
|
|
467
|
+
buffers = new Map;
|
|
468
|
+
configPath = "";
|
|
469
|
+
persistenceEnabled = true;
|
|
470
|
+
configComponent;
|
|
471
|
+
configWatcher;
|
|
472
|
+
async onInit() {
|
|
473
|
+
await this.loadConfig();
|
|
474
|
+
}
|
|
475
|
+
async init(options) {
|
|
476
|
+
if (options?.env) {
|
|
477
|
+
this.env = options.env;
|
|
478
|
+
}
|
|
479
|
+
this.setStatus("initializing");
|
|
480
|
+
if (options?.name)
|
|
481
|
+
Object.defineProperty(this, "name", { value: options.name, writable: false });
|
|
482
|
+
if (options?.version)
|
|
483
|
+
Object.defineProperty(this, "version", { value: options.version, writable: false });
|
|
484
|
+
if (options?.enabled !== undefined)
|
|
485
|
+
this._enabled = options.enabled;
|
|
486
|
+
const opts = options?.options;
|
|
487
|
+
if (opts?.configComponent) {
|
|
488
|
+
this.configComponent = opts.configComponent;
|
|
489
|
+
await this.registerConfig(opts);
|
|
490
|
+
} else {
|
|
491
|
+
throw new Error("ConfigComponent is required for EventSourceComponent initialization");
|
|
492
|
+
}
|
|
493
|
+
await this.onInit();
|
|
494
|
+
this.setStatus("running");
|
|
495
|
+
}
|
|
496
|
+
async onStart() {}
|
|
497
|
+
async onStop() {
|
|
498
|
+
this.configWatcher?.();
|
|
499
|
+
this.configWatcher = undefined;
|
|
500
|
+
const runningSources = Array.from(this.statuses.entries()).filter(([_, status]) => status === "running").map(([id]) => id);
|
|
501
|
+
for (const id of runningSources) {
|
|
502
|
+
await this.stopSource(id);
|
|
503
|
+
}
|
|
504
|
+
this.sources.clear();
|
|
505
|
+
this.statuses.clear();
|
|
506
|
+
this.processes.clear();
|
|
507
|
+
this.handlers.clear();
|
|
508
|
+
this.timers.clear();
|
|
509
|
+
this.buffers.clear();
|
|
510
|
+
}
|
|
511
|
+
async registerConfig(options) {
|
|
512
|
+
const configComponent = options.configComponent;
|
|
513
|
+
if (!configComponent)
|
|
514
|
+
return;
|
|
515
|
+
const { configPath, config } = options;
|
|
516
|
+
const envPrefix = "EVENT_SOURCE";
|
|
517
|
+
configComponent.registerComponent(EVENT_SOURCE_CONFIG_REGISTRATION);
|
|
518
|
+
if (configPath) {
|
|
519
|
+
configComponent.registerSource({
|
|
520
|
+
type: "file",
|
|
521
|
+
relativePath: configPath,
|
|
522
|
+
optional: true,
|
|
523
|
+
watch: false
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
configComponent.registerSource({
|
|
527
|
+
type: "env",
|
|
528
|
+
envPrefix,
|
|
529
|
+
priority: 20,
|
|
530
|
+
watch: false
|
|
531
|
+
});
|
|
532
|
+
await configComponent.load("event-source");
|
|
533
|
+
for (const envKey of Object.keys(process.env)) {
|
|
534
|
+
const configKey = envKeyToConfigKey(envKey, envPrefix, "event-source");
|
|
535
|
+
if (!configKey)
|
|
536
|
+
continue;
|
|
537
|
+
const value = process.env[envKey];
|
|
538
|
+
if (value !== undefined) {
|
|
539
|
+
await configComponent.set(configKey, value);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
for (const [key, value] of Object.entries(EVENT_SOURCE_DEFAULTS)) {
|
|
543
|
+
if (configComponent.get(key) === undefined) {
|
|
544
|
+
await configComponent.set(key, value);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (configComponent.get("event-source.configPath") === undefined) {
|
|
548
|
+
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
549
|
+
const dataDir = join(home, ".roy-agent");
|
|
550
|
+
await configComponent.set("event-source.configPath", join(dataDir, "event-sources.json"));
|
|
551
|
+
}
|
|
552
|
+
if (config) {
|
|
553
|
+
const flatConfig = this.flattenConfig(config);
|
|
554
|
+
for (const [key, value] of Object.entries(flatConfig)) {
|
|
555
|
+
await configComponent.set(key, value);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
this.registerConfigWatcher(configComponent);
|
|
559
|
+
}
|
|
560
|
+
buildConfig() {
|
|
561
|
+
const configComponent = this.configComponent;
|
|
562
|
+
if (!configComponent) {
|
|
563
|
+
return {
|
|
564
|
+
enabled: true,
|
|
565
|
+
persistenceEnabled: true,
|
|
566
|
+
configPath: ""
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
enabled: configComponent.get("event-source.enabled") ?? true,
|
|
571
|
+
persistenceEnabled: configComponent.get("event-source.persistenceEnabled") ?? true,
|
|
572
|
+
configPath: configComponent.get("event-source.configPath") ?? ""
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
flattenConfig(obj, prefix = "event-source") {
|
|
576
|
+
const result = {};
|
|
577
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
578
|
+
const fullKey = `${prefix}.${key}`;
|
|
579
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
580
|
+
Object.assign(result, this.flattenConfig(value, fullKey));
|
|
581
|
+
} else {
|
|
582
|
+
result[fullKey] = value;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return result;
|
|
586
|
+
}
|
|
587
|
+
registerConfigWatcher(configComponent) {
|
|
588
|
+
if (typeof configComponent.watch !== "function") {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
this.configWatcher = configComponent.watch("event-source.*", (event) => {
|
|
592
|
+
this.onConfigChange(event);
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
onConfigChange(event) {
|
|
596
|
+
logger2.info(`EventSource config changed: ${event.key}`, {
|
|
597
|
+
oldValue: event.oldValue,
|
|
598
|
+
newValue: event.newValue
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
register(config) {
|
|
602
|
+
if (this.sources.has(config.id)) {
|
|
603
|
+
throw new Error(`EventSource already exists: ${config.id}`);
|
|
604
|
+
}
|
|
605
|
+
this.sources.set(config.id, config);
|
|
606
|
+
this.statuses.set(config.id, "created");
|
|
607
|
+
logger2.debug(`EventSource registered: ${config.id} (${config.type})`);
|
|
608
|
+
this.saveConfig().catch((err) => {
|
|
609
|
+
logger2.error(`Failed to save config after registering ${config.id}:`, err);
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
unregister(id) {
|
|
613
|
+
const status = this.statuses.get(id);
|
|
614
|
+
if (status === "running") {
|
|
615
|
+
this.stopSource(id);
|
|
616
|
+
}
|
|
617
|
+
const existed = this.sources.has(id);
|
|
618
|
+
this.sources.delete(id);
|
|
619
|
+
this.statuses.delete(id);
|
|
620
|
+
this.handlers.delete(id);
|
|
621
|
+
this.buffers.delete(id);
|
|
622
|
+
if (existed) {
|
|
623
|
+
logger2.debug(`EventSource unregistered: ${id}`);
|
|
624
|
+
this.saveConfig().catch((err) => {
|
|
625
|
+
logger2.error(`Failed to save config after unregistering ${id}:`, err);
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
return existed;
|
|
629
|
+
}
|
|
630
|
+
get(id) {
|
|
631
|
+
return this.sources.get(id);
|
|
632
|
+
}
|
|
633
|
+
list() {
|
|
634
|
+
return Array.from(this.sources.values());
|
|
635
|
+
}
|
|
636
|
+
getEventSourceStatus(id) {
|
|
637
|
+
return this.statuses.get(id);
|
|
638
|
+
}
|
|
639
|
+
getEventSourceStatusById(id) {
|
|
640
|
+
return this.statuses.get(id);
|
|
641
|
+
}
|
|
642
|
+
onEvent(id, handler) {
|
|
643
|
+
this.handlers.set(id, handler);
|
|
644
|
+
}
|
|
645
|
+
offEvent(id) {
|
|
646
|
+
this.handlers.delete(id);
|
|
647
|
+
}
|
|
648
|
+
async startSource(id) {
|
|
649
|
+
const config = this.sources.get(id);
|
|
650
|
+
if (!config) {
|
|
651
|
+
throw new Error(`EventSource not found: ${id}`);
|
|
652
|
+
}
|
|
653
|
+
const currentStatus = this.statuses.get(id);
|
|
654
|
+
if (currentStatus === "running") {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
this.statuses.set(id, "starting");
|
|
658
|
+
try {
|
|
659
|
+
switch (config.type) {
|
|
660
|
+
case "lark-cli":
|
|
661
|
+
await this.startLarkCli(id, config);
|
|
662
|
+
break;
|
|
663
|
+
case "timer":
|
|
664
|
+
await this.startTimer(id, config);
|
|
665
|
+
break;
|
|
666
|
+
case "websocket":
|
|
667
|
+
await this.startWebSocket(id, config);
|
|
668
|
+
break;
|
|
669
|
+
default:
|
|
670
|
+
throw new Error(`Unsupported event source type: ${config.type}`);
|
|
671
|
+
}
|
|
672
|
+
this.statuses.set(id, "running");
|
|
673
|
+
this.getEnv()?.pushEnvEvent({
|
|
674
|
+
type: "event-source.started",
|
|
675
|
+
payload: { sourceId: id, sourceType: config.type }
|
|
676
|
+
});
|
|
677
|
+
logger2.info(`EventSource started: ${id} (${config.type})`);
|
|
678
|
+
} catch (error) {
|
|
679
|
+
this.statuses.set(id, "error");
|
|
680
|
+
logger2.error(`EventSource failed to start: ${id}`, error);
|
|
681
|
+
throw error;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
async stopSource(id) {
|
|
685
|
+
const status = this.statuses.get(id);
|
|
686
|
+
logger2.info(`[EventSource ${id}] stopSource called, current status: ${status}`);
|
|
687
|
+
if (status === "stopped" || status === "created") {
|
|
688
|
+
logger2.info(`[EventSource ${id}] already stopped/created, skipping`);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
this.statuses.set(id, "stopping");
|
|
692
|
+
const processInfo = this.processes.get(id);
|
|
693
|
+
const child = processInfo?.child;
|
|
694
|
+
logger2.info(`[EventSource ${id}] process info: ${processInfo ? `pid=${processInfo.child.pid}` : "none"}`);
|
|
695
|
+
if (processInfo && child) {
|
|
696
|
+
child.removeAllListeners("exit");
|
|
697
|
+
child.removeAllListeners("error");
|
|
698
|
+
child.stdout?.removeAllListeners("data");
|
|
699
|
+
child.stderr?.removeAllListeners("data");
|
|
700
|
+
const killProcess = (pid, signal) => {
|
|
701
|
+
try {
|
|
702
|
+
process.kill(pid, signal);
|
|
703
|
+
return true;
|
|
704
|
+
} catch (e) {
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
const pidsToKill = processInfo.pids.length > 0 ? processInfo.pids : processInfo.child.pid ? [processInfo.child.pid] : [];
|
|
709
|
+
logger2.info(`[EventSource ${id}] sending SIGTERM to ${pidsToKill.length} processes: ${pidsToKill.join(", ")}`);
|
|
710
|
+
for (const pid of pidsToKill) {
|
|
711
|
+
killProcess(pid, "SIGTERM");
|
|
712
|
+
}
|
|
713
|
+
await new Promise((resolve2) => {
|
|
714
|
+
const timeout = setTimeout(() => {
|
|
715
|
+
logger2.warn(`[EventSource ${id}] SIGTERM timeout, sending SIGKILL to ${pidsToKill.length} processes`);
|
|
716
|
+
for (const pid of pidsToKill) {
|
|
717
|
+
killProcess(pid, "SIGKILL");
|
|
718
|
+
}
|
|
719
|
+
resolve2();
|
|
720
|
+
}, 3000);
|
|
721
|
+
try {
|
|
722
|
+
const onceResult = child.once?.("exit", () => {
|
|
723
|
+
clearTimeout(timeout);
|
|
724
|
+
for (const pid of pidsToKill) {
|
|
725
|
+
if (pid !== processInfo.child.pid) {
|
|
726
|
+
killProcess(pid, "SIGKILL");
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
resolve2();
|
|
730
|
+
});
|
|
731
|
+
if (!onceResult || typeof onceResult.once !== "function") {
|
|
732
|
+
child.on("exit", () => {
|
|
733
|
+
clearTimeout(timeout);
|
|
734
|
+
for (const pid of pidsToKill) {
|
|
735
|
+
if (pid !== processInfo.child.pid) {
|
|
736
|
+
killProcess(pid, "SIGKILL");
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
resolve2();
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
} catch (e) {
|
|
743
|
+
logger2.warn(`[EventSource ${id}] failed to register exit listener: ${e}`);
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
this.processes.delete(id);
|
|
747
|
+
} else if (child) {
|
|
748
|
+
child.removeAllListeners("exit");
|
|
749
|
+
child.removeAllListeners("error");
|
|
750
|
+
child.stdout?.removeAllListeners("data");
|
|
751
|
+
child.stderr?.removeAllListeners("data");
|
|
752
|
+
const pid = child.pid;
|
|
753
|
+
const pgid = child.pgid;
|
|
754
|
+
try {
|
|
755
|
+
if (pgid && pgid > 0 && pgid !== pid) {
|
|
756
|
+
logger2.info(`[EventSource ${id}] killing process group ${pgid}`);
|
|
757
|
+
try {
|
|
758
|
+
process.kill(-pgid, "SIGTERM");
|
|
759
|
+
} catch (e) {
|
|
760
|
+
logger2.warn(`[EventSource ${id}] failed to kill process group: ${e}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
logger2.info(`[EventSource ${id}] killing main process ${pid}`);
|
|
764
|
+
child.kill("SIGTERM");
|
|
765
|
+
await new Promise((resolve2) => {
|
|
766
|
+
const timeout = setTimeout(() => {
|
|
767
|
+
logger2.warn(`[EventSource ${id}] SIGTERM timeout, sending SIGKILL`);
|
|
768
|
+
if (pgid && pgid > 0 && pgid !== pid) {
|
|
769
|
+
try {
|
|
770
|
+
process.kill(-pgid, "SIGKILL");
|
|
771
|
+
} catch (e) {
|
|
772
|
+
logger2.warn(`[EventSource ${id}] failed to kill process group with SIGKILL: ${e}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
child.kill("SIGKILL");
|
|
776
|
+
resolve2();
|
|
777
|
+
}, 3000);
|
|
778
|
+
child.once("exit", () => {
|
|
779
|
+
clearTimeout(timeout);
|
|
780
|
+
resolve2();
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
} catch (error) {
|
|
784
|
+
logger2.error(`[EventSource ${id}] Failed to kill process:`, error);
|
|
785
|
+
try {
|
|
786
|
+
child.kill("SIGKILL");
|
|
787
|
+
} catch {}
|
|
788
|
+
}
|
|
789
|
+
this.processes.delete(id);
|
|
790
|
+
} else {
|
|
791
|
+
logger2.warn(`[EventSource ${id}] no child process found in processes map`);
|
|
792
|
+
}
|
|
793
|
+
const timer = this.timers.get(id);
|
|
794
|
+
if (timer) {
|
|
795
|
+
clearInterval(timer);
|
|
796
|
+
this.timers.delete(id);
|
|
797
|
+
}
|
|
798
|
+
this.buffers.delete(id);
|
|
799
|
+
const config = this.sources.get(id);
|
|
800
|
+
this.statuses.set(id, "stopped");
|
|
801
|
+
this.getEnv()?.pushEnvEvent({
|
|
802
|
+
type: "event-source.stopped",
|
|
803
|
+
payload: { sourceId: id, sourceType: config?.type }
|
|
804
|
+
});
|
|
805
|
+
logger2.info(`EventSource stopped: ${id}`);
|
|
806
|
+
}
|
|
807
|
+
async findRelatedProcesses(pattern) {
|
|
808
|
+
const pids = [];
|
|
809
|
+
const platform = process.platform;
|
|
810
|
+
try {
|
|
811
|
+
const exec2 = getExecAsync();
|
|
812
|
+
if (platform === "win32") {
|
|
813
|
+
const escapedPattern = pattern.replace(/'/g, "''").replace(/"/g, '`"');
|
|
814
|
+
const psCommand = `Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like '*${escapedPattern}*' } | Select-Object -ExpandProperty ProcessId`;
|
|
815
|
+
const { stdout } = await exec2(`powershell -Command "${psCommand}"`);
|
|
816
|
+
const matches = stdout.replace(/\r\n/g, `
|
|
817
|
+
`).match(/\d+/g);
|
|
818
|
+
if (matches) {
|
|
819
|
+
for (const match of matches) {
|
|
820
|
+
const pid = parseInt(match, 10);
|
|
821
|
+
if (!isNaN(pid) && pid > 0) {
|
|
822
|
+
pids.push(pid);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
} else {
|
|
827
|
+
const { stdout } = await exec2(`pgrep -f "${pattern.replace(/"/g, "\\\"")}"`);
|
|
828
|
+
const lines = stdout.trim().split(`
|
|
829
|
+
`);
|
|
830
|
+
for (const line of lines) {
|
|
831
|
+
const trimmed = line.trim();
|
|
832
|
+
if (trimmed) {
|
|
833
|
+
const pid = parseInt(trimmed, 10);
|
|
834
|
+
if (!isNaN(pid) && pid > 0) {
|
|
835
|
+
pids.push(pid);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
} catch (error) {
|
|
841
|
+
logger2.debug(`[EventSource] findRelatedProcesses: no processes found for pattern "${pattern}"`);
|
|
842
|
+
}
|
|
843
|
+
return pids;
|
|
844
|
+
}
|
|
845
|
+
async trackRelatedProcesses(id) {
|
|
846
|
+
const processInfo = this.processes.get(id);
|
|
847
|
+
if (!processInfo)
|
|
848
|
+
return;
|
|
849
|
+
const command = processInfo?.command || "";
|
|
850
|
+
const commandParts = command.split(" ");
|
|
851
|
+
const baseCommand = commandParts[0];
|
|
852
|
+
const eventKeyword = "event";
|
|
853
|
+
let relatedPids = await this.findRelatedProcesses(baseCommand);
|
|
854
|
+
if (relatedPids.length < 2) {
|
|
855
|
+
const morePids = await this.findRelatedProcesses(eventKeyword);
|
|
856
|
+
for (const pid of morePids) {
|
|
857
|
+
if (!relatedPids.includes(pid)) {
|
|
858
|
+
relatedPids.push(pid);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
const ownPid = process.pid;
|
|
863
|
+
relatedPids = relatedPids.filter((pid) => pid !== ownPid);
|
|
864
|
+
for (const pid of relatedPids) {
|
|
865
|
+
if (!processInfo.pids.includes(pid)) {
|
|
866
|
+
processInfo.pids.push(pid);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
const mainPid = processInfo.child.pid;
|
|
870
|
+
if (mainPid && !processInfo.pids.includes(mainPid)) {
|
|
871
|
+
processInfo.pids.unshift(mainPid);
|
|
872
|
+
}
|
|
873
|
+
logger2.info(`[EventSource ${id}] tracked ${processInfo.pids.length} related processes: ${processInfo.pids.join(", ")}`);
|
|
874
|
+
}
|
|
875
|
+
startLarkCli(id, config) {
|
|
876
|
+
return new Promise((resolve2, reject) => {
|
|
877
|
+
const command = config.command || "lark-cli event +subscribe";
|
|
878
|
+
logger2.info(`Starting lark-cli event source: ${id}`);
|
|
879
|
+
logger2.debug(`Executing command: ${command}`);
|
|
880
|
+
const isWindows = process.platform === "win32";
|
|
881
|
+
const shell = isWindows ? "cmd.exe" : "sh";
|
|
882
|
+
const shellArgs = isWindows ? ["/c", command] : ["-c", `exec ${command}`];
|
|
883
|
+
const child = spawn(shell, shellArgs, {
|
|
884
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
885
|
+
detached: false,
|
|
886
|
+
windowsHide: true
|
|
887
|
+
});
|
|
888
|
+
this.processes.set(id, {
|
|
889
|
+
child,
|
|
890
|
+
pgid: child.pgid,
|
|
891
|
+
pids: [],
|
|
892
|
+
command
|
|
893
|
+
});
|
|
894
|
+
let startupTimeout = null;
|
|
895
|
+
let settled = false;
|
|
896
|
+
let startupConfirmed = false;
|
|
897
|
+
const handleStartupSuccess = async () => {
|
|
898
|
+
if (settled)
|
|
899
|
+
return;
|
|
900
|
+
settled = true;
|
|
901
|
+
startupConfirmed = true;
|
|
902
|
+
clearTimeout(startupTimeout);
|
|
903
|
+
this.statuses.set(id, "running");
|
|
904
|
+
logger2.info(`[EventSource ${id}] confirmed running`);
|
|
905
|
+
try {
|
|
906
|
+
await this.trackRelatedProcesses(id);
|
|
907
|
+
} catch (e) {
|
|
908
|
+
logger2.warn(`[EventSource ${id}] failed to track related processes: ${e}`);
|
|
909
|
+
}
|
|
910
|
+
resolve2();
|
|
911
|
+
};
|
|
912
|
+
startupTimeout = setTimeout(() => {
|
|
913
|
+
if (!settled && !startupConfirmed) {
|
|
914
|
+
handleStartupSuccess();
|
|
915
|
+
}
|
|
916
|
+
}, 1000);
|
|
917
|
+
child.stdout?.on("data", (data) => {
|
|
918
|
+
const output = data.toString();
|
|
919
|
+
this.processStream(id, output);
|
|
920
|
+
if (output.includes('"ok": true') || output.includes('"ok":true')) {
|
|
921
|
+
if (!settled) {
|
|
922
|
+
handleStartupSuccess();
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
child.stderr?.on("data", (data) => {
|
|
927
|
+
const output = data.toString();
|
|
928
|
+
if (output.includes('"ok": false') || output.includes('"ok":false')) {
|
|
929
|
+
if (!settled) {
|
|
930
|
+
settled = true;
|
|
931
|
+
clearTimeout(startupTimeout);
|
|
932
|
+
this.statuses.set(id, "error");
|
|
933
|
+
child.kill();
|
|
934
|
+
try {
|
|
935
|
+
const errorObj = JSON.parse(output);
|
|
936
|
+
reject(new Error(errorObj.error?.message || `lark-cli error: ${output}`));
|
|
937
|
+
} catch {
|
|
938
|
+
reject(new Error(`lark-cli error: ${output}`));
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
child.on("exit", (code) => {
|
|
944
|
+
if (!settled) {
|
|
945
|
+
if (code !== 0 && code !== null) {
|
|
946
|
+
settled = true;
|
|
947
|
+
clearTimeout(startupTimeout);
|
|
948
|
+
logger2.warn(`[EventSource ${id}] exited with code ${code}`);
|
|
949
|
+
this.statuses.set(id, "error");
|
|
950
|
+
reject(new Error(`lark-cli exited with code ${code}`));
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
child.on("error", (error) => {
|
|
955
|
+
if (!settled) {
|
|
956
|
+
settled = true;
|
|
957
|
+
clearTimeout(startupTimeout);
|
|
958
|
+
logger2.error(`[EventSource ${id}] process error:`, error);
|
|
959
|
+
this.statuses.set(id, "error");
|
|
960
|
+
reject(error);
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
async startTimer(id, config) {
|
|
966
|
+
const interval = config.interval || 60000;
|
|
967
|
+
logger2.info(`Starting timer event source: ${id}, interval: ${interval}ms`);
|
|
968
|
+
const timer = setInterval(() => {
|
|
969
|
+
const message = config.options?.message || `Timer event from ${config.name}`;
|
|
970
|
+
this.handleEvent(id, JSON.stringify({
|
|
971
|
+
type: "timer.tick",
|
|
972
|
+
payload: {
|
|
973
|
+
message,
|
|
974
|
+
timestamp: Date.now()
|
|
975
|
+
}
|
|
976
|
+
}));
|
|
977
|
+
}, interval);
|
|
978
|
+
this.timers.set(id, timer);
|
|
979
|
+
this.handleEvent(id, JSON.stringify({
|
|
980
|
+
type: "timer.tick",
|
|
981
|
+
payload: {
|
|
982
|
+
message: config.options?.message || `Timer event from ${config.name}`,
|
|
983
|
+
timestamp: Date.now()
|
|
984
|
+
}
|
|
985
|
+
}));
|
|
986
|
+
}
|
|
987
|
+
async startWebSocket(id, config) {
|
|
988
|
+
const url = config.url;
|
|
989
|
+
if (!url) {
|
|
990
|
+
throw new Error("WebSocket URL is required");
|
|
991
|
+
}
|
|
992
|
+
logger2.info(`Starting WebSocket event source: ${id}, url: ${url}`);
|
|
993
|
+
try {
|
|
994
|
+
const { WebSocket } = await import("ws");
|
|
995
|
+
const ws = new WebSocket(url, {
|
|
996
|
+
headers: config.headers
|
|
997
|
+
});
|
|
998
|
+
const wrapper = {
|
|
999
|
+
child: {
|
|
1000
|
+
kill: () => ws.close(),
|
|
1001
|
+
on: (event, cb) => {
|
|
1002
|
+
ws.on(event, cb);
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
pids: []
|
|
1006
|
+
};
|
|
1007
|
+
this.processes.set(id, wrapper);
|
|
1008
|
+
ws.on("message", (data) => {
|
|
1009
|
+
this.processStream(id, data.toString());
|
|
1010
|
+
});
|
|
1011
|
+
ws.on("error", (error) => {
|
|
1012
|
+
logger2.error(`[EventSource ${id}] WebSocket error:`, error);
|
|
1013
|
+
});
|
|
1014
|
+
ws.on("close", () => {
|
|
1015
|
+
logger2.info(`[EventSource ${id}] WebSocket closed`);
|
|
1016
|
+
this.statuses.set(id, "stopped");
|
|
1017
|
+
});
|
|
1018
|
+
} catch (error) {
|
|
1019
|
+
throw new Error(`Failed to import ws module: ${error}`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
processStream(sourceId, data) {
|
|
1023
|
+
let buffer = this.buffers.get(sourceId) || "";
|
|
1024
|
+
buffer += data;
|
|
1025
|
+
const lines = buffer.split(`
|
|
1026
|
+
`);
|
|
1027
|
+
buffer = lines.pop() || "";
|
|
1028
|
+
this.buffers.set(sourceId, buffer);
|
|
1029
|
+
for (const line of lines) {
|
|
1030
|
+
if (line.trim()) {
|
|
1031
|
+
this.handleEvent(sourceId, line);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
handleEvent(sourceId, rawData) {
|
|
1036
|
+
const config = this.sources.get(sourceId);
|
|
1037
|
+
if (!config)
|
|
1038
|
+
return;
|
|
1039
|
+
try {
|
|
1040
|
+
const rawEvent = JSON.parse(rawData);
|
|
1041
|
+
let eventType;
|
|
1042
|
+
if (rawEvent.header?.event_type) {
|
|
1043
|
+
eventType = rawEvent.header.event_type;
|
|
1044
|
+
} else if (rawEvent.schema) {
|
|
1045
|
+
eventType = `lark.${rawEvent.schema}`;
|
|
1046
|
+
} else if (rawEvent.type) {
|
|
1047
|
+
eventType = rawEvent.type;
|
|
1048
|
+
} else {
|
|
1049
|
+
eventType = "unknown";
|
|
1050
|
+
}
|
|
1051
|
+
if (config.eventTypes?.length && !this.matchEventType(eventType, config.eventTypes)) {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
const { metadata, replyChannel, recommendedAction } = this.extractMetadata(config.type, rawEvent, eventType);
|
|
1055
|
+
const message = this.formatEventMessage(config.type, rawEvent, eventType);
|
|
1056
|
+
const event = {
|
|
1057
|
+
sourceId,
|
|
1058
|
+
type: eventType,
|
|
1059
|
+
timestamp: Date.now(),
|
|
1060
|
+
payload: {
|
|
1061
|
+
sourceId,
|
|
1062
|
+
sourceType: config.type,
|
|
1063
|
+
rawEvent,
|
|
1064
|
+
message,
|
|
1065
|
+
metadata,
|
|
1066
|
+
replyChannel,
|
|
1067
|
+
recommendedAction,
|
|
1068
|
+
timestamp: Date.now()
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
const handler = this.handlers.get(sourceId);
|
|
1072
|
+
if (handler) {
|
|
1073
|
+
try {
|
|
1074
|
+
const result = handler(event);
|
|
1075
|
+
if (result instanceof Promise) {
|
|
1076
|
+
result.catch((error) => {
|
|
1077
|
+
logger2.error(`[EventSource ${sourceId}] Handler error:`, error);
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
logger2.error(`[EventSource ${sourceId}] Handler error:`, error);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
this.getEnv()?.pushEnvEvent({
|
|
1085
|
+
type: `event-source.event.${config.type}`,
|
|
1086
|
+
payload: event
|
|
1087
|
+
});
|
|
1088
|
+
} catch (error) {
|
|
1089
|
+
if (error instanceof SyntaxError) {
|
|
1090
|
+
logger2.debug(`[EventSource ${sourceId}] Non-JSON data: ${rawData.substring(0, 100)}`);
|
|
1091
|
+
const config2 = this.sources.get(sourceId);
|
|
1092
|
+
if (config2) {
|
|
1093
|
+
const event = {
|
|
1094
|
+
sourceId,
|
|
1095
|
+
type: "raw",
|
|
1096
|
+
timestamp: Date.now(),
|
|
1097
|
+
payload: {
|
|
1098
|
+
sourceId,
|
|
1099
|
+
sourceType: config2.type,
|
|
1100
|
+
rawEvent: rawData,
|
|
1101
|
+
message: rawData,
|
|
1102
|
+
metadata: {},
|
|
1103
|
+
timestamp: Date.now()
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
const handler = this.handlers.get(sourceId);
|
|
1107
|
+
if (handler) {
|
|
1108
|
+
handler(event);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
} else {
|
|
1112
|
+
logger2.error(`[EventSource ${sourceId}] Failed to handle event:`, error);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
matchEventType(eventType, patterns) {
|
|
1117
|
+
for (const pattern of patterns) {
|
|
1118
|
+
if (pattern === "*")
|
|
1119
|
+
return true;
|
|
1120
|
+
if (pattern.endsWith(".*")) {
|
|
1121
|
+
const prefix = pattern.slice(0, -2);
|
|
1122
|
+
if (eventType.startsWith(prefix + ".") || eventType === prefix)
|
|
1123
|
+
return true;
|
|
1124
|
+
}
|
|
1125
|
+
if (eventType === pattern)
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
1128
|
+
return false;
|
|
1129
|
+
}
|
|
1130
|
+
extractMetadata(sourceType, rawEvent, eventType) {
|
|
1131
|
+
const event = rawEvent;
|
|
1132
|
+
const metadata = {};
|
|
1133
|
+
let replyChannel;
|
|
1134
|
+
let recommendedAction;
|
|
1135
|
+
switch (sourceType) {
|
|
1136
|
+
case "lark-cli":
|
|
1137
|
+
const larkInnerEvent = event.event;
|
|
1138
|
+
const header = event.header;
|
|
1139
|
+
const message = larkInnerEvent?.message || event.message;
|
|
1140
|
+
const sender = larkInnerEvent?.sender || event.sender;
|
|
1141
|
+
const senderId = sender?.sender_id;
|
|
1142
|
+
if (header) {
|
|
1143
|
+
metadata.eventType = header.event_type;
|
|
1144
|
+
metadata.appId = header.app_id;
|
|
1145
|
+
metadata.tenantKey = header.tenant_key;
|
|
1146
|
+
}
|
|
1147
|
+
if (message) {
|
|
1148
|
+
metadata.chatId = message.chat_id;
|
|
1149
|
+
metadata.chatType = message.chat_type;
|
|
1150
|
+
metadata.messageId = message.message_id;
|
|
1151
|
+
metadata.messageType = message.message_type;
|
|
1152
|
+
}
|
|
1153
|
+
if (senderId) {
|
|
1154
|
+
metadata.senderId = senderId.open_id || senderId.user_id || senderId.union_id;
|
|
1155
|
+
}
|
|
1156
|
+
if (eventType === "im.message.receive_v1") {
|
|
1157
|
+
replyChannel = {
|
|
1158
|
+
type: "lark-cli",
|
|
1159
|
+
appId: metadata.appId,
|
|
1160
|
+
chatId: metadata.chatId,
|
|
1161
|
+
messageId: metadata.messageId
|
|
1162
|
+
};
|
|
1163
|
+
recommendedAction = {
|
|
1164
|
+
action: "处理飞书消息并回复",
|
|
1165
|
+
replyTo: {
|
|
1166
|
+
appId: metadata.appId,
|
|
1167
|
+
chatId: metadata.chatId,
|
|
1168
|
+
messageId: metadata.messageId
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
break;
|
|
1173
|
+
case "timer":
|
|
1174
|
+
metadata.eventType = "timer.tick";
|
|
1175
|
+
recommendedAction = {
|
|
1176
|
+
action: "执行定时任务"
|
|
1177
|
+
};
|
|
1178
|
+
break;
|
|
1179
|
+
case "websocket":
|
|
1180
|
+
metadata.eventType = eventType;
|
|
1181
|
+
replyChannel = {
|
|
1182
|
+
type: "websocket"
|
|
1183
|
+
};
|
|
1184
|
+
recommendedAction = {
|
|
1185
|
+
action: "处理 WebSocket 消息"
|
|
1186
|
+
};
|
|
1187
|
+
break;
|
|
1188
|
+
default:
|
|
1189
|
+
metadata.eventType = eventType;
|
|
1190
|
+
recommendedAction = {
|
|
1191
|
+
action: "处理事件"
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
return { metadata, replyChannel, recommendedAction };
|
|
1195
|
+
}
|
|
1196
|
+
formatEventMessage(sourceType, rawEvent, eventType) {
|
|
1197
|
+
const event = rawEvent;
|
|
1198
|
+
switch (sourceType) {
|
|
1199
|
+
case "lark-cli": {
|
|
1200
|
+
const larkEvent = event;
|
|
1201
|
+
const larkInnerEvent = larkEvent.event;
|
|
1202
|
+
const larkMessage = larkInnerEvent?.message || larkEvent.message;
|
|
1203
|
+
const larkSender = larkInnerEvent?.sender || larkEvent.sender;
|
|
1204
|
+
if (larkMessage) {
|
|
1205
|
+
const senderId = larkSender?.sender_id;
|
|
1206
|
+
const openId = senderId?.open_id || senderId?.user_id || senderId?.email || senderId?.union_id || "未知用户";
|
|
1207
|
+
let content = "无消息内容";
|
|
1208
|
+
if (larkMessage.content) {
|
|
1209
|
+
try {
|
|
1210
|
+
const contentObj = JSON.parse(larkMessage.content);
|
|
1211
|
+
content = contentObj.text || contentObj.content || larkMessage.content;
|
|
1212
|
+
} catch {
|
|
1213
|
+
content = larkMessage.content;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return `[飞书消息] ${openId}: ${content}`;
|
|
1217
|
+
}
|
|
1218
|
+
const larkSenderRecord = larkEvent.sender;
|
|
1219
|
+
const senderIdRecord = larkSenderRecord?.sender_id;
|
|
1220
|
+
if (larkEvent.message_id || larkEvent.schema?.includes("message")) {
|
|
1221
|
+
const senderId = senderIdRecord?.open_id || senderIdRecord?.user_id || senderIdRecord?.email || senderIdRecord?.union_id || "未知用户";
|
|
1222
|
+
const content = larkEvent.text || larkEvent.content || JSON.stringify(larkEvent);
|
|
1223
|
+
return `[飞书消息] ${senderId}: ${content}`;
|
|
1224
|
+
}
|
|
1225
|
+
return `[飞书事件] ${eventType || larkEvent.schema || "unknown"}`;
|
|
1226
|
+
}
|
|
1227
|
+
case "timer": {
|
|
1228
|
+
const timerEvent = event;
|
|
1229
|
+
const timerMessage = timerEvent.payload?.message || timerEvent.message || "tick";
|
|
1230
|
+
return `[定时任务] ${timerMessage}`;
|
|
1231
|
+
}
|
|
1232
|
+
case "websocket":
|
|
1233
|
+
return `[WebSocket] ${JSON.stringify(rawEvent).substring(0, 200)}`;
|
|
1234
|
+
default:
|
|
1235
|
+
return `[${sourceType}] ${JSON.stringify(rawEvent).substring(0, 200)}`;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
getConfigFilePath() {
|
|
1239
|
+
if (this.configPath) {
|
|
1240
|
+
return this.configPath;
|
|
1241
|
+
}
|
|
1242
|
+
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
1243
|
+
const dataDir = join(home, ".roy-agent");
|
|
1244
|
+
if (!existsSync2(dataDir)) {
|
|
1245
|
+
mkdirSync(dataDir, { recursive: true });
|
|
1246
|
+
}
|
|
1247
|
+
return join(dataDir, "event-sources.json");
|
|
1248
|
+
}
|
|
1249
|
+
async saveConfig() {
|
|
1250
|
+
if (!this.persistenceEnabled) {
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
try {
|
|
1254
|
+
const configPath = this.getConfigFilePath();
|
|
1255
|
+
const config = {
|
|
1256
|
+
version: "1.0.0",
|
|
1257
|
+
sources: Array.from(this.sources.values())
|
|
1258
|
+
};
|
|
1259
|
+
const content = JSON.stringify(config, null, 2);
|
|
1260
|
+
writeFileSync(configPath, content, "utf-8");
|
|
1261
|
+
logger2.debug(`Saved ${this.sources.size} event source configurations to ${configPath}`);
|
|
1262
|
+
} catch (error) {
|
|
1263
|
+
logger2.error("Failed to save event source configurations:", error);
|
|
1264
|
+
throw error;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
async loadConfig() {
|
|
1268
|
+
if (!this.persistenceEnabled) {
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
try {
|
|
1272
|
+
const configPath = this.getConfigFilePath();
|
|
1273
|
+
if (!existsSync2(configPath)) {
|
|
1274
|
+
logger2.debug(`No event source config file found at ${configPath}`);
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
const content = readFileSync2(configPath, "utf-8");
|
|
1278
|
+
const config = JSON.parse(content);
|
|
1279
|
+
if (!config.sources || !Array.isArray(config.sources)) {
|
|
1280
|
+
logger2.warn("Invalid event source config format, skipping load");
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
let loadedCount = 0;
|
|
1284
|
+
for (const source of config.sources) {
|
|
1285
|
+
if (!this.sources.has(source.id)) {
|
|
1286
|
+
this.sources.set(source.id, source);
|
|
1287
|
+
this.statuses.set(source.id, "created");
|
|
1288
|
+
loadedCount++;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
logger2.info(`Loaded ${loadedCount} event source configurations from ${configPath}`);
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
logger2.error("Failed to load event source configurations:", error);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
// src/env/event-source/event-source-agent-handler.ts
|
|
1298
|
+
init_logger();
|
|
1299
|
+
var logger3 = createLogger("event-source-agent-handler");
|
|
1300
|
+
|
|
1301
|
+
class EventSourceAgentHandler {
|
|
1302
|
+
config;
|
|
1303
|
+
constructor(config = {}) {
|
|
1304
|
+
this.config = {
|
|
1305
|
+
enabled: config.enabled ?? true,
|
|
1306
|
+
prefix: config.prefix ?? "[事件源]",
|
|
1307
|
+
includeRawEvent: config.includeRawEvent ?? false,
|
|
1308
|
+
includeTimestamp: config.includeTimestamp ?? true
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
createHandler() {
|
|
1312
|
+
return (event) => {
|
|
1313
|
+
if (!this.config.enabled) {
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
const message = this.formatEventForAgent(event);
|
|
1317
|
+
logger3.debug(`[AgentHandler] Formatted event: ${message.substring(0, 100)}`);
|
|
1318
|
+
return message;
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
formatEventForAgent(event) {
|
|
1322
|
+
const parts = [];
|
|
1323
|
+
parts.push(this.config.prefix);
|
|
1324
|
+
if (this.config.includeTimestamp) {
|
|
1325
|
+
const time = new Date(event.timestamp).toLocaleString("zh-CN");
|
|
1326
|
+
parts.push(`[${time}]`);
|
|
1327
|
+
}
|
|
1328
|
+
parts.push(`来源: ${event.payload.sourceType}`);
|
|
1329
|
+
parts.push(`类型: ${event.type}`);
|
|
1330
|
+
parts.push(`内容: ${event.payload.message}`);
|
|
1331
|
+
if (this.config.includeRawEvent) {
|
|
1332
|
+
const rawStr = typeof event.payload.rawEvent === "string" ? event.payload.rawEvent : JSON.stringify(event.payload.rawEvent);
|
|
1333
|
+
parts.push(`原始数据: ${rawStr}`);
|
|
1334
|
+
}
|
|
1335
|
+
return parts.join(" ");
|
|
1336
|
+
}
|
|
1337
|
+
updateConfig(config) {
|
|
1338
|
+
this.config = {
|
|
1339
|
+
...this.config,
|
|
1340
|
+
...config
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
getConfig() {
|
|
1344
|
+
return { ...this.config };
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
// src/env/event-source/types.ts
|
|
1348
|
+
function isValidEventSourceType(type) {
|
|
1349
|
+
return ["lark-cli", "websocket", "timer", "http-webhook", "file-watcher"].includes(type);
|
|
1350
|
+
}
|
|
1351
|
+
function getDefaultConfigForType(type) {
|
|
1352
|
+
switch (type) {
|
|
1353
|
+
case "timer":
|
|
1354
|
+
return { interval: 60000 };
|
|
1355
|
+
case "lark-cli":
|
|
1356
|
+
return { command: "lark-cli event +subscribe" };
|
|
1357
|
+
case "websocket":
|
|
1358
|
+
return {};
|
|
1359
|
+
default:
|
|
1360
|
+
return {};
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
function validateEventSourceConfig(config) {
|
|
1364
|
+
const errors = [];
|
|
1365
|
+
if (!config.id) {
|
|
1366
|
+
errors.push("EventSource ID is required");
|
|
1367
|
+
}
|
|
1368
|
+
if (!config.name) {
|
|
1369
|
+
errors.push("EventSource name is required");
|
|
1370
|
+
}
|
|
1371
|
+
if (!isValidEventSourceType(config.type)) {
|
|
1372
|
+
errors.push(`Invalid EventSource type: ${config.type}`);
|
|
1373
|
+
}
|
|
1374
|
+
if (config.type === "timer") {
|
|
1375
|
+
if (!config.interval || config.interval <= 0) {
|
|
1376
|
+
errors.push("Timer interval must be a positive number");
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
if (config.type === "lark-cli" && !config.command) {
|
|
1380
|
+
errors.push("lark-cli command is required");
|
|
1381
|
+
}
|
|
1382
|
+
if (config.type === "websocket" && !config.url) {
|
|
1383
|
+
errors.push("WebSocket URL is required");
|
|
1384
|
+
}
|
|
1385
|
+
return errors;
|
|
1386
|
+
}
|
|
1387
|
+
export { generateId, generateDescendingId, BaseEnvironment, EventSourceComponent, EventSourceAgentHandler, isValidEventSourceType, getDefaultConfigForType, validateEventSourceConfig };
|