@steadwing/openalerts 0.2.4 → 0.2.6

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 (124) hide show
  1. package/README.md +198 -137
  2. package/dist/channels/console.d.ts +6 -0
  3. package/dist/channels/console.d.ts.map +1 -0
  4. package/dist/channels/console.js +10 -0
  5. package/dist/channels/console.js.map +1 -0
  6. package/dist/channels/telegram.d.ts +12 -0
  7. package/dist/channels/telegram.d.ts.map +1 -0
  8. package/dist/channels/telegram.js +28 -0
  9. package/dist/channels/telegram.js.map +1 -0
  10. package/dist/channels/webhook.d.ts +8 -0
  11. package/dist/channels/webhook.d.ts.map +1 -0
  12. package/dist/channels/webhook.js +15 -0
  13. package/dist/channels/webhook.js.map +1 -0
  14. package/dist/cli.d.ts +3 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +234 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/config.d.ts +51 -0
  19. package/dist/config.d.ts.map +1 -0
  20. package/dist/config.js +86 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/core/alert-channel.d.ts +3 -10
  23. package/dist/core/alert-channel.d.ts.map +1 -0
  24. package/dist/core/alert-channel.js +9 -30
  25. package/dist/core/alert-channel.js.map +1 -0
  26. package/dist/core/bounded-map.d.ts +1 -0
  27. package/dist/core/bounded-map.d.ts.map +1 -0
  28. package/dist/core/bounded-map.js +1 -0
  29. package/dist/core/bounded-map.js.map +1 -0
  30. package/dist/core/engine.d.ts +6 -18
  31. package/dist/core/engine.d.ts.map +1 -0
  32. package/dist/core/engine.js +49 -98
  33. package/dist/core/engine.js.map +1 -0
  34. package/dist/core/evaluator.d.ts +1 -0
  35. package/dist/core/evaluator.d.ts.map +1 -0
  36. package/dist/core/evaluator.js +1 -0
  37. package/dist/core/evaluator.js.map +1 -0
  38. package/dist/core/event-bus.d.ts +1 -0
  39. package/dist/core/event-bus.d.ts.map +1 -0
  40. package/dist/core/event-bus.js +1 -0
  41. package/dist/core/event-bus.js.map +1 -0
  42. package/dist/core/formatter.d.ts +1 -0
  43. package/dist/core/formatter.d.ts.map +1 -0
  44. package/dist/core/formatter.js +1 -0
  45. package/dist/core/formatter.js.map +1 -0
  46. package/dist/core/rules.d.ts +1 -0
  47. package/dist/core/rules.d.ts.map +1 -0
  48. package/dist/core/rules.js +98 -0
  49. package/dist/core/rules.js.map +1 -0
  50. package/dist/core/store.d.ts +6 -9
  51. package/dist/core/store.d.ts.map +1 -0
  52. package/dist/core/store.js +43 -96
  53. package/dist/core/store.js.map +1 -0
  54. package/dist/core/types.d.ts +2 -1
  55. package/dist/core/types.d.ts.map +1 -0
  56. package/dist/core/types.js +1 -0
  57. package/dist/core/types.js.map +1 -0
  58. package/dist/db/index.d.ts +6 -0
  59. package/dist/db/index.d.ts.map +1 -0
  60. package/dist/db/index.js +31 -0
  61. package/dist/db/index.js.map +1 -0
  62. package/dist/db/queries.d.ts +157 -0
  63. package/dist/db/queries.d.ts.map +1 -0
  64. package/dist/db/queries.js +221 -0
  65. package/dist/db/queries.js.map +1 -0
  66. package/dist/db/schema.d.ts +5 -0
  67. package/dist/db/schema.d.ts.map +1 -0
  68. package/dist/db/schema.js +177 -0
  69. package/dist/db/schema.js.map +1 -0
  70. package/dist/readers/openclaw.d.ts +11 -0
  71. package/dist/readers/openclaw.d.ts.map +1 -0
  72. package/dist/readers/openclaw.js +267 -0
  73. package/dist/readers/openclaw.js.map +1 -0
  74. package/dist/server/dashboard.d.ts +2 -0
  75. package/dist/server/dashboard.d.ts.map +1 -0
  76. package/dist/server/dashboard.js +765 -0
  77. package/dist/server/dashboard.js.map +1 -0
  78. package/dist/server/index.d.ts +10 -0
  79. package/dist/server/index.d.ts.map +1 -0
  80. package/dist/server/index.js +28 -0
  81. package/dist/server/index.js.map +1 -0
  82. package/dist/server/routes.d.ts +6 -0
  83. package/dist/server/routes.d.ts.map +1 -0
  84. package/dist/server/routes.js +146 -0
  85. package/dist/server/routes.js.map +1 -0
  86. package/dist/server/sse.d.ts +21 -0
  87. package/dist/server/sse.d.ts.map +1 -0
  88. package/dist/server/sse.js +53 -0
  89. package/dist/server/sse.js.map +1 -0
  90. package/dist/watchers/files.d.ts +19 -0
  91. package/dist/watchers/files.d.ts.map +1 -0
  92. package/dist/watchers/files.js +105 -0
  93. package/dist/watchers/files.js.map +1 -0
  94. package/dist/watchers/gateway-adapter.d.ts +18 -0
  95. package/dist/watchers/gateway-adapter.d.ts.map +1 -0
  96. package/dist/watchers/gateway-adapter.js +273 -0
  97. package/dist/watchers/gateway-adapter.js.map +1 -0
  98. package/dist/watchers/gateway.d.ts +27 -0
  99. package/dist/watchers/gateway.d.ts.map +1 -0
  100. package/dist/watchers/gateway.js +131 -0
  101. package/dist/watchers/gateway.js.map +1 -0
  102. package/package.json +29 -43
  103. package/LICENSE +0 -201
  104. package/dist/core/index.d.ts +0 -12
  105. package/dist/core/index.js +0 -23
  106. package/dist/core/llm-enrichment.d.ts +0 -21
  107. package/dist/core/llm-enrichment.js +0 -180
  108. package/dist/core/platform.d.ts +0 -17
  109. package/dist/core/platform.js +0 -93
  110. package/dist/index.d.ts +0 -8
  111. package/dist/index.js +0 -213
  112. package/dist/plugin/adapter.d.ts +0 -150
  113. package/dist/plugin/adapter.js +0 -530
  114. package/dist/plugin/commands.d.ts +0 -18
  115. package/dist/plugin/commands.js +0 -103
  116. package/dist/plugin/dashboard-html.d.ts +0 -7
  117. package/dist/plugin/dashboard-html.js +0 -938
  118. package/dist/plugin/dashboard-routes.d.ts +0 -7
  119. package/dist/plugin/dashboard-routes.js +0 -336
  120. package/dist/plugin/gateway-client.d.ts +0 -39
  121. package/dist/plugin/gateway-client.js +0 -193
  122. package/dist/plugin/log-bridge.d.ts +0 -22
  123. package/dist/plugin/log-bridge.js +0 -363
  124. package/openclaw.plugin.json +0 -57
