@zhin.js/process-monitor 3.0.1 → 3.0.3

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,9 @@
1
+ import { defineCommand } from '@zhin.js/command';
2
+ import { formatProcessStatus, type ProcessMonitorConfig } from '../src/monitor.js';
3
+
4
+ export default defineCommand<ProcessMonitorConfig>({
5
+ description: '查看进程监控状态(PID、运行时长、重启统计)',
6
+ execute() {
7
+ return formatProcessStatus();
8
+ },
9
+ });
package/lib/index.d.ts CHANGED
@@ -1,13 +1,3 @@
1
- declare const plugin: import("zhin.js").Plugin;
2
- interface ProcessState {
3
- lastPid?: number;
4
- lastStartTime?: number;
5
- restartCount: number;
6
- crashCount: number;
7
- totalUptime: number;
8
- }
9
- export declare let processState: ProcessState;
10
- export declare const startTime: number;
11
- export declare function formatUptime(ms: number): string;
12
- export default plugin;
1
+ export { formatProcessStatus, formatUptime, processState, resetProcessMonitorForTests, resolveProcessMonitorConfig, startProcessMonitor, startTime, } from './monitor.js';
2
+ export type { NotifyChannel, ProcessMonitorConfig, ProcessState } from './monitor.js';
13
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,QAAA,MAAM,MAAM,0BAAc,CAAC;AAsC3B,UAAU,YAAY;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,IAAI,YAAY,EAAE,YAIxB,CAAC;AAEF,eAAO,MAAM,SAAS,QAAa,CAAC;AA2GpC,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAU/C;AA+CD,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,YAAY,EACZ,2BAA2B,EAC3B,2BAA2B,EAC3B,mBAAmB,EACnB,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
package/lib/index.js CHANGED
@@ -1,175 +1,2 @@
1
- /**
2
- * @zhin.js/process-monitor
3
- *
4
- * 进程监控与重启通知插件
5
- */
6
- import { formatCompact, usePlugin } from 'zhin.js';
7
- import os from 'os';
8
- import fs from 'fs';
9
- import path from 'path';
10
- const plugin = usePlugin();
11
- const { logger, root } = plugin;
12
- const configService = root.inject('config');
13
- const appConfig = configService?.getPrimary() || {};
14
- const config = {
15
- enabled: true,
16
- notifyChannels: [],
17
- notifyOnStart: true,
18
- notifyOnRestart: true,
19
- notifyOnCrash: true,
20
- ...appConfig['process-monitor'],
21
- };
22
- if (!config.enabled) {
23
- logger.info(formatCompact({ op: 'load', enabled: false }));
24
- }
25
- // ─── 状态管理 ────────────────────────────────────────────────────────────────
26
- const STATE_FILE = path.join(process.cwd(), 'data', 'process-state.json');
27
- export let processState = {
28
- restartCount: 0,
29
- crashCount: 0,
30
- totalUptime: 0,
31
- };
32
- export const startTime = Date.now();
33
- function loadProcessState() {
34
- try {
35
- if (fs.existsSync(STATE_FILE)) {
36
- const data = fs.readFileSync(STATE_FILE, 'utf-8');
37
- processState = JSON.parse(data);
38
- }
39
- }
40
- catch (error) {
41
- logger.warn(formatCompact({ op: 'load_state', ok: false, error: String(error) }));
42
- }
43
- }
44
- function saveProcessState() {
45
- try {
46
- fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
47
- fs.writeFileSync(STATE_FILE, JSON.stringify(processState, null, 2));
48
- }
49
- catch (error) {
50
- logger.warn(formatCompact({ op: 'save_state', ok: false, error: String(error) }));
51
- }
52
- }
53
- // ─── 启动检测 ────────────────────────────────────────────────────────────────
54
- async function detectStartupReason() {
55
- const currentPid = process.pid;
56
- const currentTime = Date.now();
57
- let reason = 'start';
58
- let uptime;
59
- if (processState.lastPid && processState.lastStartTime) {
60
- const timeSinceLastStart = currentTime - processState.lastStartTime;
61
- if (timeSinceLastStart < 5 * 60 * 1000) {
62
- reason = 'crash';
63
- processState.crashCount++;
64
- logger.warn(formatCompact({ op: 'restart', abnormal: true, since_last_s: Math.floor(timeSinceLastStart / 1000) }));
65
- }
66
- else {
67
- reason = 'restart';
68
- processState.restartCount++;
69
- logger.info(formatCompact({ op: 'restart', abnormal: false }));
70
- }
71
- uptime = timeSinceLastStart;
72
- processState.totalUptime += uptime;
73
- }
74
- else {
75
- logger.info(formatCompact({ first: true }));
76
- }
77
- processState.lastPid = currentPid;
78
- processState.lastStartTime = currentTime;
79
- saveProcessState();
80
- const record = {
81
- timestamp: new Date(),
82
- reason,
83
- uptime,
84
- pid: currentPid,
85
- hostname: os.hostname(),
86
- platform: `${os.platform()}-${os.arch()}`,
87
- nodeVersion: process.version,
88
- memory: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
89
- };
90
- const shouldNotify = (reason === 'start' && config.notifyOnStart) ||
91
- (reason === 'restart' && config.notifyOnRestart) ||
92
- (reason === 'crash' && config.notifyOnCrash);
93
- if (shouldNotify && config.notifyChannels && config.notifyChannels.length > 0) {
94
- await sendNotification(record);
95
- }
96
- }
97
- // ─── 通知 ────────────────────────────────────────────────────────────────────
98
- function formatNotificationMessage(record) {
99
- const emoji = { start: '🚀', restart: '🔄', crash: '💥' }[record.reason] || '📊';
100
- const reasonText = { start: '首次启动', restart: '正常重启', crash: '异常崩溃' }[record.reason] || '未知';
101
- const lines = [
102
- `${emoji} 【进程监控通知】`,
103
- '',
104
- `📊 事件: ${reasonText}`,
105
- `⏰ 时间: ${record.timestamp.toLocaleString('zh-CN')}`,
106
- `🖥️ 主机: ${record.hostname}`,
107
- `🔢 PID: ${record.pid}`,
108
- `💻 平台: ${record.platform}`,
109
- `📦 Node: ${record.nodeVersion}`,
110
- ];
111
- if (record.uptime) {
112
- lines.push(`⏱️ 运行时长: ${formatUptime(record.uptime)}`);
113
- }
114
- if (record.memory) {
115
- lines.push(`💾 内存: ${record.memory} MB`);
116
- }
117
- lines.push('', `📈 统计:`);
118
- lines.push(` • 总重启: ${processState.restartCount} 次`);
119
- lines.push(` • 崩溃: ${processState.crashCount} 次`);
120
- lines.push(` • 累计运行: ${formatUptime(processState.totalUptime)}`);
121
- return lines.join('\n');
122
- }
123
- export function formatUptime(ms) {
124
- const seconds = Math.floor(ms / 1000);
125
- const minutes = Math.floor(seconds / 60);
126
- const hours = Math.floor(minutes / 60);
127
- const days = Math.floor(hours / 24);
128
- if (days > 0)
129
- return `${days}天${hours % 24}小时`;
130
- if (hours > 0)
131
- return `${hours}小时${minutes % 60}分钟`;
132
- if (minutes > 0)
133
- return `${minutes}分钟`;
134
- return `${seconds}秒`;
135
- }
136
- async function sendNotification(record) {
137
- const message = formatNotificationMessage(record);
138
- for (const channel of config.notifyChannels || []) {
139
- try {
140
- if (channel.type === 'webhook') {
141
- await fetch(channel.target, {
142
- method: 'POST',
143
- headers: { 'Content-Type': 'application/json' },
144
- body: JSON.stringify({ event: 'process_restart', data: record, stats: processState }),
145
- });
146
- logger.debug(`Webhook 通知已发送: ${channel.target}`);
147
- }
148
- }
149
- catch (error) {
150
- logger.error(`发送通知失败 (${channel.type}):`, error);
151
- }
152
- }
153
- }
154
- // ─── 初始化 ──────────────────────────────────────────────────────────────────
155
- if (config.enabled) {
156
- loadProcessState();
157
- detectStartupReason();
158
- const onSigterm = () => {
159
- logger.info(formatCompact({ op: 'shutdown', signal: 'SIGTERM' }));
160
- saveProcessState();
161
- };
162
- const onSigint = () => {
163
- logger.info(formatCompact({ op: 'shutdown', signal: 'SIGINT' }));
164
- saveProcessState();
165
- };
166
- process.on('SIGTERM', onSigterm);
167
- process.on('SIGINT', onSigint);
168
- plugin.onDispose(() => {
169
- process.removeListener('SIGTERM', onSigterm);
170
- process.removeListener('SIGINT', onSigint);
171
- });
172
- }
173
- // AI 工具已迁移到 tools/*.tool.md,框架自动发现注册
174
- export default plugin;
1
+ export { formatProcessStatus, formatUptime, processState, resetProcessMonitorForTests, resolveProcessMonitorConfig, startProcessMonitor, startTime, } from './monitor.js';
175
2
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,YAAY,EACZ,2BAA2B,EAC3B,2BAA2B,EAC3B,mBAAmB,EACnB,SAAS,GACV,MAAM,cAAc,CAAC"}
@@ -0,0 +1,31 @@
1
+ export interface NotifyChannel {
2
+ type: 'user' | 'group' | 'webhook';
3
+ target: string;
4
+ platform?: string;
5
+ }
6
+ export interface ProcessMonitorConfig {
7
+ enabled?: boolean;
8
+ notifyChannels?: NotifyChannel[];
9
+ notifyOnStart?: boolean;
10
+ notifyOnRestart?: boolean;
11
+ notifyOnCrash?: boolean;
12
+ }
13
+ export interface ProcessState {
14
+ lastPid?: number;
15
+ lastStartTime?: number;
16
+ restartCount: number;
17
+ crashCount: number;
18
+ totalUptime: number;
19
+ }
20
+ export declare let processState: ProcessState;
21
+ export declare const startTime: number;
22
+ export declare function resolveProcessMonitorConfig(raw: ProcessMonitorConfig | undefined): Required<Pick<ProcessMonitorConfig, 'enabled' | 'notifyOnStart' | 'notifyOnRestart' | 'notifyOnCrash'>> & {
23
+ notifyChannels: NotifyChannel[];
24
+ };
25
+ export declare function formatUptime(ms: number): string;
26
+ export declare function formatProcessStatus(): string;
27
+ /** Start monitoring once; returns disposer for lifecycle cleanup. */
28
+ export declare function startProcessMonitor(rawConfig?: ProcessMonitorConfig): () => void;
29
+ /** Test helper: reset module state without touching disk. */
30
+ export declare function resetProcessMonitorForTests(): void;
31
+ //# sourceMappingURL=monitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitor.d.ts","sourceRoot":"","sources":["../src/monitor.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAID,eAAO,IAAI,YAAY,EAAE,YAIxB,CAAC;AAEF,eAAO,MAAM,SAAS,QAAa,CAAC;AAKpC,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,oBAAoB,GAAG,SAAS,GACpC,QAAQ,CACT,IAAI,CACF,oBAAoB,EACpB,SAAS,GAAG,eAAe,GAAG,iBAAiB,GAAG,eAAe,CAClE,CACF,GAAG;IAAE,cAAc,EAAE,aAAa,EAAE,CAAA;CAAE,CAQtC;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAU/C;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAgB5C;AAgJD,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,SAAS,CAAC,EAAE,oBAAoB,GAAG,MAAM,IAAI,CA2BhF;AAED,6DAA6D;AAC7D,wBAAgB,2BAA2B,IAAI,IAAI,CAQlD"}
package/lib/monitor.js ADDED
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Process monitor — module-level state + file-backed restart detection.
3
+ * No usePlugin; Plugin Runtime setup() calls startProcessMonitor().
4
+ */
5
+ import os from 'node:os';
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ const STATE_FILE = path.join(process.cwd(), 'data', 'process-state.json');
9
+ export let processState = {
10
+ restartCount: 0,
11
+ crashCount: 0,
12
+ totalUptime: 0,
13
+ };
14
+ export const startTime = Date.now();
15
+ let started = false;
16
+ let signalHandlers = null;
17
+ export function resolveProcessMonitorConfig(raw) {
18
+ return {
19
+ enabled: raw?.enabled ?? true,
20
+ notifyChannels: raw?.notifyChannels ?? [],
21
+ notifyOnStart: raw?.notifyOnStart ?? true,
22
+ notifyOnRestart: raw?.notifyOnRestart ?? true,
23
+ notifyOnCrash: raw?.notifyOnCrash ?? true,
24
+ };
25
+ }
26
+ export function formatUptime(ms) {
27
+ const seconds = Math.floor(ms / 1000);
28
+ const minutes = Math.floor(seconds / 60);
29
+ const hours = Math.floor(minutes / 60);
30
+ const days = Math.floor(hours / 24);
31
+ if (days > 0)
32
+ return `${days}天${hours % 24}小时`;
33
+ if (hours > 0)
34
+ return `${hours}小时${minutes % 60}分钟`;
35
+ if (minutes > 0)
36
+ return `${minutes}分钟`;
37
+ return `${seconds}秒`;
38
+ }
39
+ export function formatProcessStatus() {
40
+ const uptime = Date.now() - startTime;
41
+ const memUsage = process.memoryUsage();
42
+ return [
43
+ '📊 进程监控状态',
44
+ '',
45
+ `🚀 当前 PID: ${process.pid}`,
46
+ `⏱️ 运行时长: ${formatUptime(uptime)}`,
47
+ `💾 内存使用: ${Math.round(memUsage.heapUsed / 1024 / 1024)} MB`,
48
+ `🔄 总重启: ${processState.restartCount} 次`,
49
+ `💥 崩溃: ${processState.crashCount} 次`,
50
+ `📈 累计运行: ${formatUptime(processState.totalUptime)}`,
51
+ `🖥️ 主机: ${os.hostname()}`,
52
+ `💻 平台: ${os.platform()}-${os.arch()}`,
53
+ `📦 Node: ${process.version}`,
54
+ ].join('\n');
55
+ }
56
+ function loadProcessState() {
57
+ try {
58
+ if (fs.existsSync(STATE_FILE)) {
59
+ const data = fs.readFileSync(STATE_FILE, 'utf-8');
60
+ processState = JSON.parse(data);
61
+ }
62
+ }
63
+ catch {
64
+ // keep defaults
65
+ }
66
+ }
67
+ function saveProcessState() {
68
+ try {
69
+ fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
70
+ fs.writeFileSync(STATE_FILE, JSON.stringify(processState, null, 2));
71
+ }
72
+ catch {
73
+ // ignore
74
+ }
75
+ }
76
+ function formatNotificationMessage(record) {
77
+ const emoji = { start: '🚀', restart: '🔄', crash: '💥' }[record.reason] || '📊';
78
+ const reasonText = { start: '首次启动', restart: '正常重启', crash: '异常崩溃' }[record.reason] || '未知';
79
+ const lines = [
80
+ `${emoji} 【进程监控通知】`,
81
+ '',
82
+ `📊 事件: ${reasonText}`,
83
+ `⏰ 时间: ${record.timestamp.toLocaleString('zh-CN')}`,
84
+ `🖥️ 主机: ${record.hostname}`,
85
+ `🔢 PID: ${record.pid}`,
86
+ `💻 平台: ${record.platform}`,
87
+ `📦 Node: ${record.nodeVersion}`,
88
+ ];
89
+ if (record.uptime) {
90
+ lines.push(`⏱️ 运行时长: ${formatUptime(record.uptime)}`);
91
+ }
92
+ if (record.memory) {
93
+ lines.push(`💾 内存: ${record.memory} MB`);
94
+ }
95
+ lines.push('', `📈 统计:`);
96
+ lines.push(` • 总重启: ${processState.restartCount} 次`);
97
+ lines.push(` • 崩溃: ${processState.crashCount} 次`);
98
+ lines.push(` • 累计运行: ${formatUptime(processState.totalUptime)}`);
99
+ return lines.join('\n');
100
+ }
101
+ async function sendNotification(config, record) {
102
+ // user/group 渠道尚未实现(需要接入 OutboundHost 出站链路),
103
+ // 目前仅支持 webhook;实现时用 formatNotificationMessage(record) 生成文本。
104
+ for (const channel of config.notifyChannels) {
105
+ if (channel.type !== 'webhook')
106
+ continue;
107
+ try {
108
+ await fetch(channel.target, {
109
+ method: 'POST',
110
+ headers: { 'Content-Type': 'application/json' },
111
+ body: JSON.stringify({
112
+ event: 'process_restart',
113
+ data: record,
114
+ stats: processState,
115
+ }),
116
+ });
117
+ }
118
+ catch {
119
+ // ignore webhook errors
120
+ }
121
+ }
122
+ }
123
+ async function detectStartupReason(config) {
124
+ const currentPid = process.pid;
125
+ const currentTime = Date.now();
126
+ let reason = 'start';
127
+ let uptime;
128
+ if (processState.lastPid && processState.lastStartTime) {
129
+ const timeSinceLastStart = currentTime - processState.lastStartTime;
130
+ if (timeSinceLastStart < 5 * 60 * 1000) {
131
+ reason = 'crash';
132
+ processState.crashCount++;
133
+ }
134
+ else {
135
+ reason = 'restart';
136
+ processState.restartCount++;
137
+ }
138
+ uptime = timeSinceLastStart;
139
+ processState.totalUptime += uptime;
140
+ }
141
+ processState.lastPid = currentPid;
142
+ processState.lastStartTime = currentTime;
143
+ saveProcessState();
144
+ const record = {
145
+ timestamp: new Date(),
146
+ reason,
147
+ uptime,
148
+ pid: currentPid,
149
+ hostname: os.hostname(),
150
+ platform: `${os.platform()}-${os.arch()}`,
151
+ nodeVersion: process.version,
152
+ memory: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
153
+ };
154
+ const shouldNotify = (reason === 'start' && config.notifyOnStart) ||
155
+ (reason === 'restart' && config.notifyOnRestart) ||
156
+ (reason === 'crash' && config.notifyOnCrash);
157
+ if (shouldNotify && config.notifyChannels.length > 0) {
158
+ await sendNotification(config, record);
159
+ }
160
+ }
161
+ /** Start monitoring once; returns disposer for lifecycle cleanup. */
162
+ export function startProcessMonitor(rawConfig) {
163
+ const config = resolveProcessMonitorConfig(rawConfig);
164
+ if (!config.enabled || started) {
165
+ return () => undefined;
166
+ }
167
+ started = true;
168
+ loadProcessState();
169
+ void detectStartupReason(config);
170
+ const onSigterm = () => {
171
+ saveProcessState();
172
+ };
173
+ const onSigint = () => {
174
+ saveProcessState();
175
+ };
176
+ process.on('SIGTERM', onSigterm);
177
+ process.on('SIGINT', onSigint);
178
+ signalHandlers = { sigterm: onSigterm, sigint: onSigint };
179
+ return () => {
180
+ if (signalHandlers) {
181
+ process.removeListener('SIGTERM', signalHandlers.sigterm);
182
+ process.removeListener('SIGINT', signalHandlers.sigint);
183
+ signalHandlers = null;
184
+ }
185
+ started = false;
186
+ };
187
+ }
188
+ /** Test helper: reset module state without touching disk. */
189
+ export function resetProcessMonitorForTests() {
190
+ if (signalHandlers) {
191
+ process.removeListener('SIGTERM', signalHandlers.sigterm);
192
+ process.removeListener('SIGINT', signalHandlers.sigint);
193
+ signalHandlers = null;
194
+ }
195
+ processState = { restartCount: 0, crashCount: 0, totalUptime: 0 };
196
+ started = false;
197
+ }
198
+ //# sourceMappingURL=monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitor.js","sourceRoot":"","sources":["../src/monitor.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zhin.js/process-monitor",
3
- "version": "3.0.1",
4
- "description": "进程监控与重启通知插件",
3
+ "version": "3.0.3",
4
+ "description": "进程监控与重启通知插件 (Plugin Runtime)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "types": "./lib/index.d.ts",
@@ -14,13 +14,12 @@
14
14
  "./package.json": "./package.json"
