@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.
@@ -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?**