@hangox/mg-cli 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,192 @@
1
+ /**
2
+ * 请求处理器
3
+ * 处理 Consumer 请求,转发给 Provider
4
+ */
5
+
6
+ import type { ManagedWebSocket } from './connection-manager.js'
7
+ import { ConnectionManager } from './connection-manager.js'
8
+ import { Logger } from './logger.js'
9
+ import { MessageType, REQUEST_TIMEOUT } from '../shared/constants.js'
10
+ import { ErrorCode, MGError } from '../shared/errors.js'
11
+ import type { RequestMessage, ResponseMessage } from '../shared/types.js'
12
+ import { generateId } from '../shared/utils.js'
13
+
14
+ /** 待处理的请求 */
15
+ interface PendingRequest {
16
+ /** 请求 ID */
17
+ id: string
18
+ /** Consumer WebSocket */
19
+ consumer: ManagedWebSocket
20
+ /** 超时定时器 */
21
+ timer: NodeJS.Timeout
22
+ /** 请求时间 */
23
+ timestamp: number
24
+ }
25
+
26
+ /**
27
+ * 请求处理器
28
+ */
29
+ export class RequestHandler {
30
+ private logger: Logger
31
+ private connectionManager: ConnectionManager
32
+
33
+ /** 待处理的请求 */
34
+ private pendingRequests = new Map<string, PendingRequest>()
35
+
36
+ constructor(connectionManager: ConnectionManager, logger: Logger) {
37
+ this.connectionManager = connectionManager
38
+ this.logger = logger
39
+ }
40
+
41
+ /**
42
+ * 处理 Consumer 请求
43
+ */
44
+ async handleRequest(consumer: ManagedWebSocket, message: RequestMessage): Promise<void> {
45
+ const requestId = message.id || generateId()
46
+ const { type, pageUrl, params } = message
47
+
48
+ this.logger.info(`收到请求: ${type} (${requestId})`, { pageUrl })
49
+
50
+ // 查找目标 Provider
51
+ let provider: ManagedWebSocket | undefined
52
+
53
+ if (pageUrl) {
54
+ provider = this.connectionManager.findProviderByPageUrl(pageUrl)
55
+ if (!provider) {
56
+ this.sendError(consumer, requestId, ErrorCode.PAGE_NOT_FOUND, `未找到页面: ${pageUrl}`)
57
+ return
58
+ }
59
+ } else {
60
+ // 没有指定 pageUrl,使用第一个可用的 Provider
61
+ provider = this.connectionManager.getFirstProvider()
62
+ if (!provider) {
63
+ this.sendError(consumer, requestId, ErrorCode.NO_PAGE_CONNECTED, '没有页面连接到 Server')
64
+ return
65
+ }
66
+ }
67
+
68
+ // 创建超时定时器
69
+ const timer = setTimeout(() => {
70
+ this.handleTimeout(requestId)
71
+ }, REQUEST_TIMEOUT)
72
+
73
+ // 保存待处理请求
74
+ this.pendingRequests.set(requestId, {
75
+ id: requestId,
76
+ consumer,
77
+ timer,
78
+ timestamp: Date.now(),
79
+ })
80
+
81
+ // 转发请求给 Provider
82
+ const forwardMessage: RequestMessage = {
83
+ id: requestId,
84
+ type,
85
+ pageUrl: pageUrl || provider.connectionInfo.pageUrl,
86
+ params,
87
+ timestamp: Date.now(),
88
+ }
89
+
90
+ try {
91
+ provider.send(JSON.stringify(forwardMessage))
92
+ this.logger.info(`请求转发: ${type} -> ${provider.connectionInfo.pageUrl}`)
93
+ } catch {
94
+ this.cleanupRequest(requestId)
95
+ this.sendError(consumer, requestId, ErrorCode.CONNECTION_FAILED, '转发请求失败')
96
+ }
97
+ }
98
+
99
+ /**
100
+ * 处理 Provider 响应
101
+ */
102
+ handleResponse(response: ResponseMessage): void {
103
+ const { id } = response
104
+ const pending = this.pendingRequests.get(id)
105
+
106
+ if (!pending) {
107
+ this.logger.warn(`收到未知请求的响应: ${id}`)
108
+ return
109
+ }
110
+
111
+ this.cleanupRequest(id)
112
+
113
+ // 转发响应给 Consumer
114
+ try {
115
+ pending.consumer.send(JSON.stringify(response))
116
+ this.logger.info(
117
+ `响应返回: ${id} (${response.success ? '成功' : '失败'})`
118
+ )
119
+ } catch (error) {
120
+ this.logger.error(`响应转发失败: ${id}`, error)
121
+ }
122
+ }
123
+
124
+ /**
125
+ * 处理请求超时
126
+ */
127
+ private handleTimeout(requestId: string): void {
128
+ const pending = this.pendingRequests.get(requestId)
129
+ if (!pending) return
130
+
131
+ this.logger.warn(`请求超时: ${requestId}`)
132
+ this.cleanupRequest(requestId)
133
+
134
+ this.sendError(pending.consumer, requestId, ErrorCode.REQUEST_TIMEOUT, '请求超时')
135
+ }
136
+
137
+ /**
138
+ * 清理请求
139
+ */
140
+ private cleanupRequest(requestId: string): void {
141
+ const pending = this.pendingRequests.get(requestId)
142
+ if (pending) {
143
+ clearTimeout(pending.timer)
144
+ this.pendingRequests.delete(requestId)
145
+ }
146
+ }
147
+
148
+ /**
149
+ * 发送错误响应
150
+ */
151
+ private sendError(
152
+ consumer: ManagedWebSocket,
153
+ requestId: string,
154
+ code: ErrorCode,
155
+ message: string
156
+ ): void {
157
+ const error = new MGError(code, message)
158
+ const response: ResponseMessage = {
159
+ id: requestId,
160
+ type: MessageType.ERROR,
161
+ success: false,
162
+ data: null,
163
+ error: error.toJSON(),
164
+ }
165
+
166
+ try {
167
+ consumer.send(JSON.stringify(response))
168
+ } catch (err) {
169
+ this.logger.error(`发送错误响应失败: ${requestId}`, err)
170
+ }
171
+ }
172
+
173
+ /**
174
+ * 清理特定连接的所有待处理请求
175
+ */
176
+ cleanupConnectionRequests(connectionId: string): void {
177
+ for (const [requestId, pending] of this.pendingRequests) {
178
+ if (pending.consumer.connectionId === connectionId) {
179
+ this.cleanupRequest(requestId)
180
+ }
181
+ }
182
+ }
183
+
184
+ /**
185
+ * 清理所有待处理请求
186
+ */
187
+ cleanupAll(): void {
188
+ for (const [requestId] of this.pendingRequests) {
189
+ this.cleanupRequest(requestId)
190
+ }
191
+ }
192
+ }
@@ -0,0 +1,297 @@
1
+ /**
2
+ * WebSocket 服务器
3
+ */
4
+
5
+ import { WebSocketServer, WebSocket } from 'ws'
6
+ import type { IncomingMessage } from 'node:http'
7
+ import {
8
+ ConnectionType,
9
+ MessageType,
10
+ DEFAULT_PORT,
11
+ PORT_RANGE_START,
12
+ PORT_RANGE_END,
13
+ HEARTBEAT_INTERVAL,
14
+ } from '../shared/constants.js'
15
+ import { ErrorCode, MGError } from '../shared/errors.js'
16
+ import type { BaseMessage, RegisterMessage, ResponseMessage } from '../shared/types.js'
17
+ import { Logger, createLogger } from './logger.js'
18
+ import { ConnectionManager, ManagedWebSocket } from './connection-manager.js'
19
+ import { RequestHandler } from './request-handler.js'
20
+
21
+ export interface ServerOptions {
22
+ /** 监听端口 */
23
+ port?: number
24
+ /** 日志选项 */
25
+ logger?: Logger
26
+ }
27
+
28
+ /**
29
+ * MG WebSocket 服务器
30
+ */
31
+ export class MGServer {
32
+ private wss: WebSocketServer | null = null
33
+ private logger: Logger
34
+ private connectionManager: ConnectionManager
35
+ private requestHandler: RequestHandler
36
+
37
+ private port: number
38
+ private isRunning = false
39
+
40
+ constructor(options: ServerOptions = {}) {
41
+ this.port = options.port || DEFAULT_PORT
42
+ this.logger = options.logger || createLogger()
43
+ this.connectionManager = new ConnectionManager(this.logger)
44
+ this.requestHandler = new RequestHandler(this.connectionManager, this.logger)
45
+ }
46
+
47
+ /**
48
+ * 启动服务器
49
+ */
50
+ async start(): Promise<number> {
51
+ if (this.isRunning) {
52
+ throw new MGError(ErrorCode.SERVER_ALREADY_RUNNING, 'Server 已在运行中')
53
+ }
54
+
55
+ // 尝试绑定端口
56
+ const port = await this.findAvailablePort()
57
+
58
+ return new Promise((resolve, reject) => {
59
+ this.wss = new WebSocketServer({ port })
60
+
61
+ this.wss.on('listening', () => {
62
+ this.port = port
63
+ this.isRunning = true
64
+ this.logger.info(`Server 启动成功,监听端口: ${port}`)
65
+
66
+ // 启动心跳检查
67
+ this.connectionManager.startHeartbeatCheck(HEARTBEAT_INTERVAL)
68
+
69
+ resolve(port)
70
+ })
71
+
72
+ this.wss.on('error', (error: NodeJS.ErrnoException) => {
73
+ this.logger.error('Server 错误:', error)
74
+ reject(error)
75
+ })
76
+
77
+ this.wss.on('connection', (ws: WebSocket, request: IncomingMessage) => {
78
+ this.handleConnection(ws, request)
79
+ })
80
+ })
81
+ }
82
+
83
+ /**
84
+ * 查找可用端口
85
+ */
86
+ private async findAvailablePort(): Promise<number> {
87
+ for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
88
+ const available = await this.isPortAvailable(port)
89
+ if (available) {
90
+ return port
91
+ }
92
+ this.logger.debug(`端口 ${port} 被占用,尝试下一个`)
93
+ }
94
+
95
+ throw new MGError(
96
+ ErrorCode.PORT_EXHAUSTED,
97
+ `端口 ${PORT_RANGE_START}-${PORT_RANGE_END} 均被占用`
98
+ )
99
+ }
100
+
101
+ /**
102
+ * 检查端口是否可用
103
+ */
104
+ private isPortAvailable(port: number): Promise<boolean> {
105
+ return new Promise((resolve) => {
106
+ const testServer = new WebSocketServer({ port })
107
+
108
+ testServer.on('listening', () => {
109
+ testServer.close()
110
+ resolve(true)
111
+ })
112
+
113
+ testServer.on('error', () => {
114
+ resolve(false)
115
+ })
116
+ })
117
+ }
118
+
119
+ /**
120
+ * 处理新连接
121
+ */
122
+ private handleConnection(ws: WebSocket, _request: IncomingMessage): void {
123
+ this.logger.info('新连接建立')
124
+
125
+ // 等待注册消息
126
+ const registerTimeout = setTimeout(() => {
127
+ this.logger.warn('连接注册超时,关闭连接')
128
+ ws.close()
129
+ }, 5000)
130
+
131
+ ws.on('message', (data) => {
132
+ try {
133
+ const message = JSON.parse(data.toString()) as BaseMessage
134
+
135
+ // 处理注册消息
136
+ if (message.type === MessageType.REGISTER) {
137
+ clearTimeout(registerTimeout)
138
+ this.handleRegister(ws, message as RegisterMessage)
139
+ return
140
+ }
141
+
142
+ // 其他消息需要已注册
143
+ const managedWs = ws as ManagedWebSocket
144
+ if (!managedWs.connectionId) {
145
+ this.logger.warn('未注册的连接发送消息,忽略')
146
+ return
147
+ }
148
+
149
+ this.handleMessage(managedWs, message)
150
+ } catch (error) {
151
+ this.logger.error('消息解析失败:', error)
152
+ }
153
+ })
154
+
155
+ ws.on('close', () => {
156
+ clearTimeout(registerTimeout)
157
+ const managedWs = ws as ManagedWebSocket
158
+ if (managedWs.connectionId) {
159
+ this.requestHandler.cleanupConnectionRequests(managedWs.connectionId)
160
+ this.connectionManager.removeConnection(managedWs)
161
+ }
162
+ })
163
+
164
+ ws.on('error', (error) => {
165
+ this.logger.error('WebSocket 错误:', error)
166
+ })
167
+ }
168
+
169
+ /**
170
+ * 处理注册消息
171
+ */
172
+ private handleRegister(ws: WebSocket, message: RegisterMessage): void {
173
+ const { connectionType, pageUrl, pageId } = message.data
174
+
175
+ const managedWs = this.connectionManager.addConnection(
176
+ ws,
177
+ connectionType,
178
+ pageUrl,
179
+ pageId
180
+ )
181
+
182
+ // 发送注册确认
183
+ const ack: ResponseMessage = {
184
+ id: message.id || '',
185
+ type: MessageType.REGISTER_ACK,
186
+ success: true,
187
+ data: {
188
+ connectionId: managedWs.connectionId,
189
+ pageUrl,
190
+ },
191
+ }
192
+
193
+ ws.send(JSON.stringify(ack))
194
+ }
195
+
196
+ /**
197
+ * 处理消息
198
+ */
199
+ private handleMessage(ws: ManagedWebSocket, message: BaseMessage): void {
200
+ this.connectionManager.updateLastActive(ws)
201
+
202
+ switch (message.type) {
203
+ case MessageType.PING:
204
+ this.handlePing(ws, message)
205
+ break
206
+
207
+ case MessageType.RESPONSE:
208
+ case MessageType.ERROR:
209
+ // Provider 返回的响应
210
+ this.requestHandler.handleResponse(message as ResponseMessage)
211
+ break
212
+
213
+ default:
214
+ // Consumer 发送的请求
215
+ if (ws.connectionInfo.type === ConnectionType.CONSUMER) {
216
+ this.requestHandler.handleRequest(ws, message as any)
217
+ }
218
+ break
219
+ }
220
+ }
221
+
222
+ /**
223
+ * 处理心跳
224
+ */
225
+ private handlePing(ws: ManagedWebSocket, message: BaseMessage): void {
226
+ const pong = {
227
+ type: MessageType.PONG,
228
+ timestamp: (message as any).timestamp || Date.now(),
229
+ }
230
+ ws.send(JSON.stringify(pong))
231
+ }
232
+
233
+ /**
234
+ * 停止服务器
235
+ */
236
+ async stop(): Promise<void> {
237
+ if (!this.isRunning || !this.wss) {
238
+ return
239
+ }
240
+
241
+ this.logger.info('正在停止 Server...')
242
+
243
+ // 清理所有请求
244
+ this.requestHandler.cleanupAll()
245
+
246
+ // 关闭所有连接
247
+ this.connectionManager.closeAll()
248
+
249
+ // 关闭服务器
250
+ return new Promise((resolve) => {
251
+ this.wss!.close(() => {
252
+ this.isRunning = false
253
+ this.wss = null
254
+ this.logger.info('Server 已停止')
255
+ resolve()
256
+ })
257
+ })
258
+ }
259
+
260
+ /**
261
+ * 获取运行状态
262
+ */
263
+ getStatus(): {
264
+ running: boolean
265
+ port: number
266
+ stats: { providers: number; consumers: number; total: number }
267
+ connectedPages: string[]
268
+ } {
269
+ return {
270
+ running: this.isRunning,
271
+ port: this.port,
272
+ stats: this.connectionManager.getStats(),
273
+ connectedPages: this.connectionManager.getConnectedPageUrls(),
274
+ }
275
+ }
276
+
277
+ /**
278
+ * 获取端口
279
+ */
280
+ getPort(): number {
281
+ return this.port
282
+ }
283
+
284
+ /**
285
+ * 是否运行中
286
+ */
287
+ isServerRunning(): boolean {
288
+ return this.isRunning
289
+ }
290
+ }
291
+
292
+ /**
293
+ * 创建服务器实例
294
+ */
295
+ export function createServer(options?: ServerOptions): MGServer {
296
+ return new MGServer(options)
297
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * MG Plugin 常量定义
3
+ */
4
+
5
+ import { homedir } from 'node:os'
6
+ import { join } from 'node:path'
7
+
8
+ // ==================== 端口配置 ====================
9
+
10
+ /** 默认端口 */
11
+ export const DEFAULT_PORT = 9527
12
+
13
+ /** 端口范围:起始 */
14
+ export const PORT_RANGE_START = 9527
15
+
16
+ /** 端口范围:结束 */
17
+ export const PORT_RANGE_END = 9536
18
+
19
+ /** 最大尝试端口数 */
20
+ export const MAX_PORT_ATTEMPTS = 10
21
+
22
+ /** 端口扫描超时(毫秒) */
23
+ export const PORT_SCAN_TIMEOUT = 500
24
+
25
+ // ==================== 路径配置 ====================
26
+
27
+ /** 配置目录 */
28
+ export const CONFIG_DIR = join(homedir(), '.mg-plugin')
29
+
30
+ /** Server 状态文件 */
31
+ export const SERVER_INFO_FILE = join(CONFIG_DIR, 'server.json')
32
+
33
+ /** 日志目录 */
34
+ export const LOG_DIR = join(CONFIG_DIR, 'logs')
35
+
36
+ /** Server 日志文件 */
37
+ export const SERVER_LOG_FILE = join(LOG_DIR, 'server.log')
38
+
39
+ // ==================== 超时配置 ====================
40
+
41
+ /** 心跳间隔(毫秒)- 30 秒 */
42
+ export const HEARTBEAT_INTERVAL = 30000
43
+
44
+ /** 心跳超时(毫秒)- 90 秒(3 次心跳) */
45
+ export const HEARTBEAT_TIMEOUT = 90000
46
+
47
+ /** 请求超时(毫秒)- 30 秒 */
48
+ export const REQUEST_TIMEOUT = 30000
49
+
50
+ /** Server 启动等待超时(毫秒)- 5 秒 */
51
+ export const SERVER_START_TIMEOUT = 5000
52
+
53
+ /** CLI 重试间隔(毫秒) */
54
+ export const RETRY_INTERVALS = [1000, 2000, 4000]
55
+
56
+ /** 最大重试次数 */
57
+ export const MAX_RETRY_COUNT = 3
58
+
59
+ // ==================== 连接类型 ====================
60
+
61
+ /** 连接类型 */
62
+ export enum ConnectionType {
63
+ /** 获取端 (CLI/MCP) */
64
+ CONSUMER = 'consumer',
65
+ /** 提供端 (Injector) */
66
+ PROVIDER = 'provider',
67
+ }
68
+
69
+ // ==================== 消息类型 ====================
70
+
71
+ /** WebSocket 消息类型 */
72
+ export enum MessageType {
73
+ // 系统消息
74
+ PING = 'ping',
75
+ PONG = 'pong',
76
+ REGISTER = 'register',
77
+ REGISTER_ACK = 'register_ack',
78
+
79
+ // 业务消息
80
+ GET_NODE_BY_ID = 'get_node_by_id',
81
+ GET_ALL_NODES = 'get_all_nodes',
82
+ GET_SELECTION = 'get_selection',
83
+ EXPORT_IMAGE = 'export_image',
84
+ EXECUTE_CODE = 'execute_code',
85
+ GET_ALL_PAGES = 'get_all_pages',
86
+
87
+ // 响应
88
+ RESPONSE = 'response',
89
+ ERROR = 'error',
90
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * MG Plugin 错误码定义
3
+ */
4
+
5
+ /** 错误码枚举 */
6
+ export enum ErrorCode {
7
+ /** 无法连接到 MG Server */
8
+ CONNECTION_FAILED = 'E001',
9
+ /** 连接超时 */
10
+ CONNECTION_TIMEOUT = 'E002',
11
+ /** 没有 MasterGo 页面连接到 Server */
12
+ NO_PAGE_CONNECTED = 'E003',
13
+ /** 未找到匹配的页面 */
14
+ PAGE_NOT_FOUND = 'E004',
15
+ /** 节点不存在 */
16
+ NODE_NOT_FOUND = 'E005',
17
+ /** 没有选中任何节点 */
18
+ NO_SELECTION = 'E006',
19
+ /** mg 对象不可用 */
20
+ MG_UNAVAILABLE = 'E007',
21
+ /** 导出图片失败 */
22
+ EXPORT_FAILED = 'E008',
23
+ /** 文件写入失败 */
24
+ FILE_WRITE_FAILED = 'E009',
25
+ /** 无效的 mgp:// 链接格式 */
26
+ INVALID_LINK = 'E010',
27
+ /** 参数校验失败 */
28
+ INVALID_PARAMS = 'E011',
29
+ /** 请求超时 */
30
+ REQUEST_TIMEOUT = 'E012',
31
+ /** 所有备选端口均被占用 */
32
+ PORT_EXHAUSTED = 'E013',
33
+ /** 无法发现 Server (端口扫描失败) */
34
+ SERVER_DISCOVERY_FAILED = 'E014',
35
+ /** 自动启动 Server 失败 */
36
+ SERVER_START_FAILED = 'E015',
37
+ /** Server 已在运行中 */
38
+ SERVER_ALREADY_RUNNING = 'E016',
39
+ /** 连接断开 */
40
+ CONNECTION_LOST = 'E017',
41
+ /** 未知错误 */
42
+ UNKNOWN_ERROR = 'E099',
43
+ }
44
+
45
+ /** 错误码对应的错误名称 */
46
+ export const ErrorNames: Record<ErrorCode, string> = {
47
+ [ErrorCode.CONNECTION_FAILED]: 'CONNECTION_FAILED',
48
+ [ErrorCode.CONNECTION_TIMEOUT]: 'CONNECTION_TIMEOUT',
49
+ [ErrorCode.NO_PAGE_CONNECTED]: 'NO_PAGE_CONNECTED',
50
+ [ErrorCode.PAGE_NOT_FOUND]: 'PAGE_NOT_FOUND',
51
+ [ErrorCode.NODE_NOT_FOUND]: 'NODE_NOT_FOUND',
52
+ [ErrorCode.NO_SELECTION]: 'NO_SELECTION',
53
+ [ErrorCode.MG_UNAVAILABLE]: 'MG_UNAVAILABLE',
54
+ [ErrorCode.EXPORT_FAILED]: 'EXPORT_FAILED',
55
+ [ErrorCode.FILE_WRITE_FAILED]: 'FILE_WRITE_FAILED',
56
+ [ErrorCode.INVALID_LINK]: 'INVALID_LINK',
57
+ [ErrorCode.INVALID_PARAMS]: 'INVALID_PARAMS',
58
+ [ErrorCode.REQUEST_TIMEOUT]: 'REQUEST_TIMEOUT',
59
+ [ErrorCode.PORT_EXHAUSTED]: 'PORT_EXHAUSTED',
60
+ [ErrorCode.SERVER_DISCOVERY_FAILED]: 'SERVER_DISCOVERY_FAILED',
61
+ [ErrorCode.SERVER_START_FAILED]: 'SERVER_START_FAILED',
62
+ [ErrorCode.SERVER_ALREADY_RUNNING]: 'SERVER_ALREADY_RUNNING',
63
+ [ErrorCode.CONNECTION_LOST]: 'CONNECTION_LOST',
64
+ [ErrorCode.UNKNOWN_ERROR]: 'UNKNOWN_ERROR',
65
+ }
66
+
67
+ /** 错误码对应的默认消息 */
68
+ export const ErrorMessages: Record<ErrorCode, string> = {
69
+ [ErrorCode.CONNECTION_FAILED]: '无法连接到 MG Server',
70
+ [ErrorCode.CONNECTION_TIMEOUT]: '连接超时',
71
+ [ErrorCode.NO_PAGE_CONNECTED]: '没有 MasterGo 页面连接到 Server',
72
+ [ErrorCode.PAGE_NOT_FOUND]: '未找到匹配的页面',
73
+ [ErrorCode.NODE_NOT_FOUND]: '节点不存在',
74
+ [ErrorCode.NO_SELECTION]: '没有选中任何节点',
75
+ [ErrorCode.MG_UNAVAILABLE]: 'mg 对象不可用',
76
+ [ErrorCode.EXPORT_FAILED]: '导出图片失败',
77
+ [ErrorCode.FILE_WRITE_FAILED]: '文件写入失败',
78
+ [ErrorCode.INVALID_LINK]: '无效的 mgp:// 链接格式',
79
+ [ErrorCode.INVALID_PARAMS]: '参数校验失败',
80
+ [ErrorCode.REQUEST_TIMEOUT]: '请求超时',
81
+ [ErrorCode.PORT_EXHAUSTED]: '所有备选端口均被占用',
82
+ [ErrorCode.SERVER_DISCOVERY_FAILED]: '无法发现 Server (端口扫描失败)',
83
+ [ErrorCode.SERVER_START_FAILED]: '自动启动 Server 失败',
84
+ [ErrorCode.SERVER_ALREADY_RUNNING]: 'Server 已在运行中',
85
+ [ErrorCode.CONNECTION_LOST]: '连接断开',
86
+ [ErrorCode.UNKNOWN_ERROR]: '未知错误',
87
+ }
88
+
89
+ /** MG Plugin 错误类 */
90
+ export class MGError extends Error {
91
+ /** 错误码 */
92
+ code: ErrorCode
93
+ /** 错误名称 */
94
+ errorName: string
95
+ /** 额外详情 */
96
+ details?: Record<string, unknown>
97
+
98
+ constructor(code: ErrorCode, message?: string, details?: Record<string, unknown>) {
99
+ super(message || ErrorMessages[code])
100
+ this.name = 'MGError'
101
+ this.code = code
102
+ this.errorName = ErrorNames[code]
103
+ this.details = details
104
+ }
105
+
106
+ /** 转换为 JSON 格式 */
107
+ toJSON() {
108
+ return {
109
+ code: this.code,
110
+ name: this.errorName,
111
+ message: this.message,
112
+ details: this.details,
113
+ }
114
+ }
115
+
116
+ /** 格式化输出 */
117
+ toString() {
118
+ return `错误 [${this.code}]: ${this.message}`
119
+ }
120
+ }
121
+
122
+ /**
123
+ * 创建错误
124
+ */
125
+ export function createError(
126
+ code: ErrorCode,
127
+ message?: string,
128
+ details?: Record<string, unknown>
129
+ ): MGError {
130
+ return new MGError(code, message, details)
131
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 共享模块导出
3
+ */
4
+
5
+ export * from './constants.js'
6
+ export * from './errors.js'
7
+ export * from './types.js'
8
+ export * from './utils.js'