@johnny-joster/n9e-plugin 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +8 -0
- package/README.md +191 -0
- package/docs/plans/2026-02-12-n9e-plugin-design.md +377 -0
- package/docs/plans/implementation-plan.md +2338 -0
- package/index.ts +53 -0
- package/openclaw.plugin.json +41 -0
- package/package.json +24 -0
- package/skills/query-active-alerts/SKILL.md +144 -0
- package/skills/query-alert-mutes/SKILL.md +173 -0
- package/skills/query-alert-rules/SKILL.md +167 -0
- package/skills/query-historical-alerts/SKILL.md +165 -0
- package/src/config-schema.ts +9 -0
- package/src/n9e-client.ts +251 -0
- package/src/openclaw-plugin-sdk.d.ts +47 -0
- package/src/tools/active-alerts.ts +109 -0
- package/src/tools/alert-mutes.ts +123 -0
- package/src/tools/alert-rules.ts +120 -0
- package/src/tools/historical-alerts.ts +134 -0
- package/src/types.ts +180 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,2338 @@
|
|
|
1
|
+
# N9e Plugin Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Build a production-ready OpenClaw plugin for Nightingale alert platform integration with natural language query support.
|
|
6
|
+
|
|
7
|
+
**Architecture:** TypeScript ESM plugin that registers 4 agent tools with N9eClient for API communication. Each tool paired with a skill for agent guidance. All queries are global (cross business group).
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript (ESM), jiti runtime, node-fetch/native fetch, Zod validation, OpenClaw Plugin SDK
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Task 1: Project Foundation
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Create: `package.json`
|
|
17
|
+
- Create: `openclaw.plugin.json`
|
|
18
|
+
- Create: `tsconfig.json`
|
|
19
|
+
- Create: `.gitignore`
|
|
20
|
+
|
|
21
|
+
**Step 1: Write package.json**
|
|
22
|
+
|
|
23
|
+
Create the package manifest with proper ESM configuration:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"name": "n9e-plugin",
|
|
28
|
+
"version": "1.0.0",
|
|
29
|
+
"description": "Nightingale alert platform query plugin for OpenClaw",
|
|
30
|
+
"type": "module",
|
|
31
|
+
"main": "index.ts",
|
|
32
|
+
"openclaw": {
|
|
33
|
+
"extensions": ["./index.ts"]
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"dev": "openclaw plugins install -l .",
|
|
37
|
+
"test": "echo \"No tests yet\""
|
|
38
|
+
},
|
|
39
|
+
"keywords": ["openclaw", "plugin", "nightingale", "n9e", "alerts"],
|
|
40
|
+
"author": "Youdao",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"zod": "^3.23.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.11.0",
|
|
47
|
+
"typescript": "^5.3.3"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Step 2: Write openclaw.plugin.json**
|
|
53
|
+
|
|
54
|
+
Create the plugin manifest:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"id": "n9e-plugin",
|
|
59
|
+
"skills": ["./skills"],
|
|
60
|
+
"configSchema": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"properties": {
|
|
63
|
+
"apiBaseUrl": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"format": "uri",
|
|
66
|
+
"description": "夜莺API基础地址"
|
|
67
|
+
},
|
|
68
|
+
"apiKey": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"description": "API访问密钥"
|
|
71
|
+
},
|
|
72
|
+
"timeout": {
|
|
73
|
+
"type": "integer",
|
|
74
|
+
"minimum": 1000,
|
|
75
|
+
"maximum": 120000,
|
|
76
|
+
"default": 30000,
|
|
77
|
+
"description": "请求超时时间(毫秒)"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"required": ["apiBaseUrl", "apiKey"]
|
|
81
|
+
},
|
|
82
|
+
"uiHints": {
|
|
83
|
+
"apiBaseUrl": {
|
|
84
|
+
"label": "API基础地址",
|
|
85
|
+
"placeholder": "https://n9e-dev.inner.youdao.com/api/n9e"
|
|
86
|
+
},
|
|
87
|
+
"apiKey": {
|
|
88
|
+
"label": "API密钥",
|
|
89
|
+
"sensitive": true,
|
|
90
|
+
"placeholder": "输入您的API密钥"
|
|
91
|
+
},
|
|
92
|
+
"timeout": {
|
|
93
|
+
"label": "超时时间(毫秒)",
|
|
94
|
+
"placeholder": "30000"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Step 3: Write tsconfig.json**
|
|
101
|
+
|
|
102
|
+
Create TypeScript configuration:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"compilerOptions": {
|
|
107
|
+
"target": "ES2022",
|
|
108
|
+
"module": "ESNext",
|
|
109
|
+
"moduleResolution": "bundler",
|
|
110
|
+
"esModuleInterop": true,
|
|
111
|
+
"strict": true,
|
|
112
|
+
"skipLibCheck": true,
|
|
113
|
+
"resolveJsonModule": true,
|
|
114
|
+
"allowSyntheticDefaultImports": true,
|
|
115
|
+
"forceConsistentCasingInFileNames": true,
|
|
116
|
+
"declaration": false,
|
|
117
|
+
"outDir": "./dist"
|
|
118
|
+
},
|
|
119
|
+
"include": ["src/**/*", "index.ts"],
|
|
120
|
+
"exclude": ["node_modules"]
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Step 4: Write .gitignore**
|
|
125
|
+
|
|
126
|
+
Create git ignore file:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
node_modules/
|
|
130
|
+
dist/
|
|
131
|
+
*.log
|
|
132
|
+
.DS_Store
|
|
133
|
+
.env
|
|
134
|
+
.vscode/
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Step 5: Create directory structure**
|
|
138
|
+
|
|
139
|
+
Run: `mkdir -p src/tools skills`
|
|
140
|
+
|
|
141
|
+
**Step 6: Verify structure**
|
|
142
|
+
|
|
143
|
+
Run: `ls -R`
|
|
144
|
+
Expected: Directory tree showing package.json, openclaw.plugin.json, src/, skills/
|
|
145
|
+
|
|
146
|
+
**Step 7: Commit**
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
git init
|
|
150
|
+
git add package.json openclaw.plugin.json tsconfig.json .gitignore
|
|
151
|
+
git commit -m "feat: project foundation and configuration
|
|
152
|
+
|
|
153
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Task 2: Type Definitions
|
|
159
|
+
|
|
160
|
+
**Files:**
|
|
161
|
+
- Create: `src/types.ts`
|
|
162
|
+
- Create: `src/config-schema.ts`
|
|
163
|
+
|
|
164
|
+
**Step 1: Write src/types.ts**
|
|
165
|
+
|
|
166
|
+
Define all TypeScript interfaces matching N9e API:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Configuration types
|
|
170
|
+
export interface N9ePluginConfig {
|
|
171
|
+
apiBaseUrl: string;
|
|
172
|
+
apiKey: string;
|
|
173
|
+
timeout: number;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Alert severity mapping
|
|
177
|
+
export type AlertSeverity = 1 | 2 | 3; // 1=严重, 2=警告, 3=提示
|
|
178
|
+
|
|
179
|
+
export const SEVERITY_LABELS: Record<AlertSeverity, string> = {
|
|
180
|
+
1: "严重",
|
|
181
|
+
2: "警告",
|
|
182
|
+
3: "提示"
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Active alert (current event)
|
|
186
|
+
export interface AlertCurEvent {
|
|
187
|
+
id: number;
|
|
188
|
+
cluster: string;
|
|
189
|
+
group_id: number;
|
|
190
|
+
group_name?: string;
|
|
191
|
+
hash: string;
|
|
192
|
+
rule_id: number;
|
|
193
|
+
rule_name: string;
|
|
194
|
+
rule_note: string;
|
|
195
|
+
severity: AlertSeverity;
|
|
196
|
+
prom_for_duration: number;
|
|
197
|
+
prom_ql: string;
|
|
198
|
+
prom_eval_interval: number;
|
|
199
|
+
callbacks: string;
|
|
200
|
+
runbook_url?: string;
|
|
201
|
+
notify_recovered: number;
|
|
202
|
+
notify_channels: string;
|
|
203
|
+
notify_groups: string;
|
|
204
|
+
notify_repeat_step: number;
|
|
205
|
+
notify_max_number: number;
|
|
206
|
+
recover_duration: number;
|
|
207
|
+
created_at: number;
|
|
208
|
+
updated_at: number;
|
|
209
|
+
tags: string;
|
|
210
|
+
target_ident: string;
|
|
211
|
+
target_note: string;
|
|
212
|
+
first_trigger_time?: number;
|
|
213
|
+
trigger_time: number;
|
|
214
|
+
trigger_value: string;
|
|
215
|
+
annotations?: string;
|
|
216
|
+
rule_config?: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Historical alert event
|
|
220
|
+
export interface AlertHisEvent {
|
|
221
|
+
id: number;
|
|
222
|
+
is_recovered: number;
|
|
223
|
+
cluster: string;
|
|
224
|
+
group_id: number;
|
|
225
|
+
group_name?: string;
|
|
226
|
+
hash: string;
|
|
227
|
+
rule_id: number;
|
|
228
|
+
rule_name: string;
|
|
229
|
+
rule_note: string;
|
|
230
|
+
severity: AlertSeverity;
|
|
231
|
+
prom_for_duration: number;
|
|
232
|
+
prom_ql: string;
|
|
233
|
+
prom_eval_interval: number;
|
|
234
|
+
callbacks: string;
|
|
235
|
+
runbook_url?: string;
|
|
236
|
+
notify_recovered: number;
|
|
237
|
+
notify_channels: string;
|
|
238
|
+
notify_groups: string;
|
|
239
|
+
notify_repeat_step: number;
|
|
240
|
+
notify_max_number: number;
|
|
241
|
+
recover_duration: number;
|
|
242
|
+
created_at: number;
|
|
243
|
+
updated_at: number;
|
|
244
|
+
tags: string;
|
|
245
|
+
target_ident: string;
|
|
246
|
+
target_note: string;
|
|
247
|
+
first_trigger_time?: number;
|
|
248
|
+
trigger_time: number;
|
|
249
|
+
trigger_value: string;
|
|
250
|
+
recover_time?: number;
|
|
251
|
+
last_eval_time?: number;
|
|
252
|
+
annotations?: string;
|
|
253
|
+
rule_config?: string;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Alert rule
|
|
257
|
+
export interface AlertRule {
|
|
258
|
+
id: number;
|
|
259
|
+
group_id: number;
|
|
260
|
+
cluster: string;
|
|
261
|
+
name: string;
|
|
262
|
+
note: string;
|
|
263
|
+
severity: AlertSeverity;
|
|
264
|
+
disabled: number; // 0=enabled, 1=disabled
|
|
265
|
+
prom_for_duration: number;
|
|
266
|
+
prom_ql: string;
|
|
267
|
+
prom_eval_interval: number;
|
|
268
|
+
enable_stime?: string;
|
|
269
|
+
enable_etime?: string;
|
|
270
|
+
enable_days_of_week?: string;
|
|
271
|
+
enable_in_bg?: number;
|
|
272
|
+
notify_recovered: number;
|
|
273
|
+
notify_channels: string;
|
|
274
|
+
notify_repeat_step: number;
|
|
275
|
+
notify_max_number: number;
|
|
276
|
+
recover_duration: number;
|
|
277
|
+
callbacks: string;
|
|
278
|
+
runbook_url?: string;
|
|
279
|
+
append_tags?: string;
|
|
280
|
+
create_at: number;
|
|
281
|
+
create_by: string;
|
|
282
|
+
update_at: number;
|
|
283
|
+
update_by: string;
|
|
284
|
+
datasource_ids?: number[];
|
|
285
|
+
extra_config?: string;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Alert mute
|
|
289
|
+
export interface AlertMute {
|
|
290
|
+
id: number;
|
|
291
|
+
group_id: number;
|
|
292
|
+
cluster: string;
|
|
293
|
+
tags: string;
|
|
294
|
+
cause: string;
|
|
295
|
+
btime: number;
|
|
296
|
+
etime: number;
|
|
297
|
+
disabled: number; // 0=enabled, 1=disabled
|
|
298
|
+
create_at: number;
|
|
299
|
+
create_by: string;
|
|
300
|
+
update_at: number;
|
|
301
|
+
update_by: string;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Query parameters
|
|
305
|
+
export interface ActiveAlertQuery {
|
|
306
|
+
severity?: AlertSeverity;
|
|
307
|
+
rule_name?: string;
|
|
308
|
+
limit?: number;
|
|
309
|
+
query?: string;
|
|
310
|
+
p?: number; // page number
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export interface HistoricalAlertQuery {
|
|
314
|
+
severity?: AlertSeverity;
|
|
315
|
+
rule_name?: string;
|
|
316
|
+
stime?: number; // start timestamp (seconds)
|
|
317
|
+
etime?: number; // end timestamp (seconds)
|
|
318
|
+
limit?: number;
|
|
319
|
+
p?: number;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export interface AlertRuleQuery {
|
|
323
|
+
rule_name?: string;
|
|
324
|
+
datasource_ids?: number[];
|
|
325
|
+
disabled?: 0 | 1;
|
|
326
|
+
p?: number;
|
|
327
|
+
limit?: number;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export interface AlertMuteQuery {
|
|
331
|
+
query?: string;
|
|
332
|
+
p?: number;
|
|
333
|
+
limit?: number;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// API response wrapper
|
|
337
|
+
export interface N9eApiResponse<T> {
|
|
338
|
+
dat: T;
|
|
339
|
+
err: string;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export interface N9eListResponse<T> {
|
|
343
|
+
dat: {
|
|
344
|
+
list: T[];
|
|
345
|
+
total: number;
|
|
346
|
+
};
|
|
347
|
+
err: string;
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Step 2: Write src/config-schema.ts**
|
|
352
|
+
|
|
353
|
+
Create Zod schema for configuration validation:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import { z } from "zod";
|
|
357
|
+
|
|
358
|
+
export const n9ePluginConfigSchema = z.object({
|
|
359
|
+
apiBaseUrl: z.string().url().describe("夜莺API地址"),
|
|
360
|
+
apiKey: z.string().min(1).describe("API访问密钥"),
|
|
361
|
+
timeout: z.number().min(1000).max(120000).default(30000).describe("请求超时(毫秒)")
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
export type N9ePluginConfig = z.infer<typeof n9ePluginConfigSchema>;
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Step 3: Verify types compile**
|
|
368
|
+
|
|
369
|
+
Run: `npx tsc --noEmit`
|
|
370
|
+
Expected: No compilation errors
|
|
371
|
+
|
|
372
|
+
**Step 4: Commit**
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
git add src/types.ts src/config-schema.ts
|
|
376
|
+
git commit -m "feat: add TypeScript type definitions and config schema
|
|
377
|
+
|
|
378
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Task 3: N9eClient Implementation
|
|
384
|
+
|
|
385
|
+
**Files:**
|
|
386
|
+
- Create: `src/n9e-client.ts`
|
|
387
|
+
|
|
388
|
+
**Step 1: Write N9eClient class**
|
|
389
|
+
|
|
390
|
+
Implement the API client with error handling:
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import type {
|
|
394
|
+
N9ePluginConfig,
|
|
395
|
+
AlertCurEvent,
|
|
396
|
+
AlertHisEvent,
|
|
397
|
+
AlertRule,
|
|
398
|
+
AlertMute,
|
|
399
|
+
ActiveAlertQuery,
|
|
400
|
+
HistoricalAlertQuery,
|
|
401
|
+
AlertRuleQuery,
|
|
402
|
+
AlertMuteQuery,
|
|
403
|
+
N9eApiResponse,
|
|
404
|
+
N9eListResponse
|
|
405
|
+
} from "./types.ts";
|
|
406
|
+
|
|
407
|
+
export class N9eClient {
|
|
408
|
+
constructor(private config: N9ePluginConfig) {}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Generic HTTP request handler
|
|
412
|
+
*/
|
|
413
|
+
private async request<T>(
|
|
414
|
+
method: "GET" | "POST" | "PUT" | "DELETE",
|
|
415
|
+
path: string,
|
|
416
|
+
params?: Record<string, any>,
|
|
417
|
+
body?: any
|
|
418
|
+
): Promise<T> {
|
|
419
|
+
const url = new URL(path, this.config.apiBaseUrl);
|
|
420
|
+
|
|
421
|
+
// Add query parameters for GET requests
|
|
422
|
+
if (method === "GET" && params) {
|
|
423
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
424
|
+
if (value !== undefined && value !== null) {
|
|
425
|
+
url.searchParams.append(key, String(value));
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const headers: Record<string, string> = {
|
|
431
|
+
"Content-Type": "application/json",
|
|
432
|
+
"X-API-Key": this.config.apiKey
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
const options: RequestInit = {
|
|
436
|
+
method,
|
|
437
|
+
headers,
|
|
438
|
+
signal: AbortSignal.timeout(this.config.timeout)
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
if (body && method !== "GET") {
|
|
442
|
+
options.body = JSON.stringify(body);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
const response = await fetch(url.toString(), options);
|
|
447
|
+
|
|
448
|
+
if (!response.ok) {
|
|
449
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
450
|
+
|
|
451
|
+
// Map HTTP status to user-friendly messages
|
|
452
|
+
switch (response.status) {
|
|
453
|
+
case 401:
|
|
454
|
+
case 403:
|
|
455
|
+
throw new Error("认证失败,请检查API Key配置");
|
|
456
|
+
case 404:
|
|
457
|
+
throw new Error("未找到指定的资源");
|
|
458
|
+
case 500:
|
|
459
|
+
throw new Error("夜莺服务器错误,请稍后重试");
|
|
460
|
+
default:
|
|
461
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const data = await response.json();
|
|
466
|
+
return data as T;
|
|
467
|
+
} catch (error) {
|
|
468
|
+
if (error instanceof Error) {
|
|
469
|
+
if (error.name === "AbortError" || error.name === "TimeoutError") {
|
|
470
|
+
throw new Error("请求超时,请检查网络连接");
|
|
471
|
+
}
|
|
472
|
+
if (error.message.includes("fetch failed")) {
|
|
473
|
+
throw new Error("无法连接到夜莺平台,请检查网络或配置");
|
|
474
|
+
}
|
|
475
|
+
throw error;
|
|
476
|
+
}
|
|
477
|
+
throw new Error("未知错误");
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Query active alerts
|
|
483
|
+
*/
|
|
484
|
+
async getActiveAlerts(params: ActiveAlertQuery = {}): Promise<{ total: number; list: AlertCurEvent[] }> {
|
|
485
|
+
const queryParams = {
|
|
486
|
+
p: params.p || 1,
|
|
487
|
+
limit: params.limit || 50,
|
|
488
|
+
...(params.severity && { severity: params.severity }),
|
|
489
|
+
...(params.rule_name && { rule_name: params.rule_name }),
|
|
490
|
+
...(params.query && { query: params.query })
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const response = await this.request<N9eListResponse<AlertCurEvent>>(
|
|
494
|
+
"GET",
|
|
495
|
+
"/api/n9e/alert-cur-events/list",
|
|
496
|
+
queryParams
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
if (response.err) {
|
|
500
|
+
throw new Error(response.err);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return response.dat;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Get active alert by ID
|
|
508
|
+
*/
|
|
509
|
+
async getActiveAlertById(id: number): Promise<AlertCurEvent> {
|
|
510
|
+
const response = await this.request<N9eApiResponse<AlertCurEvent>>(
|
|
511
|
+
"GET",
|
|
512
|
+
`/api/n9e/alert-cur-event/${id}`
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
if (response.err) {
|
|
516
|
+
throw new Error(response.err);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return response.dat;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Query historical alerts
|
|
524
|
+
*/
|
|
525
|
+
async getHistoricalAlerts(params: HistoricalAlertQuery = {}): Promise<{ total: number; list: AlertHisEvent[] }> {
|
|
526
|
+
const queryParams = {
|
|
527
|
+
p: params.p || 1,
|
|
528
|
+
limit: params.limit || 50,
|
|
529
|
+
...(params.severity && { severity: params.severity }),
|
|
530
|
+
...(params.rule_name && { rule_name: params.rule_name }),
|
|
531
|
+
...(params.stime && { stime: params.stime }),
|
|
532
|
+
...(params.etime && { etime: params.etime })
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
const response = await this.request<N9eListResponse<AlertHisEvent>>(
|
|
536
|
+
"GET",
|
|
537
|
+
"/api/n9e/alert-his-events/list",
|
|
538
|
+
queryParams
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
if (response.err) {
|
|
542
|
+
throw new Error(response.err);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return response.dat;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Get historical alert by ID
|
|
550
|
+
*/
|
|
551
|
+
async getHistoricalAlertById(id: number): Promise<AlertHisEvent> {
|
|
552
|
+
const response = await this.request<N9eApiResponse<AlertHisEvent>>(
|
|
553
|
+
"GET",
|
|
554
|
+
`/api/n9e/alert-his-event/${id}`
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
if (response.err) {
|
|
558
|
+
throw new Error(response.err);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return response.dat;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Query alert rules
|
|
566
|
+
*/
|
|
567
|
+
async getAlertRules(params: AlertRuleQuery = {}): Promise<{ total: number; list: AlertRule[] }> {
|
|
568
|
+
const queryParams = {
|
|
569
|
+
p: params.p || 1,
|
|
570
|
+
limit: params.limit || 50,
|
|
571
|
+
...(params.rule_name && { rule_name: params.rule_name }),
|
|
572
|
+
...(params.disabled !== undefined && { disabled: params.disabled }),
|
|
573
|
+
...(params.datasource_ids && { datasource_ids: params.datasource_ids.join(",") })
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
const response = await this.request<N9eListResponse<AlertRule>>(
|
|
577
|
+
"GET",
|
|
578
|
+
"/api/n9e/alert-rules",
|
|
579
|
+
queryParams
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
if (response.err) {
|
|
583
|
+
throw new Error(response.err);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return response.dat;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Get alert rule by ID
|
|
591
|
+
*/
|
|
592
|
+
async getAlertRuleById(id: number): Promise<AlertRule> {
|
|
593
|
+
const response = await this.request<N9eApiResponse<AlertRule>>(
|
|
594
|
+
"GET",
|
|
595
|
+
`/api/n9e/alert-rule/${id}`
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
if (response.err) {
|
|
599
|
+
throw new Error(response.err);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return response.dat;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Query alert mutes
|
|
607
|
+
*/
|
|
608
|
+
async getAlertMutes(params: AlertMuteQuery = {}): Promise<{ total: number; list: AlertMute[] }> {
|
|
609
|
+
const queryParams = {
|
|
610
|
+
p: params.p || 1,
|
|
611
|
+
limit: params.limit || 50,
|
|
612
|
+
...(params.query && { query: params.query })
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
const response = await this.request<N9eListResponse<AlertMute>>(
|
|
616
|
+
"GET",
|
|
617
|
+
"/api/n9e/v1/n9e/alert-mutes",
|
|
618
|
+
queryParams
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
if (response.err) {
|
|
622
|
+
throw new Error(response.err);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return response.dat;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Get alert mute by ID
|
|
630
|
+
*/
|
|
631
|
+
async getAlertMuteById(id: number): Promise<AlertMute> {
|
|
632
|
+
const response = await this.request<N9eApiResponse<AlertMute>>(
|
|
633
|
+
"GET",
|
|
634
|
+
`/api/n9e/alert-mutes-detail/${id}`
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
if (response.err) {
|
|
638
|
+
throw new Error(response.err);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return response.dat;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
**Step 2: Verify client compiles**
|
|
647
|
+
|
|
648
|
+
Run: `npx tsc --noEmit`
|
|
649
|
+
Expected: No compilation errors
|
|
650
|
+
|
|
651
|
+
**Step 3: Commit**
|
|
652
|
+
|
|
653
|
+
```bash
|
|
654
|
+
git add src/n9e-client.ts
|
|
655
|
+
git commit -m "feat: implement N9eClient with error handling
|
|
656
|
+
|
|
657
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
## Task 4: Active Alerts Tool
|
|
663
|
+
|
|
664
|
+
**Files:**
|
|
665
|
+
- Create: `src/tools/active-alerts.ts`
|
|
666
|
+
|
|
667
|
+
**Step 1: Write the tool implementation**
|
|
668
|
+
|
|
669
|
+
```typescript
|
|
670
|
+
import type { ToolHandler } from "openclaw/plugin-sdk";
|
|
671
|
+
import { N9eClient } from "../n9e-client.ts";
|
|
672
|
+
import { n9ePluginConfigSchema } from "../config-schema.ts";
|
|
673
|
+
import { SEVERITY_LABELS, type AlertSeverity } from "../types.ts";
|
|
674
|
+
|
|
675
|
+
export const activeAlertsToolInputSchema = {
|
|
676
|
+
type: "object",
|
|
677
|
+
properties: {
|
|
678
|
+
severity: {
|
|
679
|
+
type: "number",
|
|
680
|
+
enum: [1, 2, 3],
|
|
681
|
+
description: "严重级别: 1=严重, 2=警告, 3=提示"
|
|
682
|
+
},
|
|
683
|
+
rule_name: {
|
|
684
|
+
type: "string",
|
|
685
|
+
description: "按规则名称筛选(支持模糊匹配)"
|
|
686
|
+
},
|
|
687
|
+
limit: {
|
|
688
|
+
type: "number",
|
|
689
|
+
minimum: 1,
|
|
690
|
+
maximum: 1000,
|
|
691
|
+
default: 50,
|
|
692
|
+
description: "返回数量限制"
|
|
693
|
+
},
|
|
694
|
+
query: {
|
|
695
|
+
type: "string",
|
|
696
|
+
description: "搜索查询字符串"
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
} as const;
|
|
700
|
+
|
|
701
|
+
export const activeAlertsToolHandler: ToolHandler = async (input, context) => {
|
|
702
|
+
try {
|
|
703
|
+
// Parse and validate config
|
|
704
|
+
const rawConfig = context.config.plugins?.entries?.["n9e-plugin"]?.config;
|
|
705
|
+
if (!rawConfig) {
|
|
706
|
+
return {
|
|
707
|
+
content: [{
|
|
708
|
+
type: "text",
|
|
709
|
+
text: "❌ 插件配置未找到,请在配置文件中添加n9e-plugin配置"
|
|
710
|
+
}],
|
|
711
|
+
isError: true
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const config = n9ePluginConfigSchema.parse(rawConfig);
|
|
716
|
+
const client = new N9eClient(config);
|
|
717
|
+
|
|
718
|
+
// Query active alerts
|
|
719
|
+
const { total, list } = await client.getActiveAlerts({
|
|
720
|
+
severity: input.severity as AlertSeverity | undefined,
|
|
721
|
+
rule_name: input.rule_name,
|
|
722
|
+
limit: input.limit || 50,
|
|
723
|
+
query: input.query
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
// Format response
|
|
727
|
+
const alerts = list.map(alert => {
|
|
728
|
+
const tags = alert.tags ? JSON.parse(alert.tags) : {};
|
|
729
|
+
return {
|
|
730
|
+
id: alert.id,
|
|
731
|
+
rule_name: alert.rule_name,
|
|
732
|
+
severity: alert.severity,
|
|
733
|
+
severity_label: SEVERITY_LABELS[alert.severity],
|
|
734
|
+
trigger_time: new Date(alert.trigger_time * 1000).toLocaleString("zh-CN", {
|
|
735
|
+
timeZone: "Asia/Shanghai"
|
|
736
|
+
}),
|
|
737
|
+
status: "triggered",
|
|
738
|
+
target_ident: alert.target_ident,
|
|
739
|
+
trigger_value: alert.trigger_value,
|
|
740
|
+
tags,
|
|
741
|
+
rule_note: alert.rule_note,
|
|
742
|
+
group_name: alert.group_name
|
|
743
|
+
};
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
// Count by severity
|
|
747
|
+
const severityCount = alerts.reduce((acc, alert) => {
|
|
748
|
+
const label = alert.severity_label;
|
|
749
|
+
acc[label] = (acc[label] || 0) + 1;
|
|
750
|
+
return acc;
|
|
751
|
+
}, {} as Record<string, number>);
|
|
752
|
+
|
|
753
|
+
const result = {
|
|
754
|
+
summary: {
|
|
755
|
+
total,
|
|
756
|
+
showing: alerts.length,
|
|
757
|
+
severity_distribution: severityCount
|
|
758
|
+
},
|
|
759
|
+
alerts
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
return {
|
|
763
|
+
content: [{
|
|
764
|
+
type: "text",
|
|
765
|
+
text: JSON.stringify(result, null, 2)
|
|
766
|
+
}]
|
|
767
|
+
};
|
|
768
|
+
} catch (error) {
|
|
769
|
+
context.logger.error("Failed to query active alerts:", error);
|
|
770
|
+
return {
|
|
771
|
+
content: [{
|
|
772
|
+
type: "text",
|
|
773
|
+
text: `❌ 查询活跃告警失败: ${error instanceof Error ? error.message : String(error)}`
|
|
774
|
+
}],
|
|
775
|
+
isError: true
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
**Step 2: Verify tool compiles**
|
|
782
|
+
|
|
783
|
+
Run: `npx tsc --noEmit`
|
|
784
|
+
Expected: No compilation errors
|
|
785
|
+
|
|
786
|
+
**Step 3: Commit**
|
|
787
|
+
|
|
788
|
+
```bash
|
|
789
|
+
git add src/tools/active-alerts.ts
|
|
790
|
+
git commit -m "feat: implement active alerts tool
|
|
791
|
+
|
|
792
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
---
|
|
796
|
+
|
|
797
|
+
## Task 5: Historical Alerts Tool
|
|
798
|
+
|
|
799
|
+
**Files:**
|
|
800
|
+
- Create: `src/tools/historical-alerts.ts`
|
|
801
|
+
|
|
802
|
+
**Step 1: Write the tool implementation**
|
|
803
|
+
|
|
804
|
+
```typescript
|
|
805
|
+
import type { ToolHandler } from "openclaw/plugin-sdk";
|
|
806
|
+
import { N9eClient } from "../n9e-client.ts";
|
|
807
|
+
import { n9ePluginConfigSchema } from "../config-schema.ts";
|
|
808
|
+
import { SEVERITY_LABELS, type AlertSeverity } from "../types.ts";
|
|
809
|
+
|
|
810
|
+
export const historicalAlertsToolInputSchema = {
|
|
811
|
+
type: "object",
|
|
812
|
+
properties: {
|
|
813
|
+
severity: {
|
|
814
|
+
type: "number",
|
|
815
|
+
enum: [1, 2, 3],
|
|
816
|
+
description: "严重级别: 1=严重, 2=警告, 3=提示"
|
|
817
|
+
},
|
|
818
|
+
rule_name: {
|
|
819
|
+
type: "string",
|
|
820
|
+
description: "按规则名称筛选(支持模糊匹配)"
|
|
821
|
+
},
|
|
822
|
+
stime: {
|
|
823
|
+
type: "number",
|
|
824
|
+
description: "开始时间戳(秒)"
|
|
825
|
+
},
|
|
826
|
+
etime: {
|
|
827
|
+
type: "number",
|
|
828
|
+
description: "结束时间戳(秒)"
|
|
829
|
+
},
|
|
830
|
+
limit: {
|
|
831
|
+
type: "number",
|
|
832
|
+
minimum: 1,
|
|
833
|
+
maximum: 1000,
|
|
834
|
+
default: 50,
|
|
835
|
+
description: "返回数量限制"
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
} as const;
|
|
839
|
+
|
|
840
|
+
export const historicalAlertsToolHandler: ToolHandler = async (input, context) => {
|
|
841
|
+
try {
|
|
842
|
+
// Parse and validate config
|
|
843
|
+
const rawConfig = context.config.plugins?.entries?.["n9e-plugin"]?.config;
|
|
844
|
+
if (!rawConfig) {
|
|
845
|
+
return {
|
|
846
|
+
content: [{
|
|
847
|
+
type: "text",
|
|
848
|
+
text: "❌ 插件配置未找到,请在配置文件中添加n9e-plugin配置"
|
|
849
|
+
}],
|
|
850
|
+
isError: true
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const config = n9ePluginConfigSchema.parse(rawConfig);
|
|
855
|
+
const client = new N9eClient(config);
|
|
856
|
+
|
|
857
|
+
// Validate time range
|
|
858
|
+
if (input.stime && input.etime && input.stime >= input.etime) {
|
|
859
|
+
return {
|
|
860
|
+
content: [{
|
|
861
|
+
type: "text",
|
|
862
|
+
text: "❌ 开始时间必须早于结束时间"
|
|
863
|
+
}],
|
|
864
|
+
isError: true
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Query historical alerts
|
|
869
|
+
const { total, list } = await client.getHistoricalAlerts({
|
|
870
|
+
severity: input.severity as AlertSeverity | undefined,
|
|
871
|
+
rule_name: input.rule_name,
|
|
872
|
+
stime: input.stime,
|
|
873
|
+
etime: input.etime,
|
|
874
|
+
limit: input.limit || 50
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
// Format response
|
|
878
|
+
const alerts = list.map(alert => {
|
|
879
|
+
const tags = alert.tags ? JSON.parse(alert.tags) : {};
|
|
880
|
+
return {
|
|
881
|
+
id: alert.id,
|
|
882
|
+
rule_name: alert.rule_name,
|
|
883
|
+
severity: alert.severity,
|
|
884
|
+
severity_label: SEVERITY_LABELS[alert.severity],
|
|
885
|
+
trigger_time: new Date(alert.trigger_time * 1000).toLocaleString("zh-CN", {
|
|
886
|
+
timeZone: "Asia/Shanghai"
|
|
887
|
+
}),
|
|
888
|
+
recover_time: alert.recover_time
|
|
889
|
+
? new Date(alert.recover_time * 1000).toLocaleString("zh-CN", {
|
|
890
|
+
timeZone: "Asia/Shanghai"
|
|
891
|
+
})
|
|
892
|
+
: null,
|
|
893
|
+
is_recovered: alert.is_recovered === 1,
|
|
894
|
+
target_ident: alert.target_ident,
|
|
895
|
+
trigger_value: alert.trigger_value,
|
|
896
|
+
tags,
|
|
897
|
+
rule_note: alert.rule_note,
|
|
898
|
+
group_name: alert.group_name
|
|
899
|
+
};
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
// Statistics
|
|
903
|
+
const severityCount = alerts.reduce((acc, alert) => {
|
|
904
|
+
const label = alert.severity_label;
|
|
905
|
+
acc[label] = (acc[label] || 0) + 1;
|
|
906
|
+
return acc;
|
|
907
|
+
}, {} as Record<string, number>);
|
|
908
|
+
|
|
909
|
+
const recoveredCount = alerts.filter(a => a.is_recovered).length;
|
|
910
|
+
|
|
911
|
+
const result = {
|
|
912
|
+
summary: {
|
|
913
|
+
total,
|
|
914
|
+
showing: alerts.length,
|
|
915
|
+
severity_distribution: severityCount,
|
|
916
|
+
recovered_count: recoveredCount,
|
|
917
|
+
unrecovered_count: alerts.length - recoveredCount
|
|
918
|
+
},
|
|
919
|
+
alerts
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
return {
|
|
923
|
+
content: [{
|
|
924
|
+
type: "text",
|
|
925
|
+
text: JSON.stringify(result, null, 2)
|
|
926
|
+
}]
|
|
927
|
+
};
|
|
928
|
+
} catch (error) {
|
|
929
|
+
context.logger.error("Failed to query historical alerts:", error);
|
|
930
|
+
return {
|
|
931
|
+
content: [{
|
|
932
|
+
type: "text",
|
|
933
|
+
text: `❌ 查询历史告警失败: ${error instanceof Error ? error.message : String(error)}`
|
|
934
|
+
}],
|
|
935
|
+
isError: true
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
**Step 2: Verify tool compiles**
|
|
942
|
+
|
|
943
|
+
Run: `npx tsc --noEmit`
|
|
944
|
+
Expected: No compilation errors
|
|
945
|
+
|
|
946
|
+
**Step 3: Commit**
|
|
947
|
+
|
|
948
|
+
```bash
|
|
949
|
+
git add src/tools/historical-alerts.ts
|
|
950
|
+
git commit -m "feat: implement historical alerts tool
|
|
951
|
+
|
|
952
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
## Task 6: Alert Rules Tool
|
|
958
|
+
|
|
959
|
+
**Files:**
|
|
960
|
+
- Create: `src/tools/alert-rules.ts`
|
|
961
|
+
|
|
962
|
+
**Step 1: Write the tool implementation**
|
|
963
|
+
|
|
964
|
+
```typescript
|
|
965
|
+
import type { ToolHandler } from "openclaw/plugin-sdk";
|
|
966
|
+
import { N9eClient } from "../n9e-client.ts";
|
|
967
|
+
import { n9ePluginConfigSchema } from "../config-schema.ts";
|
|
968
|
+
import { SEVERITY_LABELS } from "../types.ts";
|
|
969
|
+
|
|
970
|
+
export const alertRulesToolInputSchema = {
|
|
971
|
+
type: "object",
|
|
972
|
+
properties: {
|
|
973
|
+
rule_name: {
|
|
974
|
+
type: "string",
|
|
975
|
+
description: "按规则名称搜索(支持模糊匹配)"
|
|
976
|
+
},
|
|
977
|
+
datasource_ids: {
|
|
978
|
+
type: "array",
|
|
979
|
+
items: { type: "number" },
|
|
980
|
+
description: "数据源ID列表"
|
|
981
|
+
},
|
|
982
|
+
disabled: {
|
|
983
|
+
type: "number",
|
|
984
|
+
enum: [0, 1],
|
|
985
|
+
description: "规则状态: 0=启用, 1=禁用"
|
|
986
|
+
},
|
|
987
|
+
limit: {
|
|
988
|
+
type: "number",
|
|
989
|
+
minimum: 1,
|
|
990
|
+
maximum: 1000,
|
|
991
|
+
default: 50,
|
|
992
|
+
description: "返回数量限制"
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
} as const;
|
|
996
|
+
|
|
997
|
+
export const alertRulesToolHandler: ToolHandler = async (input, context) => {
|
|
998
|
+
try {
|
|
999
|
+
// Parse and validate config
|
|
1000
|
+
const rawConfig = context.config.plugins?.entries?.["n9e-plugin"]?.config;
|
|
1001
|
+
if (!rawConfig) {
|
|
1002
|
+
return {
|
|
1003
|
+
content: [{
|
|
1004
|
+
type: "text",
|
|
1005
|
+
text: "❌ 插件配置未找到,请在配置文件中添加n9e-plugin配置"
|
|
1006
|
+
}],
|
|
1007
|
+
isError: true
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const config = n9ePluginConfigSchema.parse(rawConfig);
|
|
1012
|
+
const client = new N9eClient(config);
|
|
1013
|
+
|
|
1014
|
+
// Query alert rules
|
|
1015
|
+
const { total, list } = await client.getAlertRules({
|
|
1016
|
+
rule_name: input.rule_name,
|
|
1017
|
+
datasource_ids: input.datasource_ids,
|
|
1018
|
+
disabled: input.disabled as 0 | 1 | undefined,
|
|
1019
|
+
limit: input.limit || 50
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
// Format response
|
|
1023
|
+
const rules = list.map(rule => ({
|
|
1024
|
+
id: rule.id,
|
|
1025
|
+
name: rule.name,
|
|
1026
|
+
note: rule.note,
|
|
1027
|
+
severity: rule.severity,
|
|
1028
|
+
severity_label: SEVERITY_LABELS[rule.severity],
|
|
1029
|
+
disabled: rule.disabled === 1,
|
|
1030
|
+
status: rule.disabled === 1 ? "禁用" : "启用",
|
|
1031
|
+
prom_ql: rule.prom_ql,
|
|
1032
|
+
prom_for_duration: rule.prom_for_duration,
|
|
1033
|
+
prom_eval_interval: rule.prom_eval_interval,
|
|
1034
|
+
notify_channels: rule.notify_channels,
|
|
1035
|
+
datasource_ids: rule.datasource_ids,
|
|
1036
|
+
group_id: rule.group_id,
|
|
1037
|
+
cluster: rule.cluster,
|
|
1038
|
+
created_at: new Date(rule.create_at * 1000).toLocaleString("zh-CN", {
|
|
1039
|
+
timeZone: "Asia/Shanghai"
|
|
1040
|
+
}),
|
|
1041
|
+
created_by: rule.create_by,
|
|
1042
|
+
updated_at: new Date(rule.update_at * 1000).toLocaleString("zh-CN", {
|
|
1043
|
+
timeZone: "Asia/Shanghai"
|
|
1044
|
+
}),
|
|
1045
|
+
updated_by: rule.update_by
|
|
1046
|
+
}));
|
|
1047
|
+
|
|
1048
|
+
// Statistics
|
|
1049
|
+
const enabledCount = rules.filter(r => !r.disabled).length;
|
|
1050
|
+
const disabledCount = rules.filter(r => r.disabled).length;
|
|
1051
|
+
const severityCount = rules.reduce((acc, rule) => {
|
|
1052
|
+
const label = rule.severity_label;
|
|
1053
|
+
acc[label] = (acc[label] || 0) + 1;
|
|
1054
|
+
return acc;
|
|
1055
|
+
}, {} as Record<string, number>);
|
|
1056
|
+
|
|
1057
|
+
const result = {
|
|
1058
|
+
summary: {
|
|
1059
|
+
total,
|
|
1060
|
+
showing: rules.length,
|
|
1061
|
+
enabled_count: enabledCount,
|
|
1062
|
+
disabled_count: disabledCount,
|
|
1063
|
+
severity_distribution: severityCount
|
|
1064
|
+
},
|
|
1065
|
+
rules
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
return {
|
|
1069
|
+
content: [{
|
|
1070
|
+
type: "text",
|
|
1071
|
+
text: JSON.stringify(result, null, 2)
|
|
1072
|
+
}]
|
|
1073
|
+
};
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
context.logger.error("Failed to query alert rules:", error);
|
|
1076
|
+
return {
|
|
1077
|
+
content: [{
|
|
1078
|
+
type: "text",
|
|
1079
|
+
text: `❌ 查询告警规则失败: ${error instanceof Error ? error.message : String(error)}`
|
|
1080
|
+
}],
|
|
1081
|
+
isError: true
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
**Step 2: Verify tool compiles**
|
|
1088
|
+
|
|
1089
|
+
Run: `npx tsc --noEmit`
|
|
1090
|
+
Expected: No compilation errors
|
|
1091
|
+
|
|
1092
|
+
**Step 3: Commit**
|
|
1093
|
+
|
|
1094
|
+
```bash
|
|
1095
|
+
git add src/tools/alert-rules.ts
|
|
1096
|
+
git commit -m "feat: implement alert rules tool
|
|
1097
|
+
|
|
1098
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
---
|
|
1102
|
+
|
|
1103
|
+
## Task 7: Alert Mutes Tool
|
|
1104
|
+
|
|
1105
|
+
**Files:**
|
|
1106
|
+
- Create: `src/tools/alert-mutes.ts`
|
|
1107
|
+
|
|
1108
|
+
**Step 1: Write the tool implementation**
|
|
1109
|
+
|
|
1110
|
+
```typescript
|
|
1111
|
+
import type { ToolHandler } from "openclaw/plugin-sdk";
|
|
1112
|
+
import { N9eClient } from "../n9e-client.ts";
|
|
1113
|
+
import { n9ePluginConfigSchema } from "../config-schema.ts";
|
|
1114
|
+
|
|
1115
|
+
export const alertMutesToolInputSchema = {
|
|
1116
|
+
type: "object",
|
|
1117
|
+
properties: {
|
|
1118
|
+
query: {
|
|
1119
|
+
type: "string",
|
|
1120
|
+
description: "搜索查询字符串"
|
|
1121
|
+
},
|
|
1122
|
+
limit: {
|
|
1123
|
+
type: "number",
|
|
1124
|
+
minimum: 1,
|
|
1125
|
+
maximum: 1000,
|
|
1126
|
+
default: 50,
|
|
1127
|
+
description: "返回数量限制"
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
} as const;
|
|
1131
|
+
|
|
1132
|
+
export const alertMutesToolHandler: ToolHandler = async (input, context) => {
|
|
1133
|
+
try {
|
|
1134
|
+
// Parse and validate config
|
|
1135
|
+
const rawConfig = context.config.plugins?.entries?.["n9e-plugin"]?.config;
|
|
1136
|
+
if (!rawConfig) {
|
|
1137
|
+
return {
|
|
1138
|
+
content: [{
|
|
1139
|
+
type: "text",
|
|
1140
|
+
text: "❌ 插件配置未找到,请在配置文件中添加n9e-plugin配置"
|
|
1141
|
+
}],
|
|
1142
|
+
isError: true
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
const config = n9ePluginConfigSchema.parse(rawConfig);
|
|
1147
|
+
const client = new N9eClient(config);
|
|
1148
|
+
|
|
1149
|
+
// Query alert mutes
|
|
1150
|
+
const { total, list } = await client.getAlertMutes({
|
|
1151
|
+
query: input.query,
|
|
1152
|
+
limit: input.limit || 50
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
// Format response
|
|
1156
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1157
|
+
const mutes = list.map(mute => {
|
|
1158
|
+
const tags = mute.tags ? JSON.parse(mute.tags) : {};
|
|
1159
|
+
const isActive = mute.disabled === 0 && mute.btime <= now && mute.etime >= now;
|
|
1160
|
+
const isPast = mute.etime < now;
|
|
1161
|
+
const isFuture = mute.btime > now;
|
|
1162
|
+
|
|
1163
|
+
let status = "未知";
|
|
1164
|
+
if (mute.disabled === 1) {
|
|
1165
|
+
status = "已禁用";
|
|
1166
|
+
} else if (isPast) {
|
|
1167
|
+
status = "已过期";
|
|
1168
|
+
} else if (isFuture) {
|
|
1169
|
+
status = "未生效";
|
|
1170
|
+
} else if (isActive) {
|
|
1171
|
+
status = "生效中";
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
return {
|
|
1175
|
+
id: mute.id,
|
|
1176
|
+
cause: mute.cause,
|
|
1177
|
+
tags,
|
|
1178
|
+
status,
|
|
1179
|
+
disabled: mute.disabled === 1,
|
|
1180
|
+
begin_time: new Date(mute.btime * 1000).toLocaleString("zh-CN", {
|
|
1181
|
+
timeZone: "Asia/Shanghai"
|
|
1182
|
+
}),
|
|
1183
|
+
end_time: new Date(mute.etime * 1000).toLocaleString("zh-CN", {
|
|
1184
|
+
timeZone: "Asia/Shanghai"
|
|
1185
|
+
}),
|
|
1186
|
+
group_id: mute.group_id,
|
|
1187
|
+
cluster: mute.cluster,
|
|
1188
|
+
created_at: new Date(mute.create_at * 1000).toLocaleString("zh-CN", {
|
|
1189
|
+
timeZone: "Asia/Shanghai"
|
|
1190
|
+
}),
|
|
1191
|
+
created_by: mute.create_by,
|
|
1192
|
+
updated_at: new Date(mute.update_at * 1000).toLocaleString("zh-CN", {
|
|
1193
|
+
timeZone: "Asia/Shanghai"
|
|
1194
|
+
}),
|
|
1195
|
+
updated_by: mute.update_by
|
|
1196
|
+
};
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
// Statistics
|
|
1200
|
+
const activeCount = mutes.filter(m => m.status === "生效中").length;
|
|
1201
|
+
const disabledCount = mutes.filter(m => m.disabled).length;
|
|
1202
|
+
const pastCount = mutes.filter(m => m.status === "已过期").length;
|
|
1203
|
+
const futureCount = mutes.filter(m => m.status === "未生效").length;
|
|
1204
|
+
|
|
1205
|
+
const result = {
|
|
1206
|
+
summary: {
|
|
1207
|
+
total,
|
|
1208
|
+
showing: mutes.length,
|
|
1209
|
+
active_count: activeCount,
|
|
1210
|
+
disabled_count: disabledCount,
|
|
1211
|
+
past_count: pastCount,
|
|
1212
|
+
future_count: futureCount
|
|
1213
|
+
},
|
|
1214
|
+
mutes
|
|
1215
|
+
};
|
|
1216
|
+
|
|
1217
|
+
return {
|
|
1218
|
+
content: [{
|
|
1219
|
+
type: "text",
|
|
1220
|
+
text: JSON.stringify(result, null, 2)
|
|
1221
|
+
}]
|
|
1222
|
+
};
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
context.logger.error("Failed to query alert mutes:", error);
|
|
1225
|
+
return {
|
|
1226
|
+
content: [{
|
|
1227
|
+
type: "text",
|
|
1228
|
+
text: `❌ 查询告警屏蔽失败: ${error instanceof Error ? error.message : String(error)}`
|
|
1229
|
+
}],
|
|
1230
|
+
isError: true
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
**Step 2: Verify tool compiles**
|
|
1237
|
+
|
|
1238
|
+
Run: `npx tsc --noEmit`
|
|
1239
|
+
Expected: No compilation errors
|
|
1240
|
+
|
|
1241
|
+
**Step 3: Commit**
|
|
1242
|
+
|
|
1243
|
+
```bash
|
|
1244
|
+
git add src/tools/alert-mutes.ts
|
|
1245
|
+
git commit -m "feat: implement alert mutes tool
|
|
1246
|
+
|
|
1247
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1250
|
+
---
|
|
1251
|
+
|
|
1252
|
+
## Task 8: Plugin Entry Point
|
|
1253
|
+
|
|
1254
|
+
**Files:**
|
|
1255
|
+
- Create: `index.ts`
|
|
1256
|
+
|
|
1257
|
+
**Step 1: Write plugin entry point**
|
|
1258
|
+
|
|
1259
|
+
```typescript
|
|
1260
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
1261
|
+
import {
|
|
1262
|
+
activeAlertsToolInputSchema,
|
|
1263
|
+
activeAlertsToolHandler
|
|
1264
|
+
} from "./src/tools/active-alerts.ts";
|
|
1265
|
+
import {
|
|
1266
|
+
historicalAlertsToolInputSchema,
|
|
1267
|
+
historicalAlertsToolHandler
|
|
1268
|
+
} from "./src/tools/historical-alerts.ts";
|
|
1269
|
+
import {
|
|
1270
|
+
alertRulesToolInputSchema,
|
|
1271
|
+
alertRulesToolHandler
|
|
1272
|
+
} from "./src/tools/alert-rules.ts";
|
|
1273
|
+
import {
|
|
1274
|
+
alertMutesToolInputSchema,
|
|
1275
|
+
alertMutesToolHandler
|
|
1276
|
+
} from "./src/tools/alert-mutes.ts";
|
|
1277
|
+
|
|
1278
|
+
export default function(api: OpenClawPluginApi) {
|
|
1279
|
+
// Register tool: Query active alerts
|
|
1280
|
+
api.registerTool({
|
|
1281
|
+
name: "n9e_query_active_alerts",
|
|
1282
|
+
description: "查询夜莺平台当前活跃的告警事件。支持按严重级别(1=严重,2=警告,3=提示)、规则名称筛选。用于回答"当前有哪些告警"、"最近的告警"、"有没有严重告警"等问题。",
|
|
1283
|
+
inputSchema: activeAlertsToolInputSchema,
|
|
1284
|
+
handler: activeAlertsToolHandler
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
// Register tool: Query historical alerts
|
|
1288
|
+
api.registerTool({
|
|
1289
|
+
name: "n9e_query_historical_alerts",
|
|
1290
|
+
description: "查询夜莺平台历史告警事件。支持按时间范围、严重级别、规则名称筛选。用于回答"昨天有哪些告警"、"过去一周的告警趋势"、"历史告警记录"等问题。",
|
|
1291
|
+
inputSchema: historicalAlertsToolInputSchema,
|
|
1292
|
+
handler: historicalAlertsToolHandler
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
// Register tool: Query alert rules
|
|
1296
|
+
api.registerTool({
|
|
1297
|
+
name: "n9e_query_alert_rules",
|
|
1298
|
+
description: "查询夜莺平台配置的告警规则。支持按规则名称、数据源、启用状态筛选。用于回答"有哪些告警规则"、"CPU相关的规则配置"、"查看内存监控规则"等问题。",
|
|
1299
|
+
inputSchema: alertRulesToolInputSchema,
|
|
1300
|
+
handler: alertRulesToolHandler
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
// Register tool: Query alert mutes
|
|
1304
|
+
api.registerTool({
|
|
1305
|
+
name: "n9e_query_alert_mutes",
|
|
1306
|
+
description: "查询夜莺平台告警屏蔽配置。用于回答"哪些告警被屏蔽了"、"查看告警屏蔽规则"、"为什么没有收到告警"等问题。",
|
|
1307
|
+
inputSchema: alertMutesToolInputSchema,
|
|
1308
|
+
handler: alertMutesToolHandler
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
api.logger.info("N9e plugin loaded successfully");
|
|
1312
|
+
}
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
**Step 2: Verify plugin entry compiles**
|
|
1316
|
+
|
|
1317
|
+
Run: `npx tsc --noEmit`
|
|
1318
|
+
Expected: No compilation errors
|
|
1319
|
+
|
|
1320
|
+
**Step 3: Commit**
|
|
1321
|
+
|
|
1322
|
+
```bash
|
|
1323
|
+
git add index.ts
|
|
1324
|
+
git commit -m "feat: implement plugin entry point with all tools
|
|
1325
|
+
|
|
1326
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
1327
|
+
```
|
|
1328
|
+
|
|
1329
|
+
---
|
|
1330
|
+
|
|
1331
|
+
## Task 9: Skill - Query Active Alerts
|
|
1332
|
+
|
|
1333
|
+
**Files:**
|
|
1334
|
+
- Create: `skills/query-active-alerts/SKILL.md`
|
|
1335
|
+
|
|
1336
|
+
**Step 1: Write the skill file**
|
|
1337
|
+
|
|
1338
|
+
```markdown
|
|
1339
|
+
---
|
|
1340
|
+
name: query-active-alerts
|
|
1341
|
+
description: 查询夜莺平台当前活跃的告警事件
|
|
1342
|
+
---
|
|
1343
|
+
|
|
1344
|
+
# 查询活跃告警
|
|
1345
|
+
|
|
1346
|
+
## 何时使用
|
|
1347
|
+
|
|
1348
|
+
当用户询问当前系统告警状态时使用此工具。典型场景包括:
|
|
1349
|
+
|
|
1350
|
+
- 用户询问"当前有哪些告警"、"最近的告警"、"帮我查一下告警"
|
|
1351
|
+
- 用户询问特定严重级别的告警:"有没有严重告警"、"warning级别的告警"、"严重级别的问题"
|
|
1352
|
+
- 用户询问特定规则的告警:"CPU相关的告警有哪些"、"内存告警"
|
|
1353
|
+
- 用户想了解当前系统健康状态
|
|
1354
|
+
|
|
1355
|
+
## 使用方法
|
|
1356
|
+
|
|
1357
|
+
调用 `n9e_query_active_alerts` 工具,根据用户意图填充参数:
|
|
1358
|
+
|
|
1359
|
+
### 参数说明
|
|
1360
|
+
|
|
1361
|
+
- **severity** (可选): 严重级别筛选
|
|
1362
|
+
- 1 = 严重 (critical)
|
|
1363
|
+
- 2 = 警告 (warning)
|
|
1364
|
+
- 3 = 提示 (info)
|
|
1365
|
+
- **rule_name** (可选): 从用户消息中提取的规则名称关键词,支持模糊匹配
|
|
1366
|
+
- **query** (可选): 通用搜索关键词
|
|
1367
|
+
- **limit** (可选): 返回数量,默认50条
|
|
1368
|
+
|
|
1369
|
+
### 调用示例
|
|
1370
|
+
|
|
1371
|
+
```json
|
|
1372
|
+
// 查询所有活跃告警
|
|
1373
|
+
{
|
|
1374
|
+
"limit": 10
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// 查询严重级别告警
|
|
1378
|
+
{
|
|
1379
|
+
"severity": 1,
|
|
1380
|
+
"limit": 20
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// 查询CPU相关告警
|
|
1384
|
+
{
|
|
1385
|
+
"rule_name": "CPU",
|
|
1386
|
+
"limit": 10
|
|
1387
|
+
}
|
|
1388
|
+
```
|
|
1389
|
+
|
|
1390
|
+
## 返回数据结构
|
|
1391
|
+
|
|
1392
|
+
工具返回JSON格式,包含两部分:
|
|
1393
|
+
|
|
1394
|
+
### summary 摘要
|
|
1395
|
+
- total: 总告警数
|
|
1396
|
+
- showing: 当前显示数量
|
|
1397
|
+
- severity_distribution: 各严重级别分布统计
|
|
1398
|
+
|
|
1399
|
+
### alerts 告警列表
|
|
1400
|
+
每条告警包含:
|
|
1401
|
+
- id: 告警事件ID
|
|
1402
|
+
- rule_name: 触发的规则名称
|
|
1403
|
+
- severity: 严重级别(数字)
|
|
1404
|
+
- severity_label: 严重级别(中文标签)
|
|
1405
|
+
- trigger_time: 触发时间(格式化)
|
|
1406
|
+
- target_ident: 目标标识(如主机名)
|
|
1407
|
+
- trigger_value: 触发值
|
|
1408
|
+
- tags: 标签信息(对象)
|
|
1409
|
+
- rule_note: 规则说明
|
|
1410
|
+
- group_name: 所属业务组
|
|
1411
|
+
|
|
1412
|
+
## 如何回答用户
|
|
1413
|
+
|
|
1414
|
+
1. **总结关键信息**
|
|
1415
|
+
- 首先说明总共有多少条活跃告警
|
|
1416
|
+
- 按严重级别分类汇总:"其中X条严重、Y条警告、Z条提示"
|
|
1417
|
+
|
|
1418
|
+
2. **重点展示严重告警**
|
|
1419
|
+
- 优先列出严重级别(severity=1)的告警
|
|
1420
|
+
- 每条告警说明: 规则名称、触发时间、影响目标
|
|
1421
|
+
|
|
1422
|
+
3. **提供建议**
|
|
1423
|
+
- 如果结果很多,建议用户缩小范围:"可以通过严重级别或规则名称进一步筛选"
|
|
1424
|
+
- 如果没有结果,确认:"当前没有活跃告警"
|
|
1425
|
+
|
|
1426
|
+
## 使用示例
|
|
1427
|
+
|
|
1428
|
+
### 示例 1: 查询所有告警
|
|
1429
|
+
|
|
1430
|
+
**用户**: "查一下最近的告警"
|
|
1431
|
+
|
|
1432
|
+
**操作**: 调用 `n9e_query_active_alerts({ limit: 10 })`
|
|
1433
|
+
|
|
1434
|
+
**回答模板**:
|
|
1435
|
+
> 当前有5条活跃告警,其中2条严重级别、3条警告级别。
|
|
1436
|
+
>
|
|
1437
|
+
> 严重告警:
|
|
1438
|
+
> 1. **CPU使用率过高** - 触发时间: 2026-02-12 14:30:00, 影响主机: server-01
|
|
1439
|
+
> 2. **内存不足** - 触发时间: 2026-02-12 14:25:00, 影响主机: server-02
|
|
1440
|
+
>
|
|
1441
|
+
> 警告告警:
|
|
1442
|
+
> 1. **磁盘空间不足** - 触发时间: 2026-02-12 14:20:00, 影响主机: server-03
|
|
1443
|
+
> ...
|
|
1444
|
+
|
|
1445
|
+
### 示例 2: 查询严重告警
|
|
1446
|
+
|
|
1447
|
+
**用户**: "有严重级别的告警吗"
|
|
1448
|
+
|
|
1449
|
+
**操作**: 调用 `n9e_query_active_alerts({ severity: 1 })`
|
|
1450
|
+
|
|
1451
|
+
**回答模板**:
|
|
1452
|
+
> 有2条严重告警:
|
|
1453
|
+
> 1. **CPU使用率过高** (server-01) - 触发于 2026-02-12 14:30:00, 当前CPU使用率: 95%
|
|
1454
|
+
> 2. **内存不足** (server-02) - 触发于 2026-02-12 14:25:00, 可用内存: 512MB
|
|
1455
|
+
|
|
1456
|
+
### 示例 3: 查询特定规则告警
|
|
1457
|
+
|
|
1458
|
+
**用户**: "CPU相关的告警有哪些"
|
|
1459
|
+
|
|
1460
|
+
**操作**: 调用 `n9e_query_active_alerts({ rule_name: "CPU" })`
|
|
1461
|
+
|
|
1462
|
+
**回答模板**:
|
|
1463
|
+
> 找到3条CPU相关的活跃告警:
|
|
1464
|
+
> 1. **CPU使用率过高** - server-01, 触发时间: 14:30, 当前值: 95%
|
|
1465
|
+
> 2. **CPU负载过高** - server-03, 触发时间: 14:15, 当前值: 8.5
|
|
1466
|
+
> 3. **CPU温度异常** - server-04, 触发时间: 14:10, 当前值: 85°C
|
|
1467
|
+
|
|
1468
|
+
### 示例 4: 无告警情况
|
|
1469
|
+
|
|
1470
|
+
**用户**: "有告警吗"
|
|
1471
|
+
|
|
1472
|
+
**操作**: 调用 `n9e_query_active_alerts({})`
|
|
1473
|
+
|
|
1474
|
+
**回答模板**:
|
|
1475
|
+
> 当前没有活跃告警,系统运行正常✅
|
|
1476
|
+
|
|
1477
|
+
## 注意事项
|
|
1478
|
+
|
|
1479
|
+
1. **时间格式**: 返回数据中的时间已格式化为北京时间,直接使用即可
|
|
1480
|
+
2. **数量限制**: 如果total > showing,提醒用户:"显示前N条,共M条结果"
|
|
1481
|
+
3. **标签信息**: tags字段已解析为对象,可以提取关键标签展示
|
|
1482
|
+
4. **错误处理**: 如果工具返回错误,向用户说明错误原因并建议检查配置
|
|
1483
|
+
```
|
|
1484
|
+
|
|
1485
|
+
**Step 2: Create directory**
|
|
1486
|
+
|
|
1487
|
+
Run: `mkdir -p skills/query-active-alerts`
|
|
1488
|
+
|
|
1489
|
+
**Step 3: Commit**
|
|
1490
|
+
|
|
1491
|
+
```bash
|
|
1492
|
+
git add skills/query-active-alerts/SKILL.md
|
|
1493
|
+
git commit -m "feat: add query-active-alerts skill documentation
|
|
1494
|
+
|
|
1495
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
1496
|
+
```
|
|
1497
|
+
|
|
1498
|
+
---
|
|
1499
|
+
|
|
1500
|
+
## Task 10: Skill - Query Historical Alerts
|
|
1501
|
+
|
|
1502
|
+
**Files:**
|
|
1503
|
+
- Create: `skills/query-historical-alerts/SKILL.md`
|
|
1504
|
+
|
|
1505
|
+
**Step 1: Write the skill file**
|
|
1506
|
+
|
|
1507
|
+
```markdown
|
|
1508
|
+
---
|
|
1509
|
+
name: query-historical-alerts
|
|
1510
|
+
description: 查询夜莺平台历史告警事件
|
|
1511
|
+
---
|
|
1512
|
+
|
|
1513
|
+
# 查询历史告警
|
|
1514
|
+
|
|
1515
|
+
## 何时使用
|
|
1516
|
+
|
|
1517
|
+
当用户询问过去某段时间的告警记录时使用。典型场景:
|
|
1518
|
+
|
|
1519
|
+
- "昨天有哪些告警"
|
|
1520
|
+
- "过去一周的告警趋势"
|
|
1521
|
+
- "上个月的告警统计"
|
|
1522
|
+
- "查看历史告警记录"
|
|
1523
|
+
- "某个时间段发生了什么告警"
|
|
1524
|
+
|
|
1525
|
+
## 使用方法
|
|
1526
|
+
|
|
1527
|
+
调用 `n9e_query_historical_alerts` 工具,根据用户提及的时间范围设置参数。
|
|
1528
|
+
|
|
1529
|
+
### 参数说明
|
|
1530
|
+
|
|
1531
|
+
- **severity** (可选): 严重级别 (1=严重, 2=警告, 3=提示)
|
|
1532
|
+
- **rule_name** (可选): 规则名称关键词
|
|
1533
|
+
- **stime** (可选): 开始时间戳(秒) - 需要转换相对时间
|
|
1534
|
+
- **etime** (可选): 结束时间戳(秒)
|
|
1535
|
+
- **limit** (可选): 返回数量,默认50条
|
|
1536
|
+
|
|
1537
|
+
### 时间转换
|
|
1538
|
+
|
|
1539
|
+
用户常用相对时间表达,需要转换为Unix时间戳(秒):
|
|
1540
|
+
|
|
1541
|
+
- "昨天": stime = 昨天0点, etime = 昨天23:59
|
|
1542
|
+
- "过去24小时": stime = 当前时间 - 86400秒
|
|
1543
|
+
- "本周": stime = 本周一0点, etime = 当前时间
|
|
1544
|
+
- "上周": stime = 上周一0点, etime = 上周日23:59
|
|
1545
|
+
|
|
1546
|
+
### 调用示例
|
|
1547
|
+
|
|
1548
|
+
```json
|
|
1549
|
+
// 查询昨天的告警
|
|
1550
|
+
{
|
|
1551
|
+
"stime": 1707667200,
|
|
1552
|
+
"etime": 1707753599,
|
|
1553
|
+
"limit": 50
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// 查询过去24小时严重告警
|
|
1557
|
+
{
|
|
1558
|
+
"severity": 1,
|
|
1559
|
+
"stime": 1707753600,
|
|
1560
|
+
"limit": 20
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
// 查询CPU相关历史告警
|
|
1564
|
+
{
|
|
1565
|
+
"rule_name": "CPU",
|
|
1566
|
+
"stime": 1707580800,
|
|
1567
|
+
"etime": 1707753600
|
|
1568
|
+
}
|
|
1569
|
+
```
|
|
1570
|
+
|
|
1571
|
+
## 返回数据结构
|
|
1572
|
+
|
|
1573
|
+
### summary 摘要
|
|
1574
|
+
- total: 总告警数
|
|
1575
|
+
- showing: 当前显示数量
|
|
1576
|
+
- severity_distribution: 严重级别分布
|
|
1577
|
+
- recovered_count: 已恢复数量
|
|
1578
|
+
- unrecovered_count: 未恢复数量
|
|
1579
|
+
|
|
1580
|
+
### alerts 告警列表
|
|
1581
|
+
每条告警包含:
|
|
1582
|
+
- id: 告警事件ID
|
|
1583
|
+
- rule_name: 规则名称
|
|
1584
|
+
- severity / severity_label: 严重级别
|
|
1585
|
+
- trigger_time: 触发时间
|
|
1586
|
+
- recover_time: 恢复时间(可能为null)
|
|
1587
|
+
- is_recovered: 是否已恢复(布尔值)
|
|
1588
|
+
- target_ident: 目标标识
|
|
1589
|
+
- trigger_value: 触发值
|
|
1590
|
+
- tags: 标签对象
|
|
1591
|
+
- rule_note: 规则说明
|
|
1592
|
+
- group_name: 业务组名
|
|
1593
|
+
|
|
1594
|
+
## 如何回答用户
|
|
1595
|
+
|
|
1596
|
+
1. **时间范围确认**
|
|
1597
|
+
- 明确告知用户查询的时间范围
|
|
1598
|
+
|
|
1599
|
+
2. **总结统计**
|
|
1600
|
+
- 总告警数、恢复率、严重级别分布
|
|
1601
|
+
|
|
1602
|
+
3. **趋势分析** (如适用)
|
|
1603
|
+
- 哪个时段告警最多
|
|
1604
|
+
- 哪些规则触发最频繁
|
|
1605
|
+
- 恢复时间的长短
|
|
1606
|
+
|
|
1607
|
+
4. **重点展示**
|
|
1608
|
+
- 优先展示严重级别高的
|
|
1609
|
+
- 优先展示未恢复的
|
|
1610
|
+
|
|
1611
|
+
## 使用示例
|
|
1612
|
+
|
|
1613
|
+
### 示例 1: 昨天的告警
|
|
1614
|
+
|
|
1615
|
+
**用户**: "昨天有哪些告警"
|
|
1616
|
+
|
|
1617
|
+
**操作**: 计算昨天的时间范围并调用工具
|
|
1618
|
+
|
|
1619
|
+
**回答模板**:
|
|
1620
|
+
> 昨天(2月11日)共有15条告警事件,其中12条已恢复、3条未恢复。
|
|
1621
|
+
>
|
|
1622
|
+
> 严重级别分布:
|
|
1623
|
+
> - 严重: 5条
|
|
1624
|
+
> - 警告: 8条
|
|
1625
|
+
> - 提示: 2条
|
|
1626
|
+
>
|
|
1627
|
+
> 未恢复告警:
|
|
1628
|
+
> 1. **数据库连接池耗尽** (db-server-01) - 触发于 11日 18:30
|
|
1629
|
+
> 2. **磁盘IO异常** (storage-02) - 触发于 11日 20:15
|
|
1630
|
+
> 3. **API响应超时** (api-gateway) - 触发于 11日 22:45
|
|
1631
|
+
|
|
1632
|
+
### 示例 2: 过去一周趋势
|
|
1633
|
+
|
|
1634
|
+
**用户**: "过去一周的告警趋势"
|
|
1635
|
+
|
|
1636
|
+
**操作**: 计算过去7天时间范围
|
|
1637
|
+
|
|
1638
|
+
**回答模板**:
|
|
1639
|
+
> 过去一周(2月5日-2月11日)共有87条告警:
|
|
1640
|
+
>
|
|
1641
|
+
> 趋势分析:
|
|
1642
|
+
> - 2月9日告警最多(23条),主要是网络波动导致
|
|
1643
|
+
> - CPU使用率告警占比最高(32条,37%)
|
|
1644
|
+
> - 恢复率: 91%(79/87)
|
|
1645
|
+
>
|
|
1646
|
+
> 最频繁的告警规则:
|
|
1647
|
+
> 1. CPU使用率过高 - 32次
|
|
1648
|
+
> 2. 内存不足 - 18次
|
|
1649
|
+
> 3. 磁盘空间不足 - 15次
|
|
1650
|
+
|
|
1651
|
+
### 示例 3: 特定时间段特定规则
|
|
1652
|
+
|
|
1653
|
+
**用户**: "上周CPU相关的严重告警有哪些"
|
|
1654
|
+
|
|
1655
|
+
**操作**: 设置上周时间范围、severity=1、rule_name="CPU"
|
|
1656
|
+
|
|
1657
|
+
**回答模板**:
|
|
1658
|
+
> 上周(2月5日-2月11日)共有8条CPU相关的严重告警:
|
|
1659
|
+
>
|
|
1660
|
+
> 1. **CPU使用率持续过高** (web-01)
|
|
1661
|
+
> - 触发: 2月6日 14:20 → 恢复: 2月6日 15:30 (持续1小时10分)
|
|
1662
|
+
>
|
|
1663
|
+
> 2. **CPU负载异常** (app-03)
|
|
1664
|
+
> - 触发: 2月7日 09:15 → 恢复: 2月7日 09:45 (持续30分钟)
|
|
1665
|
+
> ...
|
|
1666
|
+
|
|
1667
|
+
## 注意事项
|
|
1668
|
+
|
|
1669
|
+
1. **时间转换准确性**: 确保正确转换用户的时间表达
|
|
1670
|
+
2. **恢复状态**: 明确标识哪些告警已恢复、哪些仍在持续
|
|
1671
|
+
3. **持续时长**: 如果有恢复时间,可以计算告警持续时长
|
|
1672
|
+
4. **数据量提示**: 如果历史数据很多,建议分页或缩小范围
|
|
1673
|
+
```
|
|
1674
|
+
|
|
1675
|
+
**Step 2: Create directory**
|
|
1676
|
+
|
|
1677
|
+
Run: `mkdir -p skills/query-historical-alerts`
|
|
1678
|
+
|
|
1679
|
+
**Step 3: Commit**
|
|
1680
|
+
|
|
1681
|
+
```bash
|
|
1682
|
+
git add skills/query-historical-alerts/SKILL.md
|
|
1683
|
+
git commit -m "feat: add query-historical-alerts skill documentation
|
|
1684
|
+
|
|
1685
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
1686
|
+
```
|
|
1687
|
+
|
|
1688
|
+
---
|
|
1689
|
+
|
|
1690
|
+
## Task 11: Skill - Query Alert Rules
|
|
1691
|
+
|
|
1692
|
+
**Files:**
|
|
1693
|
+
- Create: `skills/query-alert-rules/SKILL.md`
|
|
1694
|
+
|
|
1695
|
+
**Step 1: Write the skill file**
|
|
1696
|
+
|
|
1697
|
+
```markdown
|
|
1698
|
+
---
|
|
1699
|
+
name: query-alert-rules
|
|
1700
|
+
description: 查询夜莺平台配置的告警规则
|
|
1701
|
+
---
|
|
1702
|
+
|
|
1703
|
+
# 查询告警规则
|
|
1704
|
+
|
|
1705
|
+
## 何时使用
|
|
1706
|
+
|
|
1707
|
+
当用户想了解系统配置的告警规则时使用。典型场景:
|
|
1708
|
+
|
|
1709
|
+
- "有哪些告警规则"
|
|
1710
|
+
- "CPU相关的规则配置"
|
|
1711
|
+
- "查看内存监控规则"
|
|
1712
|
+
- "这个规则是怎么配置的"
|
|
1713
|
+
- "有多少告警规则是禁用的"
|
|
1714
|
+
|
|
1715
|
+
## 使用方法
|
|
1716
|
+
|
|
1717
|
+
调用 `n9e_query_alert_rules` 工具查询规则配置。
|
|
1718
|
+
|
|
1719
|
+
### 参数说明
|
|
1720
|
+
|
|
1721
|
+
- **rule_name** (可选): 规则名称关键词,支持模糊匹配
|
|
1722
|
+
- **datasource_ids** (可选): 数据源ID列表,筛选特定数据源的规则
|
|
1723
|
+
- **disabled** (可选): 规则状态
|
|
1724
|
+
- 0 = 仅查询启用的规则
|
|
1725
|
+
- 1 = 仅查询禁用的规则
|
|
1726
|
+
- 不传 = 查询所有规则
|
|
1727
|
+
- **limit** (可选): 返回数量,默认50条
|
|
1728
|
+
|
|
1729
|
+
### 调用示例
|
|
1730
|
+
|
|
1731
|
+
```json
|
|
1732
|
+
// 查询所有规则
|
|
1733
|
+
{
|
|
1734
|
+
"limit": 50
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// 查询CPU相关规则
|
|
1738
|
+
{
|
|
1739
|
+
"rule_name": "CPU"
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// 查询禁用的规则
|
|
1743
|
+
{
|
|
1744
|
+
"disabled": 1
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// 查询特定数据源的规则
|
|
1748
|
+
{
|
|
1749
|
+
"datasource_ids": [1, 2, 3]
|
|
1750
|
+
}
|
|
1751
|
+
```
|
|
1752
|
+
|
|
1753
|
+
## 返回数据结构
|
|
1754
|
+
|
|
1755
|
+
### summary 摘要
|
|
1756
|
+
- total: 总规则数
|
|
1757
|
+
- showing: 当前显示数量
|
|
1758
|
+
- enabled_count: 启用规则数
|
|
1759
|
+
- disabled_count: 禁用规则数
|
|
1760
|
+
- severity_distribution: 严重级别分布
|
|
1761
|
+
|
|
1762
|
+
### rules 规则列表
|
|
1763
|
+
每条规则包含:
|
|
1764
|
+
- id: 规则ID
|
|
1765
|
+
- name: 规则名称
|
|
1766
|
+
- note: 规则说明
|
|
1767
|
+
- severity / severity_label: 严重级别
|
|
1768
|
+
- disabled: 是否禁用(布尔)
|
|
1769
|
+
- status: 状态文本("启用"/"禁用")
|
|
1770
|
+
- prom_ql: Prometheus查询语句(核心配置)
|
|
1771
|
+
- prom_for_duration: 持续时长(秒)
|
|
1772
|
+
- prom_eval_interval: 评估间隔(秒)
|
|
1773
|
+
- notify_channels: 通知渠道
|
|
1774
|
+
- datasource_ids: 关联的数据源ID列表
|
|
1775
|
+
- group_id: 业务组ID
|
|
1776
|
+
- cluster: 集群名称
|
|
1777
|
+
- created_at / created_by: 创建信息
|
|
1778
|
+
- updated_at / updated_by: 更新信息
|
|
1779
|
+
|
|
1780
|
+
## 如何回答用户
|
|
1781
|
+
|
|
1782
|
+
1. **总结规则概况**
|
|
1783
|
+
- 总数、启用/禁用数量、严重级别分布
|
|
1784
|
+
|
|
1785
|
+
2. **展示规则详情**
|
|
1786
|
+
- 规则名称和说明
|
|
1787
|
+
- 核心查询语句(prom_ql)
|
|
1788
|
+
- 触发条件(持续时长)
|
|
1789
|
+
- 通知配置
|
|
1790
|
+
|
|
1791
|
+
3. **突出重要信息**
|
|
1792
|
+
- 如果查询禁用规则,说明为什么可能被禁用
|
|
1793
|
+
- 如果查询特定规则,详细解释其配置逻辑
|
|
1794
|
+
|
|
1795
|
+
## 使用示例
|
|
1796
|
+
|
|
1797
|
+
### 示例 1: 查询所有规则
|
|
1798
|
+
|
|
1799
|
+
**用户**: "有哪些告警规则"
|
|
1800
|
+
|
|
1801
|
+
**操作**: 调用 `n9e_query_alert_rules({ limit: 20 })`
|
|
1802
|
+
|
|
1803
|
+
**回答模板**:
|
|
1804
|
+
> 系统共配置了45条告警规则,其中:
|
|
1805
|
+
> - 启用: 38条
|
|
1806
|
+
> - 禁用: 7条
|
|
1807
|
+
>
|
|
1808
|
+
> 严重级别分布:
|
|
1809
|
+
> - 严重: 15条
|
|
1810
|
+
> - 警告: 22条
|
|
1811
|
+
> - 提示: 8条
|
|
1812
|
+
>
|
|
1813
|
+
> 部分规则列表:
|
|
1814
|
+
> 1. **CPU使用率过高** (严重) - 当CPU使用率 > 80% 持续5分钟时触发
|
|
1815
|
+
> 2. **内存不足** (严重) - 当可用内存 < 10% 持续3分钟时触发
|
|
1816
|
+
> 3. **磁盘空间不足** (警告) - 当磁盘使用率 > 85% 持续10分钟时触发
|
|
1817
|
+
> ...
|
|
1818
|
+
|
|
1819
|
+
### 示例 2: 查询特定规则
|
|
1820
|
+
|
|
1821
|
+
**用户**: "CPU相关的规则配置"
|
|
1822
|
+
|
|
1823
|
+
**操作**: 调用 `n9e_query_alert_rules({ rule_name: "CPU" })`
|
|
1824
|
+
|
|
1825
|
+
**回答模板**:
|
|
1826
|
+
> 找到3条CPU相关的告警规则:
|
|
1827
|
+
>
|
|
1828
|
+
> **1. CPU使用率过高** (严重级别, 已启用)
|
|
1829
|
+
> - 查询语句: `100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80`
|
|
1830
|
+
> - 触发条件: 持续5分钟
|
|
1831
|
+
> - 评估间隔: 每1分钟
|
|
1832
|
+
> - 通知渠道: 企业微信、邮件
|
|
1833
|
+
> - 最后更新: 2026-01-15, 更新人: admin
|
|
1834
|
+
>
|
|
1835
|
+
> **2. CPU负载异常** (警告级别, 已启用)
|
|
1836
|
+
> - 查询语句: `node_load5 / count(node_cpu_seconds_total{mode="idle"}) without (cpu, mode) > 4`
|
|
1837
|
+
> - 触发条件: 持续3分钟
|
|
1838
|
+
> - 评估间隔: 每1分钟
|
|
1839
|
+
>
|
|
1840
|
+
> **3. CPU温度过高** (提示级别, 已禁用)
|
|
1841
|
+
> - 说明: 此规则已被禁用,可能是因为监控项不适用于虚拟化环境
|
|
1842
|
+
|
|
1843
|
+
### 示例 3: 查询禁用的规则
|
|
1844
|
+
|
|
1845
|
+
**用户**: "有多少告警规则是禁用的"
|
|
1846
|
+
|
|
1847
|
+
**操作**: 调用 `n9e_query_alert_rules({ disabled: 1 })`
|
|
1848
|
+
|
|
1849
|
+
**回答模板**:
|
|
1850
|
+
> 共有7条规则处于禁用状态:
|
|
1851
|
+
>
|
|
1852
|
+
> 1. **CPU温度过高** - 禁用原因可能: 虚拟化环境不支持
|
|
1853
|
+
> 2. **网卡丢包率** - 禁用时间: 2025-12-20
|
|
1854
|
+
> 3. **TCP连接数过高** - 禁用时间: 2025-11-15
|
|
1855
|
+
> ...
|
|
1856
|
+
>
|
|
1857
|
+
> 如需启用这些规则,请联系系统管理员。
|
|
1858
|
+
|
|
1859
|
+
## 注意事项
|
|
1860
|
+
|
|
1861
|
+
1. **PromQL解释**: 对于复杂的Prometheus查询,用通俗语言解释其含义
|
|
1862
|
+
2. **时间单位**: prom_for_duration和prom_eval_interval是秒,转换为分钟展示
|
|
1863
|
+
3. **规则说明**: 优先使用note字段的说明,如果没有则根据prom_ql推断
|
|
1864
|
+
4. **数据源**: 如果有datasource_ids,可以说明规则关联的数据源
|
|
1865
|
+
```
|
|
1866
|
+
|
|
1867
|
+
**Step 2: Create directory**
|
|
1868
|
+
|
|
1869
|
+
Run: `mkdir -p skills/query-alert-rules`
|
|
1870
|
+
|
|
1871
|
+
**Step 3: Commit**
|
|
1872
|
+
|
|
1873
|
+
```bash
|
|
1874
|
+
git add skills/query-alert-rules/SKILL.md
|
|
1875
|
+
git commit -m "feat: add query-alert-rules skill documentation
|
|
1876
|
+
|
|
1877
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
1878
|
+
```
|
|
1879
|
+
|
|
1880
|
+
---
|
|
1881
|
+
|
|
1882
|
+
## Task 12: Skill - Query Alert Mutes
|
|
1883
|
+
|
|
1884
|
+
**Files:**
|
|
1885
|
+
- Create: `skills/query-alert-mutes/SKILL.md`
|
|
1886
|
+
|
|
1887
|
+
**Step 1: Write the skill file**
|
|
1888
|
+
|
|
1889
|
+
```markdown
|
|
1890
|
+
---
|
|
1891
|
+
name: query-alert-mutes
|
|
1892
|
+
description: 查询夜莺平台告警屏蔽配置
|
|
1893
|
+
---
|
|
1894
|
+
|
|
1895
|
+
# 查询告警屏蔽
|
|
1896
|
+
|
|
1897
|
+
## 何时使用
|
|
1898
|
+
|
|
1899
|
+
当用户想了解哪些告警被屏蔽或为什么没收到告警时使用。典型场景:
|
|
1900
|
+
|
|
1901
|
+
- "哪些告警被屏蔽了"
|
|
1902
|
+
- "查看告警屏蔽规则"
|
|
1903
|
+
- "为什么没有收到告警"
|
|
1904
|
+
- "有屏蔽规则吗"
|
|
1905
|
+
- "维护窗口期的屏蔽配置"
|
|
1906
|
+
|
|
1907
|
+
## 使用方法
|
|
1908
|
+
|
|
1909
|
+
调用 `n9e_query_alert_mutes` 工具查询屏蔽配置。
|
|
1910
|
+
|
|
1911
|
+
### 参数说明
|
|
1912
|
+
|
|
1913
|
+
- **query** (可选): 搜索关键词,在cause(原因)和tags中搜索
|
|
1914
|
+
- **limit** (可选): 返回数量,默认50条
|
|
1915
|
+
|
|
1916
|
+
### 调用示例
|
|
1917
|
+
|
|
1918
|
+
```json
|
|
1919
|
+
// 查询所有屏蔽规则
|
|
1920
|
+
{
|
|
1921
|
+
"limit": 50
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
// 搜索维护相关的屏蔽
|
|
1925
|
+
{
|
|
1926
|
+
"query": "维护"
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
// 搜索特定主机的屏蔽
|
|
1930
|
+
{
|
|
1931
|
+
"query": "server-01"
|
|
1932
|
+
}
|
|
1933
|
+
```
|
|
1934
|
+
|
|
1935
|
+
## 返回数据结构
|
|
1936
|
+
|
|
1937
|
+
### summary 摘要
|
|
1938
|
+
- total: 总屏蔽规则数
|
|
1939
|
+
- showing: 当前显示数量
|
|
1940
|
+
- active_count: 生效中的规则数
|
|
1941
|
+
- disabled_count: 已禁用的规则数
|
|
1942
|
+
- past_count: 已过期的规则数
|
|
1943
|
+
- future_count: 未生效的规则数
|
|
1944
|
+
|
|
1945
|
+
### mutes 屏蔽规则列表
|
|
1946
|
+
每条规则包含:
|
|
1947
|
+
- id: 屏蔽规则ID
|
|
1948
|
+
- cause: 屏蔽原因(重要!)
|
|
1949
|
+
- tags: 匹配标签(对象)
|
|
1950
|
+
- status: 状态("生效中"/"已禁用"/"已过期"/"未生效")
|
|
1951
|
+
- disabled: 是否禁用(布尔)
|
|
1952
|
+
- begin_time: 生效开始时间
|
|
1953
|
+
- end_time: 生效结束时间
|
|
1954
|
+
- group_id: 业务组ID
|
|
1955
|
+
- cluster: 集群名称
|
|
1956
|
+
- created_at / created_by: 创建信息
|
|
1957
|
+
- updated_at / updated_by: 更新信息
|
|
1958
|
+
|
|
1959
|
+
## 状态说明
|
|
1960
|
+
|
|
1961
|
+
屏蔽规则有4种状态:
|
|
1962
|
+
1. **生效中**: disabled=0 且当前时间在 [begin_time, end_time] 内
|
|
1963
|
+
2. **已禁用**: disabled=1
|
|
1964
|
+
3. **已过期**: end_time < 当前时间
|
|
1965
|
+
4. **未生效**: begin_time > 当前时间
|
|
1966
|
+
|
|
1967
|
+
## 如何回答用户
|
|
1968
|
+
|
|
1969
|
+
1. **总结屏蔽状态**
|
|
1970
|
+
- 有多少条规则、多少生效中、多少已过期
|
|
1971
|
+
|
|
1972
|
+
2. **重点展示生效中的规则**
|
|
1973
|
+
- 屏蔽原因
|
|
1974
|
+
- 匹配的标签范围
|
|
1975
|
+
- 生效时间范围
|
|
1976
|
+
|
|
1977
|
+
3. **解释为什么没收到告警**
|
|
1978
|
+
- 如果有匹配的屏蔽规则,说明原因
|
|
1979
|
+
- 提示屏蔽的时间范围
|
|
1980
|
+
|
|
1981
|
+
## 使用示例
|
|
1982
|
+
|
|
1983
|
+
### 示例 1: 查询所有屏蔽规则
|
|
1984
|
+
|
|
1985
|
+
**用户**: "哪些告警被屏蔽了"
|
|
1986
|
+
|
|
1987
|
+
**操作**: 调用 `n9e_query_alert_mutes({})`
|
|
1988
|
+
|
|
1989
|
+
**回答模板**:
|
|
1990
|
+
> 当前共有5条告警屏蔽规则:
|
|
1991
|
+
> - 生效中: 2条
|
|
1992
|
+
> - 已过期: 2条
|
|
1993
|
+
> - 未生效: 1条
|
|
1994
|
+
>
|
|
1995
|
+
> **生效中的屏蔽规则:**
|
|
1996
|
+
>
|
|
1997
|
+
> 1. **机房维护窗口**
|
|
1998
|
+
> - 屏蔽范围: datacenter=A, 所有告警
|
|
1999
|
+
> - 生效时间: 2026-02-12 02:00 - 2026-02-12 06:00
|
|
2000
|
+
> - 原因: 机房设备升级维护
|
|
2001
|
+
>
|
|
2002
|
+
> 2. **测试环境屏蔽**
|
|
2003
|
+
> - 屏蔽范围: env=test, 所有告警
|
|
2004
|
+
> - 生效时间: 2026-02-01 00:00 - 2026-03-01 00:00
|
|
2005
|
+
> - 原因: 测试环境告警不需要通知
|
|
2006
|
+
|
|
2007
|
+
### 示例 2: 为什么没收到告警
|
|
2008
|
+
|
|
2009
|
+
**用户**: "为什么server-01的告警没有收到通知"
|
|
2010
|
+
|
|
2011
|
+
**操作**: 调用 `n9e_query_alert_mutes({ query: "server-01" })`
|
|
2012
|
+
|
|
2013
|
+
**回答模板**:
|
|
2014
|
+
> 找到1条相关的屏蔽规则:
|
|
2015
|
+
>
|
|
2016
|
+
> **server-01维护屏蔽** (生效中)
|
|
2017
|
+
> - 屏蔽范围: host=server-01
|
|
2018
|
+
> - 生效时间: 2026-02-12 14:00 - 2026-02-12 18:00
|
|
2019
|
+
> - 原因: 服务器系统升级
|
|
2020
|
+
> - 创建人: ops-admin, 创建时间: 2026-02-12 13:50
|
|
2021
|
+
>
|
|
2022
|
+
> 这条屏蔽规则导致server-01的所有告警都不会发送通知,预计18:00后恢复通知。
|
|
2023
|
+
|
|
2024
|
+
### 示例 3: 查询维护相关屏蔽
|
|
2025
|
+
|
|
2026
|
+
**用户**: "查看维护窗口期的屏蔽配置"
|
|
2027
|
+
|
|
2028
|
+
**操作**: 调用 `n9e_query_alert_mutes({ query: "维护" })`
|
|
2029
|
+
|
|
2030
|
+
**回答模板**:
|
|
2031
|
+
> 找到3条维护相关的屏蔽规则:
|
|
2032
|
+
>
|
|
2033
|
+
> **1. 机房维护窗口** (生效中)
|
|
2034
|
+
> - 范围: datacenter=A
|
|
2035
|
+
> - 时间: 2026-02-12 02:00 - 06:00
|
|
2036
|
+
>
|
|
2037
|
+
> **2. 数据库维护** (已过期)
|
|
2038
|
+
> - 范围: service=database
|
|
2039
|
+
> - 时间: 2026-02-10 22:00 - 2026-02-11 02:00
|
|
2040
|
+
> - 状态: 已结束
|
|
2041
|
+
>
|
|
2042
|
+
> **3. 网络设备维护** (未生效)
|
|
2043
|
+
> - 范围: device_type=switch
|
|
2044
|
+
> - 时间: 2026-02-15 01:00 - 05:00
|
|
2045
|
+
> - 状态: 计划中
|
|
2046
|
+
|
|
2047
|
+
### 示例 4: 没有屏蔽规则
|
|
2048
|
+
|
|
2049
|
+
**用户**: "有屏蔽规则吗"
|
|
2050
|
+
|
|
2051
|
+
**操作**: 调用 `n9e_query_alert_mutes({})`
|
|
2052
|
+
|
|
2053
|
+
**回答模板**:
|
|
2054
|
+
> 当前没有配置告警屏蔽规则,所有告警都会正常发送通知。
|
|
2055
|
+
|
|
2056
|
+
## 注意事项
|
|
2057
|
+
|
|
2058
|
+
1. **时间范围**: 明确告知用户屏蔽的开始和结束时间
|
|
2059
|
+
2. **标签匹配**: 解释tags字段,说明哪些告警会被匹配
|
|
2060
|
+
3. **过期清理**: 提示已过期的规则可以清理
|
|
2061
|
+
4. **状态区分**: 清楚区分"生效中"、"已过期"、"未生效"、"已禁用"
|
|
2062
|
+
5. **原因说明**: cause字段很重要,帮助用户理解为什么要屏蔽
|
|
2063
|
+
```
|
|
2064
|
+
|
|
2065
|
+
**Step 2: Create directory**
|
|
2066
|
+
|
|
2067
|
+
Run: `mkdir -p skills/query-alert-mutes`
|
|
2068
|
+
|
|
2069
|
+
**Step 3: Commit**
|
|
2070
|
+
|
|
2071
|
+
```bash
|
|
2072
|
+
git add skills/query-alert-mutes/SKILL.md
|
|
2073
|
+
git commit -m "feat: add query-alert-mutes skill documentation
|
|
2074
|
+
|
|
2075
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
2076
|
+
```
|
|
2077
|
+
|
|
2078
|
+
---
|
|
2079
|
+
|
|
2080
|
+
## Task 13: README Documentation
|
|
2081
|
+
|
|
2082
|
+
**Files:**
|
|
2083
|
+
- Create: `README.md`
|
|
2084
|
+
|
|
2085
|
+
**Step 1: Write README**
|
|
2086
|
+
|
|
2087
|
+
```markdown
|
|
2088
|
+
# N9e Plugin for OpenClaw
|
|
2089
|
+
|
|
2090
|
+
夜莺(Nightingale)告警平台查询插件,支持通过自然语言查询告警事件、历史告警、告警规则和告警屏蔽配置。
|
|
2091
|
+
|
|
2092
|
+
## 功能特性
|
|
2093
|
+
|
|
2094
|
+
- ✅ 查询当前活跃告警
|
|
2095
|
+
- ✅ 查询历史告警记录
|
|
2096
|
+
- ✅ 查询告警规则配置
|
|
2097
|
+
- ✅ 查询告警屏蔽规则
|
|
2098
|
+
- ✅ 自然语言交互
|
|
2099
|
+
- ✅ 全局查询(跨所有业务组)
|
|
2100
|
+
- ✅ 完善的错误处理
|
|
2101
|
+
|
|
2102
|
+
## 快速开始
|
|
2103
|
+
|
|
2104
|
+
### 1. 安装插件
|
|
2105
|
+
|
|
2106
|
+
```bash
|
|
2107
|
+
# 从本地目录安装(开发模式)
|
|
2108
|
+
openclaw plugins install -l ./n9e-plugin
|
|
2109
|
+
|
|
2110
|
+
# 或从npm安装
|
|
2111
|
+
openclaw plugins install @youdao/n9e-plugin
|
|
2112
|
+
```
|
|
2113
|
+
|
|
2114
|
+
### 2. 配置插件
|
|
2115
|
+
|
|
2116
|
+
编辑OpenClaw配置文件,添加以下配置:
|
|
2117
|
+
|
|
2118
|
+
```json
|
|
2119
|
+
{
|
|
2120
|
+
"plugins": {
|
|
2121
|
+
"entries": {
|
|
2122
|
+
"n9e-plugin": {
|
|
2123
|
+
"enabled": true,
|
|
2124
|
+
"config": {
|
|
2125
|
+
"apiBaseUrl": "https://n9e-dev.inner.youdao.com/api/n9e",
|
|
2126
|
+
"apiKey": "your-api-key-here",
|
|
2127
|
+
"timeout": 30000
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
```
|
|
2134
|
+
|
|
2135
|
+
### 3. 重启Gateway
|
|
2136
|
+
|
|
2137
|
+
```bash
|
|
2138
|
+
openclaw gateway restart
|
|
2139
|
+
```
|
|
2140
|
+
|
|
2141
|
+
### 4. 验证安装
|
|
2142
|
+
|
|
2143
|
+
```bash
|
|
2144
|
+
openclaw plugins list
|
|
2145
|
+
```
|
|
2146
|
+
|
|
2147
|
+
## 使用示例
|
|
2148
|
+
|
|
2149
|
+
与Agent对话即可使用:
|
|
2150
|
+
|
|
2151
|
+
```
|
|
2152
|
+
用户: 查一下最近的告警
|
|
2153
|
+
→ Agent调用 n9e_query_active_alerts
|
|
2154
|
+
→ 返回: 当前有5条活跃告警...
|
|
2155
|
+
|
|
2156
|
+
用户: 昨天有哪些告警
|
|
2157
|
+
→ Agent调用 n9e_query_historical_alerts
|
|
2158
|
+
→ 返回: 昨天共有15条告警...
|
|
2159
|
+
|
|
2160
|
+
用户: CPU相关的告警规则
|
|
2161
|
+
→ Agent调用 n9e_query_alert_rules
|
|
2162
|
+
→ 返回: 找到3条CPU相关规则...
|
|
2163
|
+
|
|
2164
|
+
用户: 哪些告警被屏蔽了
|
|
2165
|
+
→ Agent调用 n9e_query_alert_mutes
|
|
2166
|
+
→ 返回: 当前有2条屏蔽规则...
|
|
2167
|
+
```
|
|
2168
|
+
|
|
2169
|
+
## API Tools
|
|
2170
|
+
|
|
2171
|
+
### 1. n9e_query_active_alerts
|
|
2172
|
+
|
|
2173
|
+
查询当前活跃告警。
|
|
2174
|
+
|
|
2175
|
+
**参数:**
|
|
2176
|
+
- `severity`: 严重级别(1=严重,2=警告,3=提示)
|
|
2177
|
+
- `rule_name`: 规则名称(支持模糊匹配)
|
|
2178
|
+
- `limit`: 返回数量(默认50)
|
|
2179
|
+
- `query`: 搜索关键词
|
|
2180
|
+
|
|
2181
|
+
### 2. n9e_query_historical_alerts
|
|
2182
|
+
|
|
2183
|
+
查询历史告警记录。
|
|
2184
|
+
|
|
2185
|
+
**参数:**
|
|
2186
|
+
- `severity`: 严重级别
|
|
2187
|
+
- `rule_name`: 规则名称
|
|
2188
|
+
- `stime`: 开始时间戳(秒)
|
|
2189
|
+
- `etime`: 结束时间戳(秒)
|
|
2190
|
+
- `limit`: 返回数量(默认50)
|
|
2191
|
+
|
|
2192
|
+
### 3. n9e_query_alert_rules
|
|
2193
|
+
|
|
2194
|
+
查询告警规则配置。
|
|
2195
|
+
|
|
2196
|
+
**参数:**
|
|
2197
|
+
- `rule_name`: 规则名称
|
|
2198
|
+
- `datasource_ids`: 数据源ID列表
|
|
2199
|
+
- `disabled`: 规则状态(0=启用,1=禁用)
|
|
2200
|
+
- `limit`: 返回数量(默认50)
|
|
2201
|
+
|
|
2202
|
+
### 4. n9e_query_alert_mutes
|
|
2203
|
+
|
|
2204
|
+
查询告警屏蔽规则。
|
|
2205
|
+
|
|
2206
|
+
**参数:**
|
|
2207
|
+
- `query`: 搜索关键词
|
|
2208
|
+
- `limit`: 返回数量(默认50)
|
|
2209
|
+
|
|
2210
|
+
## 项目结构
|
|
2211
|
+
|
|
2212
|
+
```
|
|
2213
|
+
n9e-plugin/
|
|
2214
|
+
├── openclaw.plugin.json # 插件清单
|
|
2215
|
+
├── package.json
|
|
2216
|
+
├── index.ts # 插件入口
|
|
2217
|
+
├── src/
|
|
2218
|
+
│ ├── config-schema.ts # Zod配置Schema
|
|
2219
|
+
│ ├── n9e-client.ts # 夜莺API客户端
|
|
2220
|
+
│ ├── types.ts # TypeScript类型定义
|
|
2221
|
+
│ └── tools/ # 工具实现
|
|
2222
|
+
│ ├── active-alerts.ts
|
|
2223
|
+
│ ├── historical-alerts.ts
|
|
2224
|
+
│ ├── alert-rules.ts
|
|
2225
|
+
│ └── alert-mutes.ts
|
|
2226
|
+
└── skills/ # Agent技能
|
|
2227
|
+
├── query-active-alerts/
|
|
2228
|
+
├── query-historical-alerts/
|
|
2229
|
+
├── query-alert-rules/
|
|
2230
|
+
└── query-alert-mutes/
|
|
2231
|
+
```
|
|
2232
|
+
|
|
2233
|
+
## 开发
|
|
2234
|
+
|
|
2235
|
+
### 安装依赖
|
|
2236
|
+
|
|
2237
|
+
```bash
|
|
2238
|
+
npm install
|
|
2239
|
+
```
|
|
2240
|
+
|
|
2241
|
+
### 链接模式开发
|
|
2242
|
+
|
|
2243
|
+
```bash
|
|
2244
|
+
npm run dev
|
|
2245
|
+
# 等同于: openclaw plugins install -l .
|
|
2246
|
+
```
|
|
2247
|
+
|
|
2248
|
+
### 类型检查
|
|
2249
|
+
|
|
2250
|
+
```bash
|
|
2251
|
+
npx tsc --noEmit
|
|
2252
|
+
```
|
|
2253
|
+
|
|
2254
|
+
## 配置说明
|
|
2255
|
+
|
|
2256
|
+
| 字段 | 类型 | 必需 | 说明 |
|
|
2257
|
+
|------|------|------|------|
|
|
2258
|
+
| `apiBaseUrl` | string | ✅ | 夜莺API地址 |
|
|
2259
|
+
| `apiKey` | string | ✅ | API访问密钥 |
|
|
2260
|
+
| `timeout` | number | ❌ | 请求超时(毫秒),默认30000 |
|
|
2261
|
+
|
|
2262
|
+
## 错误处理
|
|
2263
|
+
|
|
2264
|
+
| 错误类型 | 提示信息 |
|
|
2265
|
+
|---------|---------|
|
|
2266
|
+
| 网络错误 | 无法连接到夜莺平台,请检查网络或配置 |
|
|
2267
|
+
| 401/403 | 认证失败,请检查API Key配置 |
|
|
2268
|
+
| 404 | 未找到指定的资源 |
|
|
2269
|
+
| 500 | 夜莺服务器错误,请稍后重试 |
|
|
2270
|
+
| 超时 | 请求超时,请检查网络连接 |
|
|
2271
|
+
|
|
2272
|
+
## 许可证
|
|
2273
|
+
|
|
2274
|
+
MIT
|
|
2275
|
+
|
|
2276
|
+
## 作者
|
|
2277
|
+
|
|
2278
|
+
Youdao DevOps Team
|
|
2279
|
+
```
|
|
2280
|
+
|
|
2281
|
+
**Step 2: Commit**
|
|
2282
|
+
|
|
2283
|
+
```bash
|
|
2284
|
+
git add README.md
|
|
2285
|
+
git commit -m "docs: add comprehensive README documentation
|
|
2286
|
+
|
|
2287
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
2288
|
+
```
|
|
2289
|
+
|
|
2290
|
+
---
|
|
2291
|
+
|
|
2292
|
+
## Task 14: Final Verification
|
|
2293
|
+
|
|
2294
|
+
**Step 1: Verify all files exist**
|
|
2295
|
+
|
|
2296
|
+
Run: `find . -type f -not -path './node_modules/*' -not -path './.git/*' | sort`
|
|
2297
|
+
Expected: Complete file listing matching project structure
|
|
2298
|
+
|
|
2299
|
+
**Step 2: Verify TypeScript compilation**
|
|
2300
|
+
|
|
2301
|
+
Run: `npx tsc --noEmit`
|
|
2302
|
+
Expected: No errors
|
|
2303
|
+
|
|
2304
|
+
**Step 3: Verify plugin manifest**
|
|
2305
|
+
|
|
2306
|
+
Run: `cat openclaw.plugin.json | head -20`
|
|
2307
|
+
Expected: Valid JSON with correct structure
|
|
2308
|
+
|
|
2309
|
+
**Step 4: Verify skills directory**
|
|
2310
|
+
|
|
2311
|
+
Run: `find skills -name "SKILL.md"`
|
|
2312
|
+
Expected: 4 SKILL.md files
|
|
2313
|
+
|
|
2314
|
+
**Step 5: Final commit**
|
|
2315
|
+
|
|
2316
|
+
```bash
|
|
2317
|
+
git add -A
|
|
2318
|
+
git commit -m "chore: final verification and cleanup
|
|
2319
|
+
|
|
2320
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
|
2321
|
+
```
|
|
2322
|
+
|
|
2323
|
+
**Step 6: Create summary**
|
|
2324
|
+
|
|
2325
|
+
Run: `git log --oneline`
|
|
2326
|
+
Expected: All commits listed in chronological order
|
|
2327
|
+
|
|
2328
|
+
---
|
|
2329
|
+
|
|
2330
|
+
## Execution Handoff
|
|
2331
|
+
|
|
2332
|
+
Plan complete and saved to `docs/plans/implementation-plan.md`. Two execution options:
|
|
2333
|
+
|
|
2334
|
+
**1. Subagent-Driven (this session)** - I dispatch fresh subagent per task, review between tasks, fast iteration
|
|
2335
|
+
|
|
2336
|
+
**2. Parallel Session (separate)** - Open new session with executing-plans, batch execution with checkpoints
|
|
2337
|
+
|
|
2338
|
+
**Which approach?**
|