@@ -1,530 +0,0 @@
1
- import { createLlmEnricher, resolveApiKeyEnvVar } from "../core/llm-enrichment.js";
2
- import { readFile } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { homedir } from "node:os";
5
- // ─── Diagnostic Event Translation ───────────────────────────────────────────
6
- //
7
- // OpenClaw emits 12 diagnostic event types through onDiagnosticEvent():
8
- // model.usage, webhook.received, webhook.processed, webhook.error,
9
- // message.queued, message.processed, session.state, session.stuck,
10
- // queue.lane.enqueue, queue.lane.dequeue, diagnostic.heartbeat,
11
- // run.attempt (reserved, not yet emitted)
12
- //
13
- // Agent/tool/session lifecycle events come through plugin hooks (api.on()),
14
- // not diagnostic events. Those are handled separately in index.ts.
15
- // ────────────────────────────────────────────────────────────────────────────
16
- const DIAGNOSTIC_EVENT_MAP = {
17
- // Infrastructure
18
- "webhook.error": "infra.error",
19
- "webhook.received": "custom", // inbound webhook arrival (informational)
20
- "webhook.processed": "custom", // webhook fully handled (informational)
21
- // LLM / Message processing
22
- "message.processed": "llm.call",
23
- "message.queued": "infra.queue_depth",
24
- "model.usage": "llm.token_usage",
25
- // Session
26
- "session.stuck": "session.stuck",
27
- "session.state": "custom", // state transitions (idle→processing→waiting)
28
- // Heartbeat
29
- "diagnostic.heartbeat": "infra.heartbeat",
30
- heartbeat: "infra.heartbeat",
31
- // Queue lanes
32
- "queue.lane.enqueue": "infra.queue_depth",
33
- "queue.lane.dequeue": "infra.queue_depth",
34
- // Watchdog (internal, injected by engine timer)
35
- "watchdog.tick": "watchdog.tick",
36
- // Reserved (not yet emitted by OpenClaw)
37
- "run.attempt": "agent.start",
38
- };
39
- /**
40
- * Normalize OpenClaw outcome values to OpenAlertsEvent outcome.
41
- * OpenClaw uses "completed"/"failed"/"success"/"error" inconsistently
42
- * across different event types.
43
- */
44
- function normalizeOutcome(raw) {
45
- if (typeof raw !== "string")
46
- return undefined;
47
- switch (raw) {
48
- case "success":
49
- case "completed":
50
- case "ok":
51
- return "success";
52
- case "error":
53
- case "failed":
54
- case "failure":
55
- return "error";
56
- case "skipped":
57
- return "skipped";
58
- case "timeout":
59
- case "timed_out":
60
- return "timeout";
61
- default:
62
- return undefined;
63
- }
64
- }
65
- /**
66
- * Translate an OpenClaw diagnostic event into a universal OpenAlertsEvent.
67
- * Returns null for unmapped event types.
68
- */
69
- export function translateOpenClawEvent(event) {
70
- const type = DIAGNOSTIC_EVENT_MAP[event.type];
71
- if (!type)
72
- return null;
73
- const base = {
74
- type,
75
- ts: typeof event.ts === "number" ? event.ts : Date.now(),
76
- channel: event.channel,
77
- sessionKey: event.sessionKey,
78
- agentId: event.agentId,
79
- durationMs: event.durationMs,
80
- outcome: normalizeOutcome(event.outcome ?? event.status),
81
- error: event.error,
82
- ageMs: event.ageMs,
83
- meta: { openclawEventType: event.type },
84
- };
85
- // ── Queue depth: from heartbeat, message.queued, or queue.lane events ────
86
- if (typeof event.queued === "number") {
87
- base.queueDepth = event.queued;
88
- }
89
- else if (typeof event.queueDepth === "number") {
90
- base.queueDepth = event.queueDepth;
91
- }
92
- else if (typeof event.depth === "number") {
93
- base.queueDepth = event.depth;
94
- }
95
- // ── model.usage: extract token counts and cost ───────────────────────────
96
- if (event.type === "model.usage") {
97
- const usage = event.usage;
98
- if (usage) {
99
- if (typeof usage.totalTokens === "number") {
100
- base.tokenCount = usage.totalTokens;
101
- }
102
- else if (typeof usage.inputTokens === "number" ||
103
- typeof usage.outputTokens === "number") {
104
- base.tokenCount =
105
- (usage.inputTokens ?? 0) +
106
- (usage.outputTokens ?? 0);
107
- }
108
- if (typeof usage.costUsd === "number") {
109
- base.costUsd = usage.costUsd;
110
- }
111
- }
112
- if (typeof event.tokenCount === "number")
113
- base.tokenCount = event.tokenCount;
114
- if (typeof event.costUsd === "number")
115
- base.costUsd = event.costUsd;
116
- if (event.model)
117
- base.meta.model = event.model;
118
- if (event.provider)
119
- base.meta.provider = event.provider;
120
- }
121
- // ── session.state: carry transition info ─────────────────────────────────
122
- if (event.type === "session.state") {
123
- if (event.state)
124
- base.meta.sessionState = event.state;
125
- if (event.previousState)
126
- base.meta.previousState = event.previousState;
127
- // Map terminal states to specific event types
128
- const state = event.state;
129
- if (state === "ended" || state === "closed") {
130
- base.type = "session.end";
131
- }
132
- else if (state === "started" || state === "created") {
133
- base.type = "session.start";
134
- }
135
- }
136
- // ── queue.lane.*: extract lane info and wait time ────────────────────────
137
- if (event.type === "queue.lane.enqueue" ||
138
- event.type === "queue.lane.dequeue") {
139
- if (typeof event.waitMs === "number")
140
- base.durationMs = event.waitMs;
141
- if (event.lane)
142
- base.meta.lane = event.lane;
143
- }
144
- // ── webhook.*: preserve HTTP method and path ─────────────────────────────
145
- if (event.type === "webhook.received" || event.type === "webhook.processed") {
146
- if (event.method)
147
- base.meta.method = event.method;
148
- if (event.path)
149
- base.meta.path = event.path;
150
- base.outcome = base.outcome ?? "success";
151
- }
152
- // ── message.queued: ensure queue depth is set ────────────────────────────
153
- if (event.type === "message.queued") {
154
- base.outcome = base.outcome ?? "success";
155
- }
156
- return base;
157
- }
158
- // ─── Plugin Hook Event Translation ──────────────────────────────────────────
159
- //
160
- // Plugin hooks provide lifecycle events that diagnostic events don't cover:
161
- // tool calls, agent lifecycle, session lifecycle, gateway lifecycle.
162
- // These functions are called from index.ts where api.on() is wired.
163
- // ────────────────────────────────────────────────────────────────────────────
164
- /** Translate after_tool_call hook data into OpenAlertsEvent. */
165
- export function translateToolCallHook(data, context) {
166
- return {
167
- type: data.error ? "tool.error" : "tool.call",
168
- ts: Date.now(),
169
- sessionKey: context.sessionId,
170
- agentId: context.agentId,
171
- durationMs: data.durationMs,
172
- outcome: data.error ? "error" : "success",
173
- error: data.error,
174
- meta: { toolName: data.toolName, source: "hook:after_tool_call" },
175
- };
176
- }
177
- /** Translate before_agent_start hook data into OpenAlertsEvent. */
178
- export function translateAgentStartHook(data, context) {
179
- return {
180
- type: "agent.start",
181
- ts: Date.now(),
182
- sessionKey: context.sessionId,
183
- agentId: context.agentId,
184
- outcome: "success",
185
- meta: { source: "hook:before_agent_start" },
186
- };
187
- }
188
- /** Translate agent_end hook data into OpenAlertsEvent. */
189
- export function translateAgentEndHook(data, context) {
190
- return {
191
- type: data.success ? "agent.end" : "agent.error",
192
- ts: Date.now(),
193
- sessionKey: context.sessionId,
194
- agentId: context.agentId,
195
- durationMs: data.durationMs,
196
- outcome: data.success ? "success" : "error",
197
- error: data.error,
198
- meta: {
199
- messageCount: data.messages?.length ?? 0,
200
- source: "hook:agent_end",
201
- },
202
- };
203
- }
204
- /** Translate session_start hook data into OpenAlertsEvent. */
205
- export function translateSessionStartHook(data) {
206
- return {
207
- type: "session.start",
208
- ts: Date.now(),
209
- sessionKey: data.sessionId,
210
- outcome: "success",
211
- meta: {
212
- resumedFrom: data.resumedFrom,
213
- source: "hook:session_start",
214
- },
215
- };
216
- }
217
- /** Translate session_end hook data into OpenAlertsEvent. */
218
- export function translateSessionEndHook(data) {
219
- return {
220
- type: "session.end",
221
- ts: Date.now(),
222
- sessionKey: data.sessionId,
223
- durationMs: data.durationMs,
224
- outcome: "success",
225
- meta: { messageCount: data.messageCount, source: "hook:session_end" },
226
- };
227
- }
228
- /** Translate message_sent hook data into OpenAlertsEvent (delivery tracking). */
229
- export function translateMessageSentHook(data, context) {
230
- return {
231
- type: data.success ? "custom" : "infra.error",
232
- ts: Date.now(),
233
- channel: context.channel,
234
- sessionKey: context.sessionId,
235
- outcome: data.success ? "success" : "error",
236
- error: data.error,
237
- meta: { to: data.to, content: data.content, source: "hook:message_sent" },
238
- };
239
- }
240
- /** Translate gateway_start hook data into OpenAlertsEvent. */
241
- export function translateGatewayStartHook(data) {
242
- return {
243
- type: "infra.heartbeat",
244
- ts: Date.now(),
245
- outcome: "success",
246
- meta: { port: data.port, source: "hook:gateway_start" },
247
- };
248
- }
249
- /** Translate gateway_stop hook data into OpenAlertsEvent. */
250
- export function translateGatewayStopHook(data) {
251
- return {
252
- type: "infra.error",
253
- ts: Date.now(),
254
- outcome: "error",
255
- error: data.reason ?? "Gateway stopped",
256
- meta: { source: "hook:gateway_stop" },
257
- };
258
- }
259
- /** Translate message_received hook data into OpenAlertsEvent (inbound tracking). */
260
- export function translateMessageReceivedHook(data, context) {
261
- return {
262
- type: "custom",
263
- ts: data.timestamp ?? Date.now(),
264
- channel: context.channelId,
265
- outcome: "success",
266
- meta: {
267
- from: data.from,
268
- content: data.content,
269
- accountId: context.accountId,
270
- openclawHook: "message_received",
271
- source: "hook:message_received",
272
- },
273
- };
274
- }
275
- /** Translate before_tool_call hook data into OpenAlertsEvent (tool start tracking). */
276
- export function translateBeforeToolCallHook(data, context) {
277
- return {
278
- type: "custom",
279
- ts: Date.now(),
280
- sessionKey: context.sessionId,
281
- agentId: context.agentId,
282
- outcome: "success",
283
- meta: {
284
- toolName: data.toolName,
285
- openclawHook: "before_tool_call",
286
- source: "hook:before_tool_call",
287
- },
288
- };
289
- }
290
- /** Translate before_compaction hook data into OpenAlertsEvent. */
291
- export function translateBeforeCompactionHook(data, context) {
292
- return {
293
- type: "custom",
294
- ts: Date.now(),
295
- sessionKey: context.sessionKey,
296
- agentId: context.agentId,
297
- outcome: "success",
298
- meta: {
299
- messageCount: data.messageCount,
300
- tokenCount: data.tokenCount,
301
- openclawHook: "before_compaction",
302
- source: "hook:before_compaction",
303
- },
304
- };
305
- }
306
- /** Translate after_compaction hook data into OpenAlertsEvent. */
307
- export function translateAfterCompactionHook(data, context) {
308
- return {
309
- type: "custom",
310
- ts: Date.now(),
311
- sessionKey: context.sessionKey,
312
- agentId: context.agentId,
313
- outcome: "success",
314
- meta: {
315
- messageCount: data.messageCount,
316
- tokenCount: data.tokenCount,
317
- compactedCount: data.compactedCount,
318
- compaction: true,
319
- openclawHook: "after_compaction",
320
- source: "hook:after_compaction",
321
- },
322
- };
323
- }
324
- /** Translate message_sending hook data into OpenAlertsEvent (pre-send tracking). */
325
- export function translateMessageSendingHook(data, context) {
326
- return {
327
- type: "custom",
328
- ts: Date.now(),
329
- channel: context.channelId,
330
- outcome: "success",
331
- meta: {
332
- to: data.to,
333
- content: data.content,
334
- accountId: context.accountId,
335
- openclawHook: "message_sending",
336
- source: "hook:message_sending",
337
- },
338
- };
339
- }
340
- /** Translate tool_result_persist hook data into OpenAlertsEvent. */
341
- export function translateToolResultPersistHook(data, context) {
342
- return {
343
- type: "custom",
344
- ts: Date.now(),
345
- sessionKey: context.sessionKey,
346
- agentId: context.agentId,
347
- outcome: "success",
348
- meta: {
349
- toolName: data.toolName ?? context.toolName,
350
- toolCallId: data.toolCallId,
351
- isSynthetic: data.isSynthetic,
352
- openclawHook: "tool_result_persist",
353
- source: "hook:tool_result_persist",
354
- },
355
- };
356
- }
357
- // ─── OpenClaw Alert Channel ─────────────────────────────────────────────────
358
- /**
359
- * AlertChannel that sends through OpenClaw's runtime channel API.
360
- * Bridges the universal AlertChannel interface to OpenClaw's messaging system.
361
- */
362
- export class OpenClawAlertChannel {
363
- name;
364
- api;
365
- target;
366
- warnedMissing = false;
367
- constructor(api, target) {
368
- this.api = api;
369
- this.target = target;
370
- this.name = `openclaw:${target.channel}`;
371
- }
372
- async send(alert, formatted) {
373
- const runtime = this.api.runtime;
374
- const channel = runtime.channel;
375
- if (!channel) {
376
- if (!this.warnedMissing) {
377
- this.warnedMissing = true;
378
- throw new Error(`runtime.channel not available — alert dropped`);
379
- }
380
- return;
381
- }
382
- const opts = this.target.accountId
383
- ? { accountId: this.target.accountId }
384
- : {};
385
- const channelMethods = {
386
- telegram: "sendMessageTelegram",
387
- discord: "sendMessageDiscord",
388
- slack: "sendMessageSlack",
389
- whatsapp: "sendMessageWhatsApp",
390
- signal: "sendMessageSignal",
391
- };
392
- const methodName = channelMethods[this.target.channel];
393
- if (!methodName) {
394
- throw new Error(`unsupported channel "${this.target.channel}" — no send method mapped`);
395
- }
396
- const channelMod = channel[this.target.channel];
397
- const sendFn = channelMod?.[methodName];
398
- if (!sendFn) {
399
- throw new Error(`${this.target.channel}.${methodName} not found on runtime — alert dropped`);
400
- }
401
- await sendFn(this.target.to, formatted, opts);
402
- }
403
- }
404
- // ─── Alert Target Resolution ────────────────────────────────────────────────
405
- /**
406
- * Resolve the alert target from plugin config or by auto-detecting from OpenClaw config.
407
- */
408
- export async function resolveAlertTarget(api, pluginConfig) {
409
- // 1. Explicit config
410
- if (pluginConfig.alertChannel && pluginConfig.alertTo) {
411
- return {
412
- channel: pluginConfig.alertChannel,
413
- to: pluginConfig.alertTo,
414
- accountId: pluginConfig.alertAccountId,
415
- };
416
- }
417
- const cfg = api.config;
418
- const channelsCfg = cfg.channels ??
419
- {};
420
- const channelKeys = [
421
- "telegram",
422
- "discord",
423
- "slack",
424
- "whatsapp",
425
- "signal",
426
- ];
427
- // 2. Auto-detect from static allowFrom in channel config
428
- for (const channelKey of channelKeys) {
429
- const channelConfig = channelsCfg[channelKey];
430
- if (!channelConfig || typeof channelConfig !== "object")
431
- continue;
432
- const target = extractFirstAllowFrom(channelKey, channelConfig);
433
- if (target)
434
- return target;
435
- }
436
- // 3. Auto-detect from pairing store (runtime-paired users)
437
- // The store lives at ~/.openclaw/credentials/<channel>-allowFrom.json
438
- const credDir = join(process.env.OPENCLAW_HOME ?? join(homedir(), ".openclaw"), "credentials");
439
- for (const channelKey of channelKeys) {
440
- const channelConfig = channelsCfg[channelKey];
441
- if (!channelConfig || typeof channelConfig !== "object")
442
- continue;
443
- try {
444
- const raw = await readFile(join(credDir, `${channelKey}-allowFrom.json`), "utf-8");
445
- const data = JSON.parse(raw);
446
- if (Array.isArray(data.allowFrom) && data.allowFrom.length > 0) {
447
- return { channel: channelKey, to: String(data.allowFrom[0]) };
448
- }
449
- }
450
- catch {
451
- // File doesn't exist or isn't valid — skip this channel
452
- }
453
- }
454
- return null;
455
- }
456
- function extractFirstAllowFrom(channel, channelConfig) {
457
- const directAllow = channelConfig.allowFrom;
458
- if (Array.isArray(directAllow) && directAllow.length > 0) {
459
- return { channel, to: String(directAllow[0]) };
460
- }
461
- for (const [key, value] of Object.entries(channelConfig)) {
462
- if (!value || typeof value !== "object")
463
- continue;
464
- const accountObj = value;
465
- const allow = accountObj.allowFrom;
466
- if (Array.isArray(allow) && allow.length > 0) {
467
- return {
468
- channel,
469
- to: String(allow[0]),
470
- accountId: key === "default" ? undefined : key,
471
- };
472
- }
473
- }
474
- return null;
475
- }
476
- // ─── Config Parsing ─────────────────────────────────────────────────────────
477
- export function parseConfig(raw) {
478
- if (!raw)
479
- return {};
480
- return {
481
- apiKey: typeof raw.apiKey === "string" ? raw.apiKey : undefined,
482
- alertChannel: typeof raw.alertChannel === "string" ? raw.alertChannel : undefined,
483
- alertTo: typeof raw.alertTo === "string" ? raw.alertTo : undefined,
484
- alertAccountId: typeof raw.alertAccountId === "string" ? raw.alertAccountId : undefined,
485
- cooldownMinutes: typeof raw.cooldownMinutes === "number" ? raw.cooldownMinutes : undefined,
486
- maxLogSizeKb: typeof raw.maxLogSizeKb === "number" ? raw.maxLogSizeKb : undefined,
487
- maxLogAgeDays: typeof raw.maxLogAgeDays === "number" ? raw.maxLogAgeDays : undefined,
488
- quiet: typeof raw.quiet === "boolean" ? raw.quiet : undefined,
489
- llmEnriched: typeof raw.llmEnriched === "boolean" ? raw.llmEnriched : undefined,
490
- rules: raw.rules && typeof raw.rules === "object"
491
- ? raw.rules
492
- : undefined,
493
- };
494
- }
495
- // ─── LLM Enricher Factory ───────────────────────────────────────────────────
496
- /**
497
- * Create an AlertEnricher from the OpenClaw plugin API.
498
- * Reads the model from api.config.agents.defaults.model.primary (e.g. "openai/gpt-5-nano")
499
- * and resolves the API key from process.env.
500
- * Returns null if no model is configured or enricher can't be created.
501
- */
502
- export function createOpenClawEnricher(api, logger) {
503
- try {
504
- const cfg = api.config;
505
- const agents = cfg.agents;
506
- const defaults = agents?.defaults;
507
- const model = defaults?.model;
508
- const primary = model?.primary;
509
- if (typeof primary !== "string" || !primary.includes("/")) {
510
- logger?.warn("openalerts: llm-enrichment skipped — no model configured at agents.defaults.model.primary");
511
- return null;
512
- }
513
- // Resolve API key here (in adapter) to keep env access separate from network calls
514
- const envVar = resolveApiKeyEnvVar(primary);
515
- if (!envVar) {
516
- logger?.warn("openalerts: llm-enrichment skipped — unknown provider");
517
- return null;
518
- }
519
- const apiKey = process.env[envVar];
520
- if (!apiKey) {
521
- logger?.warn(`openalerts: llm-enrichment skipped — ${envVar} not set in environment`);
522
- return null;
523
- }
524
- return createLlmEnricher({ modelString: primary, apiKey, logger });
525
- }
526
- catch (err) {
527
- logger?.warn(`openalerts: llm-enrichment setup failed: ${String(err)}`);
528
- return null;
529
- }
530
- }
@@ -1,18 +0,0 @@
1
- import type { OpenAlertsEngine } from "../core/index.js";
2
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
3
- type PluginCommandDef = {
4
- name: string;
5
- description: string;
6
- acceptsArgs?: boolean;
7
- requireAuth?: boolean;
8
- handler: (ctx: Record<string, unknown>) => {
9
- text: string;
10
- } | Promise<{
11
- text: string;
12
- }>;
13
- };
14
- /** Called by service to wire commands to the live engine instance. */
15
- export declare function bindEngine(engine: OpenAlertsEngine, api: OpenClawPluginApi): void;
16
- /** Create /health, /alerts, and /dashboard command definitions. */
17
- export declare function createMonitorCommands(api: OpenClawPluginApi): PluginCommandDef[];
18
- export {};
@@ -1,103 +0,0 @@
1
- import { formatAlertsOutput, formatHealthOutput } from "../core/index.js";
2
- // Engine reference, set when service starts
3
- let _engine = null;
4
- let _api = null;
5
- /** Called by service to wire commands to the live engine instance. */
6
- export function bindEngine(engine, api) {
7
- _engine = engine;
8
- _api = api;
9
- }
10
- /** Create /health, /alerts, and /dashboard command definitions. */
11
- export function createMonitorCommands(api) {
12
- return [
13
- {
14
- name: "health",
15
- description: "Show system health and monitoring status",
16
- acceptsArgs: false,
17
- handler: () => handleHealth(),
18
- },
19
- {
20
- name: "alerts",
21
- description: "Show recent alerts from OpenAlerts Monitor",
22
- acceptsArgs: false,
23
- handler: () => handleAlerts(),
24
- },
25
- {
26
- name: "dashboard",
27
- description: "Get link to the real-time OpenAlerts monitoring dashboard",
28
- acceptsArgs: false,
29
- handler: () => handleDashboard(),
30
- },
31
- {
32
- name: "test_alert",
33
- description: "Send a test alert to verify alert delivery",
34
- acceptsArgs: false,
35
- handler: () => handleTestAlert(),
36
- },
37
- ];
38
- }
39
- function handleHealth() {
40
- if (!_engine) {
41
- return { text: "OpenAlerts not initialized yet. Wait for gateway startup." };
42
- }
43
- const channelActivity = getChannelActivity();
44
- const recentEvents = _engine.getRecentEvents(50);
45
- const oneHourAgo = Date.now() - 60 * 60 * 1000;
46
- const activeAlerts = recentEvents.filter((e) => e.type === "alert" && e.ts >= oneHourAgo);
47
- return {
48
- text: formatHealthOutput({
49
- state: _engine.state,
50
- channelActivity,
51
- activeAlerts,
52
- platformConnected: _engine.platformConnected,
53
- }),
54
- };
55
- }
56
- function handleAlerts() {
57
- if (!_engine) {
58
- return { text: "OpenAlerts not initialized yet." };
59
- }
60
- const events = _engine.getRecentEvents(100);
61
- return { text: formatAlertsOutput(events) };
62
- }
63
- function handleDashboard() {
64
- if (!_engine) {
65
- return { text: "OpenAlerts not initialized yet. Wait for gateway startup." };
66
- }
67
- return {
68
- text: "OpenAlerts Dashboard: http://127.0.0.1:18789/openalerts\n\nOpen in your browser to see real-time events, alerts, and rule status.",
69
- };
70
- }
71
- function handleTestAlert() {
72
- if (!_engine) {
73
- return { text: "OpenAlerts not initialized yet. Wait for gateway startup." };
74
- }
75
- _engine.sendTestAlert();
76
- return {
77
- text: "Test alert sent. Check your alert channel (Telegram/Discord/etc) for delivery confirmation.\n\nIf you don't receive it, check /health for channel status.",
78
- };
79
- }
80
- function getChannelActivity() {
81
- if (!_api)
82
- return [];
83
- const result = [];
84
- const channels = ["telegram", "discord", "slack", "whatsapp", "signal"];
85
- for (const ch of channels) {
86
- try {
87
- const runtime = _api.runtime;
88
- const channelMod = runtime.channel;
89
- const activity = channelMod?.activity;
90
- const get = activity?.get;
91
- if (get) {
92
- const entry = get({ channel: ch });
93
- if (entry.inboundAt) {
94
- result.push({ channel: ch, lastInbound: entry.inboundAt });
95
- }
96
- }
97
- }
98
- catch {
99
- // Channel not configured
100
- }
101
- }
102
- return result;
103
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * OpenAlerts real-time monitoring dashboard.
3
- * Tabs: Activity (unified event + log timeline), System Logs, Health.
4
- * Activity shows both OpenAlerts engine events AND OpenClaw internal logs
5
- * grouped by sessionId for a complete picture of what's happening.
6
- */
7
- export declare function getDashboardHtml(): string;