@steadwing/openalerts 0.2.3 → 0.2.5
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/README.md +63 -53
- package/dist/collections/collection-manager.d.ts +50 -0
- package/dist/collections/collection-manager.js +583 -0
- package/dist/collections/event-parser.d.ts +27 -0
- package/dist/collections/event-parser.js +321 -0
- package/dist/collections/index.d.ts +6 -0
- package/dist/collections/index.js +6 -0
- package/dist/collections/persistence.d.ts +25 -0
- package/dist/collections/persistence.js +213 -0
- package/dist/collections/types.d.ts +177 -0
- package/dist/collections/types.js +15 -0
- package/dist/core/engine.js +2 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/rules.js +97 -0
- package/dist/core/types.d.ts +1 -1
- package/dist/index.js +410 -23
- package/dist/plugin/dashboard-html.js +33 -3
- package/dist/plugin/dashboard-routes.d.ts +7 -2
- package/dist/plugin/dashboard-routes.js +111 -3
- package/dist/plugin/gateway-client.d.ts +39 -0
- package/dist/plugin/gateway-client.js +200 -0
- package/openclaw.plugin.json +30 -0
- package/package.json +18 -2
|
@@ -2,10 +2,42 @@ import { readFileSync, existsSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { DEFAULTS } from "../core/index.js";
|
|
4
4
|
import { getDashboardHtml } from "./dashboard-html.js";
|
|
5
|
-
// ─── SSE connection tracking ─────────────────────────────────────────────────
|
|
6
5
|
const sseConnections = new Set();
|
|
7
|
-
|
|
6
|
+
const monitorListeners = new Set();
|
|
7
|
+
const monitorConnections = new Set();
|
|
8
|
+
// Ring buffer — last 150 agent monitor events for history replay
|
|
9
|
+
const agentMonitorBuffer = [];
|
|
10
|
+
const AGENT_MONITOR_BUF_SIZE = 150;
|
|
11
|
+
export function emitAgentMonitorEvent(event) {
|
|
12
|
+
// Store in ring buffer
|
|
13
|
+
agentMonitorBuffer.push(event);
|
|
14
|
+
if (agentMonitorBuffer.length > AGENT_MONITOR_BUF_SIZE)
|
|
15
|
+
agentMonitorBuffer.shift();
|
|
16
|
+
for (const fn of monitorListeners) {
|
|
17
|
+
try {
|
|
18
|
+
fn(event);
|
|
19
|
+
}
|
|
20
|
+
catch { /* ignore */ }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// ── Gateway event recorder ──────────────────────────────────────────────────
|
|
24
|
+
const recentGatewayEvents = [];
|
|
25
|
+
export function recordGatewayEvent(eventName, payload) {
|
|
26
|
+
recentGatewayEvents.unshift({ eventName, payload, ts: Date.now() });
|
|
27
|
+
while (recentGatewayEvents.length > 10) {
|
|
28
|
+
recentGatewayEvents.pop();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
8
31
|
export function closeDashboardConnections() {
|
|
32
|
+
for (const conn of monitorConnections) {
|
|
33
|
+
clearInterval(conn.heartbeat);
|
|
34
|
+
try {
|
|
35
|
+
conn.res.end();
|
|
36
|
+
}
|
|
37
|
+
catch { /* ignore */ }
|
|
38
|
+
}
|
|
39
|
+
monitorConnections.clear();
|
|
40
|
+
monitorListeners.clear();
|
|
9
41
|
for (const conn of sseConnections) {
|
|
10
42
|
clearInterval(conn.heartbeat);
|
|
11
43
|
clearInterval(conn.logTailer);
|
|
@@ -27,6 +59,8 @@ const RULE_IDS = [
|
|
|
27
59
|
"heartbeat-fail",
|
|
28
60
|
"queue-depth",
|
|
29
61
|
"high-error-rate",
|
|
62
|
+
"cost-hourly-spike",
|
|
63
|
+
"cost-daily-budget",
|
|
30
64
|
"tool-errors",
|
|
31
65
|
"gateway-down",
|
|
32
66
|
];
|
|
@@ -195,7 +229,7 @@ function createLogTailer(res) {
|
|
|
195
229
|
}, 2000);
|
|
196
230
|
}
|
|
197
231
|
// ─── HTTP Handler ────────────────────────────────────────────────────────────
|
|
198
|
-
export function createDashboardHandler(getEngine) {
|
|
232
|
+
export function createDashboardHandler(getEngine, getCollections) {
|
|
199
233
|
return async (req, res) => {
|
|
200
234
|
const url = req.url ?? "";
|
|
201
235
|
if (!url.startsWith("/openalerts"))
|
|
@@ -307,6 +341,27 @@ export function createDashboardHandler(getEngine) {
|
|
|
307
341
|
res.end(body);
|
|
308
342
|
return true;
|
|
309
343
|
}
|
|
344
|
+
// ── GET /openalerts/collections → Sessions, actions, execs from CollectionManager ────
|
|
345
|
+
if (url === "/openalerts/collections" && req.method === "GET") {
|
|
346
|
+
const collections = getCollections();
|
|
347
|
+
if (!collections) {
|
|
348
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
349
|
+
res.end(JSON.stringify({ error: "Collections not available" }));
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
// getActiveSessions filters out stale idle sessions (>2h inactive) from filesystem hydration
|
|
353
|
+
const sessions = collections.getActiveSessions();
|
|
354
|
+
const actions = collections.getActions();
|
|
355
|
+
const execs = collections.getExecs();
|
|
356
|
+
const stats = collections.getStats();
|
|
357
|
+
res.writeHead(200, {
|
|
358
|
+
"Content-Type": "application/json",
|
|
359
|
+
"Cache-Control": "no-cache",
|
|
360
|
+
"Access-Control-Allow-Origin": "*",
|
|
361
|
+
});
|
|
362
|
+
res.end(JSON.stringify({ sessions, actions, execs, stats }));
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
310
365
|
// ── GET /openalerts/logs → OpenClaw log entries (for Logs tab) ────
|
|
311
366
|
if (url.startsWith("/openalerts/logs") && req.method === "GET") {
|
|
312
367
|
const urlObj = new URL(url, "http://localhost");
|
|
@@ -328,6 +383,59 @@ export function createDashboardHandler(getEngine) {
|
|
|
328
383
|
}));
|
|
329
384
|
return true;
|
|
330
385
|
}
|
|
386
|
+
// ── GET /openalerts/agent-monitor/stream → Live agent activity SSE ────
|
|
387
|
+
// Receives events from plugin hooks (before_agent_start, agent_end, after_tool_call, message_received)
|
|
388
|
+
if (url === "/openalerts/agent-monitor/stream" && req.method === "GET") {
|
|
389
|
+
res.writeHead(200, {
|
|
390
|
+
"Content-Type": "text/event-stream",
|
|
391
|
+
"Cache-Control": "no-cache",
|
|
392
|
+
Connection: "keep-alive",
|
|
393
|
+
"Access-Control-Allow-Origin": "*",
|
|
394
|
+
});
|
|
395
|
+
res.flushHeaders();
|
|
396
|
+
res.write(`:ok\n\n`);
|
|
397
|
+
// Replay buffered events so client sees full current session history
|
|
398
|
+
if (agentMonitorBuffer.length > 0) {
|
|
399
|
+
try {
|
|
400
|
+
res.write(`event: history\ndata: ${JSON.stringify(agentMonitorBuffer)}\n\n`);
|
|
401
|
+
}
|
|
402
|
+
catch { /* closed before flush */ }
|
|
403
|
+
}
|
|
404
|
+
const listener = (event) => {
|
|
405
|
+
try {
|
|
406
|
+
res.write(`event: ${event.type}\ndata: ${JSON.stringify(event.data)}\n\n`);
|
|
407
|
+
}
|
|
408
|
+
catch { /* closed */ }
|
|
409
|
+
};
|
|
410
|
+
monitorListeners.add(listener);
|
|
411
|
+
const heartbeat = setInterval(() => {
|
|
412
|
+
try {
|
|
413
|
+
res.write(`:heartbeat\n\n`);
|
|
414
|
+
}
|
|
415
|
+
catch { /* closed */ }
|
|
416
|
+
}, 15_000);
|
|
417
|
+
const conn = { res, heartbeat };
|
|
418
|
+
monitorConnections.add(conn);
|
|
419
|
+
req.on("close", () => {
|
|
420
|
+
clearInterval(heartbeat);
|
|
421
|
+
monitorListeners.delete(listener);
|
|
422
|
+
monitorConnections.delete(conn);
|
|
423
|
+
});
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
// ── GET /openalerts/test/events → Last 10 gateway events ────
|
|
427
|
+
if (url === "/openalerts/test/events" && req.method === "GET") {
|
|
428
|
+
res.writeHead(200, {
|
|
429
|
+
"Content-Type": "application/json",
|
|
430
|
+
"Cache-Control": "no-cache",
|
|
431
|
+
"Access-Control-Allow-Origin": "*",
|
|
432
|
+
});
|
|
433
|
+
res.end(JSON.stringify({
|
|
434
|
+
count: recentGatewayEvents.length,
|
|
435
|
+
events: recentGatewayEvents,
|
|
436
|
+
}));
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
331
439
|
// Unknown /openalerts sub-route
|
|
332
440
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
333
441
|
res.end("Not found");
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import EventEmitter from "eventemitter3";
|
|
2
|
+
interface GatewayClientConfig {
|
|
3
|
+
url: string;
|
|
4
|
+
token: string;
|
|
5
|
+
reconnectInterval: number;
|
|
6
|
+
maxRetries: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* WebSocket client for OpenClaw Gateway
|
|
10
|
+
* - Connects to ws://127.0.0.1:18789
|
|
11
|
+
* - Handles JSON-RPC requests/responses
|
|
12
|
+
* - Emits events: agent, health, cron, chat
|
|
13
|
+
* - Auto-reconnects on disconnect
|
|
14
|
+
*/
|
|
15
|
+
export declare class GatewayClient extends EventEmitter {
|
|
16
|
+
private ws;
|
|
17
|
+
private config;
|
|
18
|
+
private pending;
|
|
19
|
+
private backoffMs;
|
|
20
|
+
private closed;
|
|
21
|
+
private connectTimer;
|
|
22
|
+
private ready;
|
|
23
|
+
constructor(config?: Partial<GatewayClientConfig>);
|
|
24
|
+
start(): void;
|
|
25
|
+
stop(): void;
|
|
26
|
+
isReady(): boolean;
|
|
27
|
+
private doConnect;
|
|
28
|
+
private sendConnectHandshake;
|
|
29
|
+
private handleMessage;
|
|
30
|
+
/**
|
|
31
|
+
* Send RPC request to gateway
|
|
32
|
+
* @example
|
|
33
|
+
* const cost = await client.request("usage.cost", { period: "day" });
|
|
34
|
+
* const sessions = await client.request("sessions.list");
|
|
35
|
+
*/
|
|
36
|
+
request<T = unknown>(method: string, params?: unknown): Promise<T>;
|
|
37
|
+
private scheduleReconnect;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { WebSocket } from "ws";
|
|
3
|
+
import EventEmitter from "eventemitter3";
|
|
4
|
+
/**
|
|
5
|
+
* WebSocket client for OpenClaw Gateway
|
|
6
|
+
* - Connects to ws://127.0.0.1:18789
|
|
7
|
+
* - Handles JSON-RPC requests/responses
|
|
8
|
+
* - Emits events: agent, health, cron, chat
|
|
9
|
+
* - Auto-reconnects on disconnect
|
|
10
|
+
*/
|
|
11
|
+
export class GatewayClient extends EventEmitter {
|
|
12
|
+
ws = null;
|
|
13
|
+
config;
|
|
14
|
+
pending = new Map();
|
|
15
|
+
backoffMs = 1000;
|
|
16
|
+
closed = false;
|
|
17
|
+
connectTimer = null;
|
|
18
|
+
ready = false;
|
|
19
|
+
constructor(config) {
|
|
20
|
+
super();
|
|
21
|
+
this.config = {
|
|
22
|
+
url: config?.url ?? "ws://127.0.0.1:18789",
|
|
23
|
+
token: config?.token ?? "",
|
|
24
|
+
reconnectInterval: config?.reconnectInterval ?? 1000,
|
|
25
|
+
maxRetries: config?.maxRetries ?? 60,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
start() {
|
|
29
|
+
if (this.closed) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.doConnect();
|
|
33
|
+
}
|
|
34
|
+
stop() {
|
|
35
|
+
this.closed = true;
|
|
36
|
+
if (this.connectTimer) {
|
|
37
|
+
clearTimeout(this.connectTimer);
|
|
38
|
+
this.connectTimer = null;
|
|
39
|
+
}
|
|
40
|
+
if (this.ws) {
|
|
41
|
+
this.ws.close();
|
|
42
|
+
this.ws = null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
isReady() {
|
|
46
|
+
return this.ready;
|
|
47
|
+
}
|
|
48
|
+
doConnect() {
|
|
49
|
+
if (this.closed || this.ws) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.ws = new WebSocket(this.config.url, {
|
|
53
|
+
maxPayload: 25 * 1024 * 1024,
|
|
54
|
+
});
|
|
55
|
+
this.ws.on("open", () => {
|
|
56
|
+
this.backoffMs = 1000;
|
|
57
|
+
// Gateway sends 'connect.challenge' event first
|
|
58
|
+
});
|
|
59
|
+
this.ws.on("message", (data) => {
|
|
60
|
+
this.handleMessage(data.toString());
|
|
61
|
+
});
|
|
62
|
+
this.ws.on("error", (err) => {
|
|
63
|
+
this.emit("error", err);
|
|
64
|
+
});
|
|
65
|
+
this.ws.on("close", () => {
|
|
66
|
+
this.ws = null;
|
|
67
|
+
this.ready = false;
|
|
68
|
+
this.emit("disconnected");
|
|
69
|
+
if (!this.closed) {
|
|
70
|
+
this.scheduleReconnect();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
sendConnectHandshake() {
|
|
75
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const id = randomUUID();
|
|
79
|
+
const frame = {
|
|
80
|
+
type: "req",
|
|
81
|
+
id,
|
|
82
|
+
method: "connect",
|
|
83
|
+
params: {
|
|
84
|
+
minProtocol: 3,
|
|
85
|
+
maxProtocol: 3,
|
|
86
|
+
client: {
|
|
87
|
+
id: "cli",
|
|
88
|
+
displayName: "OpenAlerts Monitor",
|
|
89
|
+
version: "0.1.0",
|
|
90
|
+
platform: process.platform,
|
|
91
|
+
mode: "cli",
|
|
92
|
+
},
|
|
93
|
+
role: "operator",
|
|
94
|
+
scopes: ["operator.read"],
|
|
95
|
+
caps: [],
|
|
96
|
+
commands: [],
|
|
97
|
+
permissions: {},
|
|
98
|
+
locale: "en-US",
|
|
99
|
+
userAgent: "openalerts-monitor/0.1.0",
|
|
100
|
+
auth: this.config.token ? { token: this.config.token } : undefined,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
this.pending.set(id, {
|
|
104
|
+
resolve: (result) => {
|
|
105
|
+
this.ready = true;
|
|
106
|
+
this.emit("ready", result);
|
|
107
|
+
},
|
|
108
|
+
reject: (err) => {
|
|
109
|
+
this.emit("error", new Error(`Connect handshake failed: ${err.message}`));
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
this.ws.send(JSON.stringify(frame));
|
|
113
|
+
}
|
|
114
|
+
handleMessage(raw) {
|
|
115
|
+
try {
|
|
116
|
+
const frame = JSON.parse(raw);
|
|
117
|
+
// Handle challenge-response auth
|
|
118
|
+
if (frame.type === "event" && frame.event === "connect.challenge") {
|
|
119
|
+
this.sendConnectHandshake();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (frame.type === "res") {
|
|
123
|
+
const pending = this.pending.get(frame.id);
|
|
124
|
+
if (pending) {
|
|
125
|
+
if (frame.error || frame.ok === false) {
|
|
126
|
+
const errMsg = typeof frame.error === "string"
|
|
127
|
+
? frame.error
|
|
128
|
+
: typeof frame.payload === "object" &&
|
|
129
|
+
frame.payload &&
|
|
130
|
+
"message" in frame.payload
|
|
131
|
+
? String(frame.payload.message)
|
|
132
|
+
: JSON.stringify(frame.error ?? frame.payload);
|
|
133
|
+
pending.reject(new Error(errMsg));
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
pending.resolve(frame.payload ?? frame.result);
|
|
137
|
+
}
|
|
138
|
+
this.pending.delete(frame.id);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (frame.type === "event") {
|
|
142
|
+
// Emit the event for subscribers: agent, health, cron, chat
|
|
143
|
+
this.emit(frame.event, frame.payload);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
this.emit("error", new Error(`Failed to parse frame: ${err}`));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Send RPC request to gateway
|
|
152
|
+
* @example
|
|
153
|
+
* const cost = await client.request("usage.cost", { period: "day" });
|
|
154
|
+
* const sessions = await client.request("sessions.list");
|
|
155
|
+
*/
|
|
156
|
+
request(method, params) {
|
|
157
|
+
if (!this.ready || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
158
|
+
return Promise.reject(new Error("Gateway not ready"));
|
|
159
|
+
}
|
|
160
|
+
const id = randomUUID();
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
const timeout = setTimeout(() => {
|
|
163
|
+
this.pending.delete(id);
|
|
164
|
+
reject(new Error(`Request timeout: ${method}`));
|
|
165
|
+
}, 10000);
|
|
166
|
+
this.pending.set(id, {
|
|
167
|
+
resolve: (value) => {
|
|
168
|
+
clearTimeout(timeout);
|
|
169
|
+
resolve(value);
|
|
170
|
+
},
|
|
171
|
+
reject: (err) => {
|
|
172
|
+
clearTimeout(timeout);
|
|
173
|
+
reject(err);
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
const frame = {
|
|
177
|
+
type: "req",
|
|
178
|
+
id,
|
|
179
|
+
method,
|
|
180
|
+
params,
|
|
181
|
+
};
|
|
182
|
+
if (!this.ws) {
|
|
183
|
+
this.pending.delete(id);
|
|
184
|
+
clearTimeout(timeout);
|
|
185
|
+
reject(new Error("WebSocket not connected"));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.ws.send(JSON.stringify(frame));
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
scheduleReconnect() {
|
|
192
|
+
if (this.connectTimer) {
|
|
193
|
+
clearTimeout(this.connectTimer);
|
|
194
|
+
}
|
|
195
|
+
this.connectTimer = setTimeout(() => {
|
|
196
|
+
this.backoffMs = Math.min(this.backoffMs * 2, 60000);
|
|
197
|
+
this.doConnect();
|
|
198
|
+
}, this.backoffMs);
|
|
199
|
+
}
|
|
200
|
+
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -14,6 +14,26 @@
|
|
|
14
14
|
"quiet": { "type": "boolean" },
|
|
15
15
|
"rules": {
|
|
16
16
|
"type": "object",
|
|
17
|
+
"properties": {
|
|
18
|
+
"cost-hourly-spike": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"enabled": { "type": "boolean" },
|
|
22
|
+
"threshold": { "type": "number", "minimum": 0 },
|
|
23
|
+
"cooldownMinutes": { "type": "number", "minimum": 1 }
|
|
24
|
+
},
|
|
25
|
+
"additionalProperties": false
|
|
26
|
+
},
|
|
27
|
+
"cost-daily-budget": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"properties": {
|
|
30
|
+
"enabled": { "type": "boolean" },
|
|
31
|
+
"threshold": { "type": "number", "minimum": 0 },
|
|
32
|
+
"cooldownMinutes": { "type": "number", "minimum": 1 }
|
|
33
|
+
},
|
|
34
|
+
"additionalProperties": false
|
|
35
|
+
}
|
|
36
|
+
},
|
|
17
37
|
"additionalProperties": {
|
|
18
38
|
"type": "object",
|
|
19
39
|
"properties": {
|
|
@@ -52,6 +72,16 @@
|
|
|
52
72
|
"quiet": {
|
|
53
73
|
"label": "Quiet Mode",
|
|
54
74
|
"help": "Log alerts to file only — no messages sent to your channels."
|
|
75
|
+
},
|
|
76
|
+
"rules.cost-hourly-spike.threshold": {
|
|
77
|
+
"label": "Hourly Cost Threshold (USD)",
|
|
78
|
+
"help": "Alert when total LLM spend in the last 60 minutes exceeds this amount. Default: 5.0.",
|
|
79
|
+
"advanced": true
|
|
80
|
+
},
|
|
81
|
+
"rules.cost-daily-budget.threshold": {
|
|
82
|
+
"label": "Daily Budget Threshold (USD)",
|
|
83
|
+
"help": "Alert when total LLM spend in the last 24 hours exceeds this amount. Default: 20.0.",
|
|
84
|
+
"advanced": true
|
|
55
85
|
}
|
|
56
86
|
}
|
|
57
87
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steadwing/openalerts",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenAlerts — An alerting layer for agentic frameworks",
|
|
6
6
|
"author": "Steadwing",
|
|
@@ -21,10 +21,15 @@
|
|
|
21
21
|
"publish": "npm publish",
|
|
22
22
|
"prepublishOnly": "npm run build"
|
|
23
23
|
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"eventemitter3": "^5.0.1",
|
|
26
|
+
"ws": "^8.18.0"
|
|
27
|
+
},
|
|
24
28
|
"peerDependencies": {
|
|
25
29
|
"openclaw": "*"
|
|
26
30
|
},
|
|
27
31
|
"devDependencies": {
|
|
32
|
+
"@types/ws": "^8.5.13",
|
|
28
33
|
"typescript": "^5.9.3"
|
|
29
34
|
},
|
|
30
35
|
"openclaw": {
|
|
@@ -36,7 +41,18 @@
|
|
|
36
41
|
},
|
|
37
42
|
"homepage": "https://github.com/steadwing/openalerts#readme",
|
|
38
43
|
"bugs": "https://github.com/steadwing/openalerts/issues",
|
|
39
|
-
"keywords": [
|
|
44
|
+
"keywords": [
|
|
45
|
+
"openclaw",
|
|
46
|
+
"openclaw-plugin",
|
|
47
|
+
"alerting",
|
|
48
|
+
"monitoring",
|
|
49
|
+
"observability",
|
|
50
|
+
"llm-monitoring",
|
|
51
|
+
"agent-monitoring",
|
|
52
|
+
"ai-agents",
|
|
53
|
+
"real-time-alerts",
|
|
54
|
+
"error-tracking"
|
|
55
|
+
],
|
|
40
56
|
"engines": {
|
|
41
57
|
"node": ">=18"
|
|
42
58
|
},
|