15
15
  },
16
16
  "files": [
17
+ "plugin.ts",
18
+ "schema.json",
19
+ "commands",
20
+ "tools",
17
21
  "src",
18
22
  "lib",
19
- "client",
20
- "dist",
21
- "skills",
22
- "tools",
23
- "plugin.yml",
24
23
  "README.md"
25
24
  ],
26
25
  "keywords": [
@@ -39,18 +38,43 @@
39
38
  "directory": "plugins/features/process-monitor"
40
39
  },
41
40
  "dependencies": {
42
- "zhin.js": "4.1.1"
41
+ "@zhin.js/command": "1.0.1",
42
+ "@zhin.js/plugin-runtime": "1.0.1",
43
+ "@zhin.js/tool": "1.0.1"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@types/node": "^26.1.0",
46
47
  "typescript": "^6.0.3",
47
- "zhin.js": "4.1.1"
48
+ "vitest": "^4.1.10"
49
+ },
50
+ "engines": {
51
+ "node": "^20.19.0 || >=22.12.0"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public",
55
+ "registry": "https://registry.npmjs.org"
48
56
  },
49
- "peerDependencies": {
50
- "zhin.js": "4.1.1"
57
+ "zhin": {
58
+ "protocol": 1,
59
+ "type": "plugin",
60
+ "entry": "./plugin.ts",
61
+ "engine": "^1.0.0",
62
+ "runtime": "trusted",
63
+ "features": [
64
+ {
65
+ "package": "@zhin.js/command",
66
+ "api": "^1.0.0"
67
+ },
68
+ {
69
+ "package": "@zhin.js/tool",
70
+ "api": "^1.0.0"
71
+ }
72
+ ],
73
+ "plugins": []
51
74
  },
52
75
  "scripts": {
53
76
  "build": "tsc",
54
- "dev": "tsc --watch"
77
+ "clean": "rimraf lib",
78
+ "test": "vitest run --root ../../.. plugins/features/process-monitor/tests"
55
79
  }
56
80
  }
package/plugin.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { definePlugin } from '@zhin.js/plugin-runtime';
2
+ import {
3
+ resolveProcessMonitorConfig,
4
+ startProcessMonitor,
5
+ type ProcessMonitorConfig,
6
+ } from './src/monitor.js';
7
+
8
+ /**
9
+ * Process monitor Plugin Runtime cutover:
10
+ * - setup() owns file-backed restart detection + signal cleanup
11
+ * - commands/process-status for chat status
12
+ * - tools/process-status kept as agent tool surface
13
+ */
14
+ export default definePlugin<ProcessMonitorConfig>({
15
+ name: 'process-monitor',
16
+ metadata: {
17
+ displayName: 'Process Monitor',
18
+ },
19
+ setup(context) {
20
+ const config = resolveProcessMonitorConfig(context.config.get());
21
+ const dispose = startProcessMonitor(config);
22
+ context.lifecycle.add(dispose);
23
+ },
24
+ });
package/schema.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "additionalProperties": false,
5
+ "properties": {
6
+ "enabled": {
7
+ "type": "boolean",
8
+ "default": true,
9
+ "description": "是否启用进程监控"
10
+ },
11
+ "notifyOnStart": {
12
+ "type": "boolean",
13
+ "default": true,
14
+ "description": "首次启动时通知"
15
+ },
16
+ "notifyOnRestart": {
17
+ "type": "boolean",
18
+ "default": true,
19
+ "description": "正常重启时通知"
20
+ },
21
+ "notifyOnCrash": {
22
+ "type": "boolean",
23
+ "default": true,
24
+ "description": "异常崩溃重启时通知"
25
+ },
26
+ "notifyChannels": {
27
+ "type": "array",
28
+ "default": [],
29
+ "description": "通知渠道(slice-1 仅 webhook 生效)",
30
+ "items": {
31
+ "type": "object",
32
+ "additionalProperties": false,
33
+ "required": ["type", "target"],
34
+ "properties": {
35
+ "type": {
36
+ "type": "string",
37
+ "enum": ["user", "group", "webhook"]
38
+ },
39
+ "target": { "type": "string" },
40
+ "platform": { "type": "string" }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
package/src/index.ts CHANGED
@@ -1,227 +1,10 @@
1
- /**
2
- * @zhin.js/process-monitor
3
- *
4
- * 进程监控与重启通知插件
5
- */
6
- import { formatCompact, usePlugin } from 'zhin.js';
7
- import os from 'os';
8
- import fs from 'fs';
9
- import path from 'path';
10
-
11
- const plugin = usePlugin();
12
- const { logger, root } = plugin;
13
-
14
- // ─── 配置 ────────────────────────────────────────────────────────────────────
15
-
16
- interface NotifyChannel {
17
- type: 'user' | 'group' | 'webhook';
18
- target: string;
19
- platform?: string;
20
- }
21
-
22
- interface Config {
23
- enabled?: boolean;
24
- notifyChannels?: NotifyChannel[];
25
- notifyOnStart?: boolean;
26
- notifyOnRestart?: boolean;
27
- notifyOnCrash?: boolean;
28
- }
29
-
30
- const configService = root.inject('config');
31
- const appConfig = configService?.getPrimary<{ 'process-monitor'?: Config }>() || {};
32
- const config: Config = {
33
- enabled: true,
34
- notifyChannels: [],
35
- notifyOnStart: true,
36
- notifyOnRestart: true,
37
- notifyOnCrash: true,
38
- ...appConfig['process-monitor'],
39
- };
40
-
41
- if (!config.enabled) {
42
- logger.info(formatCompact( { op: 'load', enabled: false }));
43
- }
44
-
45
- // ─── 状态管理 ────────────────────────────────────────────────────────────────
46
-
47
- const STATE_FILE = path.join(process.cwd(), 'data', 'process-state.json');
48
-
49
- interface ProcessState {
50
- lastPid?: number;
51
- lastStartTime?: number;
52
- restartCount: number;
53
- crashCount: number;
54
- totalUptime: number;
55
- }
56
-
57
- export let processState: ProcessState = {
58
- restartCount: 0,
59
- crashCount: 0,
60
- totalUptime: 0,
61
- };
62
-
63
- export const startTime = Date.now();
64
-
65
- function loadProcessState() {
66
- try {
67
- if (fs.existsSync(STATE_FILE)) {
68
- const data = fs.readFileSync(STATE_FILE, 'utf-8');
69
- processState = JSON.parse(data);
70
- }
71
- } catch (error) {
72
- logger.warn(formatCompact( { op: 'load_state', ok: false, error: String(error) }));
73
- }
74
- }
75
-
76
- function saveProcessState() {
77
- try {
78
- fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
79
- fs.writeFileSync(STATE_FILE, JSON.stringify(processState, null, 2));
80
- } catch (error) {
81
- logger.warn(formatCompact( { op: 'save_state', ok: false, error: String(error) }));
82
- }
83
- }
84
-
85
- // ─── 启动检测 ────────────────────────────────────────────────────────────────
86
-
87
- async function detectStartupReason() {
88
- const currentPid = process.pid;
89
- const currentTime = Date.now();
90
- let reason: 'start' | 'restart' | 'crash' = 'start';
91
- let uptime: number | undefined;
92
-
93
- if (processState.lastPid && processState.lastStartTime) {
94
- const timeSinceLastStart = currentTime - processState.lastStartTime;
95
-
96
- if (timeSinceLastStart < 5 * 60 * 1000) {
97
- reason = 'crash';
98
- processState.crashCount++;
99
- logger.warn(formatCompact( { op: 'restart', abnormal: true, since_last_s: Math.floor(timeSinceLastStart / 1000) }));
100
- } else {
101
- reason = 'restart';
102
- processState.restartCount++;
103
- logger.info(formatCompact( { op: 'restart', abnormal: false }));
104
- }
105
-
106
- uptime = timeSinceLastStart;
107
- processState.totalUptime += uptime;
108
- } else {
109
- logger.info(formatCompact({ first: true }));
110
- }
111
-
112
- processState.lastPid = currentPid;
113
- processState.lastStartTime = currentTime;
114
- saveProcessState();
115
-
116
- const record = {
117
- timestamp: new Date(),
118
- reason,
119
- uptime,
120
- pid: currentPid,
121
- hostname: os.hostname(),
122
- platform: `${os.platform()}-${os.arch()}`,
123
- nodeVersion: process.version,
124
- memory: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
125
- };
126
-
127
- const shouldNotify =
128
- (reason === 'start' && config.notifyOnStart) ||
129
- (reason === 'restart' && config.notifyOnRestart) ||
130
- (reason === 'crash' && config.notifyOnCrash);
131
-
132
- if (shouldNotify && config.notifyChannels && config.notifyChannels.length > 0) {
133
- await sendNotification(record);
134
- }
135
- }
136
-
137
- // ─── 通知 ────────────────────────────────────────────────────────────────────
138
-
139
- function formatNotificationMessage(record: any): string {
140
- const emoji = { start: '🚀', restart: '🔄', crash: '💥' }[record.reason] || '📊';
141
- const reasonText = { start: '首次启动', restart: '正常重启', crash: '异常崩溃' }[record.reason] || '未知';
142
-
143
- const lines = [
144
- `${emoji} 【进程监控通知】`,
145
- '',
146
- `📊 事件: ${reasonText}`,
147
- `⏰ 时间: ${record.timestamp.toLocaleString('zh-CN')}`,
148
- `🖥️ 主机: ${record.hostname}`,
149
- `🔢 PID: ${record.pid}`,
150
- `💻 平台: ${record.platform}`,
151
- `📦 Node: ${record.nodeVersion}`,
152
- ];
153
-
154
- if (record.uptime) {
155
- lines.push(`⏱️ 运行时长: ${formatUptime(record.uptime)}`);
156
- }
157
-
158
- if (record.memory) {
159
- lines.push(`💾 内存: ${record.memory} MB`);
160
- }
161
-
162
- lines.push('', `📈 统计:`);
163
- lines.push(` • 总重启: ${processState.restartCount} 次`);
164
- lines.push(` • 崩溃: ${processState.crashCount} 次`);
165
- lines.push(` • 累计运行: ${formatUptime(processState.totalUptime)}`);
166
-
167
- return lines.join('\n');
168
- }
169
-
170
- export function formatUptime(ms: number): string {
171
- const seconds = Math.floor(ms / 1000);
172
- const minutes = Math.floor(seconds / 60);
173
- const hours = Math.floor(minutes / 60);
174
- const days = Math.floor(hours / 24);
175
-
176
- if (days > 0) return `${days}天${hours % 24}小时`;
177
- if (hours > 0) return `${hours}小时${minutes % 60}分钟`;
178
- if (minutes > 0) return `${minutes}分钟`;
179
- return `${seconds}秒`;
180
- }
181
-
182
- async function sendNotification(record: any) {
183
- const message = formatNotificationMessage(record);
184
-
185
- for (const channel of config.notifyChannels || []) {
186
- try {
187
- if (channel.type === 'webhook') {
188
- await fetch(channel.target, {
189
- method: 'POST',
190
- headers: { 'Content-Type': 'application/json' },
191
- body: JSON.stringify({ event: 'process_restart', data: record, stats: processState }),
192
- });
193
- logger.debug(`Webhook 通知已发送: ${channel.target}`);
194
- }
195
- } catch (error) {
196
- logger.error(`发送通知失败 (${channel.type}):`, error);
197
- }
198
- }
199
- }
200
-
201
- // ─── 初始化 ──────────────────────────────────────────────────────────────────
202
-
203
- if (config.enabled) {
204
- loadProcessState();
205
- detectStartupReason();
206
-
207
- const onSigterm = () => {
208
- logger.info(formatCompact( { op: 'shutdown', signal: 'SIGTERM' }));
209
- saveProcessState();
210
- };
211
- const onSigint = () => {
212
- logger.info(formatCompact( { op: 'shutdown', signal: 'SIGINT' }));
213
- saveProcessState();
214
- };
215
-
216
- process.on('SIGTERM', onSigterm);
217
- process.on('SIGINT', onSigint);
218
-
219
- plugin.onDispose(() => {
220
- process.removeListener('SIGTERM', onSigterm);
221
- process.removeListener('SIGINT', onSigint);
222
- });
223
- }
224
-
225
- // AI 工具已迁移到 tools/*.tool.md,框架自动发现注册
226
-
227
- export default plugin;
1
+ export {
2
+ formatProcessStatus,
3
+ formatUptime,
4
+ processState,
5
+ resetProcessMonitorForTests,
6
+ resolveProcessMonitorConfig,
7
+ startProcessMonitor,
8
+ startTime,
9
+ } from './monitor.js';
10
+ export type { NotifyChannel, ProcessMonitorConfig, ProcessState } from './monitor.js';
package/src/monitor.ts ADDED
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Process monitor — module-level state + file-backed restart detection.
3
+ * No usePlugin; Plugin Runtime setup() calls startProcessMonitor().
4
+ */
5
+ import os from 'node:os';
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+
9
+ export interface NotifyChannel {
10
+ type: 'user' | 'group' | 'webhook';
11
+ target: string;
12
+ platform?: string;
13
+ }
14
+
15
+ export interface ProcessMonitorConfig {
16
+ enabled?: boolean;
17
+ notifyChannels?: NotifyChannel[];
18
+ notifyOnStart?: boolean;
19
+ notifyOnRestart?: boolean;
20
+ notifyOnCrash?: boolean;
21
+ }
22
+
23
+ export interface ProcessState {
24
+ lastPid?: number;
25
+ lastStartTime?: number;
26
+ restartCount: number;
27
+ crashCount: number;
28
+ totalUptime: number;
29
+ }
30
+
31
+ const STATE_FILE = path.join(process.cwd(), 'data', 'process-state.json');
32
+
33
+ export let processState: ProcessState = {
34
+ restartCount: 0,
35
+ crashCount: 0,
36
+ totalUptime: 0,
37
+ };
38
+
39
+ export const startTime = Date.now();
40
+
41
+ let started = false;
42
+ let signalHandlers: { sigterm: () => void; sigint: () => void } | null = null;
43
+
44
+ export function resolveProcessMonitorConfig(
45
+ raw: ProcessMonitorConfig | undefined,
46
+ ): Required<
47
+ Pick<
48
+ ProcessMonitorConfig,
49
+ 'enabled' | 'notifyOnStart' | 'notifyOnRestart' | 'notifyOnCrash'
50
+ >
51
+ > & { notifyChannels: NotifyChannel[] } {
52
+ return {
53
+ enabled: raw?.enabled ?? true,
54
+ notifyChannels: raw?.notifyChannels ?? [],
55
+ notifyOnStart: raw?.notifyOnStart ?? true,
56
+ notifyOnRestart: raw?.notifyOnRestart ?? true,
57
+ notifyOnCrash: raw?.notifyOnCrash ?? true,
58
+ };
59
+ }
60
+
61
+ export function formatUptime(ms: number): string {
62
+ const seconds = Math.floor(ms / 1000);
63
+ const minutes = Math.floor(seconds / 60);
64
+ const hours = Math.floor(minutes / 60);
65
+ const days = Math.floor(hours / 24);
66
+
67
+ if (days > 0) return `${days}天${hours % 24}小时`;
68
+ if (hours > 0) return `${hours}小时${minutes % 60}分钟`;
69
+ if (minutes > 0) return `${minutes}分钟`;
70
+ return `${seconds}秒`;
71
+ }
72
+
73
+ export function formatProcessStatus(): string {
74
+ const uptime = Date.now() - startTime;
75
+ const memUsage = process.memoryUsage();
76
+ return [
77
+ '📊 进程监控状态',
78
+ '',
79
+ `🚀 当前 PID: ${process.pid}`,
80
+ `⏱️ 运行时长: ${formatUptime(uptime)}`,
81
+ `💾 内存使用: ${Math.round(memUsage.heapUsed / 1024 / 1024)} MB`,
82
+ `🔄 总重启: ${processState.restartCount} 次`,
83
+ `💥 崩溃: ${processState.crashCount} 次`,
84
+ `📈 累计运行: ${formatUptime(processState.totalUptime)}`,
85
+ `🖥️ 主机: ${os.hostname()}`,
86
+ `💻 平台: ${os.platform()}-${os.arch()}`,
87
+ `📦 Node: ${process.version}`,
88
+ ].join('\n');
89
+ }
90
+
91
+ function loadProcessState(): void {
92
+ try {
93
+ if (fs.existsSync(STATE_FILE)) {
94
+ const data = fs.readFileSync(STATE_FILE, 'utf-8');
95
+ processState = JSON.parse(data);
96
+ }
97
+ } catch {
98
+ // keep defaults
99
+ }
100
+ }
101
+
102
+ function saveProcessState(): void {
103
+ try {
104
+ fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
105
+ fs.writeFileSync(STATE_FILE, JSON.stringify(processState, null, 2));
106
+ } catch {
107
+ // ignore
108
+ }
109
+ }
110
+
111
+ function formatNotificationMessage(record: {
112
+ reason: string;
113
+ timestamp: Date;
114
+ hostname: string;
115
+ pid: number;
116
+ platform: string;
117
+ nodeVersion: string;
118
+ uptime?: number;
119
+ memory?: number;
120
+ }): string {
121
+ const emoji = ({ start: '🚀', restart: '🔄', crash: '💥' } as Record<string, string>)[
122
+ record.reason
123
+ ] || '📊';
124
+ const reasonText = (
125
+ { start: '首次启动', restart: '正常重启', crash: '异常崩溃' } as Record<string, string>
126
+ )[record.reason] || '未知';
127
+
128
+ const lines = [
129
+ `${emoji} 【进程监控通知】`,
130
+ '',
131
+ `📊 事件: ${reasonText}`,
132
+ `⏰ 时间: ${record.timestamp.toLocaleString('zh-CN')}`,
133
+ `🖥️ 主机: ${record.hostname}`,
134
+ `🔢 PID: ${record.pid}`,
135
+ `💻 平台: ${record.platform}`,
136
+ `📦 Node: ${record.nodeVersion}`,
137
+ ];
138
+
139
+ if (record.uptime) {
140
+ lines.push(`⏱️ 运行时长: ${formatUptime(record.uptime)}`);
141
+ }
142
+ if (record.memory) {
143
+ lines.push(`💾 内存: ${record.memory} MB`);
144
+ }
145
+
146
+ lines.push('', `📈 统计:`);
147
+ lines.push(` • 总重启: ${processState.restartCount} 次`);
148
+ lines.push(` • 崩溃: ${processState.crashCount} 次`);
149
+ lines.push(` • 累计运行: ${formatUptime(processState.totalUptime)}`);
150
+
151
+ return lines.join('\n');
152
+ }
153
+
154
+ async function sendNotification(
155
+ config: ReturnType<typeof resolveProcessMonitorConfig>,
156
+ record: {
157
+ reason: string;
158
+ timestamp: Date;
159
+ hostname: string;
160
+ pid: number;
161
+ platform: string;
162
+ nodeVersion: string;
163
+ uptime?: number;
164
+ memory?: number;
165
+ },
166
+ ): Promise<void> {
167
+ // user/group 渠道尚未实现(需要接入 OutboundHost 出站链路),
168
+ // 目前仅支持 webhook;实现时用 formatNotificationMessage(record) 生成文本。
169
+ for (const channel of config.notifyChannels) {
170
+ if (channel.type !== 'webhook') continue;
171
+ try {
172
+ await fetch(channel.target, {
173
+ method: 'POST',
174
+ headers: { 'Content-Type': 'application/json' },
175
+ body: JSON.stringify({
176
+ event: 'process_restart',
177
+ data: record,
178
+ stats: processState,
179
+ }),
180
+ });
181
+ } catch {
182
+ // ignore webhook errors
183
+ }
184
+ }
185
+ }
186
+
187
+ async function detectStartupReason(
188
+ config: ReturnType<typeof resolveProcessMonitorConfig>,
189
+ ): Promise<void> {
190
+ const currentPid = process.pid;
191
+ const currentTime = Date.now();
192
+ let reason: 'start' | 'restart' | 'crash' = 'start';
193
+ let uptime: number | undefined;
194
+
195
+ if (processState.lastPid && processState.lastStartTime) {
196
+ const timeSinceLastStart = currentTime - processState.lastStartTime;
197
+ if (timeSinceLastStart < 5 * 60 * 1000) {
198
+ reason = 'crash';
199
+ processState.crashCount++;
200
+ } else {
201
+ reason = 'restart';
202
+ processState.restartCount++;
203
+ }
204
+ uptime = timeSinceLastStart;
205
+ processState.totalUptime += uptime;
206
+ }
207
+
208
+ processState.lastPid = currentPid;
209
+ processState.lastStartTime = currentTime;
210
+ saveProcessState();
211
+
212
+ const record = {
213
+ timestamp: new Date(),
214
+ reason,
215
+ uptime,
216
+ pid: currentPid,
217
+ hostname: os.hostname(),
218
+ platform: `${os.platform()}-${os.arch()}`,
219
+ nodeVersion: process.version,
220
+ memory: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
221
+ };
222
+
223
+ const shouldNotify =
224
+ (reason === 'start' && config.notifyOnStart) ||
225
+ (reason === 'restart' && config.notifyOnRestart) ||
226
+ (reason === 'crash' && config.notifyOnCrash);
227
+
228
+ if (shouldNotify && config.notifyChannels.length > 0) {
229
+ await sendNotification(config, record);
230
+ }
231
+ }
232
+
233
+ /** Start monitoring once; returns disposer for lifecycle cleanup. */
234
+ export function startProcessMonitor(rawConfig?: ProcessMonitorConfig): () => void {
235
+ const config = resolveProcessMonitorConfig(rawConfig);
236
+ if (!config.enabled || started) {
237
+ return () => undefined;
238
+ }
239
+ started = true;
240
+ loadProcessState();
241
+ void detectStartupReason(config);
242
+
243
+ const onSigterm = () => {
244
+ saveProcessState();
245
+ };
246
+ const onSigint = () => {
247
+ saveProcessState();
248
+ };
249
+ process.on('SIGTERM', onSigterm);
250
+ process.on('SIGINT', onSigint);
251
+ signalHandlers = { sigterm: onSigterm, sigint: onSigint };
252
+
253
+ return () => {
254
+ if (signalHandlers) {
255
+ process.removeListener('SIGTERM', signalHandlers.sigterm);
256
+ process.removeListener('SIGINT', signalHandlers.sigint);
257
+ signalHandlers = null;
258
+ }
259
+ started = false;
260
+ };
261
+ }
262
+
263
+ /** Test helper: reset module state without touching disk. */
264
+ export function resetProcessMonitorForTests(): void {
265
+ if (signalHandlers) {
266
+ process.removeListener('SIGTERM', signalHandlers.sigterm);
267
+ process.removeListener('SIGINT', signalHandlers.sigint);
268
+ signalHandlers = null;
269
+ }
270
+ processState = { restartCount: 0, crashCount: 0, totalUptime: 0 };
271
+ started = false;
272
+ }
@@ -0,0 +1,12 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { formatProcessStatus } from '../src/monitor.js';
3
+
4
+ export default defineAgentTool({
5
+ description: '查看进程监控状态,包括 PID、运行时长、内存、重启和崩溃统计',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {},
9
+ },
10
+ approval: 'never',
11
+ execute: () => formatProcessStatus(),
12
+ });
package/plugin.yml DELETED
@@ -1,2 +0,0 @@
1
- name: process-monitor
2
- description: 进程监控与重启通知插件,提供进程状态查看和异常通知
@@ -1,21 +0,0 @@
1
- import os from 'os';
2
- import { processState, startTime, formatUptime } from '../../src/index.js';
3
-
4
- export default async function processStatus(): Promise<string> {
5
- const uptime = Date.now() - startTime;
6
- const memUsage = process.memoryUsage();
7
-
8
- return [
9
- '📊 进程监控状态',
10
- '',
11
- `🚀 当前 PID: ${process.pid}`,
12
- `⏱️ 运行时长: ${formatUptime(uptime)}`,
13
- `💾 内存使用: ${Math.round(memUsage.heapUsed / 1024 / 1024)} MB`,
14
- `🔄 总重启: ${processState.restartCount} 次`,
15
- `💥 崩溃: ${processState.crashCount} 次`,
16
- `📈 累计运行: ${formatUptime(processState.totalUptime)}`,
17
- `🖥️ 主机: ${os.hostname()}`,
18
- `💻 平台: ${os.platform()}-${os.arch()}`,
19
- `📦 Node: ${process.version}`,
20
- ].join('\n');
21
- }
@@ -1,7 +0,0 @@
1
- ---
2
- name: process_status
3
- description: 查看进程监控状态,包括 PID、运行时长、内存、重启和崩溃统计
4
- tags: [监控, 进程]
5
- keywords: [进程状态, 监控状态, process status]
6
- handler: ./handler.ts
7
- ---